Skip to content

Commit

Permalink
feat: upload multiple roms
Browse files Browse the repository at this point in the history
  • Loading branch information
thenick775 committed Jan 9, 2025
1 parent 20d5927 commit 9c8a23c
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 69 deletions.
6 changes: 5 additions & 1 deletion gbajs3/src/components/modals/upload-patches.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,11 @@ export const UploadPatchesModal = () => {
hideAcceptedFiles={!value?.length}
multiple
>
<p>Drag and drop patch files here, or click to upload files</p>
<p>
Drag and drop patch files here,
<br />
or click to upload files
</p>
</DragAndDropInput>
)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { screen, waitForElementToBeRemoved } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import { describe, expect, it, vi } from 'vitest';

import { UploadRomModal } from './upload-rom.tsx';
import { UploadRomsModal } from './upload-roms.tsx';
import { testRomLocation } from '../../../test/mocks/handlers.ts';
import { renderWithContext } from '../../../test/render-with-context.tsx';
import * as contextHooks from '../../hooks/context.tsx';
Expand All @@ -12,7 +12,7 @@ import { productTourLocalStorageKey } from '../product-tour/consts.tsx';

import type { GBAEmulator } from '../../emulator/mgba/mgba-emulator.tsx';

describe('<UploadRomModal />', () => {
describe('<UploadRomsModal />', () => {
it('uploads file and closes modal', async () => {
const setIsModalOpenSpy = vi.fn();
const uploadRomSpy: (file: File, cb?: () => void) => void = vi.fn(
Expand Down Expand Up @@ -51,7 +51,7 @@ describe('<UploadRomModal />', () => {

const testRom = new File(['Some rom file contents'], 'rom1.gba');

renderWithContext(<UploadRomModal />);
renderWithContext(<UploadRomsModal />);

const romInput = screen.getByTestId('hidden-file-input');

Expand All @@ -67,9 +67,157 @@ describe('<UploadRomModal />', () => {
expect(uploadRomSpy).toHaveBeenCalledOnce();
expect(uploadRomSpy).toHaveBeenCalledWith(testRom, expect.anything());

expect(runGameSpy).toHaveBeenCalledOnce();
expect(runGameSpy).toHaveBeenCalledWith('rom1.gba');
expect(syncActionIfEnabledSpy).toHaveBeenCalledOnce();
expect(setIsModalOpenSpy).toHaveBeenCalledOnce();
expect(setIsModalOpenSpy).toHaveBeenCalledWith(false);
});

it('uploads multiple files, loads first file, and closes modal', async () => {
const setIsModalOpenSpy = vi.fn();
const uploadRomSpy: (file: File, cb?: () => void) => void = vi.fn(
(_file, cb) => cb && cb()
);
const syncActionIfEnabledSpy = vi.fn();
const runGameSpy = vi.fn(() => true);

const {
useEmulatorContext: originalEmulator,
useModalContext: originalModal
} = await vi.importActual<typeof contextHooks>('../../hooks/context.tsx');
const { useAddCallbacks: originalCallbacks } = await vi.importActual<
typeof addCallbackHooks
>('../../hooks/emulator/use-add-callbacks.tsx');

vi.spyOn(contextHooks, 'useEmulatorContext').mockImplementation(() => ({
...originalEmulator(),
emulator: {
uploadRom: uploadRomSpy
} as GBAEmulator
}));

vi.spyOn(contextHooks, 'useModalContext').mockImplementation(() => ({
...originalModal(),
setIsModalOpen: setIsModalOpenSpy
}));

vi.spyOn(addCallbackHooks, 'useAddCallbacks').mockImplementation(() => ({
...originalCallbacks(),
syncActionIfEnabled: syncActionIfEnabledSpy
}));

vi.spyOn(runGameHooks, 'useRunGame').mockReturnValue(runGameSpy);

const testRomFiles = [
new File(['Some rom file contents 1'], 'rom1.gba'),
new File(['Some rom file contents 2'], 'rom2.gba')
];

renderWithContext(<UploadRomsModal />);

const romInput = screen.getByTestId('hidden-file-input');

expect(romInput).toBeInTheDocument();

await userEvent.upload(romInput, testRomFiles);

expect(screen.getByText('Files to upload:')).toBeVisible();
expect(screen.getByText('rom1.gba')).toBeVisible();
expect(screen.getByText('rom2.gba')).toBeVisible();

await userEvent.click(screen.getByRole('button', { name: 'Upload' }));

expect(uploadRomSpy).toHaveBeenCalledTimes(2);
expect(uploadRomSpy).toHaveBeenCalledWith(
testRomFiles[0],
expect.anything()
);
expect(uploadRomSpy).toHaveBeenCalledWith(
testRomFiles[1],
expect.anything()
);

expect(runGameSpy).toHaveBeenCalledOnce();
expect(runGameSpy).toHaveBeenCalledWith('rom1.gba');

expect(syncActionIfEnabledSpy).toHaveBeenCalledOnce();
expect(setIsModalOpenSpy).toHaveBeenCalledOnce();
expect(setIsModalOpenSpy).toHaveBeenCalledWith(false);
});

it('uploads multiple files, loads selected file, and closes modal', async () => {
const setIsModalOpenSpy = vi.fn();
const uploadRomSpy: (file: File, cb?: () => void) => void = vi.fn(
(_file, cb) => cb && cb()
);
const syncActionIfEnabledSpy = vi.fn();
const runGameSpy = vi.fn(() => true);

const {
useEmulatorContext: originalEmulator,
useModalContext: originalModal
} = await vi.importActual<typeof contextHooks>('../../hooks/context.tsx');
const { useAddCallbacks: originalCallbacks } = await vi.importActual<
typeof addCallbackHooks
>('../../hooks/emulator/use-add-callbacks.tsx');

vi.spyOn(contextHooks, 'useEmulatorContext').mockImplementation(() => ({
...originalEmulator(),
emulator: {
uploadRom: uploadRomSpy
} as GBAEmulator
}));

vi.spyOn(contextHooks, 'useModalContext').mockImplementation(() => ({
...originalModal(),
setIsModalOpen: setIsModalOpenSpy
}));

vi.spyOn(addCallbackHooks, 'useAddCallbacks').mockImplementation(() => ({
...originalCallbacks(),
syncActionIfEnabled: syncActionIfEnabledSpy
}));

vi.spyOn(runGameHooks, 'useRunGame').mockReturnValue(runGameSpy);

const testRomFiles = [
new File(['Some rom file contents 1'], 'rom1.gba'),
new File(['Some rom file contents 2'], 'rom2.gba')
];

renderWithContext(<UploadRomsModal />);

const romInput = screen.getByTestId('hidden-file-input');

expect(romInput).toBeInTheDocument();

await userEvent.upload(romInput, testRomFiles);

await userEvent.click(
screen.getByLabelText('Run rom2.gba', { selector: 'input' })
);
expect(
screen.getByLabelText('Run rom2.gba', { selector: 'input' })
).toBeChecked();

await userEvent.click(screen.getByRole('button', { name: 'Upload' }));

expect(uploadRomSpy).toHaveBeenCalledTimes(2);
expect(uploadRomSpy).toHaveBeenCalledWith(
testRomFiles[0],
expect.anything()
);
expect(uploadRomSpy).toHaveBeenCalledWith(
testRomFiles[1],
expect.anything()
);

expect(runGameSpy).toHaveBeenCalledOnce();
expect(runGameSpy).toHaveBeenCalledWith('rom2.gba');

expect(syncActionIfEnabledSpy).toHaveBeenCalledOnce();
expect(setIsModalOpenSpy).toHaveBeenCalledOnce();
expect(setIsModalOpenSpy).toHaveBeenCalledWith(false);
});

Expand Down Expand Up @@ -112,7 +260,7 @@ describe('<UploadRomModal />', () => {

vi.spyOn(runGameHooks, 'useRunGame').mockReturnValue(runGameSpy);

renderWithContext(<UploadRomModal />);
renderWithContext(<UploadRomsModal />);

const uploadRomFromURLInput = screen.getByLabelText('Upload from a URL');

Expand Down Expand Up @@ -157,7 +305,7 @@ describe('<UploadRomModal />', () => {
} as GBAEmulator
}));

renderWithContext(<UploadRomModal />);
renderWithContext(<UploadRomsModal />);

const uploadRomFromURLInput = screen.getByLabelText('Upload from a URL');

Expand All @@ -184,12 +332,27 @@ describe('<UploadRomModal />', () => {
).toBeVisible();
});

it('renders invalid url error', async () => {
renderWithContext(<UploadRomsModal />);

await userEvent.type(
screen.getByLabelText('Upload from a URL'),
`invalid url`
);

await userEvent.click(screen.getByRole('button', { name: 'Upload' }));

expect(screen.getByText(/Invalid URL/)).toBeVisible();
});

it('renders form validation error', async () => {
renderWithContext(<UploadRomModal />);
renderWithContext(<UploadRomsModal />);

await userEvent.click(screen.getByRole('button', { name: 'Upload' }));

expect(screen.getByText(/A rom file or URL is required/)).toBeVisible();
expect(
screen.getByText(/At least one rom file or URL is required/)
).toBeVisible();
});

it('closes modal using the close button', async () => {
Expand All @@ -203,7 +366,7 @@ describe('<UploadRomModal />', () => {
setIsModalOpen: setIsModalOpenSpy
}));

renderWithContext(<UploadRomModal />);
renderWithContext(<UploadRomsModal />);

// click the close button
const closeButton = screen.getByText('Close', { selector: 'button' });
Expand All @@ -228,7 +391,7 @@ describe('<UploadRomModal />', () => {
'{"hasCompletedProductTourIntro":"finished"}'
);

renderWithContext(<UploadRomModal />);
renderWithContext(<UploadRomsModal />);

expect(
await screen.findByText(

Check failure on line 397 in gbajs3/src/components/modals/upload-roms.spec.tsx

View workflow job for this annotation

GitHub Actions / gbajs3 / build

src/components/modals/upload-roms.spec.tsx > <UploadRomsModal /> > renders tour steps

TestingLibraryElementError: Unable to find an element with the text: Use this area to drag and drop your rom or zipped rom file, or click to select a file.. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. Ignored nodes: comments, script, style <body> <div> <div class="sc-gaZyOd bDXZYu" > <h3 class="sc-eaUbBy kKcaHj" id="modalHeader" > Upload Rom </h3> <button aria-label="Close" class="sc-kuACkN kwWrDR" /> </div> <div class="sc-dDvxFM gddIne" > <form aria-label="Upload Roms Form" id=":ri:" > <div aria-label="Upload Rom" class="sc-cGXZpB dqqRlS" id=":ri:--drag-and-drop" role="presentation" tabindex="0" > <input data-testid="hidden-file-input" multiple="" name="romFiles" style="border: 0px; clip: rect(0px, 0px, 0px, 0px); clip-path: inset(50%); height: 1px; margin: 0px -1px -1px 0px; overflow: hidden; padding: 0px; position: absolute; width: 1px; white-space: nowrap;" tabindex="-1" type="file" /> <svg class="sc-jZCRgm cFFget" fill="currentColor" height="1em" stroke="currentColor" stroke-width="0" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg" > <path d="M13 19v-4h3l-4-5-4 5h3v4z" /> <path d="M7 19h2v-2H7c-1.654 0-3-1.346-3-3 0-1.404 1.199-2.756 2.673-3.015l.581-.102.192-.558C8.149 8.274 9.895 7 12 7c2.757 0 5 2.243 5 5v1h1c1.103 0 2 .897 2 2s-.897 2-2 2h-3v2h3c2.206 0 4-1.794 4-4a4.01 4.01 0 0 0-3.056-3.888C18.507 7.67 15.56 5 12 5 9.244 5 6.85 6.611 5.757 9.15 3.609 9.792 2 11.82 2 14c0 2.757 2.243 5 5 5z" /> </svg> <p> Drag and drop roms or zipped rom files here, or click to upload files </p> </div> <div aria-orientation="horizontal" class="MuiDivider-root MuiDivider-fullWidth MuiDivider-withChildren css-1g5jb2t-MuiDivider-root" role="separator" > <span class="MuiDivider-wrapper css-1134oje-MuiDivider-wrapper" > or </span> </div> <div aria-label="Upload Rom From URL" class="MuiFormControl-root MuiFormControl-fullWidth MuiTextField-root css-cmpglg-MuiFormControl-root-MuiTextField-root" > <label class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeSmall MuiInputLabel-filled MuiFormLabel-colorPrimary MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeSmall MuiInputLabel-filled css-1y3nboe-MuiFormLabel-root-MuiInputLabel-root" data-shrink="false" for=":ri:--rom-url" id=":ri:--rom-url-label" > Upload from a URL </label> <div class="MuiInputBase-root MuiFilledInput-root MuiFilledInput-underline MuiInputBase-colorPrimary MuiInputBase-fullWidth MuiInputBase-formControl MuiInputBase-sizeSmall css-1mb1do-MuiInputBase-root-MuiFilledInput-root" > <input aria-invalid="false" autocomplete="romURL" class="MuiInputBase-input MuiFilledInput-input MuiInputBase-inputSizeSmall css-1pemltt-MuiInputBase-input-MuiFilledInput-input" id=":ri:--rom-url" name="romURL" type="text" value="" /> </div> </div> </form> </div> <div class="sc-jqVXSH bvHTje" data-testid="modal-footer:wrapper" > <button class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiBu
Expand Down
Loading

0 comments on commit 9c8a23c

Please sign in to comment.