Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

utils.spec.ts 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. import mime from 'mime'
  2. import { upload } from '@/service/base'
  3. import {
  4. downloadFile,
  5. fileIsUploaded,
  6. fileUpload,
  7. getFileAppearanceType,
  8. getFileExtension,
  9. getFileNameFromUrl,
  10. getFilesInLogs,
  11. getProcessedFiles,
  12. getProcessedFilesFromResponse,
  13. getSupportFileExtensionList,
  14. getSupportFileType,
  15. isAllowedFileExtension,
  16. } from './utils'
  17. import { FileAppearanceTypeEnum } from './types'
  18. import { SupportUploadFileTypes } from '@/app/components/workflow/types'
  19. import { TransferMethod } from '@/types/app'
  20. import { FILE_EXTS } from '../prompt-editor/constants'
  21. jest.mock('mime', () => ({
  22. __esModule: true,
  23. default: {
  24. getAllExtensions: jest.fn(),
  25. },
  26. }))
  27. jest.mock('@/service/base', () => ({
  28. upload: jest.fn(),
  29. }))
  30. describe('file-uploader utils', () => {
  31. beforeEach(() => {
  32. jest.clearAllMocks()
  33. })
  34. describe('fileUpload', () => {
  35. it('should handle successful file upload', async () => {
  36. const mockFile = new File(['test'], 'test.txt')
  37. const mockCallbacks = {
  38. onProgressCallback: jest.fn(),
  39. onSuccessCallback: jest.fn(),
  40. onErrorCallback: jest.fn(),
  41. }
  42. jest.mocked(upload).mockResolvedValue({ id: '123' })
  43. await fileUpload({
  44. file: mockFile,
  45. ...mockCallbacks,
  46. })
  47. expect(upload).toHaveBeenCalled()
  48. expect(mockCallbacks.onSuccessCallback).toHaveBeenCalledWith({ id: '123' })
  49. })
  50. })
  51. describe('getFileExtension', () => {
  52. it('should get extension from mimetype', () => {
  53. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf']))
  54. expect(getFileExtension('file', 'application/pdf')).toBe('pdf')
  55. })
  56. it('should get extension from mimetype and file name 1', () => {
  57. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf']))
  58. expect(getFileExtension('file.pdf', 'application/pdf')).toBe('pdf')
  59. })
  60. it('should get extension from mimetype with multiple ext candidates with filename hint', () => {
  61. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['der', 'crt', 'pem']))
  62. expect(getFileExtension('file.pem', 'application/x-x509-ca-cert')).toBe('pem')
  63. })
  64. it('should get extension from mimetype with multiple ext candidates without filename hint', () => {
  65. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['der', 'crt', 'pem']))
  66. expect(getFileExtension('file', 'application/x-x509-ca-cert')).toBe('der')
  67. })
  68. it('should get extension from filename if mimetype fails', () => {
  69. jest.mocked(mime.getAllExtensions).mockReturnValue(null)
  70. expect(getFileExtension('file.txt', '')).toBe('txt')
  71. expect(getFileExtension('file.txt.docx', '')).toBe('docx')
  72. expect(getFileExtension('file', '')).toBe('')
  73. })
  74. it('should return empty string for remote files', () => {
  75. expect(getFileExtension('file.txt', '', true)).toBe('')
  76. })
  77. })
  78. describe('getFileAppearanceType', () => {
  79. it('should identify gif files', () => {
  80. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['gif']))
  81. expect(getFileAppearanceType('image.gif', 'image/gif'))
  82. .toBe(FileAppearanceTypeEnum.gif)
  83. })
  84. it('should identify image files', () => {
  85. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['jpg']))
  86. expect(getFileAppearanceType('image.jpg', 'image/jpeg'))
  87. .toBe(FileAppearanceTypeEnum.image)
  88. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['jpeg']))
  89. expect(getFileAppearanceType('image.jpeg', 'image/jpeg'))
  90. .toBe(FileAppearanceTypeEnum.image)
  91. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['png']))
  92. expect(getFileAppearanceType('image.png', 'image/png'))
  93. .toBe(FileAppearanceTypeEnum.image)
  94. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['webp']))
  95. expect(getFileAppearanceType('image.webp', 'image/webp'))
  96. .toBe(FileAppearanceTypeEnum.image)
  97. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['svg']))
  98. expect(getFileAppearanceType('image.svg', 'image/svgxml'))
  99. .toBe(FileAppearanceTypeEnum.image)
  100. })
  101. it('should identify video files', () => {
  102. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mp4']))
  103. expect(getFileAppearanceType('video.mp4', 'video/mp4'))
  104. .toBe(FileAppearanceTypeEnum.video)
  105. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mov']))
  106. expect(getFileAppearanceType('video.mov', 'video/quicktime'))
  107. .toBe(FileAppearanceTypeEnum.video)
  108. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mpeg']))
  109. expect(getFileAppearanceType('video.mpeg', 'video/mpeg'))
  110. .toBe(FileAppearanceTypeEnum.video)
  111. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['webm']))
  112. expect(getFileAppearanceType('video.web', 'video/webm'))
  113. .toBe(FileAppearanceTypeEnum.video)
  114. })
  115. it('should identify audio files', () => {
  116. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mp3']))
  117. expect(getFileAppearanceType('audio.mp3', 'audio/mpeg'))
  118. .toBe(FileAppearanceTypeEnum.audio)
  119. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['m4a']))
  120. expect(getFileAppearanceType('audio.m4a', 'audio/mp4'))
  121. .toBe(FileAppearanceTypeEnum.audio)
  122. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['wav']))
  123. expect(getFileAppearanceType('audio.wav', 'audio/vnd.wav'))
  124. .toBe(FileAppearanceTypeEnum.audio)
  125. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['amr']))
  126. expect(getFileAppearanceType('audio.amr', 'audio/AMR'))
  127. .toBe(FileAppearanceTypeEnum.audio)
  128. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mpga']))
  129. expect(getFileAppearanceType('audio.mpga', 'audio/mpeg'))
  130. .toBe(FileAppearanceTypeEnum.audio)
  131. })
  132. it('should identify code files', () => {
  133. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['html']))
  134. expect(getFileAppearanceType('index.html', 'text/html'))
  135. .toBe(FileAppearanceTypeEnum.code)
  136. })
  137. it('should identify PDF files', () => {
  138. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf']))
  139. expect(getFileAppearanceType('doc.pdf', 'application/pdf'))
  140. .toBe(FileAppearanceTypeEnum.pdf)
  141. })
  142. it('should identify markdown files', () => {
  143. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['md']))
  144. expect(getFileAppearanceType('file.md', 'text/markdown'))
  145. .toBe(FileAppearanceTypeEnum.markdown)
  146. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['markdown']))
  147. expect(getFileAppearanceType('file.markdown', 'text/markdown'))
  148. .toBe(FileAppearanceTypeEnum.markdown)
  149. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mdx']))
  150. expect(getFileAppearanceType('file.mdx', 'text/mdx'))
  151. .toBe(FileAppearanceTypeEnum.markdown)
  152. })
  153. it('should identify excel files', () => {
  154. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['xlsx']))
  155. expect(getFileAppearanceType('doc.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'))
  156. .toBe(FileAppearanceTypeEnum.excel)
  157. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['xls']))
  158. expect(getFileAppearanceType('doc.xls', 'application/vnd.ms-excel'))
  159. .toBe(FileAppearanceTypeEnum.excel)
  160. })
  161. it('should identify word files', () => {
  162. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['doc']))
  163. expect(getFileAppearanceType('doc.doc', 'application/msword'))
  164. .toBe(FileAppearanceTypeEnum.word)
  165. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['docx']))
  166. expect(getFileAppearanceType('doc.docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'))
  167. .toBe(FileAppearanceTypeEnum.word)
  168. })
  169. it('should identify word files', () => {
  170. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['ppt']))
  171. expect(getFileAppearanceType('doc.ppt', 'application/vnd.ms-powerpoint'))
  172. .toBe(FileAppearanceTypeEnum.ppt)
  173. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pptx']))
  174. expect(getFileAppearanceType('doc.pptx', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'))
  175. .toBe(FileAppearanceTypeEnum.ppt)
  176. })
  177. it('should identify document files', () => {
  178. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['txt']))
  179. expect(getFileAppearanceType('file.txt', 'text/plain'))
  180. .toBe(FileAppearanceTypeEnum.document)
  181. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['csv']))
  182. expect(getFileAppearanceType('file.csv', 'text/csv'))
  183. .toBe(FileAppearanceTypeEnum.document)
  184. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['msg']))
  185. expect(getFileAppearanceType('file.msg', 'application/vnd.ms-outlook'))
  186. .toBe(FileAppearanceTypeEnum.document)
  187. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['eml']))
  188. expect(getFileAppearanceType('file.eml', 'message/rfc822'))
  189. .toBe(FileAppearanceTypeEnum.document)
  190. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['xml']))
  191. expect(getFileAppearanceType('file.xml', 'application/rssxml'))
  192. .toBe(FileAppearanceTypeEnum.document)
  193. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['epub']))
  194. expect(getFileAppearanceType('file.epub', 'application/epubzip'))
  195. .toBe(FileAppearanceTypeEnum.document)
  196. })
  197. it('should handle null mime extension', () => {
  198. jest.mocked(mime.getAllExtensions).mockReturnValue(null)
  199. expect(getFileAppearanceType('file.txt', 'text/plain'))
  200. .toBe(FileAppearanceTypeEnum.document)
  201. })
  202. })
  203. describe('getSupportFileType', () => {
  204. it('should return custom type when isCustom is true', () => {
  205. expect(getSupportFileType('file.txt', '', true))
  206. .toBe(SupportUploadFileTypes.custom)
  207. })
  208. it('should return file type when isCustom is false', () => {
  209. expect(getSupportFileType('file.txt', 'text/plain'))
  210. .toBe(SupportUploadFileTypes.document)
  211. })
  212. })
  213. describe('getProcessedFiles', () => {
  214. it('should process files correctly', () => {
  215. const files = [{
  216. id: '123',
  217. name: 'test.txt',
  218. size: 1024,
  219. type: 'text/plain',
  220. progress: 100,
  221. supportFileType: 'document',
  222. transferMethod: TransferMethod.remote_url,
  223. url: 'http://example.com',
  224. uploadedId: '123',
  225. }]
  226. const result = getProcessedFiles(files)
  227. expect(result[0]).toEqual({
  228. type: 'document',
  229. transfer_method: TransferMethod.remote_url,
  230. url: 'http://example.com',
  231. upload_file_id: '123',
  232. })
  233. })
  234. })
  235. describe('getProcessedFilesFromResponse', () => {
  236. it('should process files correctly', () => {
  237. const files = [{
  238. related_id: '2a38e2ca-1295-415d-a51d-65d4ff9912d9',
  239. extension: '.jpeg',
  240. filename: 'test.jpeg',
  241. size: 2881761,
  242. mime_type: 'image/jpeg',
  243. transfer_method: TransferMethod.local_file,
  244. type: 'image',
  245. url: 'https://upload.dify.dev/files/xxx/file-preview',
  246. }]
  247. const result = getProcessedFilesFromResponse(files)
  248. expect(result[0]).toEqual({
  249. id: '2a38e2ca-1295-415d-a51d-65d4ff9912d9',
  250. name: 'test.jpeg',
  251. size: 2881761,
  252. type: 'image/jpeg',
  253. progress: 100,
  254. transferMethod: TransferMethod.local_file,
  255. supportFileType: 'image',
  256. uploadedId: '2a38e2ca-1295-415d-a51d-65d4ff9912d9',
  257. url: 'https://upload.dify.dev/files/xxx/file-preview',
  258. })
  259. })
  260. })
  261. describe('getFileNameFromUrl', () => {
  262. it('should extract filename from URL', () => {
  263. expect(getFileNameFromUrl('http://example.com/path/file.txt'))
  264. .toBe('file.txt')
  265. })
  266. })
  267. describe('getSupportFileExtensionList', () => {
  268. it('should handle custom file types', () => {
  269. const result = getSupportFileExtensionList(
  270. [SupportUploadFileTypes.custom],
  271. ['.pdf', '.txt', '.doc'],
  272. )
  273. expect(result).toEqual(['PDF', 'TXT', 'DOC'])
  274. })
  275. it('should handle standard file types', () => {
  276. const mockFileExts = {
  277. image: ['JPG', 'PNG'],
  278. document: ['PDF', 'TXT'],
  279. video: ['MP4', 'MOV'],
  280. }
  281. // Temporarily mock FILE_EXTS
  282. const originalFileExts = { ...FILE_EXTS }
  283. Object.assign(FILE_EXTS, mockFileExts)
  284. const result = getSupportFileExtensionList(
  285. ['image', 'document'],
  286. [],
  287. )
  288. expect(result).toEqual(['JPG', 'PNG', 'PDF', 'TXT'])
  289. // Restore original FILE_EXTS
  290. Object.assign(FILE_EXTS, originalFileExts)
  291. })
  292. it('should return empty array for empty inputs', () => {
  293. const result = getSupportFileExtensionList([], [])
  294. expect(result).toEqual([])
  295. })
  296. it('should prioritize custom types over standard types', () => {
  297. const mockFileExts = {
  298. image: ['JPG', 'PNG'],
  299. }
  300. // Temporarily mock FILE_EXTS
  301. const originalFileExts = { ...FILE_EXTS }
  302. Object.assign(FILE_EXTS, mockFileExts)
  303. const result = getSupportFileExtensionList(
  304. [SupportUploadFileTypes.custom, 'image'],
  305. ['.csv', '.xml'],
  306. )
  307. expect(result).toEqual(['CSV', 'XML'])
  308. // Restore original FILE_EXTS
  309. Object.assign(FILE_EXTS, originalFileExts)
  310. })
  311. })
  312. describe('isAllowedFileExtension', () => {
  313. it('should validate allowed file extensions', () => {
  314. jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf']))
  315. expect(isAllowedFileExtension(
  316. 'test.pdf',
  317. 'application/pdf',
  318. ['document'],
  319. ['.pdf'],
  320. )).toBe(true)
  321. })
  322. })
  323. describe('getFilesInLogs', () => {
  324. const mockFileData = {
  325. dify_model_identity: '__dify__file__',
  326. related_id: '123',
  327. filename: 'test.pdf',
  328. size: 1024,
  329. mime_type: 'application/pdf',
  330. transfer_method: 'local_file',
  331. type: 'document',
  332. url: 'http://example.com/test.pdf',
  333. }
  334. it('should handle empty or null input', () => {
  335. expect(getFilesInLogs(null)).toEqual([])
  336. expect(getFilesInLogs({})).toEqual([])
  337. expect(getFilesInLogs(undefined)).toEqual([])
  338. })
  339. it('should process single file object', () => {
  340. const input = {
  341. file1: mockFileData,
  342. }
  343. const expected = [{
  344. varName: 'file1',
  345. list: [{
  346. id: '123',
  347. name: 'test.pdf',
  348. size: 1024,
  349. type: 'application/pdf',
  350. progress: 100,
  351. transferMethod: 'local_file',
  352. supportFileType: 'document',
  353. uploadedId: '123',
  354. url: 'http://example.com/test.pdf',
  355. }],
  356. }]
  357. expect(getFilesInLogs(input)).toEqual(expected)
  358. })
  359. it('should process array of files', () => {
  360. const input = {
  361. files: [mockFileData, mockFileData],
  362. }
  363. const expected = [{
  364. varName: 'files',
  365. list: [
  366. {
  367. id: '123',
  368. name: 'test.pdf',
  369. size: 1024,
  370. type: 'application/pdf',
  371. progress: 100,
  372. transferMethod: 'local_file',
  373. supportFileType: 'document',
  374. uploadedId: '123',
  375. url: 'http://example.com/test.pdf',
  376. },
  377. {
  378. id: '123',
  379. name: 'test.pdf',
  380. size: 1024,
  381. type: 'application/pdf',
  382. progress: 100,
  383. transferMethod: 'local_file',
  384. supportFileType: 'document',
  385. uploadedId: '123',
  386. url: 'http://example.com/test.pdf',
  387. },
  388. ],
  389. }]
  390. expect(getFilesInLogs(input)).toEqual(expected)
  391. })
  392. it('should ignore non-file objects and arrays', () => {
  393. const input = {
  394. regularString: 'not a file',
  395. regularNumber: 123,
  396. regularArray: [1, 2, 3],
  397. regularObject: { key: 'value' },
  398. file: mockFileData,
  399. }
  400. const expected = [{
  401. varName: 'file',
  402. list: [{
  403. id: '123',
  404. name: 'test.pdf',
  405. size: 1024,
  406. type: 'application/pdf',
  407. progress: 100,
  408. transferMethod: 'local_file',
  409. supportFileType: 'document',
  410. uploadedId: '123',
  411. url: 'http://example.com/test.pdf',
  412. }],
  413. }]
  414. expect(getFilesInLogs(input)).toEqual(expected)
  415. })
  416. it('should handle mixed file types in array', () => {
  417. const input = {
  418. mixedFiles: [
  419. mockFileData,
  420. { notAFile: true },
  421. mockFileData,
  422. ],
  423. }
  424. const expected = [{
  425. varName: 'mixedFiles',
  426. list: [
  427. {
  428. id: '123',
  429. name: 'test.pdf',
  430. size: 1024,
  431. type: 'application/pdf',
  432. progress: 100,
  433. transferMethod: 'local_file',
  434. supportFileType: 'document',
  435. uploadedId: '123',
  436. url: 'http://example.com/test.pdf',
  437. },
  438. {
  439. id: undefined,
  440. name: undefined,
  441. progress: 100,
  442. size: 0,
  443. supportFileType: undefined,
  444. transferMethod: undefined,
  445. type: undefined,
  446. uploadedId: undefined,
  447. url: undefined,
  448. },
  449. {
  450. id: '123',
  451. name: 'test.pdf',
  452. size: 1024,
  453. type: 'application/pdf',
  454. progress: 100,
  455. transferMethod: 'local_file',
  456. supportFileType: 'document',
  457. uploadedId: '123',
  458. url: 'http://example.com/test.pdf',
  459. },
  460. ],
  461. }]
  462. expect(getFilesInLogs(input)).toEqual(expected)
  463. })
  464. })
  465. describe('fileIsUploaded', () => {
  466. it('should identify uploaded files', () => {
  467. expect(fileIsUploaded({
  468. uploadedId: '123',
  469. progress: 100,
  470. } as any)).toBe(true)
  471. })
  472. it('should identify remote files as uploaded', () => {
  473. expect(fileIsUploaded({
  474. transferMethod: TransferMethod.remote_url,
  475. progress: 100,
  476. } as any)).toBe(true)
  477. })
  478. })
  479. describe('downloadFile', () => {
  480. let mockAnchor: HTMLAnchorElement
  481. let createElementMock: jest.SpyInstance
  482. let appendChildMock: jest.SpyInstance
  483. let removeChildMock: jest.SpyInstance
  484. beforeEach(() => {
  485. // Mock createElement and appendChild
  486. mockAnchor = {
  487. href: '',
  488. download: '',
  489. style: { display: '' },
  490. target: '',
  491. title: '',
  492. click: jest.fn(),
  493. } as unknown as HTMLAnchorElement
  494. createElementMock = jest.spyOn(document, 'createElement').mockReturnValue(mockAnchor as any)
  495. appendChildMock = jest.spyOn(document.body, 'appendChild').mockImplementation((node: Node) => {
  496. return node
  497. })
  498. removeChildMock = jest.spyOn(document.body, 'removeChild').mockImplementation((node: Node) => {
  499. return node
  500. })
  501. })
  502. afterEach(() => {
  503. jest.resetAllMocks()
  504. })
  505. it('should create and trigger download with correct attributes', () => {
  506. const url = 'https://example.com/test.pdf'
  507. const filename = 'test.pdf'
  508. downloadFile(url, filename)
  509. // Verify anchor element was created with correct properties
  510. expect(createElementMock).toHaveBeenCalledWith('a')
  511. expect(mockAnchor.href).toBe(url)
  512. expect(mockAnchor.download).toBe(filename)
  513. expect(mockAnchor.style.display).toBe('none')
  514. expect(mockAnchor.target).toBe('_blank')
  515. expect(mockAnchor.title).toBe(filename)
  516. // Verify DOM operations
  517. expect(appendChildMock).toHaveBeenCalledWith(mockAnchor)
  518. expect(mockAnchor.click).toHaveBeenCalled()
  519. expect(removeChildMock).toHaveBeenCalledWith(mockAnchor)
  520. })
  521. it('should handle empty filename', () => {
  522. const url = 'https://example.com/test.pdf'
  523. const filename = ''
  524. downloadFile(url, filename)
  525. expect(mockAnchor.download).toBe('')
  526. expect(mockAnchor.title).toBe('')
  527. })
  528. it('should handle empty url', () => {
  529. const url = ''
  530. const filename = 'test.pdf'
  531. downloadFile(url, filename)
  532. expect(mockAnchor.href).toBe('')
  533. })
  534. })
  535. })