diff --git a/js/components/FileChooser.tsx b/js/components/FileChooser.tsx index 88492cf..46d947d 100644 --- a/js/components/FileChooser.tsx +++ b/js/components/FileChooser.tsx @@ -60,17 +60,24 @@ const InputFileChooser = ({ // // which allows pretty generous interpretations. // - // Update(whscullin) - Adding the MIME type seems to block loading anything - // from iCloud when using Safari, so reverting to simply using extensions for - // now. - const newAccept = []; + // However, it turns out that Safari does not like MIME types when + // reading from iCloud. It may be related to: + // + // https://bugs.webkit.org/show_bug.cgi?id=244666 + // + // or it could be related to some permissions issue with iCloud. For + // the moment, not adding the MIME type is sufficient. + const newAccept: string[] = []; for (const type of accept) { - for (const [/* typeString */, suffixes] of Object.entries(type.accept)) { + for (let [/* typeString */, suffixes] of Object.entries(type.accept)) { // newAccept.push(typeString); - if (Array.isArray(suffixes)) { - newAccept.push(...suffixes); - } else { - newAccept.push(suffixes); + if (!Array.isArray(suffixes)) { + suffixes = [suffixes]; + } + for (const suffix of suffixes) { + if (!newAccept.includes(suffix)) { + newAccept.push(suffix); + } } } } @@ -157,6 +164,10 @@ const FilePickerChooser = ({ * Using `window.showOpenFilePicker` has the advantage of allowing read/write * access to the file, whereas the regular input element only gives read * access. + * + * The FileChooser takes an optional `accept` parameter that specifies which + * types of files can be opened. The parameter is a map of MIME type to file + * extension. If the MIME type is the empty string, t */ export const FileChooser = ({ onChange, diff --git a/test/components/FileChooser.spec.tsx b/test/components/FileChooser.spec.tsx index 99d7c76..1f137a9 100644 --- a/test/components/FileChooser.spec.tsx +++ b/test/components/FileChooser.spec.tsx @@ -22,6 +22,17 @@ const FAKE_FILE_HANDLE = { 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 @@ -44,6 +55,13 @@ describe('FileChooser', () => { 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(); @@ -97,6 +115,23 @@ describe('FileChooser', () => { 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).toBeCalledWith<[OpenFilePickerOptions]>({ + 'excludeAcceptAllOption': true, + 'multiple': false, + 'types': TEST_FILE_TYPES + }); + expect(onChange).toBeCalledWith([]); + }); + }); + it('should fire a callback with empty list when no files are selected', async () => { mockFilePicker.mockResolvedValueOnce([]); const onChange = jest.fn();