Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: upload multiple roms #242

Merged
merged 1 commit into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,11 +391,11 @@ describe('<UploadRomModal />', () => {
'{"hasCompletedProductTourIntro":"finished"}'
);

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

expect(
await screen.findByText(
'Use this area to drag and drop your rom or zipped rom file, or click to select a file.'
'Use this area to drag and drop roms or zipped rom files, or click to select files.'
)
).toBeInTheDocument();
expect(
Expand All @@ -242,7 +405,7 @@ describe('<UploadRomModal />', () => {
).toBeInTheDocument();
expect(
screen.getByText(
'You may drop or select one rom at a time, once uploaded your game will boot!'
'You may drop or select multiple files, once uploaded the selected game will boot!'
)
).toBeInTheDocument();

Expand All @@ -253,7 +416,7 @@ describe('<UploadRomModal />', () => {

expect(
screen.getByText(
'Use this area to drag and drop your rom or zipped rom file, or click to select a file.'
'Use this area to drag and drop roms or zipped rom files, or click to select files.'
)
).toBeVisible();
expect(
Expand All @@ -263,7 +426,7 @@ describe('<UploadRomModal />', () => {
).toBeVisible();
expect(
screen.getByText(
'You may drop or select one rom at a time, once uploaded your game will boot!'
'You may drop or select multiple files, once uploaded the selected game will boot!'
)
).toBeVisible();
});
Expand Down
Loading
Loading