2022-05-28 17:52:48 +00:00
|
|
|
/**
|
|
|
|
* @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;
|
|
|
|
|
2023-08-12 22:49:46 +00:00
|
|
|
const TEST_FILE_TYPES = [
|
|
|
|
{
|
|
|
|
description: 'Test description 1',
|
|
|
|
accept: { 'mime1': ['.ext1', '.ext2'] }
|
|
|
|
},
|
|
|
|
{
|
|
|
|
description: 'Test description 2',
|
|
|
|
accept: { 'mime2': ['.ext1', '.ext2'] }
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
2022-05-31 15:38:40 +00:00
|
|
|
const NOP = () => { /* do nothing */ };
|
|
|
|
|
2022-05-28 17:52:48 +00:00
|
|
|
// 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', () => {
|
2022-05-31 15:38:40 +00:00
|
|
|
const { container } = render(<FileChooser control='input' onChange={NOP} />);
|
2022-05-28 17:52:48 +00:00
|
|
|
|
|
|
|
expect(container).not.toBeNull();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should use the file input element', async () => {
|
2022-05-31 15:38:40 +00:00
|
|
|
render(<FileChooser control='input' onChange={NOP} />);
|
2022-05-28 17:52:48 +00:00
|
|
|
|
|
|
|
const inputElement = await screen.findByRole('button') as HTMLInputElement;
|
|
|
|
expect(inputElement.type).toBe('file');
|
|
|
|
});
|
|
|
|
|
2023-08-12 22:49:46 +00:00
|
|
|
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');
|
|
|
|
});
|
|
|
|
|
2022-05-28 17:52:48 +00:00
|
|
|
it('should fire a callback with empty list when no files are selected', async () => {
|
|
|
|
const onChange = jest.fn();
|
|
|
|
render(<FileChooser control='input' onChange={onChange} />);
|
|
|
|
const inputElement = await screen.findByRole('button') as HTMLInputElement;
|
|
|
|
inputElement.files = EMPTY_FILE_LIST;
|
|
|
|
fireEvent.change(inputElement);
|
|
|
|
await waitFor(() => {
|
|
|
|
expect(onChange).toBeCalledWith([]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should fire a callback with a file handle when a file is selected', async () => {
|
|
|
|
const onChange = jest.fn<ReturnType<FileChooserProps['onChange']>, Parameters<FileChooserProps['onChange']>>();
|
|
|
|
render(<FileChooser control='input' onChange={onChange} />);
|
|
|
|
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.');
|
2022-06-03 17:12:30 +00:00
|
|
|
await expect(handle.queryPermission({ mode: 'readwrite' })).resolves.toBe('denied');
|
2022-05-28 17:52:48 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('picker-base chooser', () => {
|
|
|
|
const mockFilePicker = jest.fn<ReturnType<ShowOpenFilePicker>, Parameters<ShowOpenFilePicker>>();
|
|
|
|
|
|
|
|
beforeEach(() => {
|
2022-05-30 16:29:48 +00:00
|
|
|
if (typeof window.showOpenFilePicker !== 'undefined') {
|
|
|
|
throw new Error('window.showOpenFilePicker not undefined');
|
|
|
|
}
|
2022-05-28 17:52:48 +00:00
|
|
|
window.showOpenFilePicker = mockFilePicker as unknown as ShowOpenFilePicker;
|
|
|
|
mockFilePicker.mockReset();
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
window.showOpenFilePicker = undefined as unknown as typeof window.showOpenFilePicker;
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should be instantiable', () => {
|
2022-05-31 15:38:40 +00:00
|
|
|
const { container } = render(<FileChooser control='picker' onChange={NOP} />);
|
2022-05-28 17:52:48 +00:00
|
|
|
|
|
|
|
expect(container).not.toBeNull();
|
|
|
|
});
|
|
|
|
|
2023-08-12 22:49:46 +00:00
|
|
|
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([]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-05-28 17:52:48 +00:00
|
|
|
it('should fire a callback with empty list when no files are selected', async () => {
|
|
|
|
mockFilePicker.mockResolvedValueOnce([]);
|
|
|
|
const onChange = jest.fn();
|
|
|
|
render(<FileChooser control='picker' onChange={onChange} />);
|
|
|
|
|
|
|
|
fireEvent.click(await screen.findByText('Choose File'));
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
expect(mockFilePicker).toBeCalled();
|
|
|
|
expect(onChange).toBeCalledWith([]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
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<FileChooserProps['onChange']>, Parameters<FileChooserProps['onChange']>>();
|
|
|
|
render(<FileChooser control='picker' onChange={onChange} />);
|
|
|
|
|
|
|
|
fireEvent.click(await screen.findByText('Choose File'));
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
expect(mockFilePicker).toBeCalled();
|
|
|
|
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);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2022-05-30 16:29:48 +00:00
|
|
|
});
|