Improve FileChooser behavior with file input element (#197)

Before, the `FileChooser` could create a malformed `accept` parameter
by repeating extensions if they were specified by several MIME types.
This no longer happens and there is a test for it.
This commit is contained in:
Ian Flanigan 2023-08-13 00:49:46 +02:00 committed by GitHub
parent 4490cc9bc7
commit 0047b9dbb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 55 additions and 9 deletions

View File

@ -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,

View File

@ -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(<FileChooser control='input' onChange={onChange} accept={TEST_FILE_TYPES} />);
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(<FileChooser control='input' onChange={onChange} />);
@ -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(<FileChooser control='picker' onChange={onChange} accept={TEST_FILE_TYPES} />);
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();