diff --git a/apps/central-scan/frontend/config/jest/cssTransform.js b/apps/central-scan/frontend/config/jest/cssTransform.js deleted file mode 100644 index 74c21818a3..0000000000 --- a/apps/central-scan/frontend/config/jest/cssTransform.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -// This is a custom Jest transformer turning style imports into empty objects. -// http://facebook.github.io/jest/docs/en/webpack.html - -module.exports = { - process() { - return { - code: 'module.exports = {};' - }; - }, - getCacheKey() { - // The output is always the same. - return 'cssTransform'; - }, -}; diff --git a/apps/central-scan/frontend/jest.config.js b/apps/central-scan/frontend/jest.config.js deleted file mode 100644 index 00fc3d0898..0000000000 --- a/apps/central-scan/frontend/jest.config.js +++ /dev/null @@ -1,30 +0,0 @@ -const shared = require('../../../jest.config.shared'); - -/** - * @type {import('@jest/types').Config.InitialOptions} - */ -module.exports = { - ...shared, - collectCoverageFrom: [ - 'src/**/*.{js,jsx,ts,tsx}', - '!src/config/*', - '!src/**/*.d.ts', - '!src/index.tsx', - ], - coverageThreshold: { - global: { - branches: -66, - lines: -77, - }, - }, - resetMocks: true, - setupFiles: ['react-app-polyfill/jsdom'], - setupFilesAfterEnv: ['/src/setupTests.ts'], - testEnvironment: 'jsdom', - transform: { - '^.+\\.css$': '/config/jest/cssTransform.js', - }, - transformIgnorePatterns: [ - '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$', - ], -}; diff --git a/apps/central-scan/frontend/package.json b/apps/central-scan/frontend/package.json index c807087a6d..522a004253 100644 --- a/apps/central-scan/frontend/package.json +++ b/apps/central-scan/frontend/package.json @@ -22,9 +22,9 @@ "stylelint:run": "stylelint 'src/**/*.{js,jsx,ts,tsx}'", "stylelint:run:fix": "stylelint 'src/**/*.{js,jsx,ts,tsx}' --fix", "test": "is-ci test:ci test:watch", - "test:ci": "TZ=America/Anchorage jest --coverage --reporters=default --reporters=jest-junit --maxWorkers=6", - "test:coverage": "TZ=America/Anchorage jest --coverage", - "test:watch": "TZ=America/Anchorage jest --watch", + "test:ci": "TZ=America/Anchorage vitest run --coverage", + "test:coverage": "TZ=America/Anchorage vitest --coverage", + "test:watch": "TZ=America/Anchorage vitest", "type-check": "tsc --build" }, "browserslist": { @@ -77,7 +77,6 @@ "@types/fast-text-encoding": "^1.0.1", "@types/fetch-mock": "^7.3.2", "@types/history": "4.7.11", - "@types/jest": "^29.5.3", "@types/kiosk-browser": "workspace:*", "@types/node": "20.16.0", "@types/pify": "^3.0.2", @@ -89,6 +88,7 @@ "@types/styled-components": "^5.1.26", "@types/testing-library__jest-dom": "^5.14.9", "@vitejs/plugin-react": "^1.3.2", + "@vitest/coverage-istanbul": "^2.1.8", "@votingworks/backend": "workspace:*", "@votingworks/central-scan-backend": "workspace:*", "@votingworks/fixtures": "workspace:*", @@ -102,20 +102,14 @@ "fetch-mock": "9.11.0", "history": "4.10.1", "is-ci-cli": "2.2.0", - "jest": "^29.6.2", - "jest-environment-jsdom": "^29.6.2", - "jest-fetch-mock": "^3.0.3", - "jest-junit": "^16.0.0", - "jest-styled-components": "^7.1.1", - "jest-watch-typeahead": "^2.2.2", "lint-staged": "11.0.0", "node-fetch": "^2.6.0", "react-app-polyfill": "3.0.0", "react-refresh": "^0.9.0", "sort-package-json": "^1.50.0", - "ts-jest": "29.1.1", "type-fest": "^0.18.0", - "vite": "4.5.2" + "vite": "4.5.2", + "vitest": "^2.1.8" }, "packageManager": "pnpm@8.15.5", "vx": { diff --git a/apps/central-scan/frontend/src/api/hmpb.test.ts b/apps/central-scan/frontend/src/api/hmpb.test.ts index 754829b171..726e7e71df 100644 --- a/apps/central-scan/frontend/src/api/hmpb.test.ts +++ b/apps/central-scan/frontend/src/api/hmpb.test.ts @@ -1,3 +1,4 @@ +import { expect, test } from 'vitest'; import fetchMock from 'fetch-mock'; import { Scan } from '@votingworks/api'; import { fetchNextBallotSheetToReview } from './hmpb'; diff --git a/apps/central-scan/frontend/src/app.test.tsx b/apps/central-scan/frontend/src/app.test.tsx index d07cca21d7..3f7c50e843 100644 --- a/apps/central-scan/frontend/src/app.test.tsx +++ b/apps/central-scan/frontend/src/app.test.tsx @@ -1,3 +1,4 @@ +import { afterEach, beforeEach, expect, test, vi } from 'vitest'; import fetchMock from 'fetch-mock'; import { readElectionGeneralDefinition } from '@votingworks/fixtures'; import { @@ -16,7 +17,7 @@ import { } from '@votingworks/types'; import userEvent from '@testing-library/user-event'; import { mockUsbDriveStatus } from '@votingworks/ui'; -import { render, waitFor, within, screen } from '../test/react_testing_library'; +import { render, within, screen } from '../test/react_testing_library'; import { App } from './app'; import { ApiMock, createApiMock } from '../test/api'; import { mockBatch, mockStatus } from '../test/fixtures'; @@ -27,7 +28,7 @@ const electionKey = constructElectionKey(electionDefinition.election); let apiMock: ApiMock; beforeEach(() => { - jest.restoreAllMocks(); + vi.restoreAllMocks(); apiMock = createApiMock(); apiMock.setAuthStatus({ @@ -118,7 +119,7 @@ test('renders without crashing', async () => { apiMock.expectGetElectionRecord(electionDefinition); render(); - await waitFor(() => fetchMock.called()); + await vi.waitFor(() => fetchMock.called()); }); test('clicking Scan Batch will scan a batch', async () => { @@ -148,7 +149,7 @@ test('clicking "Save CVRs" shows modal and makes a request to export', async () // wait for the config to load const saveButton = screen.getButton('Save CVRs'); - await waitFor(() => expect(saveButton).toBeEnabled()); + await vi.waitFor(() => expect(saveButton).toBeEnabled()); userEvent.click(saveButton); await screen.findByRole('alertdialog'); apiMock.expectExportCastVoteRecords({ isMinimalExport: true }); @@ -324,7 +325,9 @@ test('system administrator can log in and unconfigure machine', async () => { apiMock.expectGetSystemSettings(); apiMock.expectGetTestMode(true); userEvent.click(within(modal).getButton('Delete All Election Data')); - await waitFor(() => expect(screen.queryByRole('alertdialog')).toBeNull()); + await vi.waitFor(() => { + expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument(); + }, 5000); }); test('election manager cannot auth onto machine with different election', async () => { diff --git a/apps/central-scan/frontend/src/components/delete_batch_modal.test.tsx b/apps/central-scan/frontend/src/components/delete_batch_modal.test.tsx index 7e97511131..49b271732f 100644 --- a/apps/central-scan/frontend/src/components/delete_batch_modal.test.tsx +++ b/apps/central-scan/frontend/src/components/delete_batch_modal.test.tsx @@ -1,12 +1,13 @@ +import { afterEach, beforeEach, expect, test, vi } from 'vitest'; import userEvent from '@testing-library/user-event'; import { ApiMock, createApiMock, provideApi } from '../../test/api'; -import { render, screen, waitFor } from '../../test/react_testing_library'; +import { render, screen } from '../../test/react_testing_library'; import { DeleteBatchModal } from './delete_batch_modal'; let apiMock: ApiMock; beforeEach(() => { - jest.restoreAllMocks(); + vi.restoreAllMocks(); apiMock = createApiMock(); }); @@ -15,7 +16,7 @@ afterEach(() => { }); test('allows canceling', async () => { - const onClose = jest.fn(); + const onClose = vi.fn(); render( provideApi( @@ -32,7 +33,7 @@ test('allows canceling', async () => { }); test('closes on success', async () => { - const onClose = jest.fn(); + const onClose = vi.fn(); render( provideApi( @@ -46,7 +47,7 @@ test('closes on success', async () => { apiMock.expectDeleteBatch({ batchId: 'a' }); userEvent.click(screen.getByText('Delete Batch')); - await waitFor(() => { + await vi.waitFor(() => { expect(onClose).toHaveBeenCalled(); }); }); diff --git a/apps/central-scan/frontend/src/components/export_results_modal.test.tsx b/apps/central-scan/frontend/src/components/export_results_modal.test.tsx index c0f6fb7ad2..7d4cdfadb1 100644 --- a/apps/central-scan/frontend/src/components/export_results_modal.test.tsx +++ b/apps/central-scan/frontend/src/components/export_results_modal.test.tsx @@ -1,3 +1,4 @@ +import { afterEach, beforeEach, expect, test, vi } from 'vitest'; import { err } from '@votingworks/basics'; import type { UsbDriveStatus } from '@votingworks/usb-drive'; import userEvent from '@testing-library/user-event'; @@ -26,7 +27,7 @@ test('render insert USB screen when there is not a valid, mounted usb drive', as for (const usbDriveStatus of usbDriveStatuses) { apiMock.setUsbDriveStatus(usbDriveStatus); - const closeFn = jest.fn(); + const closeFn = vi.fn(); const { unmount } = renderInAppContext( , { @@ -43,7 +44,7 @@ test('render insert USB screen when there is not a valid, mounted usb drive', as }); test('render export modal when a usb drive is mounted as expected and allows export', async () => { - const closeFn = jest.fn(); + const closeFn = vi.fn(); apiMock.setUsbDriveStatus(mockUsbDriveStatus('mounted')); renderInAppContext(, { apiMock, @@ -64,7 +65,7 @@ test('render export modal when a usb drive is mounted as expected and allows exp }); test('render export modal with errors when appropriate', async () => { - const closeFn = jest.fn(); + const closeFn = vi.fn(); apiMock.setUsbDriveStatus(mockUsbDriveStatus('mounted')); renderInAppContext(, { apiMock, @@ -83,7 +84,7 @@ test('render export modal with errors when appropriate', async () => { }); test('render export modal with errors when appropriate - backup', async () => { - const closeFn = jest.fn(); + const closeFn = vi.fn(); apiMock.setUsbDriveStatus(mockUsbDriveStatus('mounted')); renderInAppContext(, { apiMock, diff --git a/apps/central-scan/frontend/src/components/scan_button.test.tsx b/apps/central-scan/frontend/src/components/scan_button.test.tsx index 20e386538a..24f88c454b 100644 --- a/apps/central-scan/frontend/src/components/scan_button.test.tsx +++ b/apps/central-scan/frontend/src/components/scan_button.test.tsx @@ -1,3 +1,4 @@ +import { afterEach, beforeEach, expect, test, vi } from 'vitest'; import userEvent from '@testing-library/user-event'; import { screen } from '../../test/react_testing_library'; import { ScanButton } from './scan_button'; @@ -7,7 +8,7 @@ import { renderInAppContext } from '../../test/render_in_app_context'; let apiMock: ApiMock; beforeEach(() => { - jest.restoreAllMocks(); + vi.restoreAllMocks(); apiMock = createApiMock(); }); diff --git a/apps/central-scan/frontend/src/components/test_scan_button.test.tsx b/apps/central-scan/frontend/src/components/test_scan_button.test.tsx index 0192152359..ac9a1f5687 100644 --- a/apps/central-scan/frontend/src/components/test_scan_button.test.tsx +++ b/apps/central-scan/frontend/src/components/test_scan_button.test.tsx @@ -1,20 +1,17 @@ +import { afterEach, beforeEach, expect, test, vi } from 'vitest'; import userEvent from '@testing-library/user-event'; import { deferred } from '@votingworks/basics'; import { ScanDiagnosticOutcome } from '@votingworks/central-scan-backend'; import { ApiMock, createApiMock } from '../../test/api'; import { mockStatus } from '../../test/fixtures'; -import { - screen, - waitFor, - waitForElementToBeRemoved, -} from '../../test/react_testing_library'; +import { screen } from '../../test/react_testing_library'; import { renderInAppContext } from '../../test/render_in_app_context'; import { TestScanButton } from './test_scan_button'; let apiMock: ApiMock; beforeEach(() => { - jest.restoreAllMocks(); + vi.restoreAllMocks(); apiMock = createApiMock(); }); @@ -29,12 +26,12 @@ test('disabled behavior', async () => { expect(button).toBeEnabled(); apiMock.setStatus(mockStatus({ isScannerAttached: false })); - await waitFor(() => { + await vi.waitFor(() => { expect(button).toBeDisabled(); }); apiMock.setStatus(mockStatus({ isScannerAttached: true })); - await waitFor(() => { + await vi.waitFor(() => { expect(button).toBeEnabled(); }); @@ -42,7 +39,9 @@ test('disabled behavior', async () => { await screen.findButton('Scan'); apiMock.setStatus(mockStatus({ isScannerAttached: false })); - await waitForElementToBeRemoved(() => screen.queryButton('Scan')); + await vi.waitFor(() => { + expect(screen.queryButton('Scan')).not.toBeInTheDocument(); + }); screen.getByText(/No scanner is currently detected/); }); diff --git a/apps/central-scan/frontend/src/components/toggle_test_mode_button.test.tsx b/apps/central-scan/frontend/src/components/toggle_test_mode_button.test.tsx index 0734fbc232..8eb56a8beb 100644 --- a/apps/central-scan/frontend/src/components/toggle_test_mode_button.test.tsx +++ b/apps/central-scan/frontend/src/components/toggle_test_mode_button.test.tsx @@ -1,3 +1,4 @@ +import { afterEach, beforeEach, expect, test, vi } from 'vitest'; import userEvent from '@testing-library/user-event'; import { render, screen, within } from '../../test/react_testing_library'; import { ToggleTestModeButton } from './toggle_test_mode_button'; @@ -7,7 +8,7 @@ import { mockStatus } from '../../test/fixtures'; let apiMock: ApiMock; beforeEach(() => { - jest.restoreAllMocks(); + vi.restoreAllMocks(); apiMock = createApiMock(); }); diff --git a/apps/central-scan/frontend/src/screens/ballot_eject_screen.test.tsx b/apps/central-scan/frontend/src/screens/ballot_eject_screen.test.tsx index da96ee3f63..67698d2579 100644 --- a/apps/central-scan/frontend/src/screens/ballot_eject_screen.test.tsx +++ b/apps/central-scan/frontend/src/screens/ballot_eject_screen.test.tsx @@ -1,3 +1,4 @@ +import { afterEach, beforeEach, expect, test, vi } from 'vitest'; import { mockBaseLogger, LogEventId } from '@votingworks/logging'; import { AdjudicationReason, @@ -48,7 +49,7 @@ test('says the sheet is unreadable if it is', async () => { }) ); - const logger = mockBaseLogger({ fn: jest.fn }); + const logger = mockBaseLogger({ fn: vi.fn }); renderInAppContext(, { apiMock, logger }); @@ -63,7 +64,6 @@ test('says the sheet is unreadable if it is', async () => { 'Confirm Ballot Removed' ); - expect(logger.log).toHaveBeenCalledTimes(1); expect(logger.log).toHaveBeenCalledWith( LogEventId.ScanAdjudicationInfo, 'election_manager', @@ -159,7 +159,7 @@ test('says the ballot sheet is overvoted if it is', async () => { }) ); - const logger = mockBaseLogger({ fn: jest.fn }); + const logger = mockBaseLogger({ fn: vi.fn }); renderInAppContext(, { apiMock, logger }); @@ -271,7 +271,7 @@ test('says the ballot sheet is undervoted if it is', async () => { }) ); - const logger = mockBaseLogger({ fn: jest.fn }); + const logger = mockBaseLogger({ fn: vi.fn }); renderInAppContext(, { apiMock, logger }); @@ -390,7 +390,7 @@ test('says the ballot sheet is blank if it is', async () => { }) ); - const logger = mockBaseLogger({ fn: jest.fn }); + const logger = mockBaseLogger({ fn: vi.fn }); renderInAppContext(, { apiMock, logger }); @@ -458,7 +458,7 @@ test('calls out official ballot sheets in test mode', async () => { }) ); - const logger = mockBaseLogger({ fn: jest.fn }); + const logger = mockBaseLogger({ fn: vi.fn }); renderInAppContext(, { apiMock, logger }); @@ -524,7 +524,7 @@ test('calls out test ballot sheets in live mode', async () => { }) ); - const logger = mockBaseLogger({ fn: jest.fn }); + const logger = mockBaseLogger({ fn: vi.fn }); renderInAppContext(, { apiMock, @@ -577,7 +577,7 @@ test('shows invalid election screen when appropriate', async () => { }) ); - const logger = mockBaseLogger({ fn: jest.fn }); + const logger = mockBaseLogger({ fn: vi.fn }); renderInAppContext(, { apiMock, @@ -695,7 +695,7 @@ test('does not allow tabulating the overvote if disallowCastingOvervotes is set' }) ); - const logger = mockBaseLogger({ fn: jest.fn }); + const logger = mockBaseLogger({ fn: vi.fn }); renderInAppContext(, { apiMock, logger }); @@ -736,7 +736,7 @@ test('says the scanner needs cleaning if a streak is detected', async () => { }) ); - const logger = mockBaseLogger({ fn: jest.fn }); + const logger = mockBaseLogger({ fn: vi.fn }); renderInAppContext(, { apiMock, logger }); diff --git a/apps/central-scan/frontend/src/screens/diagnostics_screen.test.tsx b/apps/central-scan/frontend/src/screens/diagnostics_screen.test.tsx index 60481eb8df..445b5f87aa 100644 --- a/apps/central-scan/frontend/src/screens/diagnostics_screen.test.tsx +++ b/apps/central-scan/frontend/src/screens/diagnostics_screen.test.tsx @@ -1,3 +1,4 @@ +import { afterEach, beforeEach, test } from 'vitest'; import { mockUsbDriveStatus } from '@votingworks/ui'; import { readElectionTwoPartyPrimaryDefinition } from '@votingworks/fixtures'; import { screen } from '../../test/react_testing_library'; diff --git a/apps/central-scan/frontend/src/screens/scan_ballots_screen.test.tsx b/apps/central-scan/frontend/src/screens/scan_ballots_screen.test.tsx index d322be9a9d..0702731fcf 100644 --- a/apps/central-scan/frontend/src/screens/scan_ballots_screen.test.tsx +++ b/apps/central-scan/frontend/src/screens/scan_ballots_screen.test.tsx @@ -1,7 +1,8 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { hasTextAcrossElements } from '@votingworks/test-utils'; import userEvent from '@testing-library/user-event'; import type { ScanStatus } from '@votingworks/central-scan-backend'; -import { screen, waitFor, within } from '../../test/react_testing_library'; +import { screen, within } from '../../test/react_testing_library'; import { ScanBallotsScreen, ScanBallotsScreenProps, @@ -101,7 +102,7 @@ test('Delete All Batches button', async () => { // progress message await screen.findByText('Deleting Batches'); - await waitFor(() => + await vi.waitFor(() => expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument() ); }); diff --git a/apps/central-scan/frontend/src/screens/settings_screen.test.tsx b/apps/central-scan/frontend/src/screens/settings_screen.test.tsx index 78cbc8ae06..d39aab9fe5 100644 --- a/apps/central-scan/frontend/src/screens/settings_screen.test.tsx +++ b/apps/central-scan/frontend/src/screens/settings_screen.test.tsx @@ -1,12 +1,8 @@ +import { afterEach, beforeEach, expect, test, vi } from 'vitest'; import userEvent from '@testing-library/user-event'; import { createMemoryHistory } from 'history'; import { mockUsbDriveStatus } from '@votingworks/ui'; -import { - screen, - waitFor, - waitForElementToBeRemoved, - within, -} from '../../test/react_testing_library'; +import { screen, within } from '../../test/react_testing_library'; import { renderInAppContext } from '../../test/render_in_app_context'; import { SettingsScreenProps, SettingsScreen } from './settings_screen'; import { ApiMock, createApiMock } from '../../test/api'; @@ -53,7 +49,7 @@ test('clicking "Save Backup" triggers modal', async () => { apiMock.setUsbDriveStatus(mockUsbDriveStatus('ejected')); await screen.findByText('USB Drive Ejected'); userEvent.click(screen.getButton('Close')); - await waitFor(() => + await vi.waitFor(() => expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument() ); }); @@ -87,7 +83,7 @@ test('clicking "Unconfigure Machine" calls backend', async () => { }); test('clicking "Update Date and Time" shows modal to set clock', async () => { - jest.useFakeTimers().setSystemTime(new Date('2020-10-31T00:00:00.000')); + vi.useFakeTimers().setSystemTime(new Date('2020-10-31T00:00:00.000')); renderScreen(); @@ -114,7 +110,9 @@ test('clicking "Update Date and Time" shows modal to set clock', async () => { .resolves(); apiMock.expectLogOut(); userEvent.click(within(modal).getByRole('button', { name: 'Save' })); - await waitForElementToBeRemoved(screen.queryByRole('alertdialog')); + await vi.waitFor(() => { + expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument(); + }); - jest.useRealTimers(); + vi.useRealTimers(); }); diff --git a/apps/central-scan/frontend/src/screens/system_administrator_settings_screen.test.tsx b/apps/central-scan/frontend/src/screens/system_administrator_settings_screen.test.tsx index ab6e568d60..b7a3e11ea6 100644 --- a/apps/central-scan/frontend/src/screens/system_administrator_settings_screen.test.tsx +++ b/apps/central-scan/frontend/src/screens/system_administrator_settings_screen.test.tsx @@ -1,7 +1,8 @@ +import { afterEach, beforeEach, expect, test, vi } from 'vitest'; import userEvent from '@testing-library/user-event'; import { ok } from '@votingworks/basics'; import { mockUsbDriveStatus } from '@votingworks/ui'; -import { screen, waitFor } from '../../test/react_testing_library'; +import { screen } from '../../test/react_testing_library'; import { renderInAppContext } from '../../test/render_in_app_context'; import { SystemAdministratorSettingsScreen } from './system_administrator_settings_screen'; import { createApiMock, ApiMock } from '../../test/api'; @@ -50,7 +51,7 @@ test('Exporting logs', async () => { await screen.findByText('Select a log format:'); userEvent.click(screen.getButton('Save')); userEvent.click(await screen.findButton('Close')); - await waitFor(() => + await vi.waitFor(() => expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument() ); }); diff --git a/apps/central-scan/frontend/src/setupTests.ts b/apps/central-scan/frontend/src/setupTests.ts index 49eb93d4ed..7bd7880ec5 100644 --- a/apps/central-scan/frontend/src/setupTests.ts +++ b/apps/central-scan/frontend/src/setupTests.ts @@ -1,24 +1,20 @@ -import '@testing-library/jest-dom/extend-expect'; +import matchers from '@testing-library/jest-dom/matchers'; +import { afterAll, beforeAll, beforeEach, expect, vi } from 'vitest'; import { clearTemporaryRootDir, setupTemporaryRootDir, } from '@votingworks/fixtures'; import fetchMock from 'fetch-mock'; -import jestFetchMock from 'jest-fetch-mock'; -import 'jest-styled-components'; import { TextDecoder, TextEncoder } from 'node:util'; -import { configure } from '../test/react_testing_library'; +import { cleanup, configure } from '../test/react_testing_library'; -configure({ asyncUtilTimeout: 5_000 }); +expect.extend(matchers); -// styled-components version 5.3.1 and above requires this remapping for jest -// environments, reference: https://github.com/styled-components/styled-components/issues/3570 -jest.mock('styled-components', () => - jest.requireActual('styled-components/dist/styled-components.browser.cjs.js') -); +configure({ asyncUtilTimeout: 5_000 }); beforeEach(() => { - jestFetchMock.enableMocks(); + vi.clearAllMocks(); + cleanup(); fetchMock.reset(); fetchMock.mock(); }); diff --git a/apps/central-scan/frontend/src/util/relative_rect.test.ts b/apps/central-scan/frontend/src/util/relative_rect.test.ts index 9992813722..846102d903 100644 --- a/apps/central-scan/frontend/src/util/relative_rect.test.ts +++ b/apps/central-scan/frontend/src/util/relative_rect.test.ts @@ -1,3 +1,4 @@ +import { expect, test } from 'vitest'; import { relativeRect } from './relative_rect'; test('ratio rect', () => { diff --git a/apps/central-scan/frontend/test/api.tsx b/apps/central-scan/frontend/test/api.tsx index d84dace220..46dd3a7ea3 100644 --- a/apps/central-scan/frontend/test/api.tsx +++ b/apps/central-scan/frontend/test/api.tsx @@ -1,3 +1,4 @@ +import { Mock, vi } from 'vitest'; import React from 'react'; import type { Api, @@ -21,16 +22,16 @@ import { ApiClientContext, createQueryClient, systemCallApi } from '../src/api'; import { DEFAULT_STATUS } from './fixtures'; export type MockApiClient = Omit, 'getBatteryInfo'> & { - // Because this is polled so frequently, we opt for a standard jest mock instead of a + // Because this is polled so frequently, we opt for a standard vitest mock instead of a // libs/test-utils mock since the latter requires every call to be explicitly mocked - getBatteryInfo: jest.Mock; + getBatteryInfo: Mock; }; export function createMockApiClient(): MockApiClient { const mockApiClient = createMockClient(); // For some reason, using an object spread to override the polling methods breaks the rest // of the mockApiClient, so we override like this instead - (mockApiClient.getBatteryInfo as unknown as jest.Mock) = jest.fn(() => + (mockApiClient.getBatteryInfo as unknown as Mock) = vi.fn(() => Promise.resolve({ level: 1, discharging: false }) ); return mockApiClient as unknown as MockApiClient; diff --git a/apps/central-scan/frontend/vitest.config.ts b/apps/central-scan/frontend/vitest.config.ts new file mode 100644 index 0000000000..65a2ce6aaa --- /dev/null +++ b/apps/central-scan/frontend/vitest.config.ts @@ -0,0 +1,29 @@ +import { join } from 'path'; +import { defineConfig } from '../../../vitest.config.shared.mjs'; + +export default defineConfig({ + test: { + environment: 'jsdom', + mockReset: true, + setupFiles: ['react-app-polyfill/jsdom', 'src/setupTests.ts'], + coverage: { + thresholds: { + lines: 91, + branches: 86, + }, + exclude: [ + 'src/config', + 'src/**/*.d.ts', + 'src/index.tsx', + '**/*.test.{ts,tsx}', + 'src/stubs', + ], + }, + alias: [ + { + find: '@votingworks/ui', + replacement: join(__dirname, '../../../libs/ui/src/index.ts'), + }, + ], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05b6ed24d1..15c9964f11 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -965,9 +965,6 @@ importers: '@types/history': specifier: 4.7.11 version: 4.7.11 - '@types/jest': - specifier: ^29.5.3 - version: 29.5.3 '@types/kiosk-browser': specifier: workspace:* version: link:../../../libs/@types/kiosk-browser @@ -1001,6 +998,9 @@ importers: '@vitejs/plugin-react': specifier: ^1.3.2 version: 1.3.2 + '@vitest/coverage-istanbul': + specifier: ^2.1.8 + version: 2.1.8(vitest@2.1.8) '@votingworks/backend': specifier: workspace:* version: link:../../../libs/backend @@ -1040,24 +1040,6 @@ importers: is-ci-cli: specifier: 2.2.0 version: 2.2.0 - jest: - specifier: ^29.6.2 - version: 29.6.2(@types/node@20.16.0) - jest-environment-jsdom: - specifier: ^29.6.2 - version: 29.6.2 - jest-fetch-mock: - specifier: ^3.0.3 - version: 3.0.3 - jest-junit: - specifier: ^16.0.0 - version: 16.0.0 - jest-styled-components: - specifier: ^7.1.1 - version: 7.1.1(styled-components@5.3.11) - jest-watch-typeahead: - specifier: ^2.2.2 - version: 2.2.2(jest@29.6.2) lint-staged: specifier: 11.0.0 version: 11.0.0 @@ -1073,15 +1055,15 @@ importers: sort-package-json: specifier: ^1.50.0 version: 1.53.1 - ts-jest: - specifier: 29.1.1 - version: 29.1.1(@babel/core@7.26.0)(@jest/types@29.6.1)(esbuild@0.21.2)(jest@29.6.2)(typescript@5.6.2) type-fest: specifier: ^0.18.0 version: 0.18.1 vite: specifier: 4.5.2 version: 4.5.2(@types/node@20.16.0) + vitest: + specifier: ^2.1.8 + version: 2.1.8(@types/node@20.16.0)(jsdom@20.0.1) apps/central-scan/frontend/prodserver: dependencies: @@ -11976,7 +11958,7 @@ packages: vitest: 2.1.8 dependencies: '@istanbuljs/schema': 0.1.3 - debug: 4.3.7 + debug: 4.4.0 istanbul-lib-coverage: 3.2.2 istanbul-lib-instrument: 6.0.3 istanbul-lib-report: 3.0.1 @@ -14110,6 +14092,17 @@ packages: dependencies: ms: 2.1.3 + /debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + /decamelize-keys@1.1.0: resolution: {integrity: sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==} engines: {node: '>=0.10.0'} @@ -14630,8 +14623,8 @@ packages: resolution: {integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==} dev: true - /es-module-lexer@1.5.4: - resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + /es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} /es-set-tostringtag@2.0.1: resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} @@ -23284,8 +23277,8 @@ packages: hasBin: true dependencies: cac: 6.7.14 - debug: 4.3.7 - es-module-lexer: 1.5.4 + debug: 4.4.0 + es-module-lexer: 1.6.0 pathe: 1.1.2 vite: 5.4.11(@types/node@20.16.0) transitivePeerDependencies: