mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-12 06:19:03 +08:00
Chore/add unit test for utils (#17858)
This commit is contained in:
parent
4ef297bf38
commit
59b2e1ab82
614
web/app/components/base/file-uploader/utils.spec.ts
Normal file
614
web/app/components/base/file-uploader/utils.spec.ts
Normal file
@ -0,0 +1,614 @@
|
|||||||
|
import mime from 'mime'
|
||||||
|
import { upload } from '@/service/base'
|
||||||
|
import {
|
||||||
|
downloadFile,
|
||||||
|
fileIsUploaded,
|
||||||
|
fileUpload,
|
||||||
|
getFileAppearanceType,
|
||||||
|
getFileExtension,
|
||||||
|
getFileNameFromUrl,
|
||||||
|
getFilesInLogs,
|
||||||
|
getProcessedFiles,
|
||||||
|
getProcessedFilesFromResponse,
|
||||||
|
getSupportFileExtensionList,
|
||||||
|
getSupportFileType,
|
||||||
|
isAllowedFileExtension,
|
||||||
|
} from './utils'
|
||||||
|
import { FileAppearanceTypeEnum } from './types'
|
||||||
|
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
||||||
|
import { TransferMethod } from '@/types/app'
|
||||||
|
import { FILE_EXTS } from '../prompt-editor/constants'
|
||||||
|
|
||||||
|
jest.mock('mime', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: {
|
||||||
|
getExtension: jest.fn(),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.mock('@/service/base', () => ({
|
||||||
|
upload: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('file-uploader utils', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('fileUpload', () => {
|
||||||
|
it('should handle successful file upload', async () => {
|
||||||
|
const mockFile = new File(['test'], 'test.txt')
|
||||||
|
const mockCallbacks = {
|
||||||
|
onProgressCallback: jest.fn(),
|
||||||
|
onSuccessCallback: jest.fn(),
|
||||||
|
onErrorCallback: jest.fn(),
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.mocked(upload).mockResolvedValue({ id: '123' })
|
||||||
|
|
||||||
|
await fileUpload({
|
||||||
|
file: mockFile,
|
||||||
|
...mockCallbacks,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(upload).toHaveBeenCalled()
|
||||||
|
expect(mockCallbacks.onSuccessCallback).toHaveBeenCalledWith({ id: '123' })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getFileExtension', () => {
|
||||||
|
it('should get extension from mimetype', () => {
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('pdf')
|
||||||
|
expect(getFileExtension('file', 'application/pdf')).toBe('pdf')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should get extension from filename if mimetype fails', () => {
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue(null)
|
||||||
|
expect(getFileExtension('file.txt', '')).toBe('txt')
|
||||||
|
expect(getFileExtension('file.txt.docx', '')).toBe('docx')
|
||||||
|
expect(getFileExtension('file', '')).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return empty string for remote files', () => {
|
||||||
|
expect(getFileExtension('file.txt', '', true)).toBe('')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getFileAppearanceType', () => {
|
||||||
|
it('should identify gif files', () => {
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('gif')
|
||||||
|
expect(getFileAppearanceType('image.gif', 'image/gif'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.gif)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should identify image files', () => {
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('jpg')
|
||||||
|
expect(getFileAppearanceType('image.jpg', 'image/jpeg'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.image)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('jpeg')
|
||||||
|
expect(getFileAppearanceType('image.jpeg', 'image/jpeg'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.image)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('png')
|
||||||
|
expect(getFileAppearanceType('image.png', 'image/png'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.image)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('webp')
|
||||||
|
expect(getFileAppearanceType('image.webp', 'image/webp'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.image)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('svg')
|
||||||
|
expect(getFileAppearanceType('image.svg', 'image/svgxml'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.image)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should identify video files', () => {
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('mp4')
|
||||||
|
expect(getFileAppearanceType('video.mp4', 'video/mp4'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.video)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('mov')
|
||||||
|
expect(getFileAppearanceType('video.mov', 'video/quicktime'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.video)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('mpeg')
|
||||||
|
expect(getFileAppearanceType('video.mpeg', 'video/mpeg'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.video)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('webm')
|
||||||
|
expect(getFileAppearanceType('video.web', 'video/webm'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.video)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should identify audio files', () => {
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('mp3')
|
||||||
|
expect(getFileAppearanceType('audio.mp3', 'audio/mpeg'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.audio)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('m4a')
|
||||||
|
expect(getFileAppearanceType('audio.m4a', 'audio/mp4'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.audio)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('wav')
|
||||||
|
expect(getFileAppearanceType('audio.wav', 'audio/vnd.wav'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.audio)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('amr')
|
||||||
|
expect(getFileAppearanceType('audio.amr', 'audio/AMR'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.audio)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('mpga')
|
||||||
|
expect(getFileAppearanceType('audio.mpga', 'audio/mpeg'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.audio)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should identify code files', () => {
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('html')
|
||||||
|
expect(getFileAppearanceType('index.html', 'text/html'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.code)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should identify PDF files', () => {
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('pdf')
|
||||||
|
expect(getFileAppearanceType('doc.pdf', 'application/pdf'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.pdf)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should identify markdown files', () => {
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('md')
|
||||||
|
expect(getFileAppearanceType('file.md', 'text/markdown'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.markdown)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('markdown')
|
||||||
|
expect(getFileAppearanceType('file.markdown', 'text/markdown'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.markdown)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('mdx')
|
||||||
|
expect(getFileAppearanceType('file.mdx', 'text/mdx'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.markdown)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should identify excel files', () => {
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('xlsx')
|
||||||
|
expect(getFileAppearanceType('doc.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.excel)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('xls')
|
||||||
|
expect(getFileAppearanceType('doc.xls', 'application/vnd.ms-excel'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.excel)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should identify word files', () => {
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('doc')
|
||||||
|
expect(getFileAppearanceType('doc.doc', 'application/msword'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.word)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('docx')
|
||||||
|
expect(getFileAppearanceType('doc.docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.word)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should identify word files', () => {
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('ppt')
|
||||||
|
expect(getFileAppearanceType('doc.ppt', 'application/vnd.ms-powerpoint'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.ppt)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('pptx')
|
||||||
|
expect(getFileAppearanceType('doc.pptx', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.ppt)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should identify document files', () => {
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('txt')
|
||||||
|
expect(getFileAppearanceType('file.txt', 'text/plain'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.document)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('csv')
|
||||||
|
expect(getFileAppearanceType('file.csv', 'text/csv'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.document)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('msg')
|
||||||
|
expect(getFileAppearanceType('file.msg', 'application/vnd.ms-outlook'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.document)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('eml')
|
||||||
|
expect(getFileAppearanceType('file.eml', 'message/rfc822'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.document)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('xml')
|
||||||
|
expect(getFileAppearanceType('file.xml', 'application/rssxml'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.document)
|
||||||
|
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('epub')
|
||||||
|
expect(getFileAppearanceType('file.epub', 'application/epubzip'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.document)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle null mime extension', () => {
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue(null)
|
||||||
|
expect(getFileAppearanceType('file.txt', 'text/plain'))
|
||||||
|
.toBe(FileAppearanceTypeEnum.document)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getSupportFileType', () => {
|
||||||
|
it('should return custom type when isCustom is true', () => {
|
||||||
|
expect(getSupportFileType('file.txt', '', true))
|
||||||
|
.toBe(SupportUploadFileTypes.custom)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return file type when isCustom is false', () => {
|
||||||
|
expect(getSupportFileType('file.txt', 'text/plain'))
|
||||||
|
.toBe(SupportUploadFileTypes.document)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getProcessedFiles', () => {
|
||||||
|
it('should process files correctly', () => {
|
||||||
|
const files = [{
|
||||||
|
id: '123',
|
||||||
|
name: 'test.txt',
|
||||||
|
size: 1024,
|
||||||
|
type: 'text/plain',
|
||||||
|
progress: 100,
|
||||||
|
supportFileType: 'document',
|
||||||
|
transferMethod: TransferMethod.remote_url,
|
||||||
|
url: 'http://example.com',
|
||||||
|
uploadedId: '123',
|
||||||
|
}]
|
||||||
|
|
||||||
|
const result = getProcessedFiles(files)
|
||||||
|
expect(result[0]).toEqual({
|
||||||
|
type: 'document',
|
||||||
|
transfer_method: TransferMethod.remote_url,
|
||||||
|
url: 'http://example.com',
|
||||||
|
upload_file_id: '123',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getProcessedFilesFromResponse', () => {
|
||||||
|
it('should process files correctly', () => {
|
||||||
|
const files = [{
|
||||||
|
related_id: '2a38e2ca-1295-415d-a51d-65d4ff9912d9',
|
||||||
|
extension: '.jpeg',
|
||||||
|
filename: 'test.jpeg',
|
||||||
|
size: 2881761,
|
||||||
|
mime_type: 'image/jpeg',
|
||||||
|
transfer_method: TransferMethod.local_file,
|
||||||
|
type: 'image',
|
||||||
|
url: 'https://upload.dify.dev/files/xxx/file-preview',
|
||||||
|
}]
|
||||||
|
|
||||||
|
const result = getProcessedFilesFromResponse(files)
|
||||||
|
expect(result[0]).toEqual({
|
||||||
|
id: '2a38e2ca-1295-415d-a51d-65d4ff9912d9',
|
||||||
|
name: 'test.jpeg',
|
||||||
|
size: 2881761,
|
||||||
|
type: 'image/jpeg',
|
||||||
|
progress: 100,
|
||||||
|
transferMethod: TransferMethod.local_file,
|
||||||
|
supportFileType: 'image',
|
||||||
|
uploadedId: '2a38e2ca-1295-415d-a51d-65d4ff9912d9',
|
||||||
|
url: 'https://upload.dify.dev/files/xxx/file-preview',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getFileNameFromUrl', () => {
|
||||||
|
it('should extract filename from URL', () => {
|
||||||
|
expect(getFileNameFromUrl('http://example.com/path/file.txt'))
|
||||||
|
.toBe('file.txt')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getSupportFileExtensionList', () => {
|
||||||
|
it('should handle custom file types', () => {
|
||||||
|
const result = getSupportFileExtensionList(
|
||||||
|
[SupportUploadFileTypes.custom],
|
||||||
|
['.pdf', '.txt', '.doc'],
|
||||||
|
)
|
||||||
|
expect(result).toEqual(['PDF', 'TXT', 'DOC'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle standard file types', () => {
|
||||||
|
const mockFileExts = {
|
||||||
|
image: ['JPG', 'PNG'],
|
||||||
|
document: ['PDF', 'TXT'],
|
||||||
|
video: ['MP4', 'MOV'],
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporarily mock FILE_EXTS
|
||||||
|
const originalFileExts = { ...FILE_EXTS }
|
||||||
|
Object.assign(FILE_EXTS, mockFileExts)
|
||||||
|
|
||||||
|
const result = getSupportFileExtensionList(
|
||||||
|
['image', 'document'],
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
expect(result).toEqual(['JPG', 'PNG', 'PDF', 'TXT'])
|
||||||
|
|
||||||
|
// Restore original FILE_EXTS
|
||||||
|
Object.assign(FILE_EXTS, originalFileExts)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return empty array for empty inputs', () => {
|
||||||
|
const result = getSupportFileExtensionList([], [])
|
||||||
|
expect(result).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should prioritize custom types over standard types', () => {
|
||||||
|
const mockFileExts = {
|
||||||
|
image: ['JPG', 'PNG'],
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporarily mock FILE_EXTS
|
||||||
|
const originalFileExts = { ...FILE_EXTS }
|
||||||
|
Object.assign(FILE_EXTS, mockFileExts)
|
||||||
|
|
||||||
|
const result = getSupportFileExtensionList(
|
||||||
|
[SupportUploadFileTypes.custom, 'image'],
|
||||||
|
['.csv', '.xml'],
|
||||||
|
)
|
||||||
|
expect(result).toEqual(['CSV', 'XML'])
|
||||||
|
|
||||||
|
// Restore original FILE_EXTS
|
||||||
|
Object.assign(FILE_EXTS, originalFileExts)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('isAllowedFileExtension', () => {
|
||||||
|
it('should validate allowed file extensions', () => {
|
||||||
|
jest.mocked(mime.getExtension).mockReturnValue('pdf')
|
||||||
|
expect(isAllowedFileExtension(
|
||||||
|
'test.pdf',
|
||||||
|
'application/pdf',
|
||||||
|
['document'],
|
||||||
|
['.pdf'],
|
||||||
|
)).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getFilesInLogs', () => {
|
||||||
|
const mockFileData = {
|
||||||
|
dify_model_identity: '__dify__file__',
|
||||||
|
related_id: '123',
|
||||||
|
filename: 'test.pdf',
|
||||||
|
size: 1024,
|
||||||
|
mime_type: 'application/pdf',
|
||||||
|
transfer_method: 'local_file',
|
||||||
|
type: 'document',
|
||||||
|
url: 'http://example.com/test.pdf',
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should handle empty or null input', () => {
|
||||||
|
expect(getFilesInLogs(null)).toEqual([])
|
||||||
|
expect(getFilesInLogs({})).toEqual([])
|
||||||
|
expect(getFilesInLogs(undefined)).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should process single file object', () => {
|
||||||
|
const input = {
|
||||||
|
file1: mockFileData,
|
||||||
|
}
|
||||||
|
|
||||||
|
const expected = [{
|
||||||
|
varName: 'file1',
|
||||||
|
list: [{
|
||||||
|
id: '123',
|
||||||
|
name: 'test.pdf',
|
||||||
|
size: 1024,
|
||||||
|
type: 'application/pdf',
|
||||||
|
progress: 100,
|
||||||
|
transferMethod: 'local_file',
|
||||||
|
supportFileType: 'document',
|
||||||
|
uploadedId: '123',
|
||||||
|
url: 'http://example.com/test.pdf',
|
||||||
|
}],
|
||||||
|
}]
|
||||||
|
|
||||||
|
expect(getFilesInLogs(input)).toEqual(expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should process array of files', () => {
|
||||||
|
const input = {
|
||||||
|
files: [mockFileData, mockFileData],
|
||||||
|
}
|
||||||
|
|
||||||
|
const expected = [{
|
||||||
|
varName: 'files',
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
id: '123',
|
||||||
|
name: 'test.pdf',
|
||||||
|
size: 1024,
|
||||||
|
type: 'application/pdf',
|
||||||
|
progress: 100,
|
||||||
|
transferMethod: 'local_file',
|
||||||
|
supportFileType: 'document',
|
||||||
|
uploadedId: '123',
|
||||||
|
url: 'http://example.com/test.pdf',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '123',
|
||||||
|
name: 'test.pdf',
|
||||||
|
size: 1024,
|
||||||
|
type: 'application/pdf',
|
||||||
|
progress: 100,
|
||||||
|
transferMethod: 'local_file',
|
||||||
|
supportFileType: 'document',
|
||||||
|
uploadedId: '123',
|
||||||
|
url: 'http://example.com/test.pdf',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
|
|
||||||
|
expect(getFilesInLogs(input)).toEqual(expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should ignore non-file objects and arrays', () => {
|
||||||
|
const input = {
|
||||||
|
regularString: 'not a file',
|
||||||
|
regularNumber: 123,
|
||||||
|
regularArray: [1, 2, 3],
|
||||||
|
regularObject: { key: 'value' },
|
||||||
|
file: mockFileData,
|
||||||
|
}
|
||||||
|
|
||||||
|
const expected = [{
|
||||||
|
varName: 'file',
|
||||||
|
list: [{
|
||||||
|
id: '123',
|
||||||
|
name: 'test.pdf',
|
||||||
|
size: 1024,
|
||||||
|
type: 'application/pdf',
|
||||||
|
progress: 100,
|
||||||
|
transferMethod: 'local_file',
|
||||||
|
supportFileType: 'document',
|
||||||
|
uploadedId: '123',
|
||||||
|
url: 'http://example.com/test.pdf',
|
||||||
|
}],
|
||||||
|
}]
|
||||||
|
|
||||||
|
expect(getFilesInLogs(input)).toEqual(expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle mixed file types in array', () => {
|
||||||
|
const input = {
|
||||||
|
mixedFiles: [
|
||||||
|
mockFileData,
|
||||||
|
{ notAFile: true },
|
||||||
|
mockFileData,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const expected = [{
|
||||||
|
varName: 'mixedFiles',
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
id: '123',
|
||||||
|
name: 'test.pdf',
|
||||||
|
size: 1024,
|
||||||
|
type: 'application/pdf',
|
||||||
|
progress: 100,
|
||||||
|
transferMethod: 'local_file',
|
||||||
|
supportFileType: 'document',
|
||||||
|
uploadedId: '123',
|
||||||
|
url: 'http://example.com/test.pdf',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
progress: 100,
|
||||||
|
size: 0,
|
||||||
|
supportFileType: undefined,
|
||||||
|
transferMethod: undefined,
|
||||||
|
type: undefined,
|
||||||
|
uploadedId: undefined,
|
||||||
|
url: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '123',
|
||||||
|
name: 'test.pdf',
|
||||||
|
size: 1024,
|
||||||
|
type: 'application/pdf',
|
||||||
|
progress: 100,
|
||||||
|
transferMethod: 'local_file',
|
||||||
|
supportFileType: 'document',
|
||||||
|
uploadedId: '123',
|
||||||
|
url: 'http://example.com/test.pdf',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
|
|
||||||
|
expect(getFilesInLogs(input)).toEqual(expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('fileIsUploaded', () => {
|
||||||
|
it('should identify uploaded files', () => {
|
||||||
|
expect(fileIsUploaded({
|
||||||
|
uploadedId: '123',
|
||||||
|
progress: 100,
|
||||||
|
} as any)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should identify remote files as uploaded', () => {
|
||||||
|
expect(fileIsUploaded({
|
||||||
|
transferMethod: TransferMethod.remote_url,
|
||||||
|
progress: 100,
|
||||||
|
} as any)).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('downloadFile', () => {
|
||||||
|
let mockAnchor: HTMLAnchorElement
|
||||||
|
let createElementMock: jest.SpyInstance
|
||||||
|
let appendChildMock: jest.SpyInstance
|
||||||
|
let removeChildMock: jest.SpyInstance
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Mock createElement and appendChild
|
||||||
|
mockAnchor = {
|
||||||
|
href: '',
|
||||||
|
download: '',
|
||||||
|
style: { display: '' },
|
||||||
|
target: '',
|
||||||
|
title: '',
|
||||||
|
click: jest.fn(),
|
||||||
|
} as unknown as HTMLAnchorElement
|
||||||
|
|
||||||
|
createElementMock = jest.spyOn(document, 'createElement').mockReturnValue(mockAnchor as any)
|
||||||
|
appendChildMock = jest.spyOn(document.body, 'appendChild').mockImplementation((node: Node) => {
|
||||||
|
return node
|
||||||
|
})
|
||||||
|
removeChildMock = jest.spyOn(document.body, 'removeChild').mockImplementation((node: Node) => {
|
||||||
|
return node
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create and trigger download with correct attributes', () => {
|
||||||
|
const url = 'https://example.com/test.pdf'
|
||||||
|
const filename = 'test.pdf'
|
||||||
|
|
||||||
|
downloadFile(url, filename)
|
||||||
|
|
||||||
|
// Verify anchor element was created with correct properties
|
||||||
|
expect(createElementMock).toHaveBeenCalledWith('a')
|
||||||
|
expect(mockAnchor.href).toBe(url)
|
||||||
|
expect(mockAnchor.download).toBe(filename)
|
||||||
|
expect(mockAnchor.style.display).toBe('none')
|
||||||
|
expect(mockAnchor.target).toBe('_blank')
|
||||||
|
expect(mockAnchor.title).toBe(filename)
|
||||||
|
|
||||||
|
// Verify DOM operations
|
||||||
|
expect(appendChildMock).toHaveBeenCalledWith(mockAnchor)
|
||||||
|
expect(mockAnchor.click).toHaveBeenCalled()
|
||||||
|
expect(removeChildMock).toHaveBeenCalledWith(mockAnchor)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle empty filename', () => {
|
||||||
|
const url = 'https://example.com/test.pdf'
|
||||||
|
const filename = ''
|
||||||
|
|
||||||
|
downloadFile(url, filename)
|
||||||
|
|
||||||
|
expect(mockAnchor.download).toBe('')
|
||||||
|
expect(mockAnchor.title).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle empty url', () => {
|
||||||
|
const url = ''
|
||||||
|
const filename = 'test.pdf'
|
||||||
|
|
||||||
|
downloadFile(url, filename)
|
||||||
|
|
||||||
|
expect(mockAnchor.href).toBe('')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -53,6 +53,6 @@ export const getInputVars = (text: string): ValueSelector[] => {
|
|||||||
export const FILE_EXTS: Record<string, string[]> = {
|
export const FILE_EXTS: Record<string, string[]> = {
|
||||||
[SupportUploadFileTypes.image]: ['JPG', 'JPEG', 'PNG', 'GIF', 'WEBP', 'SVG'],
|
[SupportUploadFileTypes.image]: ['JPG', 'JPEG', 'PNG', 'GIF', 'WEBP', 'SVG'],
|
||||||
[SupportUploadFileTypes.document]: ['TXT', 'MD', 'MDX', 'MARKDOWN', 'PDF', 'HTML', 'XLSX', 'XLS', 'DOC', 'DOCX', 'CSV', 'EML', 'MSG', 'PPTX', 'PPT', 'XML', 'EPUB'],
|
[SupportUploadFileTypes.document]: ['TXT', 'MD', 'MDX', 'MARKDOWN', 'PDF', 'HTML', 'XLSX', 'XLS', 'DOC', 'DOCX', 'CSV', 'EML', 'MSG', 'PPTX', 'PPT', 'XML', 'EPUB'],
|
||||||
[SupportUploadFileTypes.audio]: ['MP3', 'M4A', 'WAV', 'WEBM', 'AMR', 'MPGA'],
|
[SupportUploadFileTypes.audio]: ['MP3', 'M4A', 'WAV', 'AMR', 'MPGA'],
|
||||||
[SupportUploadFileTypes.video]: ['MP4', 'MOV', 'MPEG', 'MPGA'],
|
[SupportUploadFileTypes.video]: ['MP4', 'MOV', 'MPEG', 'WEBM'],
|
||||||
}
|
}
|
||||||
|
65
web/hooks/use-timestamp.spec.ts
Normal file
65
web/hooks/use-timestamp.spec.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { renderHook } from '@testing-library/react'
|
||||||
|
import useTimestamp from './use-timestamp'
|
||||||
|
|
||||||
|
jest.mock('@/context/app-context', () => ({
|
||||||
|
useAppContext: jest.fn(() => ({
|
||||||
|
userProfile: {
|
||||||
|
id: '8b18e24b-1ac8-4262-aa5c-e9aa95c76846',
|
||||||
|
name: 'test',
|
||||||
|
avatar: null,
|
||||||
|
avatar_url: null,
|
||||||
|
email: 'test@dify.ai',
|
||||||
|
is_password_set: false,
|
||||||
|
interface_language: 'zh-Hans',
|
||||||
|
interface_theme: 'light',
|
||||||
|
timezone: 'Asia/Shanghai',
|
||||||
|
last_login_at: 1744188761,
|
||||||
|
last_login_ip: '127.0.0.1',
|
||||||
|
created_at: 1728444483,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('useTimestamp', () => {
|
||||||
|
describe('formatTime', () => {
|
||||||
|
it('should format unix timestamp correctly', () => {
|
||||||
|
const { result } = renderHook(() => useTimestamp())
|
||||||
|
const timestamp = 1704132000
|
||||||
|
|
||||||
|
expect(result.current.formatTime(timestamp, 'YYYY-MM-DD HH:mm:ss'))
|
||||||
|
.toBe('2024-01-02 02:00:00')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should format with different patterns', () => {
|
||||||
|
const { result } = renderHook(() => useTimestamp())
|
||||||
|
const timestamp = 1704132000
|
||||||
|
|
||||||
|
expect(result.current.formatTime(timestamp, 'MM/DD/YYYY'))
|
||||||
|
.toBe('01/02/2024')
|
||||||
|
|
||||||
|
expect(result.current.formatTime(timestamp, 'HH:mm'))
|
||||||
|
.toBe('02:00')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('formatDate', () => {
|
||||||
|
it('should format date string correctly', () => {
|
||||||
|
const { result } = renderHook(() => useTimestamp())
|
||||||
|
const dateString = '2024-01-01T12:00:00Z'
|
||||||
|
|
||||||
|
expect(result.current.formatDate(dateString, 'YYYY-MM-DD HH:mm:ss'))
|
||||||
|
.toBe('2024-01-01 20:00:00')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should format with different patterns', () => {
|
||||||
|
const { result } = renderHook(() => useTimestamp())
|
||||||
|
const dateString = '2024-01-01T12:00:00Z'
|
||||||
|
|
||||||
|
expect(result.current.formatDate(dateString, 'MM/DD/YYYY'))
|
||||||
|
.toBe('01/01/2024')
|
||||||
|
|
||||||
|
expect(result.current.formatDate(dateString, 'HH:mm'))
|
||||||
|
.toBe('20:00')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -147,7 +147,7 @@ const config: Config = {
|
|||||||
// setupFiles: [],
|
// setupFiles: [],
|
||||||
|
|
||||||
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||||
// setupFilesAfterEnv: [],
|
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||||
|
|
||||||
// The number of seconds after which a test is considered as slow and reported as such in the results.
|
// The number of seconds after which a test is considered as slow and reported as such in the results.
|
||||||
// slowTestThreshold: 5,
|
// slowTestThreshold: 5,
|
||||||
|
1
web/jest.setup.ts
Normal file
1
web/jest.setup.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
import '@testing-library/jest-dom'
|
@ -1,4 +1,5 @@
|
|||||||
import { formatFileSize, formatNumber, formatTime } from './format'
|
import { downloadFile, formatFileSize, formatNumber, formatTime } from './format'
|
||||||
|
|
||||||
describe('formatNumber', () => {
|
describe('formatNumber', () => {
|
||||||
test('should correctly format integers', () => {
|
test('should correctly format integers', () => {
|
||||||
expect(formatNumber(1234567)).toBe('1,234,567')
|
expect(formatNumber(1234567)).toBe('1,234,567')
|
||||||
@ -59,3 +60,45 @@ describe('formatTime', () => {
|
|||||||
expect(formatTime(7200)).toBe('2.00 h')
|
expect(formatTime(7200)).toBe('2.00 h')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
describe('downloadFile', () => {
|
||||||
|
test('should create a link and trigger a download correctly', () => {
|
||||||
|
// Mock data
|
||||||
|
const blob = new Blob(['test content'], { type: 'text/plain' })
|
||||||
|
const fileName = 'test-file.txt'
|
||||||
|
const mockUrl = 'blob:mockUrl'
|
||||||
|
|
||||||
|
// Mock URL.createObjectURL
|
||||||
|
const createObjectURLMock = jest.fn().mockReturnValue(mockUrl)
|
||||||
|
const revokeObjectURLMock = jest.fn()
|
||||||
|
Object.defineProperty(window.URL, 'createObjectURL', { value: createObjectURLMock })
|
||||||
|
Object.defineProperty(window.URL, 'revokeObjectURL', { value: revokeObjectURLMock })
|
||||||
|
|
||||||
|
// Mock createElement and appendChild
|
||||||
|
const mockLink = {
|
||||||
|
href: '',
|
||||||
|
download: '',
|
||||||
|
click: jest.fn(),
|
||||||
|
remove: jest.fn(),
|
||||||
|
}
|
||||||
|
const createElementMock = jest.spyOn(document, 'createElement').mockReturnValue(mockLink as any)
|
||||||
|
const appendChildMock = jest.spyOn(document.body, 'appendChild').mockImplementation((node: Node) => {
|
||||||
|
return node
|
||||||
|
})
|
||||||
|
|
||||||
|
// Call the function
|
||||||
|
downloadFile({ data: blob, fileName })
|
||||||
|
|
||||||
|
// Assertions
|
||||||
|
expect(createObjectURLMock).toHaveBeenCalledWith(blob)
|
||||||
|
expect(createElementMock).toHaveBeenCalledWith('a')
|
||||||
|
expect(mockLink.href).toBe(mockUrl)
|
||||||
|
expect(mockLink.download).toBe(fileName)
|
||||||
|
expect(appendChildMock).toHaveBeenCalledWith(mockLink)
|
||||||
|
expect(mockLink.click).toHaveBeenCalled()
|
||||||
|
expect(mockLink.remove).toHaveBeenCalled()
|
||||||
|
expect(revokeObjectURLMock).toHaveBeenCalledWith(mockUrl)
|
||||||
|
|
||||||
|
// Clean up mocks
|
||||||
|
jest.restoreAllMocks()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
295
web/utils/index.spec.ts
Normal file
295
web/utils/index.spec.ts
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
import {
|
||||||
|
asyncRunSafe,
|
||||||
|
canFindTool,
|
||||||
|
correctModelProvider,
|
||||||
|
correctToolProvider,
|
||||||
|
fetchWithRetry,
|
||||||
|
getPurifyHref,
|
||||||
|
getTextWidthWithCanvas,
|
||||||
|
randomString,
|
||||||
|
removeSpecificQueryParam,
|
||||||
|
sleep,
|
||||||
|
} from './index'
|
||||||
|
|
||||||
|
describe('sleep', () => {
|
||||||
|
it('should wait for the specified time', async () => {
|
||||||
|
const timeVariance = 10
|
||||||
|
const sleepTime = 100
|
||||||
|
const start = Date.now()
|
||||||
|
await sleep(sleepTime)
|
||||||
|
const elapsed = Date.now() - start
|
||||||
|
expect(elapsed).toBeGreaterThanOrEqual(sleepTime - timeVariance)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('asyncRunSafe', () => {
|
||||||
|
it('should return [null, result] when promise resolves', async () => {
|
||||||
|
const result = await asyncRunSafe(Promise.resolve('success'))
|
||||||
|
expect(result).toEqual([null, 'success'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return [error] when promise rejects', async () => {
|
||||||
|
const error = new Error('test error')
|
||||||
|
const result = await asyncRunSafe(Promise.reject(error))
|
||||||
|
expect(result).toEqual([error])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return [Error] when promise rejects with undefined', async () => {
|
||||||
|
// eslint-disable-next-line prefer-promise-reject-errors
|
||||||
|
const result = await asyncRunSafe(Promise.reject())
|
||||||
|
expect(result[0]).toBeInstanceOf(Error)
|
||||||
|
expect(result[0]?.message).toBe('unknown error')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getTextWidthWithCanvas', () => {
|
||||||
|
let originalCreateElement: any
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Store original implementation
|
||||||
|
originalCreateElement = document.createElement
|
||||||
|
|
||||||
|
// Mock canvas and context
|
||||||
|
const measureTextMock = jest.fn().mockReturnValue({ width: 100 })
|
||||||
|
const getContextMock = jest.fn().mockReturnValue({
|
||||||
|
measureText: measureTextMock,
|
||||||
|
font: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
document.createElement = jest.fn().mockReturnValue({
|
||||||
|
getContext: getContextMock,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Restore original implementation
|
||||||
|
document.createElement = originalCreateElement
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return the width of text', () => {
|
||||||
|
const width = getTextWidthWithCanvas('test text')
|
||||||
|
expect(width).toBe(100)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return 0 if context is not available', () => {
|
||||||
|
// Override mock for this test
|
||||||
|
document.createElement = jest.fn().mockReturnValue({
|
||||||
|
getContext: () => null,
|
||||||
|
})
|
||||||
|
|
||||||
|
const width = getTextWidthWithCanvas('test text')
|
||||||
|
expect(width).toBe(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('randomString', () => {
|
||||||
|
it('should generate string of specified length', () => {
|
||||||
|
const result = randomString(10)
|
||||||
|
expect(result.length).toBe(10)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should only contain valid characters', () => {
|
||||||
|
const result = randomString(100)
|
||||||
|
const validChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_'
|
||||||
|
for (const char of result)
|
||||||
|
expect(validChars).toContain(char)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should generate different strings on consecutive calls', () => {
|
||||||
|
const result1 = randomString(20)
|
||||||
|
const result2 = randomString(20)
|
||||||
|
expect(result1).not.toEqual(result2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getPurifyHref', () => {
|
||||||
|
it('should return empty string for falsy input', () => {
|
||||||
|
expect(getPurifyHref('')).toBe('')
|
||||||
|
expect(getPurifyHref(undefined as any)).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should escape HTML characters', () => {
|
||||||
|
expect(getPurifyHref('<script>alert("xss")</script>')).not.toContain('<script>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('fetchWithRetry', () => {
|
||||||
|
it('should return successfully on first try', async () => {
|
||||||
|
const successData = { status: 'success' }
|
||||||
|
const promise = Promise.resolve(successData)
|
||||||
|
|
||||||
|
const result = await fetchWithRetry(promise)
|
||||||
|
|
||||||
|
expect(result).toEqual([null, successData])
|
||||||
|
})
|
||||||
|
|
||||||
|
// it('should retry and succeed on second attempt', async () => {
|
||||||
|
// let attemptCount = 0
|
||||||
|
// const mockFn = new Promise((resolve, reject) => {
|
||||||
|
// attemptCount++
|
||||||
|
// if (attemptCount === 1)
|
||||||
|
// reject(new Error('First attempt failed'))
|
||||||
|
// else
|
||||||
|
// resolve('success')
|
||||||
|
// })
|
||||||
|
|
||||||
|
// const result = await fetchWithRetry(mockFn)
|
||||||
|
|
||||||
|
// expect(result).toEqual([null, 'success'])
|
||||||
|
// expect(attemptCount).toBe(2)
|
||||||
|
// })
|
||||||
|
|
||||||
|
// it('should stop after max retries and return last error', async () => {
|
||||||
|
// const testError = new Error('Test error')
|
||||||
|
// const promise = Promise.reject(testError)
|
||||||
|
|
||||||
|
// const result = await fetchWithRetry(promise, 2)
|
||||||
|
|
||||||
|
// expect(result).toEqual([testError])
|
||||||
|
// })
|
||||||
|
|
||||||
|
// it('should handle non-Error rejection with custom error', async () => {
|
||||||
|
// const stringError = 'string error message'
|
||||||
|
// const promise = Promise.reject(stringError)
|
||||||
|
|
||||||
|
// const result = await fetchWithRetry(promise, 0)
|
||||||
|
|
||||||
|
// expect(result[0]).toBeInstanceOf(Error)
|
||||||
|
// expect(result[0]?.message).toBe('unknown error')
|
||||||
|
// })
|
||||||
|
|
||||||
|
// it('should use default 3 retries when retries parameter is not provided', async () => {
|
||||||
|
// let attempts = 0
|
||||||
|
// const mockFn = () => new Promise((resolve, reject) => {
|
||||||
|
// attempts++
|
||||||
|
// reject(new Error(`Attempt ${attempts} failed`))
|
||||||
|
// })
|
||||||
|
|
||||||
|
// await fetchWithRetry(mockFn())
|
||||||
|
|
||||||
|
// expect(attempts).toBe(4) // Initial attempt + 3 retries
|
||||||
|
// })
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('correctModelProvider', () => {
|
||||||
|
it('should return empty string for falsy input', () => {
|
||||||
|
expect(correctModelProvider('')).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return the provider if it already contains a slash', () => {
|
||||||
|
expect(correctModelProvider('company/model')).toBe('company/model')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should format google provider correctly', () => {
|
||||||
|
expect(correctModelProvider('google')).toBe('langgenius/gemini/google')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should format standard providers correctly', () => {
|
||||||
|
expect(correctModelProvider('openai')).toBe('langgenius/openai/openai')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('correctToolProvider', () => {
|
||||||
|
it('should return empty string for falsy input', () => {
|
||||||
|
expect(correctToolProvider('')).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return the provider if toolInCollectionList is true', () => {
|
||||||
|
expect(correctToolProvider('any-provider', true)).toBe('any-provider')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return the provider if it already contains a slash', () => {
|
||||||
|
expect(correctToolProvider('company/tool')).toBe('company/tool')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should format special tool providers correctly', () => {
|
||||||
|
expect(correctToolProvider('stepfun')).toBe('langgenius/stepfun_tool/stepfun')
|
||||||
|
expect(correctToolProvider('jina')).toBe('langgenius/jina_tool/jina')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should format standard tool providers correctly', () => {
|
||||||
|
expect(correctToolProvider('standard')).toBe('langgenius/standard/standard')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('canFindTool', () => {
|
||||||
|
it('should match when IDs are identical', () => {
|
||||||
|
expect(canFindTool('tool-id', 'tool-id')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should match when provider ID is formatted with standard pattern', () => {
|
||||||
|
expect(canFindTool('langgenius/tool-id/tool-id', 'tool-id')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should match when provider ID is formatted with tool pattern', () => {
|
||||||
|
expect(canFindTool('langgenius/tool-id_tool/tool-id', 'tool-id')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not match when IDs are completely different', () => {
|
||||||
|
expect(canFindTool('provider-a', 'tool-b')).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('removeSpecificQueryParam', () => {
|
||||||
|
let originalLocation: any
|
||||||
|
let originalReplaceState: any
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
originalLocation = window.location
|
||||||
|
originalReplaceState = window.history.replaceState
|
||||||
|
|
||||||
|
const mockUrl = new URL('https://example.com?param1=value1¶m2=value2¶m3=value3')
|
||||||
|
|
||||||
|
// Mock window.location using defineProperty to handle URL properly
|
||||||
|
delete (window as any).location
|
||||||
|
Object.defineProperty(window, 'location', {
|
||||||
|
writable: true,
|
||||||
|
value: {
|
||||||
|
...originalLocation,
|
||||||
|
href: mockUrl.href,
|
||||||
|
search: mockUrl.search,
|
||||||
|
toString: () => mockUrl.toString(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
window.history.replaceState = jest.fn()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
Object.defineProperty(window, 'location', {
|
||||||
|
writable: true,
|
||||||
|
value: originalLocation,
|
||||||
|
})
|
||||||
|
window.history.replaceState = originalReplaceState
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should remove a single query parameter', () => {
|
||||||
|
removeSpecificQueryParam('param2')
|
||||||
|
expect(window.history.replaceState).toHaveBeenCalledTimes(1)
|
||||||
|
const replaceStateCall = (window.history.replaceState as jest.Mock).mock.calls[0]
|
||||||
|
expect(replaceStateCall[0]).toBe(null)
|
||||||
|
expect(replaceStateCall[1]).toBe('')
|
||||||
|
expect(replaceStateCall[2]).toMatch(/param1=value1/)
|
||||||
|
expect(replaceStateCall[2]).toMatch(/param3=value3/)
|
||||||
|
expect(replaceStateCall[2]).not.toMatch(/param2=value2/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should remove multiple query parameters', () => {
|
||||||
|
removeSpecificQueryParam(['param1', 'param3'])
|
||||||
|
expect(window.history.replaceState).toHaveBeenCalledTimes(1)
|
||||||
|
const replaceStateCall = (window.history.replaceState as jest.Mock).mock.calls[0]
|
||||||
|
expect(replaceStateCall[2]).toMatch(/param2=value2/)
|
||||||
|
expect(replaceStateCall[2]).not.toMatch(/param1=value1/)
|
||||||
|
expect(replaceStateCall[2]).not.toMatch(/param3=value3/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle non-existent parameters gracefully', () => {
|
||||||
|
removeSpecificQueryParam('nonexistent')
|
||||||
|
|
||||||
|
expect(window.history.replaceState).toHaveBeenCalledTimes(1)
|
||||||
|
const replaceStateCall = (window.history.replaceState as jest.Mock).mock.calls[0]
|
||||||
|
expect(replaceStateCall[2]).toMatch(/param1=value1/)
|
||||||
|
expect(replaceStateCall[2]).toMatch(/param2=value2/)
|
||||||
|
expect(replaceStateCall[2]).toMatch(/param3=value3/)
|
||||||
|
})
|
||||||
|
})
|
Loading…
x
Reference in New Issue
Block a user