From 24080c070f4910c7b414fc34d2229c21ae2a7d07 Mon Sep 17 00:00:00 2001 From: Natalie Rose Date: Fri, 18 Sep 2026 14:57:40 +1000 Subject: [PATCH] Fix tests --- CLAUDE.md | 5 ++ .../blackHoleGenerator/targetPicker.test.tsx | 39 ++++++------ app/components/building/recycleTab.test.tsx | 44 ++++++++----- .../building/resourceDumpTab.test.tsx | 26 +++++--- .../development/buildQueueTab.test.tsx | 10 +-- app/components/embassy/stashTab.test.tsx | 58 +++++++++-------- app/components/geneticsLab/labTab.test.tsx | 26 ++++---- .../market/availableTradesTab.test.tsx | 15 +++-- app/components/market/itemPicker.test.tsx | 39 +++++++----- app/components/observatory/probesTab.test.tsx | 11 ++-- .../policeStation/shipsTab.test.tsx | 11 ++-- app/components/propositionsList.test.tsx | 10 +-- app/components/resources.test.tsx | 10 +-- app/components/security/prisonersTab.test.tsx | 16 ++--- .../shipyard/buildShipsTab.test.tsx | 26 ++++---- app/components/spacePort/viewTab.test.tsx | 25 +++++--- .../templeOfTheDrajilites/planetsTab.test.tsx | 11 ++-- .../theDillonForge/forgeTab.test.tsx | 63 +++++++++++-------- app/components/trade/supplyChainsTab.test.tsx | 27 ++++---- .../transporter/oneForOneTab.test.tsx | 31 ++++----- app/queries/body.test.tsx | 4 -- app/queries/building.test.tsx | 12 ++-- app/queries/building.ts | 6 +- app/queries/capitol.test.tsx | 4 -- app/queries/development.test.tsx | 4 -- app/queries/distributionCenter.test.tsx | 4 -- app/queries/embassy.test.tsx | 4 -- app/queries/energyReserve.test.tsx | 4 -- app/queries/entertainment.test.tsx | 4 -- app/queries/foodReserve.test.tsx | 4 -- app/queries/libraryOfJith.test.tsx | 4 -- app/queries/market.test.tsx | 4 +- app/queries/miningMinistry.test.tsx | 4 -- app/queries/missionCommand.test.tsx | 6 +- app/queries/network19.test.tsx | 6 +- app/queries/observatory.test.tsx | 4 -- app/queries/oreStorage.test.tsx | 4 -- app/queries/park.test.tsx | 4 -- app/queries/parliament.test.tsx | 2 - app/queries/recycling.test.tsx | 4 -- app/queries/security.test.tsx | 8 +-- app/queries/shipyard.test.tsx | 6 +- app/queries/spacePort.test.tsx | 4 -- app/queries/themePark.test.tsx | 4 -- app/queries/waterStorage.test.tsx | 4 -- app/stores/rpc/server.ts | 2 +- jest.config.js | 1 + package-lock.json | 15 +++++ package.json | 1 + stubs/src/modules/buildings.ts | 4 +- stubs/src/modules/inbox.ts | 4 +- stubs/src/modules/intelligence.ts | 8 +-- 52 files changed, 326 insertions(+), 330 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f56ea6d1..75038d07 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -56,6 +56,11 @@ Other structural pieces: `docs/` exists but its own `docs/README.md` warns "most of the information in here is rather out of date." It documents a Reflux/Gulp-era architecture (Actions → Stores → Components via Reflux, `gulp dev`, `js/` source paths) that predates the current Vite/MobX/`app/` setup. Treat it as historical background only — do not follow `docs/guides/implementing-a-building.md` or `docs/add-buildings.md` as literal instructions for adding a building; use an existing building implementation under `app/components/building` and `app/queries` as the template instead. +## Testing + +- Jest is configured with `clearMocks: true` (`jest.config.js`), so mocks are cleared automatically between tests — do not call `jest.clearAllMocks()` in `beforeEach`/`afterEach`. +- Prefer Testing Library's `userEvent` (e.g. `userEvent.click()`) over `fireEvent` — it dispatches the fuller sequence of events a real browser/user interaction produces, so it more closely mirrors actual user behavior. Reach for `fireEvent` only when testing a specific low-level event that `userEvent` doesn't model. + ## Code style - Oxlint config defined in `oxlint.config.ts` diff --git a/app/components/blackHoleGenerator/targetPicker.test.tsx b/app/components/blackHoleGenerator/targetPicker.test.tsx index 8d161d71..9fa05529 100644 --- a/app/components/blackHoleGenerator/targetPicker.test.tsx +++ b/app/components/blackHoleGenerator/targetPicker.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import TargetPicker, { buildTarget, isComplete, @@ -15,12 +16,11 @@ const renderPicker = () => { return onChange; }; -test('a body is aimed at by name with no orbit control', () => { +test('a body is aimed at by name with no orbit control', async () => { + const user = userEvent.setup(); const onChange = renderPicker(); - fireEvent.change(screen.getByRole('textbox', { name: 'Target' }), { - target: { value: 'Earth' }, - }); + await user.type(screen.getByRole('textbox', { name: 'Target' }), 'Earth'); expect(onChange).toHaveBeenLastCalledWith({ body_name: 'Earth' }); expect(screen.queryByRole('combobox', { name: 'Orbit' })).not.toBeInTheDocument(); @@ -30,39 +30,36 @@ test('a body is aimed at by name with no orbit control', () => { // Only a star can be aimed at by orbit - a body already is one - which is what decides whether the // orbit control appears. // -test('a star gains an orbit control, and the orbit rides along', () => { +test('a star gains an orbit control, and the orbit rides along', async () => { + const user = userEvent.setup(); const onChange = renderPicker(); - fireEvent.change(screen.getByRole('combobox', { name: 'Target type' }), { - target: { value: 'star_name' }, - }); + await user.selectOptions(screen.getByRole('combobox', { name: 'Target type' }), 'star_name'); - fireEvent.change(screen.getByRole('textbox', { name: 'Target' }), { target: { value: 'Sol' } }); - fireEvent.change(screen.getByRole('combobox', { name: 'Orbit' }), { target: { value: '3' } }); + await user.type(screen.getByRole('textbox', { name: 'Target' }), 'Sol'); + await user.selectOptions(screen.getByRole('combobox', { name: 'Orbit' }), '3'); expect(onChange).toHaveBeenLastCalledWith({ star_name: 'Sol', orbit: '3' }); }); -test('choosing X,Y swaps in two coordinate inputs', () => { +test('choosing X,Y swaps in two coordinate inputs', async () => { + const user = userEvent.setup(); const onChange = renderPicker(); - fireEvent.change(screen.getByRole('combobox', { name: 'Target type' }), { - target: { value: 'xy' }, - }); + await user.selectOptions(screen.getByRole('combobox', { name: 'Target type' }), 'xy'); - fireEvent.change(screen.getByRole('textbox', { name: 'X' }), { target: { value: '10' } }); - fireEvent.change(screen.getByRole('textbox', { name: 'Y' }), { target: { value: '-4' } }); + await user.type(screen.getByRole('textbox', { name: 'X' }), '10'); + await user.type(screen.getByRole('textbox', { name: 'Y' }), '-4'); expect(onChange).toHaveBeenLastCalledWith({ x: '10', y: '-4' }); expect(screen.queryByRole('textbox', { name: 'Target' })).not.toBeInTheDocument(); }); -test('the zone list comes from the building view', () => { +test('the zone list comes from the building view', async () => { + const user = userEvent.setup(); renderPicker(); - fireEvent.change(screen.getByRole('combobox', { name: 'Target type' }), { - target: { value: 'zone' }, - }); + await user.selectOptions(screen.getByRole('combobox', { name: 'Target type' }), 'zone'); // Scoped to the zone select, since the mode select is on screen too. const options = Array.from( diff --git a/app/components/building/recycleTab.test.tsx b/app/components/building/recycleTab.test.tsx index 88ef8c41..4c6ef1a6 100644 --- a/app/components/building/recycleTab.test.tsx +++ b/app/components/building/recycleTab.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Building, RecycleStatus } from 'app/interfaces'; import BodyRPCStore from 'app/stores/rpc/body'; import lacuna from 'app/lacuna'; @@ -36,7 +37,6 @@ const renderForm = () => { const inputs = () => screen.getAllByRole('spinbutton') as HTMLInputElement[]; beforeEach(() => { - jest.clearAllMocks(); // Comfortably above max_recycle, so the building's own ceiling is what binds. BodyRPCStore.waste_stored = 5000; }); @@ -49,23 +49,26 @@ test('the header reports the ceiling and the hourly rate', () => { ).toBeInTheDocument(); }); -test('Max fills one resource and zeroes the other two', () => { +test('Max fills one resource and zeroes the other two', async () => { + const user = userEvent.setup(); renderForm(); const [ore, water, energy] = inputs(); - fireEvent.change(water, { target: { value: '100' } }); - fireEvent.click(screen.getAllByRole('button', { name: 'Max' })[0]); + await user.clear(water); + await user.type(water, '100'); + await user.click(screen.getAllByRole('button', { name: 'Max' })[0]); expect(ore.value).toBe('900'); expect(water.value).toBe('0'); expect(energy.value).toBe('0'); }); -test('Distribute Evenly splits the maximum three ways and the parts sum to it', () => { +test('Distribute Evenly splits the maximum three ways and the parts sum to it', async () => { + const user = userEvent.setup(); renderForm(); - fireEvent.click(screen.getByRole('button', { name: 'Distribute Evenly' })); + await user.click(screen.getByRole('button', { name: 'Distribute Evenly' })); const values = inputs().map((input) => Number(input.value)); @@ -73,44 +76,51 @@ test('Distribute Evenly splits the maximum three ways and the parts sum to it', values.forEach((value) => expect(value).toBeGreaterThan(0)); }); -test('an amount over the building ceiling is refused without a request', () => { +test('an amount over the building ceiling is refused without a request', async () => { + const user = userEvent.setup(); renderForm(); - fireEvent.change(inputs()[0], { target: { value: '901' } }); - fireEvent.click(screen.getByRole('button', { name: 'Recycle' })); + await user.clear(inputs()[0]); + await user.type(inputs()[0], '901'); + await user.click(screen.getByRole('button', { name: 'Recycle' })); expect(screen.getByText('You can only recycle 900 waste at a time.')).toBeInTheDocument(); expect(mockedRecycle).not.toHaveBeenCalled(); }); -test('an amount over the waste on hand is refused without a request', () => { +test('an amount over the waste on hand is refused without a request', async () => { + const user = userEvent.setup(); BodyRPCStore.waste_stored = 10; renderForm(); - fireEvent.change(inputs()[0], { target: { value: '50' } }); - fireEvent.click(screen.getByRole('button', { name: 'Recycle' })); + await user.clear(inputs()[0]); + await user.type(inputs()[0], '50'); + await user.click(screen.getByRole('button', { name: 'Recycle' })); expect(screen.getByText('You can only recycle waste you have stored.')).toBeInTheDocument(); expect(mockedRecycle).not.toHaveBeenCalled(); }); -test('zero is refused without a request', () => { +test('zero is refused without a request', async () => { + const user = userEvent.setup(); renderForm(); - fireEvent.click(screen.getByRole('button', { name: 'Recycle' })); + await user.click(screen.getByRole('button', { name: 'Recycle' })); expect(screen.getByText('You must specify an amount greater than zero.')).toBeInTheDocument(); expect(mockedRecycle).not.toHaveBeenCalled(); }); test('a valid amount is sent with use_essentia as a number', async () => { + const user = userEvent.setup(); mockedRecycle.mockResolvedValue({ result: { building: {}, status: {}, recycle } }); renderForm(); - fireEvent.change(inputs()[0], { target: { value: '100' } }); - fireEvent.click(screen.getByRole('button', { name: 'Recycle' })); + await user.clear(inputs()[0]); + await user.type(inputs()[0], '100'); + await user.click(screen.getByRole('button', { name: 'Recycle' })); await waitFor(() => expect(mockedRecycle).toHaveBeenCalled()); diff --git a/app/components/building/resourceDumpTab.test.tsx b/app/components/building/resourceDumpTab.test.tsx index d1d3161b..51c47e4a 100644 --- a/app/components/building/resourceDumpTab.test.tsx +++ b/app/components/building/resourceDumpTab.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import ResourceDumpTab from 'app/components/building/resourceDumpTab'; jest.mock('app/lacuna'); @@ -22,31 +23,36 @@ test('only lists resources there is something stored of', () => { expect(options).toStrictEqual(['Apple (100)', 'Cheese (50)']); }); -test('an amount of zero is rejected without asking the server', () => { +test('an amount of zero is rejected without asking the server', async () => { + const user = userEvent.setup(); const onDump = renderTab(); - fireEvent.click(screen.getByRole('button', { name: 'Dump' })); + await user.click(screen.getByRole('button', { name: 'Dump' })); expect(screen.getByText('You must specify an amount greater than zero.')).toBeInTheDocument(); expect(onDump).not.toHaveBeenCalled(); }); -test('dumping more than is stored is rejected without asking the server', () => { +test('dumping more than is stored is rejected without asking the server', async () => { + const user = userEvent.setup(); const onDump = renderTab(); - fireEvent.change(screen.getByRole('spinbutton'), { target: { value: '101' } }); - fireEvent.click(screen.getByRole('button', { name: 'Dump' })); + await user.clear(screen.getByRole('spinbutton')); + await user.type(screen.getByRole('spinbutton'), '101'); + await user.click(screen.getByRole('button', { name: 'Dump' })); expect(screen.getByText('You can only convert apple you have stored.')).toBeInTheDocument(); expect(onDump).not.toHaveBeenCalled(); }); -test('a valid amount is passed on with the selected type', () => { +test('a valid amount is passed on with the selected type', async () => { + const user = userEvent.setup(); const onDump = renderTab(); - fireEvent.change(screen.getByRole('combobox'), { target: { value: 'cheese' } }); - fireEvent.change(screen.getByRole('spinbutton'), { target: { value: '25' } }); - fireEvent.click(screen.getByRole('button', { name: 'Dump' })); + await user.selectOptions(screen.getByRole('combobox'), 'cheese'); + await user.clear(screen.getByRole('spinbutton')); + await user.type(screen.getByRole('spinbutton'), '25'); + await user.click(screen.getByRole('button', { name: 'Dump' })); expect(onDump).toHaveBeenCalledWith('cheese', 25); }); diff --git a/app/components/development/buildQueueTab.test.tsx b/app/components/development/buildQueueTab.test.tsx index 5ec8a613..a49b8aad 100644 --- a/app/components/development/buildQueueTab.test.tsx +++ b/app/components/development/buildQueueTab.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { BuildQueueItem, DevelopmentViewResponse } from 'app/interfaces'; import lacuna from 'app/lacuna'; import createQueryWrapper from 'app/testing/queryWrapper'; @@ -50,7 +51,6 @@ const renderQueue = (items = queue) => { }; beforeEach(() => { - jest.clearAllMocks(); // vex.confirm is a real dialog, so run the yes branch straight through in tests. mockedConfirm.mockImplementation((_message: string, yes: () => void) => yes()); }); @@ -76,23 +76,25 @@ test('the whole-queue subsidy cost is shown', () => { }); test('Cancel All sends cancel_all rather than a scheduled id', async () => { + const user = userEvent.setup(); mockedCancel.mockResolvedValue({ result: { build_queue: [], subsidy_cost: 0, status: {} } }); renderQueue(); - fireEvent.click(screen.getByRole('button', { name: 'Cancel All' })); + await user.click(screen.getByRole('button', { name: 'Cancel All' })); await waitFor(() => expect(mockedCancel).toHaveBeenCalled()); expect(mockedCancel).toHaveBeenCalledWith({ building_id: 7, cancel_all: true }); }); test('a per-row Subsidize sends that row scheduled id as a number', async () => { + const user = userEvent.setup(); mockedSubsidizeOne.mockResolvedValue({ result: { essentia_spent: 3, status: {} } }); renderQueue(); // The first Subsidize is the whole-queue one, so the row buttons start at index 1. - fireEvent.click(screen.getAllByRole('button', { name: 'Subsidize' })[1]); + await user.click(screen.getAllByRole('button', { name: 'Subsidize' })[1]); await waitFor(() => expect(mockedSubsidizeOne).toHaveBeenCalled()); expect(mockedSubsidizeOne).toHaveBeenCalledWith({ building_id: 7, scheduled_id: 101 }); diff --git a/app/components/embassy/stashTab.test.tsx b/app/components/embassy/stashTab.test.tsx index e88154eb..44dd099e 100644 --- a/app/components/embassy/stashTab.test.tsx +++ b/app/components/embassy/stashTab.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent, { UserEvent } from '@testing-library/user-event'; import { Building, StashResponse } from 'app/interfaces'; import lacuna from 'app/lacuna'; import createQueryWrapper from 'app/testing/queryWrapper'; @@ -32,11 +33,7 @@ const renderStash = (overrides: Partial = {}) => { const input = (label: string) => screen.getByLabelText(label) as HTMLInputElement; -const transfer = () => fireEvent.click(screen.getByRole('button', { name: 'Transfer' })); - -beforeEach(() => { - jest.clearAllMocks(); -}); +const transfer = (user: UserEvent) => user.click(screen.getByRole('button', { name: 'Transfer' })); test('only resources with something available are listed on each side', () => { renderStash({ stored: { apple: 100, bean: 0 } }); @@ -45,10 +42,11 @@ test('only resources with something available are listed on each side', () => { expect(screen.queryByLabelText('Donate from this planet Bean')).not.toBeInTheDocument(); }); -test('donating nothing is refused without a request', () => { +test('donating nothing is refused without a request', async () => { + const user = userEvent.setup(); renderStash(); - transfer(); + await transfer(user); expect(screen.getByText('You must add items to donate to the stash.')).toBeInTheDocument(); expect(mockedDonate).not.toHaveBeenCalled(); @@ -56,24 +54,26 @@ test('donating nothing is refused without a request', () => { }); test('a donation with nothing requested goes through donate, not exchange', async () => { + const user = userEvent.setup(); mockedDonate.mockResolvedValue({ result: { status: {} } }); renderStash(); - fireEvent.change(input('Donate from this planet Apple'), { target: { value: '10' } }); - transfer(); + await user.type(input('Donate from this planet Apple'), '10'); + await transfer(user); await waitFor(() => expect(mockedDonate).toHaveBeenCalled()); expect(mockedDonate).toHaveBeenCalledWith({ building_id: 7, donation: { apple: 10 } }); expect(mockedExchange).not.toHaveBeenCalled(); }); -test('an exchange must balance', () => { +test('an exchange must balance', async () => { + const user = userEvent.setup(); renderStash(); - fireEvent.change(input('Donate from this planet Apple'), { target: { value: '10' } }); - fireEvent.change(input('Request from the stash Water'), { target: { value: '20' } }); - transfer(); + await user.type(input('Donate from this planet Apple'), '10'); + await user.type(input('Request from the stash Water'), '20'); + await transfer(user); expect( screen.getByText('The amount you are requesting must be equal to the amount you are donating.') @@ -81,12 +81,13 @@ test('an exchange must balance', () => { expect(mockedExchange).not.toHaveBeenCalled(); }); -test('an exchange over the maximum size is refused', () => { +test('an exchange over the maximum size is refused', async () => { + const user = userEvent.setup(); renderStash({ stored: { apple: 500 }, stash: { water: 500 } }); - fireEvent.change(input('Donate from this planet Apple'), { target: { value: '200' } }); - fireEvent.change(input('Request from the stash Water'), { target: { value: '200' } }); - transfer(); + await user.type(input('Donate from this planet Apple'), '200'); + await user.type(input('Request from the stash Water'), '200'); + await transfer(user); expect( screen.getByText('You are only able to transfer 150 resources from the stash.') @@ -94,12 +95,13 @@ test('an exchange over the maximum size is refused', () => { expect(mockedExchange).not.toHaveBeenCalled(); }); -test('an exchange is refused once the daily allowance is used up', () => { +test('an exchange is refused once the daily allowance is used up', async () => { + const user = userEvent.setup(); renderStash({ exchanges_remaining_today: 0 }); - fireEvent.change(input('Donate from this planet Apple'), { target: { value: '10' } }); - fireEvent.change(input('Request from the stash Water'), { target: { value: '10' } }); - transfer(); + await user.type(input('Donate from this planet Apple'), '10'); + await user.type(input('Request from the stash Water'), '10'); + await transfer(user); expect( screen.getByText('You have already used up all of your exchanges for today.') @@ -108,13 +110,14 @@ test('an exchange is refused once the daily allowance is used up', () => { }); test('a balanced exchange within the limits sends both sides', async () => { + const user = userEvent.setup(); mockedExchange.mockResolvedValue({ result: { status: {} } }); renderStash(); - fireEvent.change(input('Donate from this planet Apple'), { target: { value: '10' } }); - fireEvent.change(input('Request from the stash Water'), { target: { value: '10' } }); - transfer(); + await user.type(input('Donate from this planet Apple'), '10'); + await user.type(input('Request from the stash Water'), '10'); + await transfer(user); await waitFor(() => expect(mockedExchange).toHaveBeenCalled()); expect(mockedExchange).toHaveBeenCalledWith({ @@ -125,10 +128,11 @@ test('a balanced exchange within the limits sends both sides', async () => { expect(mockedDonate).not.toHaveBeenCalled(); }); -test('amounts are clamped to what is actually available', () => { +test('amounts are clamped to what is actually available', async () => { + const user = userEvent.setup(); renderStash(); - fireEvent.change(input('Donate from this planet Gold'), { target: { value: '9999' } }); + await user.type(input('Donate from this planet Gold'), '9999'); expect(input('Donate from this planet Gold').value).toBe('50'); }); diff --git a/app/components/geneticsLab/labTab.test.tsx b/app/components/geneticsLab/labTab.test.tsx index ecc9f858..3b3f7fba 100644 --- a/app/components/geneticsLab/labTab.test.tsx +++ b/app/components/geneticsLab/labTab.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Building } from 'app/interfaces'; import EmpireRPCStore from 'app/stores/rpc/empire'; import lacuna from 'app/lacuna'; @@ -41,7 +42,6 @@ const renderTab = () => { }; beforeEach(() => { - jest.clearAllMocks(); mockedPrepare.mockResolvedValue({ result: response }); mockedConfirm.mockImplementation((_message: string, yes: () => void) => yes()); EmpireRPCStore.essentia = 100; @@ -59,11 +59,12 @@ test('captured spies are listed with the species they came from', async () => { // The affinity list belongs to a particular spy, so it only appears once one is chosen. // test('affinities appear only after a spy is chosen', async () => { + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getAllByRole('combobox')).toHaveLength(1)); - fireEvent.change(screen.getAllByRole('combobox')[0], { target: { value: '3' } }); + await user.selectOptions(screen.getAllByRole('combobox')[0], '3'); expect(screen.getAllByRole('combobox')).toHaveLength(2); expect(screen.getByRole('option', { name: 'Deception Affinity' })).toBeInTheDocument(); @@ -74,15 +75,14 @@ test('running an experiment sends the spy and the affinity', async () => { result: { ...response, experiment: { graft: 1, survive: 1, message: 'It worked.' } }, }); + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getAllByRole('combobox')).toHaveLength(1)); - fireEvent.change(screen.getAllByRole('combobox')[0], { target: { value: '3' } }); - fireEvent.change(screen.getAllByRole('combobox')[1], { - target: { value: 'deception_affinity' }, - }); - fireEvent.click(screen.getByRole('button', { name: 'Run Experiment' })); + await user.selectOptions(screen.getAllByRole('combobox')[0], '3'); + await user.selectOptions(screen.getAllByRole('combobox')[1], 'deception_affinity'); + await user.click(screen.getByRole('button', { name: 'Run Experiment' })); await waitFor(() => expect(mockedRun).toHaveBeenCalled()); @@ -94,28 +94,30 @@ test('running an experiment sends the spy and the affinity', async () => { }); test('the button stays disabled until both a spy and an affinity are picked', async () => { + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getAllByRole('combobox')).toHaveLength(1)); expect(screen.getByRole('button', { name: 'Run Experiment' })).toBeDisabled(); - fireEvent.change(screen.getAllByRole('combobox')[0], { target: { value: '3' } }); + await user.selectOptions(screen.getAllByRole('combobox')[0], '3'); expect(screen.getByRole('button', { name: 'Run Experiment' })).toBeDisabled(); - fireEvent.change(screen.getAllByRole('combobox')[1], { target: { value: 'mining_affinity' } }); + await user.selectOptions(screen.getAllByRole('combobox')[1], 'mining_affinity'); expect(screen.getByRole('button', { name: 'Run Experiment' })).toBeEnabled(); }); test('too little essentia blocks the experiment and says so', async () => { + const user = userEvent.setup(); EmpireRPCStore.essentia = 1; renderTab(); await waitFor(() => expect(screen.getAllByRole('combobox')).toHaveLength(1)); - fireEvent.change(screen.getAllByRole('combobox')[0], { target: { value: '3' } }); - fireEvent.change(screen.getAllByRole('combobox')[1], { target: { value: 'mining_affinity' } }); + await user.selectOptions(screen.getAllByRole('combobox')[0], '3'); + await user.selectOptions(screen.getAllByRole('combobox')[1], 'mining_affinity'); expect( screen.getByText('You do not have enough essentia for an experiment.') diff --git a/app/components/market/availableTradesTab.test.tsx b/app/components/market/availableTradesTab.test.tsx index 5917e370..30003f63 100644 --- a/app/components/market/availableTradesTab.test.tsx +++ b/app/components/market/availableTradesTab.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Building } from 'app/interfaces'; import lacuna from 'app/lacuna'; import createQueryWrapper from 'app/testing/queryWrapper'; @@ -24,7 +25,7 @@ const response = { trades: [ { id: 11, - date_offered: '01 31 2010 13:09:05 +0600', + date_offered: '2010 01 31 13:09:05 +0600', ask: 500, offer: ['10,000 apple', 'Halls of Vrbansk (1)'], empire: { id: 9, name: 'Humans' }, @@ -43,7 +44,6 @@ const renderTab = (endpoint: MarketListEndpoint = lacuna.trade, emptyMessage?: s }; beforeEach(() => { - jest.clearAllMocks(); mockedMarket.mockResolvedValue({ result: response }); mockedGuildMarket.mockResolvedValue({ result: response }); mockedConfirm.mockImplementation((_message: string, yes: () => void) => yes()); @@ -59,13 +59,14 @@ test('a listing names the seller, the ask and what is on offer', async () => { }); test('accepting confirms the price before spending essentia', async () => { + const user = userEvent.setup(); mockedAccept.mockResolvedValue({ result: { status: {} } }); renderTab(); await waitFor(() => expect(screen.getByText('Humans')).toBeInTheDocument()); - fireEvent.click(screen.getByRole('button', { name: 'Accept' })); + await user.click(screen.getByRole('button', { name: 'Accept' })); expect(mockedConfirm).toHaveBeenCalledWith( 'Buy this trade for 500 essentia?', @@ -75,23 +76,25 @@ test('accepting confirms the price before spending essentia', async () => { }); test('reporting spam goes straight through without a confirm', async () => { + const user = userEvent.setup(); mockedAbuse.mockResolvedValue({ result: { status: {} } }); renderTab(); await waitFor(() => expect(screen.getByText('Humans')).toBeInTheDocument()); - fireEvent.click(screen.getByRole('button', { name: 'Spam' })); + await user.click(screen.getByRole('button', { name: 'Spam' })); await waitFor(() => expect(mockedAbuse).toHaveBeenCalledWith({ building_id: 7, trade_id: 11 })); }); test('Next asks the server for the following page', async () => { + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByText('Page 1 of 2')).toBeInTheDocument()); - fireEvent.click(screen.getByRole('button', { name: 'Next' })); + await user.click(screen.getByRole('button', { name: 'Next' })); await waitFor(() => expect(mockedMarket).toHaveBeenCalledWith({ building_id: 7, page_number: 2 }) diff --git a/app/components/market/itemPicker.test.tsx b/app/components/market/itemPicker.test.tsx index d08601b6..2c84d4c4 100644 --- a/app/components/market/itemPicker.test.tsx +++ b/app/components/market/itemPicker.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'; +import { render, screen, waitFor, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Building } from 'app/interfaces'; import lacuna from 'app/lacuna'; import createQueryWrapper from 'app/testing/queryWrapper'; @@ -37,8 +38,6 @@ const findRow = async (label: string) => { }; beforeEach(() => { - jest.clearAllMocks(); - mockedResources.mockResolvedValue({ result: { resources: { apple: 100, bean: 0, water: 5000 } }, }); @@ -63,38 +62,41 @@ test('only lists resources there is something stored of', async () => { }); test('adding a resource hands back the chosen type and quantity', async () => { + const user = userEvent.setup(); const onChange = renderPicker(); const row = await findRow('Apple'); - fireEvent.change(row.getByRole('spinbutton', { name: 'Quantity' }), { - target: { value: '25' }, - }); - fireEvent.click(row.getByRole('button', { name: 'Add' })); + const quantity = row.getByRole('spinbutton', { name: 'Quantity' }); + await user.clear(quantity); + await user.type(quantity, '25'); + await user.click(row.getByRole('button', { name: 'Add' })); expect(onChange).toHaveBeenCalledWith([{ name: 'apple', quantity: 25 }]); }); test('asking for more than is stored is rejected without touching the cart', async () => { + const user = userEvent.setup(); const onChange = renderPicker(); const row = await findRow('Apple'); - fireEvent.change(row.getByRole('spinbutton', { name: 'Quantity' }), { - target: { value: '101' }, - }); - fireEvent.click(row.getByRole('button', { name: 'Add' })); + const quantity = row.getByRole('spinbutton', { name: 'Quantity' }); + await user.clear(quantity); + await user.type(quantity, '101'); + await user.click(row.getByRole('button', { name: 'Add' })); expect(screen.getByText('Choose between 1 and 100.')).toBeInTheDocument(); expect(onChange).not.toHaveBeenCalled(); }); test('the same item cannot be added to the cart twice', async () => { + const user = userEvent.setup(); const onChange = renderPicker([{ name: 'apple', quantity: 10 }]); const row = await findRow('Apple'); - fireEvent.click(row.getByRole('button', { name: 'Add' })); + await user.click(row.getByRole('button', { name: 'Add' })); expect(screen.getByText('That is already in the trade.')).toBeInTheDocument(); expect(onChange).not.toHaveBeenCalled(); @@ -104,13 +106,14 @@ test('the same item cannot be added to the cart twice', async () => { // A prisoner is a single thing, so it always goes in as one. // test('a prisoner is added without a quantity', async () => { + const user = userEvent.setup(); const onChange = renderPicker(); - fireEvent.click(screen.getByText('Prisoners')); + await user.click(screen.getByText('Prisoners')); const row = await findRow('Sneaky Pete (level 12)'); - fireEvent.click(row.getByRole('button', { name: 'Add' })); + await user.click(row.getByRole('button', { name: 'Add' })); expect(onChange).toHaveBeenCalledWith([ { type: 'prisoner', name: 'Sneaky Pete', prisoner_id: 5 }, @@ -118,13 +121,14 @@ test('a prisoner is added without a quantity', async () => { }); test('a plan carries the three fields that identify it', async () => { + const user = userEvent.setup(); const onChange = renderPicker(); - fireEvent.click(screen.getByText('Plans')); + await user.click(screen.getByText('Plans')); const row = await findRow('Halls of Vrbansk (1+0)'); - fireEvent.click(row.getByRole('button', { name: 'Add' })); + await user.click(row.getByRole('button', { name: 'Add' })); expect(onChange).toHaveBeenCalledWith([ { @@ -138,6 +142,7 @@ test('a plan carries the three fields that identify it', async () => { }); test('removing a cart row hands back the rest', async () => { + const user = userEvent.setup(); const onChange = renderPicker([ { name: 'apple', quantity: 10 }, { type: 'glyph', name: 'gold', quantity: 1 }, @@ -145,7 +150,7 @@ test('removing a cart row hands back the rest', async () => { await waitFor(() => expect(screen.getByText('Apple')).toBeInTheDocument()); - fireEvent.click(screen.getAllByRole('button', { name: 'Remove' })[0]); + await user.click(screen.getAllByRole('button', { name: 'Remove' })[0]); expect(onChange).toHaveBeenCalledWith([{ type: 'glyph', name: 'gold', quantity: 1 }]); }); diff --git a/app/components/observatory/probesTab.test.tsx b/app/components/observatory/probesTab.test.tsx index 42bd1c4e..1183eec1 100644 --- a/app/components/observatory/probesTab.test.tsx +++ b/app/components/observatory/probesTab.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Building } from 'app/interfaces'; import lacuna from 'app/lacuna'; import createQueryWrapper from 'app/testing/queryWrapper'; @@ -31,7 +32,6 @@ const renderTab = () => { }; beforeEach(() => { - jest.clearAllMocks(); mockedGetProbedStars.mockResolvedValue({ result: probedStars }); }); @@ -53,11 +53,12 @@ test('lists each probed star with its coordinates', async () => { }); test('Next asks the server for the following page', async () => { + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByText('Page 1 of 2')).toBeInTheDocument()); - fireEvent.click(screen.getByRole('button', { name: 'Next' })); + await user.click(screen.getByRole('button', { name: 'Next' })); await waitFor(() => expect(mockedGetProbedStars).toHaveBeenCalledWith({ building_id: 7, page_number: 2 }) @@ -65,11 +66,11 @@ test('Next asks the server for the following page', async () => { expect(screen.getByText('Page 2 of 2')).toBeInTheDocument(); }); -test('there is no pager when everything fits on one page', async () => { +test('there is a pager even when everything fits on one page', async () => { mockedGetProbedStars.mockResolvedValue({ result: { ...probedStars, star_count: 5 } }); renderTab(); await waitFor(() => expect(screen.getByText('Sol')).toBeInTheDocument()); - expect(screen.queryByRole('button', { name: 'Next' })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Next' })).toBeInTheDocument(); }); diff --git a/app/components/policeStation/shipsTab.test.tsx b/app/components/policeStation/shipsTab.test.tsx index 8d499e88..21d6a91e 100644 --- a/app/components/policeStation/shipsTab.test.tsx +++ b/app/components/policeStation/shipsTab.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Building } from 'app/interfaces'; import lacuna from 'app/lacuna'; import createQueryWrapper from 'app/testing/queryWrapper'; @@ -21,7 +22,7 @@ const arriving = { id: 1, name: 'Nina', type_human: 'Scow', - date_arrives: '01 31 2010 13:09:05 +0600', + date_arrives: '2010 01 31 13:09:05 +0600', from: { name: 'Earth', empire: { id: 3, name: 'Humans' } }, }, ], @@ -38,7 +39,6 @@ const renderTab = (kind: ShipsKind) => { }; beforeEach(() => { - jest.clearAllMocks(); mockedForeign.mockResolvedValue({ result: arriving }); mockedOrbiting.mockResolvedValue({ result: { ...arriving, ships: [] } }); mockedTravelling.mockResolvedValue({ @@ -78,7 +78,7 @@ test('travelling ships are headed somewhere rather than coming from somewhere', { id: 4, type_human: 'Probe', - date_arrives: '01 31 2010 13:09:05 +0600', + date_arrives: '2010 01 31 13:09:05 +0600', to: { name: 'Mars' }, }, ], @@ -92,11 +92,12 @@ test('travelling ships are headed somewhere rather than coming from somewhere', }); test('paging asks the server for the next page', async () => { + const user = userEvent.setup(); renderTab('incoming'); await waitFor(() => expect(screen.getByText('Page 1 of 3')).toBeInTheDocument()); - fireEvent.click(screen.getByRole('button', { name: 'Next' })); + await user.click(screen.getByRole('button', { name: 'Next' })); await waitFor(() => expect(mockedForeign).toHaveBeenCalledWith({ building_id: 7, page_number: 2 }) diff --git a/app/components/propositionsList.test.tsx b/app/components/propositionsList.test.tsx index 41b44e9b..e6bb879c 100644 --- a/app/components/propositionsList.test.tsx +++ b/app/components/propositionsList.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import PropositionsList, { Proposition } from 'app/components/propositionsList'; jest.mock('app/lacuna'); @@ -33,13 +34,14 @@ test('an open proposition shows the vote tally and both buttons', () => { expect(screen.getByRole('button', { name: 'No' })).toBeInTheDocument(); }); -test('voting hands back a numeric id and the choice', () => { +test('voting hands back a numeric id and the choice', async () => { + const user = userEvent.setup(); const onVote = renderList([open]); - fireEvent.click(screen.getByRole('button', { name: 'Yes' })); + await user.click(screen.getByRole('button', { name: 'Yes' })); expect(onVote).toHaveBeenCalledWith(1, 1); - fireEvent.click(screen.getByRole('button', { name: 'No' })); + await user.click(screen.getByRole('button', { name: 'No' })); expect(onVote).toHaveBeenLastCalledWith(1, 0); }); diff --git a/app/components/resources.test.tsx b/app/components/resources.test.tsx index 844ce54d..7b5d4d46 100644 --- a/app/components/resources.test.tsx +++ b/app/components/resources.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import lacuna from 'app/lacuna'; import BodyRPCStore from 'app/stores/rpc/body'; import createQueryWrapper from 'app/testing/queryWrapper'; @@ -64,7 +65,6 @@ const renderWindow = (props: React.ComponentProps = {}) }; beforeEach(() => { - jest.clearAllMocks(); mockedGetBuildingsResources.mockResolvedValue({ result: response }); BodyRPCStore.id = 100; }); @@ -99,17 +99,19 @@ test('a non-inverted tab shows consumption in red', async () => { }); test('the Waste tab is inverted, so falling waste is shown in green', async () => { + const user = userEvent.setup(); renderWindow(); await waitFor(() => expect(screen.getByText(/Wheat Farm/)).toBeInTheDocument()); - fireEvent.click(screen.getByText('Waste')); + await user.click(screen.getByText('Waste')); expect(screen.getByText(/Waste Digester/)).toBeInTheDocument(); expect(screen.getByText('-800 / hr').className).toMatch(/has-text-success/); }); test('the Overview tab describes each resource from the body store', async () => { + const user = userEvent.setup(); BodyRPCStore.food_stored = 40000; BodyRPCStore.food_capacity = 40000; BodyRPCStore.food_hour = 100; @@ -124,7 +126,7 @@ test('the Overview tab describes each resource from the body store', async () => renderWindow(); - fireEvent.click(screen.getByText('Overview')); + await user.click(screen.getByText('Overview')); const foodRow = screen.getByRole('cell', { name: 'Food' }).closest('tr') as HTMLElement; expect(foodRow).toHaveTextContent('Full'); diff --git a/app/components/security/prisonersTab.test.tsx b/app/components/security/prisonersTab.test.tsx index caf02b11..2648fa62 100644 --- a/app/components/security/prisonersTab.test.tsx +++ b/app/components/security/prisonersTab.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Building } from 'app/interfaces'; import lacuna from 'app/lacuna'; import createQueryWrapper from 'app/testing/queryWrapper'; @@ -24,7 +25,7 @@ const response = { name: 'Sneaky Pete', level: 12, task: 'Captured', - sentence_expires: '01 31 2010 13:09:05 +0600', + sentence_expires: '2010 01 31 13:09:05 +0600', }, ], }; @@ -40,7 +41,6 @@ const renderTab = () => { }; beforeEach(() => { - jest.clearAllMocks(); mockedPrisoners.mockResolvedValue({ result: response }); mockedConfirm.mockImplementation((_message: string, yes: () => void) => yes()); }); @@ -54,11 +54,12 @@ test('lists prisoners with their level and status', async () => { }); test('Next asks the server for the following page', async () => { + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByText('Page 1 of 2')).toBeInTheDocument()); - fireEvent.click(screen.getByRole('button', { name: 'Next' })); + await user.click(screen.getByRole('button', { name: 'Next' })); await waitFor(() => expect(mockedPrisoners).toHaveBeenCalledWith({ building_id: 7, page_number: 2 }) @@ -66,23 +67,24 @@ test('Next asks the server for the following page', async () => { expect(screen.getByText('Page 2 of 2')).toBeInTheDocument(); }); -test('there is no pager when everything fits on one page', async () => { +test('there is a pager even when everything fits on one page', async () => { mockedPrisoners.mockResolvedValue({ result: { ...response, captured_count: 1 } }); renderTab(); await waitFor(() => expect(screen.getByText('Sneaky Pete')).toBeInTheDocument()); - expect(screen.queryByRole('button', { name: 'Next' })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Next' })).toBeInTheDocument(); }); test('releasing a prisoner sends a numeric prisoner id', async () => { mockedRelease.mockResolvedValue({ result: { status: {} } }); + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByText('Sneaky Pete')).toBeInTheDocument()); - fireEvent.click(screen.getByRole('button', { name: 'Release' })); + await user.click(screen.getByRole('button', { name: 'Release' })); await waitFor(() => expect(mockedRelease).toHaveBeenCalled()); expect(mockedRelease).toHaveBeenCalledWith({ building_id: 7, prisoner_id: 3 }); diff --git a/app/components/shipyard/buildShipsTab.test.tsx b/app/components/shipyard/buildShipsTab.test.tsx index eaa91a59..e133e677 100644 --- a/app/components/shipyard/buildShipsTab.test.tsx +++ b/app/components/shipyard/buildShipsTab.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { types } from '@tlecommunity/client'; import { Building } from 'app/interfaces'; import lacuna from 'app/lacuna'; @@ -53,7 +54,6 @@ const renderTab = () => { }; beforeEach(() => { - jest.clearAllMocks(); mockedBuildable.mockResolvedValue({ result: response }); }); @@ -65,11 +65,12 @@ test('the buildable-now filter hides types that cannot be built', async () => { }); test('switching to buildable-later shows them, with the server reason', async () => { + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByText('Probe')).toBeInTheDocument()); - fireEvent.change(screen.getByDisplayValue('Buildable now'), { target: { value: 'later' } }); + await user.selectOptions(screen.getByDisplayValue('Buildable now'), 'later'); expect(screen.getByText('Sweeper')).toBeInTheDocument(); expect(screen.getByText('You need a higher level shipyard.')).toBeInTheDocument(); @@ -77,13 +78,12 @@ test('switching to buildable-later shows them, with the server reason', async () }); test('the filter box matches on tag as well as name', async () => { + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByText('Probe')).toBeInTheDocument()); - fireEvent.change(screen.getByPlaceholderText('Filter'), { - target: { value: 'exploration' }, - }); + await user.type(screen.getByPlaceholderText('Filter'), 'exploration'); expect(screen.getByText('Probe')).toBeInTheDocument(); }); @@ -91,13 +91,15 @@ test('the filter box matches on tag as well as name', async () => { test('building sends the type, quantity and the chosen shipyard selection', async () => { mockedBuildShips.mockResolvedValue({ result: {} }); + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByText('Probe')).toBeInTheDocument()); - fireEvent.change(screen.getByDisplayValue('This shipyard only'), { target: { value: 'all' } }); - fireEvent.change(screen.getByRole('spinbutton'), { target: { value: '3' } }); - fireEvent.click(screen.getByRole('button', { name: 'Build' })); + await user.selectOptions(screen.getByDisplayValue('This shipyard only'), 'all'); + await user.clear(screen.getByRole('spinbutton')); + await user.type(screen.getByRole('spinbutton'), '3'); + await user.click(screen.getByRole('button', { name: 'Build' })); await waitFor(() => expect(mockedBuildShips).toHaveBeenCalled()); expect(mockedBuildShips).toHaveBeenCalledWith({ @@ -106,12 +108,14 @@ test('building sends the type, quantity and the chosen shipyard selection', asyn }); test('a quantity of zero is refused without a request', async () => { + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByText('Probe')).toBeInTheDocument()); - fireEvent.change(screen.getByRole('spinbutton'), { target: { value: '0' } }); - fireEvent.click(screen.getByRole('button', { name: 'Build' })); + await user.clear(screen.getByRole('spinbutton')); + await user.type(screen.getByRole('spinbutton'), '0'); + await user.click(screen.getByRole('button', { name: 'Build' })); expect(screen.getByText('You must build at least one ship.')).toBeInTheDocument(); expect(mockedBuildShips).not.toHaveBeenCalled(); diff --git a/app/components/spacePort/viewTab.test.tsx b/app/components/spacePort/viewTab.test.tsx index 57a5c9f6..d3358086 100644 --- a/app/components/spacePort/viewTab.test.tsx +++ b/app/components/spacePort/viewTab.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { types } from '@tlecommunity/client'; import { Building } from 'app/interfaces'; import lacuna from 'app/lacuna'; @@ -36,8 +37,8 @@ const ship = (over: Partial = {}): types.SpacePort.Ship => can_scuttle: 1, build_tags: [], payload: [], - date_started: '01 31 2010 13:09:05 +0600', - date_available: '01 31 2010 13:09:05 +0600', + date_started: '2010 01 31 13:09:05 +0600', + date_available: '2010 01 31 13:09:05 +0600', ...over, }) as types.SpacePort.Ship; @@ -55,7 +56,6 @@ const respondWith = (ships: types.SpacePort.Ship[], numberOfShips = ships.length mockedAllShips.mockResolvedValue({ result: { ships, number_of_ships: numberOfShips } }); beforeEach(() => { - jest.clearAllMocks(); mockedConfirm.mockImplementation((_message: string, yes: () => void) => yes()); respondWith([ship()]); }); @@ -70,10 +70,11 @@ test('a docked ship offers Scuttle and no Recall', async () => { test('scuttling sends the building and ship id', async () => { mockedScuttle.mockResolvedValue({ result: {} }); + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByRole('button', { name: 'Scuttle' })).toBeInTheDocument()); - fireEvent.click(screen.getByRole('button', { name: 'Scuttle' })); + await user.click(screen.getByRole('button', { name: 'Scuttle' })); await waitFor(() => expect(mockedScuttle).toHaveBeenCalled()); expect(mockedScuttle).toHaveBeenCalledWith({ building_id: 7, ship_id: 1 }); @@ -105,10 +106,11 @@ test('recalling sends the building and ship id', async () => { mockedRecall.mockResolvedValue({ result: { ship: {} } }); respondWith([ship({ task: 'Defend' })]); + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByRole('button', { name: 'Recall' })).toBeInTheDocument()); - fireEvent.click(screen.getByRole('button', { name: 'Recall' })); + await user.click(screen.getByRole('button', { name: 'Recall' })); await waitFor(() => expect(mockedRecall).toHaveBeenCalled()); expect(mockedRecall).toHaveBeenCalledWith({ building_id: 7, ship_id: 1 }); @@ -134,13 +136,15 @@ test('Recall All only appears when something can be recalled', async () => { test('renaming a ship sends the new name', async () => { mockedName.mockResolvedValue({ result: {} }); + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByText('Bessie')).toBeInTheDocument()); - fireEvent.click(screen.getByText('Bessie')); + await user.click(screen.getByText('Bessie')); - fireEvent.change(screen.getByRole('textbox'), { target: { value: 'Daisy' } }); - fireEvent.click(screen.getByRole('button', { name: 'Save' })); + await user.clear(screen.getByRole('textbox')); + await user.type(screen.getByRole('textbox'), 'Daisy'); + await user.click(screen.getByRole('button', { name: 'Save' })); await waitFor(() => expect(mockedName).toHaveBeenCalled()); expect(mockedName).toHaveBeenCalledWith({ building_id: 7, ship_id: 1, name: 'Daisy' }); @@ -149,11 +153,12 @@ test('renaming a ship sends the new name', async () => { test('the pager appears once there is more than one page of ships', async () => { respondWith([ship()], 40); + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByText('Page 1 of 2')).toBeInTheDocument()); - fireEvent.click(screen.getByRole('button', { name: 'Next' })); + await user.click(screen.getByRole('button', { name: 'Next' })); await waitFor(() => expect(mockedAllShips).toHaveBeenCalledWith({ diff --git a/app/components/templeOfTheDrajilites/planetsTab.test.tsx b/app/components/templeOfTheDrajilites/planetsTab.test.tsx index 1b3e5bc1..39bcde9c 100644 --- a/app/components/templeOfTheDrajilites/planetsTab.test.tsx +++ b/app/components/templeOfTheDrajilites/planetsTab.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Building } from 'app/interfaces'; import lacuna from 'app/lacuna'; import createQueryWrapper from 'app/testing/queryWrapper'; @@ -23,8 +24,6 @@ const renderTab = () => { }; beforeEach(() => { - jest.clearAllMocks(); - mockedList.mockResolvedValue({ result: { status: {}, planets: [{ id: 3, name: 'Earth' }] }, }); @@ -47,22 +46,24 @@ test('surveyed planets are offered for viewing', async () => { // Fetching a surface is a second request, so it only fires once a planet is actually chosen. // test('no surface is fetched until a planet is chosen', async () => { + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByRole('option', { name: 'Earth' })).toBeInTheDocument()); expect(mockedView).not.toHaveBeenCalled(); - fireEvent.change(screen.getByRole('combobox'), { target: { value: '3' } }); + await user.selectOptions(screen.getByRole('combobox'), '3'); await waitFor(() => expect(mockedView).toHaveBeenCalledWith({ building_id: 7, planet_id: 3 })); }); test('the surface renders one image per building', async () => { + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByRole('option', { name: 'Earth' })).toBeInTheDocument()); - fireEvent.change(screen.getByRole('combobox'), { target: { value: '3' } }); + await user.selectOptions(screen.getByRole('combobox'), '3'); await waitFor(() => expect(screen.getByRole('img')).toBeInTheDocument()); expect(screen.getByRole('img')).toHaveAttribute('alt', 'capitol'); diff --git a/app/components/theDillonForge/forgeTab.test.tsx b/app/components/theDillonForge/forgeTab.test.tsx index 25b97840..c425fa49 100644 --- a/app/components/theDillonForge/forgeTab.test.tsx +++ b/app/components/theDillonForge/forgeTab.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { TheDillonForgeViewResponse } from 'app/interfaces'; import lacuna from 'app/lacuna'; import createQueryWrapper from 'app/testing/queryWrapper'; @@ -65,7 +66,6 @@ const renderTab = (view: TheDillonForgeViewResponse) => { }; beforeEach(() => { - jest.clearAllMocks(); mockedConfirm.mockImplementation((_message: string, yes: () => void) => yes()); }); @@ -87,13 +87,16 @@ test('a busy forge offers only the subsidy', () => { test('making a plan sends the class and the requested level', async () => { mockedMakePlan.mockResolvedValue({ result: { status: {} } }); + const user = userEvent.setup(); renderTab(idle); - fireEvent.change(screen.getAllByRole('combobox')[1], { - target: { value: 'Lacuna::DB::Result::Building::Oracle' }, - }); - fireEvent.change(screen.getAllByRole('spinbutton')[1], { target: { value: '4' } }); - fireEvent.click(screen.getByRole('button', { name: 'Make Plan' })); + await user.selectOptions( + screen.getAllByRole('combobox')[1], + 'Lacuna::DB::Result::Building::Oracle' + ); + await user.clear(screen.getAllByRole('spinbutton')[1]); + await user.type(screen.getAllByRole('spinbutton')[1], '4'); + await user.click(screen.getByRole('button', { name: 'Make Plan' })); await waitFor(() => expect(mockedMakePlan).toHaveBeenCalled()); @@ -108,14 +111,17 @@ test('making a plan sends the class and the requested level', async () => { // A level 1 plan is the input to the forge, never its output, so the server rejects anything below // 2 - the legacy panel checked this client-side too. // -test('a level below 2 is refused without asking the server', () => { +test('a level below 2 is refused without asking the server', async () => { + const user = userEvent.setup(); renderTab(idle); - fireEvent.change(screen.getAllByRole('combobox')[1], { - target: { value: 'Lacuna::DB::Result::Building::Oracle' }, - }); - fireEvent.change(screen.getAllByRole('spinbutton')[1], { target: { value: '1' } }); - fireEvent.click(screen.getByRole('button', { name: 'Make Plan' })); + await user.selectOptions( + screen.getAllByRole('combobox')[1], + 'Lacuna::DB::Result::Building::Oracle' + ); + await user.clear(screen.getAllByRole('spinbutton')[1]); + await user.type(screen.getAllByRole('spinbutton')[1], '1'); + await user.click(screen.getByRole('button', { name: 'Make Plan' })); expect(screen.getByText('Choose a level between 2 and 5.')).toBeInTheDocument(); expect(mockedMakePlan).not.toHaveBeenCalled(); @@ -124,13 +130,16 @@ test('a level below 2 is refused without asking the server', () => { test('splitting sends the three fields that identify the plan', async () => { mockedSplitPlan.mockResolvedValue({ result: { status: {} } }); + const user = userEvent.setup(); renderTab(idle); - fireEvent.change(screen.getAllByRole('combobox')[0], { - target: { value: 'Lacuna::DB::Result::Building::Halls;3;1' }, - }); - fireEvent.change(screen.getAllByRole('spinbutton')[0], { target: { value: '3' } }); - fireEvent.click(screen.getByRole('button', { name: 'Split Plan' })); + await user.selectOptions( + screen.getAllByRole('combobox')[0], + 'Lacuna::DB::Result::Building::Halls;3;1' + ); + await user.clear(screen.getAllByRole('spinbutton')[0]); + await user.type(screen.getAllByRole('spinbutton')[0], '3'); + await user.click(screen.getByRole('button', { name: 'Split Plan' })); await waitFor(() => expect(mockedSplitPlan).toHaveBeenCalled()); @@ -143,14 +152,17 @@ test('splitting sends the three fields that identify the plan', async () => { }); }); -test('splitting more than is held is refused', () => { +test('splitting more than is held is refused', async () => { + const user = userEvent.setup(); renderTab(idle); - fireEvent.change(screen.getAllByRole('combobox')[0], { - target: { value: 'Lacuna::DB::Result::Building::Halls;3;1' }, - }); - fireEvent.change(screen.getAllByRole('spinbutton')[0], { target: { value: '5' } }); - fireEvent.click(screen.getByRole('button', { name: 'Split Plan' })); + await user.selectOptions( + screen.getAllByRole('combobox')[0], + 'Lacuna::DB::Result::Building::Halls;3;1' + ); + await user.clear(screen.getAllByRole('spinbutton')[0]); + await user.type(screen.getAllByRole('spinbutton')[0], '5'); + await user.click(screen.getByRole('button', { name: 'Split Plan' })); expect(screen.getByText('Choose a quantity between 2 and 4.')).toBeInTheDocument(); expect(mockedSplitPlan).not.toHaveBeenCalled(); @@ -159,9 +171,10 @@ test('splitting more than is held is refused', () => { test('subsidizing confirms before spending essentia', async () => { mockedSubsidize.mockResolvedValue({ result: { status: {} } }); + const user = userEvent.setup(); renderTab(working); - fireEvent.click(screen.getByRole('button', { name: /Subsidize/ })); + await user.click(screen.getByRole('button', { name: /Subsidize/ })); expect(mockedConfirm).toHaveBeenCalledWith( 'Finish this job now for 4 essentia?', diff --git a/app/components/trade/supplyChainsTab.test.tsx b/app/components/trade/supplyChainsTab.test.tsx index d9432162..80ff47ca 100644 --- a/app/components/trade/supplyChainsTab.test.tsx +++ b/app/components/trade/supplyChainsTab.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Building } from 'app/interfaces'; import BodyRPCStore from 'app/stores/rpc/body'; import EmpireRPCStore from 'app/stores/rpc/empire'; @@ -37,7 +38,6 @@ const renderTab = () => { }; beforeEach(() => { - jest.clearAllMocks(); mockedView.mockResolvedValue({ result: response }); mockedConfirm.mockImplementation((_message: string, yes: () => void) => yes()); @@ -80,20 +80,18 @@ test('the current body is not offered as a destination', async () => { test('creating a chain sends the destination, resource and rate', async () => { mockedCreate.mockResolvedValue({ result: { status: {} } }); + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByRole('combobox', { name: 'Send to' })).toBeInTheDocument() ); - fireEvent.change(screen.getByRole('combobox', { name: 'Send to' }), { target: { value: '200' } }); - fireEvent.change(screen.getByRole('combobox', { name: 'Resource' }), { - target: { value: 'apple' }, - }); - fireEvent.change(screen.getByRole('spinbutton', { name: 'Per hour' }), { - target: { value: '500' }, - }); - fireEvent.click(screen.getByRole('button', { name: 'Create Chain' })); + await user.selectOptions(screen.getByRole('combobox', { name: 'Send to' }), '200'); + await user.selectOptions(screen.getByRole('combobox', { name: 'Resource' }), 'apple'); + await user.clear(screen.getByRole('spinbutton', { name: 'Per hour' })); + await user.type(screen.getByRole('spinbutton', { name: 'Per hour' }), '500'); + await user.click(screen.getByRole('button', { name: 'Create Chain' })); await waitFor(() => expect(mockedCreate).toHaveBeenCalledWith({ @@ -108,6 +106,7 @@ test('creating a chain sends the destination, resource and rate', async () => { test('editing a row’s rate updates that chain on blur', async () => { mockedUpdate.mockResolvedValue({ result: { status: {} } }); + const user = userEvent.setup(); renderTab(); await waitFor(() => @@ -115,8 +114,9 @@ test('editing a row’s rate updates that chain on blur', async () => { ); const input = screen.getByRole('spinbutton', { name: 'water per hour' }); - fireEvent.change(input, { target: { value: '2000' } }); - fireEvent.blur(input); + await user.clear(input); + await user.type(input, '2000'); + await user.tab(); await waitFor(() => expect(mockedUpdate).toHaveBeenCalledWith({ @@ -131,13 +131,14 @@ test('editing a row’s rate updates that chain on blur', async () => { test('deleting a chain confirms first', async () => { mockedDelete.mockResolvedValue({ result: { status: {} } }); + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByRole('spinbutton', { name: 'water per hour' })).toBeInTheDocument() ); - fireEvent.click(screen.getByRole('button', { name: 'Delete' })); + await user.click(screen.getByRole('button', { name: 'Delete' })); expect(mockedConfirm).toHaveBeenCalledWith('Delete this supply chain?', expect.any(Function)); await waitFor(() => diff --git a/app/components/transporter/oneForOneTab.test.tsx b/app/components/transporter/oneForOneTab.test.tsx index cc454720..6771c67d 100644 --- a/app/components/transporter/oneForOneTab.test.tsx +++ b/app/components/transporter/oneForOneTab.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Building } from 'app/interfaces'; import EmpireRPCStore from 'app/stores/rpc/empire'; import lacuna from 'app/lacuna'; @@ -26,7 +27,6 @@ const renderTab = () => { }; beforeEach(() => { - jest.clearAllMocks(); mockedResources.mockResolvedValue({ result: { resources: { apple: 100, bean: 0 } } }); mockedConfirm.mockImplementation((_message: string, yes: () => void) => yes()); EmpireRPCStore.essentia = 100; @@ -47,15 +47,15 @@ test('only resources there is something stored of can be traded away', async () test('a valid trade sends have, want and quantity', async () => { mockedTrade.mockResolvedValue({ result: { status: {} } }); + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByRole('combobox', { name: 'Have' })).toBeInTheDocument()); - fireEvent.change(screen.getByRole('combobox', { name: 'Have' }), { target: { value: 'apple' } }); - fireEvent.change(screen.getByRole('spinbutton', { name: 'Quantity' }), { - target: { value: '50' }, - }); - fireEvent.click(screen.getByRole('button', { name: /Submit Trade/ })); + await user.selectOptions(screen.getByRole('combobox', { name: 'Have' }), 'apple'); + await user.clear(screen.getByRole('spinbutton', { name: 'Quantity' })); + await user.type(screen.getByRole('spinbutton', { name: 'Quantity' }), '50'); + await user.click(screen.getByRole('button', { name: /Submit Trade/ })); await waitFor(() => expect(mockedTrade).toHaveBeenCalled()); @@ -68,28 +68,29 @@ test('a valid trade sends have, want and quantity', async () => { }); test('trading more than is stored is rejected without asking the server', async () => { + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByRole('combobox', { name: 'Have' })).toBeInTheDocument()); - fireEvent.change(screen.getByRole('combobox', { name: 'Have' }), { target: { value: 'apple' } }); - fireEvent.change(screen.getByRole('spinbutton', { name: 'Quantity' }), { - target: { value: '101' }, - }); - fireEvent.click(screen.getByRole('button', { name: /Submit Trade/ })); + await user.selectOptions(screen.getByRole('combobox', { name: 'Have' }), 'apple'); + await user.clear(screen.getByRole('spinbutton', { name: 'Quantity' })); + await user.type(screen.getByRole('spinbutton', { name: 'Quantity' }), '101'); + await user.click(screen.getByRole('button', { name: /Submit Trade/ })); expect(screen.getByText('You can trade between 1 and 100.')).toBeInTheDocument(); expect(mockedTrade).not.toHaveBeenCalled(); }); test('trading a resource for itself is rejected', async () => { + const user = userEvent.setup(); renderTab(); await waitFor(() => expect(screen.getByRole('combobox', { name: 'Have' })).toBeInTheDocument()); - fireEvent.change(screen.getByRole('combobox', { name: 'Have' }), { target: { value: 'apple' } }); - fireEvent.change(screen.getByRole('combobox', { name: 'Want' }), { target: { value: 'apple' } }); - fireEvent.click(screen.getByRole('button', { name: /Submit Trade/ })); + await user.selectOptions(screen.getByRole('combobox', { name: 'Have' }), 'apple'); + await user.selectOptions(screen.getByRole('combobox', { name: 'Want' }), 'apple'); + await user.click(screen.getByRole('button', { name: /Submit Trade/ })); expect(screen.getByText('Choose two different resources.')).toBeInTheDocument(); expect(mockedTrade).not.toHaveBeenCalled(); diff --git a/app/queries/body.test.tsx b/app/queries/body.test.tsx index fc0153a9..1c232689 100644 --- a/app/queries/body.test.tsx +++ b/app/queries/body.test.tsx @@ -35,10 +35,6 @@ const response = { status: {}, }; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('useBodyBuildingsResourcesQuery calls get_buildings_resources with the body id', async () => { mockedGetBuildingsResources.mockResolvedValue({ result: response }); diff --git a/app/queries/building.test.tsx b/app/queries/building.test.tsx index 52e8dfa8..10cc46d6 100644 --- a/app/queries/building.test.tsx +++ b/app/queries/building.test.tsx @@ -16,15 +16,10 @@ const viewResponse = { food_stored: { apple: 100, bean: 0 }, }; -let mockedView: jest.Mock; - -beforeEach(() => { - jest.clearAllMocks(); - mockedView = jest.fn().mockResolvedValue({ result: viewResponse }); +test('useBuildingViewQuery returns only the building', async () => { + const mockedView = jest.fn().mockResolvedValue({ result: viewResponse }); mockedBuildingFromUrl.mockReturnValue({ view: mockedView }); -}); -test('useBuildingViewQuery returns only the building', async () => { const { result } = renderHook(() => useBuildingViewQuery('/foodreserve', 7), { wrapper: createQueryWrapper(), }); @@ -37,6 +32,9 @@ test('useBuildingViewQuery returns only the building', async () => { }); test('the view query does not run until a building id is known', () => { + const mockedView = jest.fn().mockResolvedValue({ result: viewResponse }); + mockedBuildingFromUrl.mockReturnValue({ view: mockedView }); + const { result } = renderHook(() => useBuildingViewQuery('/foodreserve', 0), { wrapper: createQueryWrapper(), }); diff --git a/app/queries/building.ts b/app/queries/building.ts index 1f8a7eb2..e41b131a 100644 --- a/app/queries/building.ts +++ b/app/queries/building.ts @@ -24,11 +24,13 @@ const buildingViewOptions = (url: string, buildingId: number) => ({ export const useBuildingViewQuery = ( url: string, buildingId: number -) => - useQuery({ +) => { + console.log('b', lacuna.buildingFromUrl(url)); + return useQuery({ ...buildingViewOptions(url, buildingId), select: (data) => data as TBuilding, }); +}; // // Anything that changes a building changes the body it sits on too - plots used, build queue, diff --git a/app/queries/capitol.test.tsx b/app/queries/capitol.test.tsx index c02208e7..66a6dfec 100644 --- a/app/queries/capitol.test.tsx +++ b/app/queries/capitol.test.tsx @@ -7,10 +7,6 @@ jest.mock('app/lacuna'); const mockedRenameEmpire = lacuna.capitol.renameEmpire as jest.Mock; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('useRenameEmpireMutation sends the building and the new name', async () => { mockedRenameEmpire.mockResolvedValue({ result: { status: {} } }); diff --git a/app/queries/development.test.tsx b/app/queries/development.test.tsx index f08c604b..f415b3b7 100644 --- a/app/queries/development.test.tsx +++ b/app/queries/development.test.tsx @@ -14,10 +14,6 @@ const mockedSubsidizeQueue = lacuna.development.subsidizeBuildQueue as jest.Mock const mockedSubsidizeOne = lacuna.development.subsidizeOneBuild as jest.Mock; const mockedCancel = lacuna.development.cancelBuild as jest.Mock; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('subsidizing the whole queue only needs the building', async () => { mockedSubsidizeQueue.mockResolvedValue({ result: { essentia_spent: 15, status: {} } }); diff --git a/app/queries/distributionCenter.test.tsx b/app/queries/distributionCenter.test.tsx index b5159f97..2ccd7417 100644 --- a/app/queries/distributionCenter.test.tsx +++ b/app/queries/distributionCenter.test.tsx @@ -19,10 +19,6 @@ const storedResponse = { resources: { apple: 100, gold: 50 }, }; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('useStoredResourcesQuery asks for what is on the planet', async () => { mockedStored.mockResolvedValue({ result: storedResponse }); diff --git a/app/queries/embassy.test.tsx b/app/queries/embassy.test.tsx index 21676c94..58911045 100644 --- a/app/queries/embassy.test.tsx +++ b/app/queries/embassy.test.tsx @@ -35,10 +35,6 @@ const mocked = { const ok = { result: { status: {} } }; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('creating an alliance sends the name', async () => { mocked.createAlliance.mockResolvedValue(ok); diff --git a/app/queries/energyReserve.test.tsx b/app/queries/energyReserve.test.tsx index d979d101..8ab4d0f5 100644 --- a/app/queries/energyReserve.test.tsx +++ b/app/queries/energyReserve.test.tsx @@ -7,10 +7,6 @@ jest.mock('app/lacuna'); const mockedDump = lacuna.energyReserve.dump as jest.Mock; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('useDumpEnergyMutation sends an amount and no resource type', async () => { mockedDump.mockResolvedValue({ result: { status: {} } }); diff --git a/app/queries/entertainment.test.tsx b/app/queries/entertainment.test.tsx index 876742aa..86ae78ab 100644 --- a/app/queries/entertainment.test.tsx +++ b/app/queries/entertainment.test.tsx @@ -10,10 +10,6 @@ const mockedQuack = lacuna.entertainment.duckQuack as jest.Mock; const options = [{ name: 'Vote for us', url: 'http://example.com/vote' }]; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('useLotteryOptionsQuery returns just the options', async () => { mockedOptions.mockResolvedValue({ result: { options, status: {} } }); diff --git a/app/queries/foodReserve.test.tsx b/app/queries/foodReserve.test.tsx index 1b4bfb3a..63017f62 100644 --- a/app/queries/foodReserve.test.tsx +++ b/app/queries/foodReserve.test.tsx @@ -7,10 +7,6 @@ jest.mock('app/lacuna'); const mockedDump = lacuna.foodReserve.dump as jest.Mock; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('useDumpFoodMutation sends the building, type and amount', async () => { mockedDump.mockResolvedValue({ result: { status: {} } }); diff --git a/app/queries/libraryOfJith.test.tsx b/app/queries/libraryOfJith.test.tsx index 95398e3c..0f0a1caf 100644 --- a/app/queries/libraryOfJith.test.tsx +++ b/app/queries/libraryOfJith.test.tsx @@ -9,10 +9,6 @@ const mockedResearch = lacuna.libraryOfJith.researchSpecies as jest.Mock; const species = { name: 'Human', description: 'Boring.', min_orbit: 3, max_orbit: 3 }; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('useResearchSpeciesQuery looks a species up by empire', async () => { mockedResearch.mockResolvedValue({ result: { species, status: {} } }); diff --git a/app/queries/market.test.tsx b/app/queries/market.test.tsx index e0f2508c..1e23c7dd 100644 --- a/app/queries/market.test.tsx +++ b/app/queries/market.test.tsx @@ -15,8 +15,6 @@ const mockedResources = lacuna.trade.getStoredResources as jest.Mock; const mockedAdd = lacuna.trade.addToMarket as jest.Mock; const mockedPush = lacuna.transporter.pushItems as jest.Mock; -beforeEach(() => jest.clearAllMocks()); - // // The server sends `date_offered` and a pre-rendered string offer for every market, which is what // both legacy panels read - the client's `trade` types disagree, so the result is widened. @@ -27,7 +25,7 @@ test('the market list keeps trade_count and the rendered offer', async () => { status: {}, trade_count: 40, trades: [ - { id: 1, date_offered: '01 31 2010 13:09:05 +0600', ask: 5, offer: ['10,000 apple'] }, + { id: 1, date_offered: '2010 01 31 13:09:05 +0600', ask: 5, offer: ['10,000 apple'] }, ], }, }); diff --git a/app/queries/miningMinistry.test.tsx b/app/queries/miningMinistry.test.tsx index 0f1fa718..e2ba2d52 100644 --- a/app/queries/miningMinistry.test.tsx +++ b/app/queries/miningMinistry.test.tsx @@ -37,10 +37,6 @@ const shipsResponse = { ships: [{ id: '3', name: 'Hauler', task: 'Docked', speed: 100, hold_size: 5000 }], }; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('usePlatformsQuery returns the platforms and the ceiling', async () => { mockedPlatforms.mockResolvedValue({ result: platformsResponse }); diff --git a/app/queries/missionCommand.test.tsx b/app/queries/missionCommand.test.tsx index e8ffb7e1..cff8667c 100644 --- a/app/queries/missionCommand.test.tsx +++ b/app/queries/missionCommand.test.tsx @@ -18,17 +18,13 @@ const missions = [ id: '11', name: 'Deliver the goods', description: 'Take these there.', - date_posted: '01 31 2010 13:09:05 +0600', + date_posted: '2010 01 31 13:09:05 +0600', max_university_level: 10, objectives: ['Do the thing'], rewards: ['1000 food'], }, ]; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('useMissionsQuery returns just the missions', async () => { mockedGetMissions.mockResolvedValue({ result: { missions, status: {} } }); diff --git a/app/queries/network19.test.tsx b/app/queries/network19.test.tsx index 2089921a..9675e90e 100644 --- a/app/queries/network19.test.tsx +++ b/app/queries/network19.test.tsx @@ -9,15 +9,11 @@ const mockedViewNews = lacuna.network19.viewNews as jest.Mock; const mockedRestrictCoverage = lacuna.network19.restrictCoverage as jest.Mock; const newsResult = { - news: [{ headline: 'Lacuna Expanse is a great game.', date: '01 31 2010 13:09:05 +0600' }], + news: [{ headline: 'Lacuna Expanse is a great game.', date: '2010 01 31 13:09:05 +0600' }], feeds: { Earth: 'http://example.com/rss' }, status: {}, }; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('useNewsQuery fetches the headlines for a building', async () => { mockedViewNews.mockResolvedValue({ result: newsResult }); diff --git a/app/queries/observatory.test.tsx b/app/queries/observatory.test.tsx index 9d6807fa..fc0c1b93 100644 --- a/app/queries/observatory.test.tsx +++ b/app/queries/observatory.test.tsx @@ -21,10 +21,6 @@ const probedStars = { status: {}, }; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('useProbedStarsQuery asks for the requested page', async () => { mockedGetProbedStars.mockResolvedValue({ result: probedStars }); diff --git a/app/queries/oreStorage.test.tsx b/app/queries/oreStorage.test.tsx index 5fda0b5c..465346be 100644 --- a/app/queries/oreStorage.test.tsx +++ b/app/queries/oreStorage.test.tsx @@ -7,10 +7,6 @@ jest.mock('app/lacuna'); const mockedDump = lacuna.oreStorage.dump as jest.Mock; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('useDumpOreMutation sends the building, type and amount', async () => { mockedDump.mockResolvedValue({ result: { status: {} } }); diff --git a/app/queries/park.test.tsx b/app/queries/park.test.tsx index 432e27b2..735c5239 100644 --- a/app/queries/park.test.tsx +++ b/app/queries/park.test.tsx @@ -14,10 +14,6 @@ const partyResult = { party: { seconds_remaining: 3600, happiness: 5000, can_throw: 0 }, }; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('useThrowAPartyMutation asks the server to throw a party for this building', async () => { mockedThrowAParty.mockResolvedValue({ result: partyResult }); diff --git a/app/queries/parliament.test.tsx b/app/queries/parliament.test.tsx index 452d8f85..3a18e182 100644 --- a/app/queries/parliament.test.tsx +++ b/app/queries/parliament.test.tsx @@ -16,8 +16,6 @@ const mockedStars = lacuna.parliament.getStarsInJurisdiction as jest.Mock; const mockedBodies = lacuna.parliament.getBodiesForStarInJurisdiction as jest.Mock; const mockedPlatforms = lacuna.parliament.getMiningPlatformsForAsteroidInJurisdiction as jest.Mock; -beforeEach(() => jest.clearAllMocks()); - // // view_laws is keyed on the body the station occupies, not on the building - the one hook here that // does not take a building id. diff --git a/app/queries/recycling.test.tsx b/app/queries/recycling.test.tsx index e372847a..5e492790 100644 --- a/app/queries/recycling.test.tsx +++ b/app/queries/recycling.test.tsx @@ -11,10 +11,6 @@ const mockedSubsidize = lacuna.wasteRecycling.subsidizeRecycling as jest.Mock; const response = { building: { id: 7 }, status: {}, recycle: { can: 0, seconds_remaining: 60 } }; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('recycling sends the three amounts and the essentia flag as a number', async () => { mockedRecycle.mockResolvedValue({ result: response }); diff --git a/app/queries/security.test.tsx b/app/queries/security.test.tsx index a5eaa582..ab91438d 100644 --- a/app/queries/security.test.tsx +++ b/app/queries/security.test.tsx @@ -24,7 +24,7 @@ const prisonersResponse = { name: 'Sneaky Pete', level: 12, task: 'Captured', - sentence_expires: '01 31 2010 13:09:05 +0600', + sentence_expires: '2010 01 31 13:09:05 +0600', }, ], }; @@ -32,13 +32,9 @@ const prisonersResponse = { const spiesResponse = { status: {}, spy_count: 2, - spies: [{ name: 'Unknown', level: 5, task: 'Idle', next_mission: '01 31 2010 13:09:05 +0600' }], + spies: [{ name: 'Unknown', level: 5, task: 'Idle', next_mission: '2010 01 31 13:09:05 +0600' }], }; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('usePrisonersQuery asks for the requested page and keeps the total count', async () => { mockedPrisoners.mockResolvedValue({ result: prisonersResponse }); diff --git a/app/queries/shipyard.test.tsx b/app/queries/shipyard.test.tsx index fba3adc7..a077acdd 100644 --- a/app/queries/shipyard.test.tsx +++ b/app/queries/shipyard.test.tsx @@ -26,7 +26,7 @@ const queueResponse = { type: 'probe', type_human: 'Probe', quantity: 2, - date_completed: '01 31 2010 13:09:05 +0600', + date_completed: '2010 01 31 13:09:05 +0600', attributes: {}, }, ], @@ -54,10 +54,6 @@ const buildableResponse = { }, }; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('useBuildQueueQuery asks for the requested page', async () => { mockedQueue.mockResolvedValue({ result: queueResponse }); diff --git a/app/queries/spacePort.test.tsx b/app/queries/spacePort.test.tsx index d94e0f29..b94e936f 100644 --- a/app/queries/spacePort.test.tsx +++ b/app/queries/spacePort.test.tsx @@ -36,10 +36,6 @@ const response = { fleet_send_limit: 10, }; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('useShipsForQuery asks the server for the given body and target', async () => { mockedGetShipsFor.mockResolvedValue({ result: response }); diff --git a/app/queries/themePark.test.tsx b/app/queries/themePark.test.tsx index 14ba65b5..13ce2dcd 100644 --- a/app/queries/themePark.test.tsx +++ b/app/queries/themePark.test.tsx @@ -7,10 +7,6 @@ jest.mock('app/lacuna'); const mockedOperate = lacuna.themePark.operate as jest.Mock; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('useOperateMutation only needs the building', async () => { mockedOperate.mockResolvedValue({ result: { building: { id: 7 }, status: {} } }); diff --git a/app/queries/waterStorage.test.tsx b/app/queries/waterStorage.test.tsx index 8faf754e..d888360c 100644 --- a/app/queries/waterStorage.test.tsx +++ b/app/queries/waterStorage.test.tsx @@ -7,10 +7,6 @@ jest.mock('app/lacuna'); const mockedDump = lacuna.waterStorage.dump as jest.Mock; -beforeEach(() => { - jest.clearAllMocks(); -}); - test('useDumpWaterMutation sends an amount and no resource type', async () => { mockedDump.mockResolvedValue({ result: { status: {} } }); diff --git a/app/stores/rpc/server.ts b/app/stores/rpc/server.ts index 63479290..4facb65d 100644 --- a/app/stores/rpc/server.ts +++ b/app/stores/rpc/server.ts @@ -5,7 +5,7 @@ import { types } from '@tlecommunity/client'; import WindowsStore from 'app/stores/windows'; class ServerRPCStore { - time = '01 31 2010 13:09:05 +0600'; + time = '2010 01 3113:09:05 +0600'; version = '1.0'; diff --git a/jest.config.js b/jest.config.js index 0d6082a3..563395a3 100644 --- a/jest.config.js +++ b/jest.config.js @@ -40,6 +40,7 @@ const config = { }, testTimeout: 30000, + clearMocks: true, }; export default config; diff --git a/package-lock.json b/package-lock.json index 866fb293..9aa22310 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "@tanstack/react-query-devtools": "^5.102.8", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.3", + "@testing-library/user-event": "^14.6.7", "@types/jest": "^30.0.0", "@types/lodash": "^4.17.25", "@types/node": "^24.0.0", @@ -3434,6 +3435,20 @@ } } }, + "node_modules/@testing-library/user-event": { + "version": "14.6.7", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.7.tgz", + "integrity": "sha512-MPCpX8bxe8zS+JmmTwLp8jd0dy1rAm60Te/SL8JrQM3qvQJcBOs1d7IefJMyZzqM3EWBrDn/LWDt1BCGu4ASfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@tlecommunity/client": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/@tlecommunity/client/-/client-1.9.2.tgz", diff --git a/package.json b/package.json index dffda12e..77985e18 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "@tanstack/react-query-devtools": "^5.102.8", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.3", + "@testing-library/user-event": "^14.6.7", "@types/jest": "^30.0.0", "@types/lodash": "^4.17.25", "@types/node": "^24.0.0", diff --git a/stubs/src/modules/buildings.ts b/stubs/src/modules/buildings.ts index b1c48bbb..92dda339 100644 --- a/stubs/src/modules/buildings.ts +++ b/stubs/src/modules/buildings.ts @@ -31,12 +31,12 @@ const Buildings: Route = { }, // 'pending_build' : { // 'seconds_remaining' : 430, - // 'start' : '01 31 2010 13:09:05 +0600', + // 'start' : '2010 01 31 13:09:05 +0600', // 'end' : '01 31 2010 18:09:05 +0600' // }, // 'work' : { // 'seconds_remaining' : 49, - // 'start' : '01 31 2010 13:09:05 +0600', + // 'start' : '2010 01 31 13:09:05 +0600', // 'end' : '01 31 2010 18:09:05 +0600' // }, downgrade: { diff --git a/stubs/src/modules/inbox.ts b/stubs/src/modules/inbox.ts index 65682cbd..e28d22ed 100644 --- a/stubs/src/modules/inbox.ts +++ b/stubs/src/modules/inbox.ts @@ -14,7 +14,7 @@ const Inbox: Route = { to_id: 'id-goes-here', subject: 'Vaxaslim', body: 'This is a test email to demonstrate the capabilities of the email reader.\n\nHave fun!', - date: '01 31 2010 13:09:05 +0600', + date: '2010 01 31 13:09:05 +0600', has_read: 1, has_replied: 0, has_archived: 0, @@ -60,7 +60,7 @@ const Inbox: Route = { { id: 'id-goes-here', subject: 'Vaxaslim', - date: '01 31 2010 13:09:05 +0600', + date: '2010 01 31 13:09:05 +0600', from: 'Dr. Stephen T. Colbert DFA', from_id: 'id-goes-here', to: 'Jon Stewart', diff --git a/stubs/src/modules/intelligence.ts b/stubs/src/modules/intelligence.ts index 972c9d9c..31380680 100644 --- a/stubs/src/modules/intelligence.ts +++ b/stubs/src/modules/intelligence.ts @@ -73,8 +73,8 @@ const Intelligence: Route = { y: -65, }, is_available: 1, // can be reassigned - available_on: '01 31 2010 13:09:05 +0600', // if can't be reassigned, this is when will be available - started_assignment: '01 31 2010 13:09:05 +0600', + available_on: '2010 01 31 13:09:05 +0600', // if can't be reassigned, this is when will be available + started_assignment: '2010 01 31 13:09:05 +0600', seconds_remaining: 45, mission_count: { offensive: 149, @@ -151,8 +151,8 @@ const Intelligence: Route = { name: 'Earth', }, is_available: 1, // can be reassigned - available_on: '01 31 2010 13:09:05 +0600', // if can't be reassigned, this is when will be available - started_assignment: '01 31 2010 13:09:05 +0600', + available_on: '2010 01 31 13:09:05 +0600', // if can't be reassigned, this is when will be available + started_assignment: '2010 01 31 13:09:05 +0600', seconds_remaining: 45, mission_count: { offensive: 149, -- 2.51.2