/** * @jest-environment ./test/env/jsdom-with-backdoors */ import { h } from 'preact'; import { fireEvent, render, screen, waitFor } from '@testing-library/preact'; import 'test/env/jsdom-with-backdoors'; import { FileChooser, FileChooserProps } from '../../js/components/FileChooser'; type ShowOpenFilePicker = typeof window.showOpenFilePicker; const FAKE_FILE_HANDLE = { kind: 'file', name: 'file name', createWritable: jest.fn(), getFile: jest.fn(), isSameEntry: jest.fn(), queryPermission: jest.fn(), requestPermission: jest.fn(), isFile: true, isDirectory: false, } as const; const TEST_FILE_TYPES = [ { description: 'Test description 1', accept: { mime1: ['.ext1', '.ext2'] }, }, { description: 'Test description 2', accept: { mime2: ['.ext1', '.ext2'] }, }, ]; const NOP = () => { /* do nothing */ }; // eslint-disable-next-line no-undef const EMPTY_FILE_LIST = backdoors.newFileList(); const FAKE_FILE = new File([], 'fake'); describe('FileChooser', () => { describe('input-based chooser', () => { it('should be instantiable', () => { const { container } = render( ); expect(container).not.toBeNull(); }); it('should use the file input element', async () => { render(); const inputElement = (await screen.findByRole( 'button' )) as HTMLInputElement; expect(inputElement.type).toBe('file'); }); it('should pass the correct MIME types and file extensions to the picker', async () => { const onChange = jest.fn(); render( ); const inputElement = (await screen.findByRole( 'button' )) as HTMLInputElement; expect(inputElement.accept).toBe('.ext1,.ext2'); }); it('should fire a callback with empty list when no files are selected', async () => { const onChange = jest.fn(); render(); const inputElement = (await screen.findByRole( 'button' )) as HTMLInputElement; inputElement.files = EMPTY_FILE_LIST; fireEvent.change(inputElement); await waitFor(() => { expect(onChange).toHaveBeenCalledWith([]); }); }); it('should fire a callback with a file handle when a file is selected', async () => { const onChange = jest.fn< ReturnType, Parameters >(); render(); const inputElement = (await screen.findByRole( 'button' )) as HTMLInputElement; // eslint-disable-next-line no-undef inputElement.files = backdoors.newFileList(FAKE_FILE); fireEvent.change(inputElement); await waitFor(async () => { expect(onChange).toHaveBeenCalled(); const handleList = onChange.mock.calls[0][0]; expect(handleList).toHaveLength(1); const handle = handleList[0]; expect(handle.kind).toBe('file'); expect(handle.name).toBe(FAKE_FILE.name); await expect(handle.getFile()).resolves.toBe(FAKE_FILE); await expect(handle.createWritable()).rejects.toEqual( 'File not writable.' ); await expect( handle.queryPermission({ mode: 'readwrite' }) ).resolves.toBe('denied'); }); }); }); describe('picker-base chooser', () => { const mockFilePicker = jest.fn< ReturnType, Parameters >(); beforeEach(() => { if (typeof window.showOpenFilePicker !== 'undefined') { throw new Error('window.showOpenFilePicker not undefined'); } window.showOpenFilePicker = mockFilePicker as unknown as ShowOpenFilePicker; mockFilePicker.mockReset(); }); afterEach(() => { window.showOpenFilePicker = undefined as unknown as typeof window.showOpenFilePicker; }); it('should be instantiable', () => { const { container } = render( ); expect(container).not.toBeNull(); }); it('should pass the correct MIME types and file extensions to the picker', async () => { mockFilePicker.mockResolvedValueOnce([]); const onChange = jest.fn(); render( ); fireEvent.click(await screen.findByText('Choose File')); await waitFor(() => { expect(mockFilePicker).toHaveBeenCalledWith< [OpenFilePickerOptions] >({ excludeAcceptAllOption: true, multiple: false, types: TEST_FILE_TYPES, }); expect(onChange).toHaveBeenCalledWith([]); }); }); it('should fire a callback with empty list when no files are selected', async () => { mockFilePicker.mockResolvedValueOnce([]); const onChange = jest.fn(); render(); fireEvent.click(await screen.findByText('Choose File')); await waitFor(() => { expect(mockFilePicker).toHaveBeenCalled(); expect(onChange).toHaveBeenCalledWith([]); }); }); it('should fire a callback with a file handle when a file is selected', async () => { mockFilePicker.mockResolvedValueOnce([FAKE_FILE_HANDLE]); const onChange = jest.fn< ReturnType, Parameters >(); render(); fireEvent.click(await screen.findByText('Choose File')); await waitFor(() => { expect(mockFilePicker).toHaveBeenCalled(); expect(onChange).toHaveBeenCalled(); const handleList = onChange.mock.calls[0][0]; expect(handleList).toHaveLength(1); const handle = handleList[0]; expect(handle.kind).toBe(FAKE_FILE_HANDLE.kind); expect(handle.name).toBe(FAKE_FILE_HANDLE.name); }); }); }); });