From 39e9399a7fc083038c515f903850f5c9618ef849 Mon Sep 17 00:00:00 2001 From: Jonah Kagan Date: Tue, 10 Dec 2024 08:49:13 -0800 Subject: [PATCH] Create libs/test-utils-vitest (#5703) * Create libs/test-utils-vitest Copies all files from libs/test-utils that rely on `jest` and migrate them to use `vitest` instead. This will enable any packages that currently use libs/test-utils to be migrated to vitest by using the vitest version of these utils. Eventually, when we're done migrating and there are no more jest tests, we can move these back into libs/test-utils, replacing the jest versions. * Regenerate CircleCI config --- .circleci/config.yml | 24 + libs/test-utils-vitest/.eslintignore | 3 + libs/test-utils-vitest/.eslintrc.json | 7 + libs/test-utils-vitest/.gitignore | 0 libs/test-utils-vitest/package.json | 46 + libs/test-utils-vitest/src/advance_timers.ts | 25 + .../src/child_process.test.ts | 109 ++ libs/test-utils-vitest/src/child_process.ts | 180 ++++ libs/test-utils-vitest/src/console.test.ts | 39 + libs/test-utils-vitest/src/console.ts | 182 ++++ libs/test-utils-vitest/src/index.ts | 6 + .../src/mock_function.test.ts | 344 ++++++ libs/test-utils-vitest/src/mock_function.ts | 345 ++++++ libs/test-utils-vitest/src/mock_kiosk.ts | 15 + libs/test-utils-vitest/src/mock_of.ts | 18 + .../src/mock_use_audio_controls.ts | 17 + libs/test-utils-vitest/tsconfig.build.json | 16 + libs/test-utils-vitest/tsconfig.json | 19 + libs/test-utils-vitest/vitest.config.ts | 21 + pnpm-lock.yaml | 988 +++++++++++++++++- vxsuite.code-workspace | 4 + 21 files changed, 2379 insertions(+), 29 deletions(-) create mode 100644 libs/test-utils-vitest/.eslintignore create mode 100644 libs/test-utils-vitest/.eslintrc.json create mode 100644 libs/test-utils-vitest/.gitignore create mode 100644 libs/test-utils-vitest/package.json create mode 100644 libs/test-utils-vitest/src/advance_timers.ts create mode 100644 libs/test-utils-vitest/src/child_process.test.ts create mode 100644 libs/test-utils-vitest/src/child_process.ts create mode 100644 libs/test-utils-vitest/src/console.test.ts create mode 100644 libs/test-utils-vitest/src/console.ts create mode 100644 libs/test-utils-vitest/src/index.ts create mode 100644 libs/test-utils-vitest/src/mock_function.test.ts create mode 100644 libs/test-utils-vitest/src/mock_function.ts create mode 100644 libs/test-utils-vitest/src/mock_kiosk.ts create mode 100644 libs/test-utils-vitest/src/mock_of.ts create mode 100644 libs/test-utils-vitest/src/mock_use_audio_controls.ts create mode 100644 libs/test-utils-vitest/tsconfig.build.json create mode 100644 libs/test-utils-vitest/tsconfig.json create mode 100644 libs/test-utils-vitest/vitest.config.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index 993e54db3f..040d6c9724 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1125,6 +1125,29 @@ jobs: - store_test_results: path: libs/test-utils/reports/ + # @votingworks/test-utils-vitest + test-libs-test-utils-vitest: + executor: nodejs + resource_class: xlarge + steps: + - checkout-and-install + - run: + name: Build + command: | + pnpm --dir libs/test-utils-vitest build + - run: + name: Lint + command: | + pnpm --dir libs/test-utils-vitest lint + - run: + name: Test + command: | + pnpm --dir libs/test-utils-vitest test + environment: + JEST_JUNIT_OUTPUT_DIR: ./reports/ + - store_test_results: + path: libs/test-utils-vitest/reports/ + # @votingworks/types test-libs-types: executor: nodejs @@ -1447,6 +1470,7 @@ workflows: - test-libs-pdi-scanner - test-libs-printing - test-libs-test-utils + - test-libs-test-utils-vitest - test-libs-types - test-libs-types-rs - test-libs-ui diff --git a/libs/test-utils-vitest/.eslintignore b/libs/test-utils-vitest/.eslintignore new file mode 100644 index 0000000000..ca254321c0 --- /dev/null +++ b/libs/test-utils-vitest/.eslintignore @@ -0,0 +1,3 @@ +build +coverage +vitest.config.ts \ No newline at end of file diff --git a/libs/test-utils-vitest/.eslintrc.json b/libs/test-utils-vitest/.eslintrc.json new file mode 100644 index 0000000000..291c40eeef --- /dev/null +++ b/libs/test-utils-vitest/.eslintrc.json @@ -0,0 +1,7 @@ +{ + "extends": ["plugin:vx/recommended"], + "rules": { + // Disable JSDOC rule as code is self-documenting. + "vx/gts-jsdoc": "off" + } +} diff --git a/libs/test-utils-vitest/.gitignore b/libs/test-utils-vitest/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libs/test-utils-vitest/package.json b/libs/test-utils-vitest/package.json new file mode 100644 index 0000000000..8f29f808f5 --- /dev/null +++ b/libs/test-utils-vitest/package.json @@ -0,0 +1,46 @@ +{ + "name": "@votingworks/test-utils-vitest", + "version": "1.0.0", + "private": true, + "description": "Test utilities for the monorepo using vitest instead of jest", + "license": "GPL-3.0-only", + "main": "build/index.js", + "types": "build/index.d.ts", + "files": [ + "build" + ], + "scripts": { + "build": "pnpm --filter $npm_package_name... build:self", + "build:self": "tsc --build tsconfig.build.json", + "clean": "pnpm --filter $npm_package_name... clean:self", + "clean:self": "rm -rf build && tsc --build --clean tsconfig.build.json", + "lint": "pnpm type-check && eslint .", + "lint:fix": "pnpm type-check && eslint . --fix", + "pre-commit": "lint-staged", + "test": "is-ci test:ci test:watch", + "test:ci": "vitest --coverage --reporter junit --outputFile reports/junit.xml", + "test:coverage": "vitest run --coverage", + "test:watch": "vitest", + "type-check": "tsc --build" + }, + "dependencies": { + "@testing-library/react": "^15.0.7", + "@votingworks/basics": "workspace:*", + "@votingworks/types": "workspace:*", + "chalk": "4.1.2", + "jest-diff": "^29.6.2", + "react": "18.3.1", + "vitest": "^2.1.8" + }, + "devDependencies": { + "@types/kiosk-browser": "workspace:*", + "@types/node": "20.16.0", + "@types/react": "18.3.3", + "@vitest/coverage-istanbul": "^2.1.8", + "eslint-plugin-vx": "workspace:*", + "is-ci-cli": "2.2.0", + "lint-staged": "11.0.0", + "sort-package-json": "^1.50.0" + }, + "packageManager": "pnpm@8.15.5" +} diff --git a/libs/test-utils-vitest/src/advance_timers.ts b/libs/test-utils-vitest/src/advance_timers.ts new file mode 100644 index 0000000000..67a8449e5a --- /dev/null +++ b/libs/test-utils-vitest/src/advance_timers.ts @@ -0,0 +1,25 @@ +import { vi } from 'vitest'; +import { act, waitFor } from '@testing-library/react'; + +export const IDLE_TIMEOUT_SECONDS = 5 * 60; // 5 minute + +export function advanceTimers(seconds = 0): void { + const maxSeconds = IDLE_TIMEOUT_SECONDS; + if (seconds > maxSeconds) { + throw new Error(`Seconds value should not be greater than ${maxSeconds}`); + } + act(() => { + vi.advanceTimersByTime(seconds * 1000); + }); +} + +export async function advancePromises(): Promise { + await waitFor(() => { + // Wait for promises. + }); +} + +export async function advanceTimersAndPromises(seconds = 0): Promise { + advanceTimers(seconds); + await advancePromises(); +} diff --git a/libs/test-utils-vitest/src/child_process.test.ts b/libs/test-utils-vitest/src/child_process.test.ts new file mode 100644 index 0000000000..3acb9c6daf --- /dev/null +++ b/libs/test-utils-vitest/src/child_process.test.ts @@ -0,0 +1,109 @@ +import { expect, test, vi } from 'vitest'; +import { Buffer } from 'node:buffer'; +import { mockChildProcess, mockReadable, mockWritable } from './child_process'; + +test('mockReadable', () => { + const onReadable = vi.fn(); + const onData = vi.fn(); + const onEnd = vi.fn(); + const readable = mockReadable() + .on('readable', onReadable) + .on('data', onData) + .on('end', onEnd); + + expect(onReadable).not.toHaveBeenCalled(); + expect(onData).not.toHaveBeenCalled(); + expect(onEnd).not.toHaveBeenCalled(); + + readable.append('abcd'); + expect(onReadable).toHaveBeenCalledTimes(1); + expect(onData).toHaveBeenNthCalledWith(1, 'abcd'); + expect(onEnd).not.toHaveBeenCalled(); + expect(readable.read(1)).toEqual('a'); + expect(readable.read(1)).toEqual('b'); + expect(readable.read()).toEqual('cd'); + expect(readable.read()).toBeUndefined(); + + expect(readable.isPaused()).toEqual(false); + readable.pause(); + expect(readable.isPaused()).toEqual(true); + + readable.append('efgh'); + expect(onReadable).toHaveBeenCalledTimes(1); + expect(onData).toHaveBeenCalledTimes(1); + expect(onEnd).not.toHaveBeenCalled(); + readable.resume(); + expect(onReadable).toHaveBeenCalledTimes(2); + expect(onData).toHaveBeenNthCalledWith(2, 'efgh'); + expect(onEnd).not.toHaveBeenCalled(); + readable.append('ijkl'); + expect(onData).toHaveBeenNthCalledWith(3, 'ijkl'); + expect(readable.read(5)).toEqual('efghi'); + expect(readable.read(100)).toEqual('jkl'); + + readable.end(); + expect(onEnd).toHaveBeenCalledTimes(1); +}); + +test('mockWritable', async () => { + const writable = mockWritable(); + expect(writable.toBuffer()).toEqual(Buffer.of()); + expect(writable.toString()).toEqual(''); + + writable.write(Buffer.of(1, 2, 3)); + expect(writable.toBuffer()).toEqual(Buffer.of(1, 2, 3)); + expect(writable.toString()).toEqual('\x01\x02\x03'); // mirrors `Buffer.of(1, 2, 3)` + + writable.write('hi!', 'ascii'); + expect(writable.toBuffer()).toEqual(Buffer.of(1, 2, 3, 104, 105, 33)); + expect(writable.toString()).toEqual('\x01\x02\x03hi!'); // mirrors `Buffer.of(1, 2, 3, 104, 105, 33)` + + { + const writeCallback = vi.fn(); + writable.write('', 'utf-8', writeCallback); + await new Promise((resolve) => { + process.nextTick(resolve); + }); + expect(writeCallback).toHaveBeenCalledWith(); + } + + { + const writeCallback = vi.fn(); + writable.write('', writeCallback); + await new Promise((resolve) => { + process.nextTick(resolve); + }); + expect(writeCallback).toHaveBeenCalledWith(); + } + + // @ts-expect-error - testing invalid argument type + expect(() => writable.write('', 88)).toThrowError( + 'encoding expected to be a string' + ); + + const endCallback = vi.fn(); + writable.end(endCallback); + await new Promise((resolve) => { + process.nextTick(resolve); + }); + expect(endCallback).toHaveBeenCalledWith(); + + expect(writable.writes).toEqual([ + { chunk: Buffer.of(1, 2, 3), encoding: undefined }, + { chunk: 'hi!', encoding: 'ascii' }, + { chunk: '', encoding: 'utf-8' }, + { chunk: '', encoding: undefined }, + ]); +}); + +test('mockChildProcess', () => { + const child = mockChildProcess(); + + expect(typeof child.pid).toEqual('number'); + child.stdin.write('hello child!\n'); + + const onExit = vi.fn(); + child.on('exit', onExit); + child.emit('exit'); + expect(onExit).toHaveBeenCalled(); +}); diff --git a/libs/test-utils-vitest/src/child_process.ts b/libs/test-utils-vitest/src/child_process.ts new file mode 100644 index 0000000000..3e65594a6f --- /dev/null +++ b/libs/test-utils-vitest/src/child_process.ts @@ -0,0 +1,180 @@ +import { vi } from 'vitest'; +import { Buffer } from 'node:buffer'; +import { ChildProcess } from 'node:child_process'; +import { EventEmitter } from 'node:events'; +import { Readable, Writable } from 'node:stream'; + +export interface MockReadable extends Readable { + append(chunk: string): void; + end(): void; +} + +export interface MockWritable extends Writable { + toBuffer(): Buffer; + toString(): string; + writes: ReadonlyArray<{ chunk: unknown; encoding?: string }>; +} + +/** + * Makes a mock readable stream. + */ +export function mockReadable(): MockReadable { + const readable = new EventEmitter() as MockReadable; + let buffer: string | undefined; + let isPaused = false; + let pendingChunks: unknown[] = []; + + function flush(): void { + for (const chunk of pendingChunks) { + buffer = (buffer ?? '') + chunk; + readable.emit('readable'); + readable.emit('data', chunk); + } + pendingChunks = []; + } + + readable.resume = vi.fn(() => { + isPaused = false; + flush(); + return readable; + }); + readable.pause = vi.fn(() => { + isPaused = true; + return readable; + }); + readable.isPaused = vi.fn().mockImplementation(() => isPaused); + readable.setEncoding = vi.fn(); + readable.append = vi.fn((chunk): void => { + pendingChunks.push(chunk); + if (!isPaused) { + flush(); + } + }); + readable.read = vi.fn((size): unknown => { + if (typeof buffer === 'string') { + const readSize = size ?? buffer.length; + const result = buffer.slice(0, readSize); + buffer = buffer.length <= readSize ? undefined : buffer.slice(readSize); + return result; + } + + return undefined; + }); + readable.end = vi.fn(() => { + readable.emit('end'); + }); + return readable; +} + +/** + * Makes a mock writable stream. + */ +export function mockWritable(): MockWritable { + const writable = new EventEmitter() as MockWritable; + const writes: Array<{ chunk: unknown; encoding?: string }> = []; + + writable.writes = writes; + writable.write = vi.fn((...args: unknown[]): boolean => { + let chunk: unknown; + let encoding: unknown; + let callback: unknown; + + if (args.length === 3) { + [chunk, encoding, callback] = args; + } else if (args.length === 2 && typeof args[1] === 'function') { + [chunk, callback] = args; + } else if (args.length === 2) { + [chunk, encoding] = args; + } else { + [chunk] = args; + } + + if (typeof encoding !== 'undefined' && typeof encoding !== 'string') { + throw new TypeError('encoding expected to be a string'); + } + + if (typeof chunk !== 'undefined') { + writes.push({ chunk, encoding }); + } + + process.nextTick(() => { + if (typeof callback === 'function') { + callback(); + } + }); + + return true; + }); + + writable.end = vi.fn((...args: unknown[]): MockWritable => { + let chunk: unknown; + let encoding: unknown; + let callback: unknown; + + if (args.length === 3) { + [chunk, encoding, callback] = args; + } else if (args.length === 2 && typeof args[1] === 'function') { + [chunk, callback] = args; + } else if (args.length === 2) { + [chunk, encoding] = args; + } else { + [callback] = args; + } + + if (typeof encoding !== 'undefined' && typeof encoding !== 'string') { + throw new TypeError('encoding expected to be a string'); + } + + if (typeof chunk !== 'undefined') { + writes.push({ chunk, encoding }); + } + + process.nextTick(() => { + if (typeof callback === 'function') { + callback(); + } + }); + + return writable; + }); + + writable.toBuffer = () => + writes.reduce( + (result, { chunk }) => + Buffer.concat([result, Buffer.from(chunk as Buffer | string)]), + Buffer.of() + ); + + writable.toString = () => + writes.reduce( + (result, { chunk, encoding }) => + result + + (typeof chunk === 'string' + ? chunk + : (chunk as Buffer).toString(encoding as BufferEncoding | undefined)), + '' + ); + + return writable; +} + +export interface MockChildProcess extends ChildProcess { + stdin: MockWritable; + stdout: MockReadable; + stderr: MockReadable; +} + +/** + * Creates a mock child process with mock streams. + */ +export function mockChildProcess(): MockChildProcess { + const result: Partial = { + pid: Math.floor(Math.random() * 10_000), + stdin: mockWritable(), + stdout: mockReadable(), + stderr: mockReadable(), + kill: vi.fn(), + }; + + return Object.assign(new EventEmitter(), result) as MockChildProcess; +} diff --git a/libs/test-utils-vitest/src/console.test.ts b/libs/test-utils-vitest/src/console.test.ts new file mode 100644 index 0000000000..32240f138b --- /dev/null +++ b/libs/test-utils-vitest/src/console.test.ts @@ -0,0 +1,39 @@ +/* eslint-disable no-console */ +import { expect, test } from 'vitest'; +import { suppressingConsoleOutput } from './console'; + +test.each(['log', 'warn', 'error'] as const)( + 'suppressingConsoleOutput replaces console.%s with a mock', + (method) => { + const original = console[method]; + suppressingConsoleOutput(() => { + console[method]('test'); + expect(console[method]).toHaveBeenNthCalledWith(1, 'test'); + }); + expect(console[method]).toStrictEqual(original); + } +); + +test('suppressingConsoleOutput returns the value returned by the callback', () => { + expect(suppressingConsoleOutput(() => 'test')).toEqual('test'); +}); + +test('suppressingConsoleOutput resolves to the value returned by the callback', async () => { + await expect( + suppressingConsoleOutput(async () => await Promise.resolve('test')) + ).resolves.toEqual('test'); +}); + +test('suppressingConsoleOutput throws the error thrown by the callback', () => { + expect(() => + suppressingConsoleOutput(() => { + throw new Error('test'); + }) + ).toThrowError('test'); +}); + +test('suppressingConsoleOutput rejects with the error thrown by the callback', async () => { + await expect( + suppressingConsoleOutput(() => Promise.reject(new Error('test'))) + ).rejects.toThrowError('test'); +}); diff --git a/libs/test-utils-vitest/src/console.ts b/libs/test-utils-vitest/src/console.ts new file mode 100644 index 0000000000..666d832036 --- /dev/null +++ b/libs/test-utils-vitest/src/console.ts @@ -0,0 +1,182 @@ +import { afterAll, beforeAll, expect, vi } from 'vitest'; +import chalk from 'chalk'; +import { MaybePromise, Optional, assert, iter } from '@votingworks/basics'; + +const capturedCallCountsByTest = new Map< + string, + Map<'log' | 'warn' | 'error', { count: number }> +>(); + +/** + * Strips ANSI escape codes from a string. When printing a box around a string + * with ANSI escape codes, the box will be the wrong size. This function removes + * the ANSI escape codes so that the box is the correct size. + */ +function stripAnsi(str: string): string { + // eslint-disable-next-line no-control-regex + return str.replace(/\x1b\[[0-9;]*m/g, ''); +} + +function printLinesInBox(lines: string[], out: NodeJS.WritableStream) { + const boxTopLeft = '┌'; + const boxTopRight = '┐'; + const boxTopAndBottom = '─'; + const boxLeft = '│'; + const boxRight = '│'; + const boxBottomLeft = '└'; + const boxBottomRight = '┘'; + + const longestLine = + iter(lines) + .map((line) => stripAnsi(line).length) + .max() ?? 0; + + const boxTop = `${boxTopLeft}${boxTopAndBottom.repeat( + longestLine + 2 + )}${boxTopRight}`; + const boxBottom = `${boxBottomLeft}${boxTopAndBottom.repeat( + longestLine + 2 + )}${boxBottomRight}`; + + out.write(chalk.dim(`${boxTop}\n`)); + for (const line of lines) { + out.write( + chalk.dim( + `${boxLeft} ${line}${' '.repeat( + longestLine - stripAnsi(line).length + )} ${boxRight}\n` + ) + ); + } + out.write(chalk.dim(`${boxBottom}\n`)); +} + +if (typeof beforeAll === 'function' && typeof afterAll === 'function') { + beforeAll(() => { + capturedCallCountsByTest.clear(); + }); + + afterAll(() => { + const shouldPrintSummary = process.env['CI'] !== 'true'; + + if (!shouldPrintSummary) { + return; + } + + const allSummaries = Array.from(capturedCallCountsByTest).flatMap( + ([testName, capturedCallCounts]) => { + const summaries = Array.from(capturedCallCounts.entries()) + .filter(([, { count }]) => count > 0) + .map( + ([name, { count }]) => `${count} ${name}${count > 1 ? 's' : ''}` + ); + + return summaries.length > 0 + ? [`${testName} (${summaries.join(', ')})`] + : []; + } + ); + + if (allSummaries.length > 0) { + printLinesInBox( + [ + '🤫 Some tests suppressed console output:', + '', + ...allSummaries, + '', + `Run with ${chalk.italic( + 'SUPPRESS_CONSOLE_OUTPUT=false' + )} to see the output.`, + ], + process.stderr + ); + process.stderr.write('\n'); + } + }); +} + +/** + * Suppresses console output during the execution of a function. Resolves to the + * return value of the function. + */ +export function suppressingConsoleOutput(fn: () => Promise): Promise; + +/** + * Suppresses console output during the execution of a function. Returns the + * return value of the function. + */ +export function suppressingConsoleOutput(fn: () => T): T; + +export function suppressingConsoleOutput( + fn: () => MaybePromise +): MaybePromise { + if (process.env['SUPPRESS_CONSOLE_OUTPUT'] === 'false') { + return fn(); + } + + const { currentTestName } = expect.getState(); + assert(currentTestName !== undefined); + const capturedCallCounts = + capturedCallCountsByTest.get(currentTestName) ?? + new Map([ + ['log', { count: 0 }], + ['warn', { count: 0 }], + ['error', { count: 0 }], + ]); + capturedCallCountsByTest.set(currentTestName, capturedCallCounts); + const logStats = capturedCallCounts.get('log') as { count: number }; + const warnStats = capturedCallCounts.get('warn') as { count: number }; + const errorStats = capturedCallCounts.get('error') as { count: number }; + + const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { + logStats.count += 1; + }); + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { + warnStats.count += 1; + }); + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => { + errorStats.count += 1; + }); + + function cleanup() { + logSpy.mockRestore(); + warnSpy.mockRestore(); + errorSpy.mockRestore(); + } + + let value: Optional>; + let error: unknown; + let success = false; + + try { + value = fn(); + success = true; + } catch (e) { + error = e; + } + + if (!success) { + cleanup(); + throw error; + } + + if ( + typeof value === 'object' && + value !== null && + typeof (value as Promise).then === 'function' + ) { + return (value as Promise).then( + (v) => { + cleanup(); + return v; + }, + (e) => { + cleanup(); + throw e; + } + ); + } + + cleanup(); + return value as T; +} diff --git a/libs/test-utils-vitest/src/index.ts b/libs/test-utils-vitest/src/index.ts new file mode 100644 index 0000000000..1439c75a85 --- /dev/null +++ b/libs/test-utils-vitest/src/index.ts @@ -0,0 +1,6 @@ +export * from './advance_timers'; +export * from './child_process'; +export * from './console'; +export * from './mock_kiosk'; +export * from './mock_of'; +export * from './mock_use_audio_controls'; diff --git a/libs/test-utils-vitest/src/mock_function.test.ts b/libs/test-utils-vitest/src/mock_function.test.ts new file mode 100644 index 0000000000..6b5ac0a742 --- /dev/null +++ b/libs/test-utils-vitest/src/mock_function.test.ts @@ -0,0 +1,344 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { describe, expect, it } from 'vitest'; +import { mockFunction } from './mock_function'; + +describe('mockFunction', () => { + function add(num1: number, num2: number): number { + throw new Error('Not implemented'); + } + + it('creates a mock function that returns a value', () => { + const addMock = mockFunction('add'); + addMock.expectCallWith(1, 2).returns(3); + expect(addMock(1, 2)).toEqual(3); + addMock.assertComplete(); + }); + + it('ensures the actual input matches the expected input', () => { + const addMock = mockFunction('add'); + addMock.expectCallWith(1, 2).returns(3); + const expectedMessage = + 'Mismatched call to mock function:\nExpected: add(1, 2)\nActual: add(1, 3)'; + expect(() => addMock(1, 3)).toThrow(expectedMessage); + }); + + it('errors on unexpected calls', () => { + const addMock = mockFunction('add'); + const expectedMessage = 'Unexpected call to mock function: add(1, 2)'; + expect(() => addMock(1, 2)).toThrow(expectedMessage); + }); + + it('handles multiple calls', () => { + const addMock = mockFunction('add'); + addMock.expectCallWith(1, 2).returns(3); + addMock.expectCallWith(1, 3).returns(4); + expect(addMock(1, 2)).toEqual(3); + expect(addMock(1, 3)).toEqual(4); + addMock.assertComplete(); + }); + + it('only allows calls to be used once', () => { + const addMock = mockFunction('add'); + addMock.expectCallWith(1, 2).returns(3); + expect(addMock(1, 2)).toEqual(3); + expect(() => addMock(1, 2)).toThrowErrorMatchingInlineSnapshot( + `[Error: Unexpected call to mock function: add(1, 2)]` + ); + }); + + it('enforces the order of calls', () => { + const addMock = mockFunction('add'); + addMock.expectCallWith(1, 2).returns(3); + addMock.expectCallWith(1, 3).returns(4); + expect(() => addMock(1, 3)).toThrowErrorMatchingInlineSnapshot(` + [Error: Mismatched call to mock function: + Expected: add(1, 2) + Actual: add(1, 3) + Input diff: - Expected + + Received + + Array [ + 1, + - 2, + + 3, + ]] + `); + }); + + it('supports all different types of arguments', () => { + function funcWithManyTypes( + a: string, + b: number, + c: boolean, + d: null, + e: undefined, + f: { foo: string }, + g: number[] + ): string { + throw new Error('Not implemented'); + } + const funcMock = + mockFunction('funcWithManyTypes'); + funcMock + .expectCallWith('a', 1, true, null, undefined, { foo: 'bar' }, [1, 2]) + .returns('success'); + expect( + funcMock('a', 1, true, null, undefined, { foo: 'bar' }, [1, 2]) + ).toEqual('success'); + expect(() => + funcMock('a', 1, true, null, undefined, { foo: 'bar' }, [1, 2]) + ).toThrowErrorMatchingInlineSnapshot( + `[Error: Unexpected call to mock function: funcWithManyTypes('a', 1, true, null, undefined, { foo: 'bar' }, [ 1, 2 ])]` + ); + funcMock.reset(); + funcMock + .expectCallWith('a', 1, true, null, undefined, { foo: 'bar' }, [1, 2]) + .returns('success'); + expect(() => + funcMock('a', 1, true, null, undefined, { foo: 'wrong' }, [1, 2]) + ).toThrowErrorMatchingInlineSnapshot(` + [Error: Mismatched call to mock function: + Expected: funcWithManyTypes('a', 1, true, null, undefined, { foo: 'bar' }, [ 1, 2 ]) + Actual: funcWithManyTypes('a', 1, true, null, undefined, { foo: 'wrong' }, [ 1, 2 ]) + Input diff: - Expected + + Received + + Array [ + "a", + 1, + true, + null, + undefined, + Object { + - "foo": "bar", + + "foo": "wrong", + }, + Array [ + 1, + 2, + ], + ]] + `); + }); + + it('enforces correct types', () => { + const addMock = mockFunction('add'); + // @ts-expect-error - wrong argument type + addMock.expectCallWith('1', 2).returns(3); + // @ts-expect-error - wrong return type + addMock.expectCallWith(1, 2).returns('3'); + }); + + it('supports async functions', async () => { + // eslint-disable-next-line @typescript-eslint/require-await + async function addAsync(num1: number, num2: number): Promise { + throw new Error('Not implemented'); + } + const addMock = mockFunction('addAsync'); + addMock.expectCallWith(1, 2).returns(Promise.resolve(3)); + addMock.expectCallWith(1, 2).resolves(3); + addMock.expectRepeatedCallsWith(1, 2).resolves(3); + expect(await addMock(1, 2)).toEqual(3); + expect(await addMock(1, 2)).toEqual(3); + expect(await addMock(1, 2)).toEqual(3); + expect(await addMock(1, 2)).toEqual(3); + }); + + it('supports mocking an exception', () => { + const addMock = mockFunction('add'); + const error = new Error('Mock error'); + addMock.expectCallWith(1, 2).throws(error); + expect(() => addMock(1, 2)).toThrow(error); + }); + + it('supports repeated calls', () => { + const addMock = mockFunction('add'); + addMock.expectRepeatedCallsWith(1, 2).returns(3); + expect(addMock(1, 2)).toEqual(3); + expect(addMock(1, 2)).toEqual(3); + expect(addMock(1, 2)).toEqual(3); + addMock.expectCallWith(1, 2).returns(4); + expect(addMock(1, 2)).toEqual(4); + addMock.expectRepeatedCallsWith(1, 2).returns(5); + expect(addMock(1, 2)).toEqual(5); + expect(addMock(1, 2)).toEqual(5); + addMock.assertComplete(); + }); + + it('errors if actual input doesnt match expected input of repeated call', () => { + const addMock = mockFunction('add'); + addMock.expectRepeatedCallsWith(1, 2).returns(3); + expect(addMock(1, 2)).toEqual(3); + expect(() => addMock(1, 3)).toThrowErrorMatchingInlineSnapshot(` + [Error: Mismatched call to mock function: + Expected: add(1, 2) (repeated) + Actual: add(1, 3) + Input diff: - Expected + + Received + + Array [ + 1, + - 2, + + 3, + ]] + `); + }); + + it('supports optional repeated calls', () => { + const addMock = mockFunction('add'); + addMock.expectOptionalRepeatedCallsWith(1, 2).returns(3); + addMock.assertComplete(); + expect(addMock(1, 2)).toEqual(3); + expect(addMock(1, 2)).toEqual(3); + addMock.assertComplete(); + }); + + it('assertComplete errors if not all expected calls are used', () => { + const addMock = mockFunction('add'); + addMock.expectCallWith(1, 2).returns(3); + addMock.expectCallWith(2, 2).returns(4); + addMock(1, 2); + expect(() => addMock.assertComplete()).toThrowErrorMatchingInlineSnapshot(` + [Error: Mismatch between expected mock function calls and actual mock function calls: + + Call #0 + Expected: add(1, 2) + Actual: add(1, 2) + + Call #1 + Expected: add(2, 2) + Actual: ] + `); + }); + + it('assertComplete errors if not all repeated expected calls are used', () => { + const addMock = mockFunction('add'); + addMock.expectCallWith(1, 2).returns(3); + addMock.expectRepeatedCallsWith(2, 2).returns(4); + addMock(1, 2); + expect(() => addMock.assertComplete()).toThrowErrorMatchingInlineSnapshot(` + [Error: Mismatch between expected mock function calls and actual mock function calls: + + Call #0 + Expected: add(1, 2) + Actual: add(1, 2) + + Call #1 + Expected: add(2, 2) (repeated) + Actual: ] + `); + }); + + it('assertComplete errors if there are unexpected calls', () => { + const addMock = mockFunction('add'); + addMock.expectCallWith(1, 2).returns(3); + expect(addMock(1, 2)).toEqual(3); + expect(() => addMock(1, 3)).toThrow(); + expect(() => addMock.assertComplete()).toThrowErrorMatchingInlineSnapshot(` + [Error: Mismatch between expected mock function calls and actual mock function calls: + + Call #0 + Expected: add(1, 2) + Actual: add(1, 2) + + Call #1 + Expected: + Actual: add(1, 3)] + `); + }); + + it('assertComplete errors if there are unexpected calls with repeated calls', () => { + const addMock = mockFunction('add'); + addMock.expectRepeatedCallsWith(1, 2).returns(3); + expect(addMock(1, 2)).toEqual(3); + expect(() => addMock(1, 3)).toThrow(); + expect(() => addMock.assertComplete()).toThrowErrorMatchingInlineSnapshot(` + [Error: Mismatch between expected mock function calls and actual mock function calls: + + Call #0 + Expected: add(1, 2) (repeated) + Actual: add(1, 2) + + Call #1 + Expected: add(1, 2) (repeated) + Actual: add(1, 3) + Input diff: - Expected + + Received + + Array [ + 1, + - 2, + + 3, + ]] + `); + }); + + it('assertComplete errors if there are mismatched calls', () => { + const addMock = mockFunction('add'); + addMock.expectCallWith(1, 2).returns(3); + addMock.expectCallWith(1, 3).returns(4); + expect(() => addMock(1, 3)).toThrow(); + expect(() => addMock(1, 2)).toThrow(); + expect(() => addMock.assertComplete()).toThrowErrorMatchingInlineSnapshot(` + [Error: Mismatch between expected mock function calls and actual mock function calls: + + Call #0 + Expected: add(1, 2) + Actual: add(1, 3) + Input diff: - Expected + + Received + + Array [ + 1, + - 2, + + 3, + ]] + `); + }); + + it('assertComplete errors if there are mismatched calls with repeated calls', () => { + const addMock = mockFunction('add'); + addMock.expectRepeatedCallsWith(1, 3).returns(4); + addMock.expectCallWith(1, 2).returns(3); + expect(() => addMock(1, 2)).toThrow(); + expect(() => addMock(1, 3)).toThrow(); + expect(() => addMock.assertComplete()).toThrowErrorMatchingInlineSnapshot(` + [Error: Mismatch between expected mock function calls and actual mock function calls: + + Call #0 + Expected: add(1, 3) (repeated) + Actual: add(1, 2) + Input diff: - Expected + + Received + + Array [ + 1, + - 3, + + 2, + ]] + `); + }); + + it('reset clears the mock state', () => { + const addMock = mockFunction('add'); + + addMock.expectCallWith(1, 2).returns(3); + expect(addMock(1, 2)).toEqual(3); + addMock.assertComplete(); + addMock.reset(); + + addMock.expectCallWith(1, 2).returns(3); + addMock.reset(); + + expect(() => addMock(1, 2)).toThrow(); + addMock.reset(); + + addMock.expectRepeatedCallsWith(1, 2).returns(3); + expect(addMock(1, 2)).toEqual(3); + expect(addMock(1, 2)).toEqual(3); + addMock.assertComplete(); + addMock.reset(); + + addMock.assertComplete(); + }); +}); diff --git a/libs/test-utils-vitest/src/mock_function.ts b/libs/test-utils-vitest/src/mock_function.ts new file mode 100644 index 0000000000..427f02b87a --- /dev/null +++ b/libs/test-utils-vitest/src/mock_function.ts @@ -0,0 +1,345 @@ +import { inspect } from 'node:util'; +import { diff } from 'jest-diff'; +import { assertDefined, deepEqual } from '@votingworks/basics'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyFunc = (...args: any[]) => any; + +interface Call { + input: Parameters; +} + +type ActualCall = Call & { + matchingExpectedCallIndex?: number; +}; + +type ExpectedCall = + | ({ + output: ReturnType; + repeated: boolean; + optional?: boolean; + } & Call) + | ({ + error: unknown; + repeated: false; + optional?: false; + } & Call); + +interface ReturnHelpers { + returns(output: ReturnType): void; + throws(error: unknown): void; +} + +interface AsyncReturnHelpers extends ReturnHelpers { + resolves(output: Awaited>): void; +} + +export class MockFunctionError extends Error {} + +export interface MockFunction { + (...input: Parameters): ReturnType; + expectCallWith( + ...input: Parameters + ): ReturnType extends Promise + ? AsyncReturnHelpers + : ReturnHelpers; + expectRepeatedCallsWith( + ...input: Parameters + ): ReturnType extends Promise + ? Omit, 'throws'> + : Omit, 'throws'>; + expectOptionalRepeatedCallsWith( + ...input: Parameters + ): ReturnType extends Promise + ? Omit, 'throws'> + : Omit, 'throws'>; + reset(): void; + assertComplete(): void; +} + +interface MockFunctionState { + expectedCalls: Array>; + actualCalls: Array>; +} + +function formatFunctionCall(name: string, input: unknown[]): string { + return `${name}(${input + .map((arg) => inspect(arg, { depth: null })) + .join(', ')})`; +} + +function formatExpectedAndActualCalls( + name: string, + actualCall?: Call, + expectedCall?: ExpectedCall +): string { + const expectedStr = expectedCall + ? formatFunctionCall(name, expectedCall.input) + + (expectedCall.repeated ? ' (repeated)' : '') + + (expectedCall.optional ? ' (optional)' : '') + : ''; + const actualStr = actualCall + ? formatFunctionCall(name, actualCall.input) + : ''; + const diffStr = + expectedCall && + actualCall && + !deepEqual(actualCall.input, expectedCall.input) + ? diff(expectedCall.input, actualCall.input) + : undefined; + return [`Expected: ${expectedStr}`, `Actual: ${actualStr}`] + .concat(diffStr ? [`Input diff: ${diffStr}`] : []) + .join('\n'); +} + +/** + * Creates a mock function, similar to jest.fn(), but with stricter rules to + * help you avoid making unintentional mistakes. + * + * Example usage: + * + * // Let's say we have a real function, add, that we want to mock + * function add(num1: number, num2: number): number { + * return num1 + num2; + * } + * + * // We can create a mock function that behaves the same way + * const addMock = mockFunction('add'); + * addMock.expectCallWith(1, 2).returns(3); + * addMock(1, 2); // -> returns 3 + * addMock.expectCallWith(1, NaN).throws(new Error('Cant add NaN')); + * addMock(1, NaN); // -> throws Error('Cant add NaN') + * + * // Unlike jest.fn(), mockFunction is strict... + * + * // Each expected call can only be used once, in order + * addMock.expectCallWith(1, 2).returns(3); + * addMock(1, 2); // -> returns 3 + * addMock(1, 2); // -> throws an error because we didn't expect this call + * + * // The actual input must match the expected input + * addMock.expectCallWith(1, 2).returns(3); + * addMock(1, 3); // -> throws an error because the input doesn't match + * + * // Every expected call must be used + * addMock.expectCallWith(1, 2).returns(3); + * addMock.assertComplete(); // -> throws an error because we didn't use the expected call + * + * // You can also expect repeated calls, which is useful for functions that are polled + * addMock.expectedRepeatedCallsWith(1, 2).returns(3); + * addMock(1, 2); // -> returns 3 + * addMock(1, 2); // -> returns 3 + * addMock(1, 3); // -> throws an error because the input doesn't match + * + * Recommendations: + * - Always call mockFunction.assertComplete() to ensure that all expected calls were used + * - If you are going to reuse a mock between test cases, call mockFunction.reset() in afterEach + */ +// eslint-disable-next-line vx/gts-no-return-type-only-generics +export function mockFunction( + name: string +): MockFunction { + const state: MockFunctionState = { + expectedCalls: [], + actualCalls: [], + }; + + // Find the next expected call that should be used for the given actual call. + // We track which expected call each actual call matched and then base the + // next appropriate expected call on that. + function findExpectedCallForActualCallIndex(actualCallIndex: number) { + const previousActualCall = state.actualCalls[actualCallIndex - 1]; + if (!previousActualCall) { + return state.expectedCalls[0]; + } + + // If the previous actual call didn't match any expected call, then we don't + // know what the next expected call should be. + if (previousActualCall.matchingExpectedCallIndex === undefined) { + throw new MockFunctionError( + 'Previous call to mock function did not match expected calls' + ); + } + + // If we have a next expected call, use that + const nextExpectedCallIndex = + previousActualCall.matchingExpectedCallIndex + 1; + if (nextExpectedCallIndex < state.expectedCalls.length) { + return state.expectedCalls[nextExpectedCallIndex]; + } + + // Otherwise, maybe the previous expected call was a repeated expected call, + // so we can use that + const previousExpectedCall = + state.expectedCalls[previousActualCall.matchingExpectedCallIndex]; + if (assertDefined(previousExpectedCall).repeated) { + return previousExpectedCall; + } + + return undefined; + } + + const mock: MockFunction = (...input: Parameters) => { + // Special case - [] and [undefined] are equivalent as args + if (input.length === 1 && input[0] === undefined) { + // eslint-disable-next-line no-param-reassign + input = [] as unknown as Parameters; + } + + const expectedCall = findExpectedCallForActualCallIndex( + state.actualCalls.length + ); + const callMatches = expectedCall && deepEqual(input, expectedCall.input); + + state.actualCalls.push({ + input, + matchingExpectedCallIndex: callMatches + ? state.expectedCalls.indexOf(expectedCall) + : undefined, + }); + + if (!expectedCall) { + const message = `Unexpected call to mock function: ${formatFunctionCall( + name, + input + )}`; + throw new MockFunctionError(message); + } + + if (!callMatches) { + const message = `Mismatched call to mock function:\n${formatExpectedAndActualCalls( + name, + { input }, + expectedCall + )}`; + throw new MockFunctionError(message); + } + + if ('error' in expectedCall) { + throw expectedCall.error; + } + + return expectedCall.output; + }; + + mock.expectCallWith = (...input: Parameters) => ({ + returns(output: ReturnType) { + state.expectedCalls.push({ input, output, repeated: false }); + }, + throws(error: unknown) { + state.expectedCalls.push({ input, error, repeated: false }); + }, + resolves(output: Awaited>) { + state.expectedCalls.push({ + input, + output: Promise.resolve(output) as ReturnType, + repeated: false, + }); + }, + }); + + mock.expectRepeatedCallsWith = (...input: Parameters) => ({ + returns(output: ReturnType) { + state.expectedCalls.push({ + input, + output, + repeated: true, + }); + }, + resolves(output: Awaited>) { + state.expectedCalls.push({ + input, + output: Promise.resolve(output) as ReturnType, + repeated: true, + }); + }, + }); + + mock.expectOptionalRepeatedCallsWith = (...input: Parameters) => ({ + returns(output: ReturnType) { + state.expectedCalls.push({ + input, + output, + repeated: true, + optional: true, + }); + }, + resolves(output: Awaited>) { + state.expectedCalls.push({ + input, + output: Promise.resolve(output) as ReturnType, + repeated: true, + optional: true, + }); + }, + }); + + mock.reset = () => { + state.expectedCalls = []; + state.actualCalls = []; + }; + + mock.assertComplete = () => { + // We want to check: + // - Every actual call matched an expected call + // - Every expected call was used + // So we build a correspondence of actual calls with their expected calls. + + const actualCallsWithTheirExpectedCalls = state.actualCalls.map( + (actualCall, actualCallIndex) => { + const expectedCall = + findExpectedCallForActualCallIndex(actualCallIndex); + return { + actualCall, + expectedCall, + }; + } + ); + + const unusedExpectedCalls = state.expectedCalls.filter( + (expectedCall) => + !expectedCall.optional && + !actualCallsWithTheirExpectedCalls.some( + ({ expectedCall: usedExpectedCall }) => + usedExpectedCall === expectedCall + ) + ); + + const callCorrespondence = [ + ...actualCallsWithTheirExpectedCalls, + ...unusedExpectedCalls.map((expectedCall) => ({ + actualCall: undefined, + expectedCall, + })), + ]; + + // We can only accurately report a correspondence of expected and actual + // calls up to the first mismatched call. After that, we don't know which + // expected calls were used for which actual calls. + const firstMismatchedCallIndex = callCorrespondence.findIndex( + ({ actualCall, expectedCall }) => + !( + actualCall && + expectedCall && + actualCall.matchingExpectedCallIndex !== undefined + ) + ); + + if (firstMismatchedCallIndex !== -1) { + const message = `Mismatch between expected mock function calls and actual mock function calls:\n\n${callCorrespondence + .slice(0, firstMismatchedCallIndex + 1) + .map( + ({ actualCall, expectedCall }, index) => + `Call #${index}\n${formatExpectedAndActualCalls( + name, + actualCall, + expectedCall + )}` + ) + .join('\n\n')}`; + throw new MockFunctionError(message); + } + }; + + return mock; +} diff --git a/libs/test-utils-vitest/src/mock_kiosk.ts b/libs/test-utils-vitest/src/mock_kiosk.ts new file mode 100644 index 0000000000..5c6f41031a --- /dev/null +++ b/libs/test-utils-vitest/src/mock_kiosk.ts @@ -0,0 +1,15 @@ +import { Mocked, vi } from 'vitest'; + +export type MockKiosk = Mocked; + +/** + * Builds a `Kiosk` instance with mock methods. + */ +export function mockKiosk(): MockKiosk { + return { + quit: vi.fn(), + log: vi.fn(), + captureScreenshot: vi.fn().mockResolvedValue(Uint8Array.of()), + showOpenDialog: vi.fn(), + }; +} diff --git a/libs/test-utils-vitest/src/mock_of.ts b/libs/test-utils-vitest/src/mock_of.ts new file mode 100644 index 0000000000..c68da61be8 --- /dev/null +++ b/libs/test-utils-vitest/src/mock_of.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { MockedFunction } from 'vitest'; + +/** + * Returns a properly-typed mock for an already-mocked function. + * + * @example + * + * import * as fs from 'node:fs' + * vi.mock('node:fs') + * const readFileMock = mockOf(fs.readFile) + * readFileMock.mockImplementation(…) + */ +export function mockOf any>( + fn: T +): MockedFunction { + return fn as MockedFunction; +} diff --git a/libs/test-utils-vitest/src/mock_use_audio_controls.ts b/libs/test-utils-vitest/src/mock_use_audio_controls.ts new file mode 100644 index 0000000000..44be98d803 --- /dev/null +++ b/libs/test-utils-vitest/src/mock_use_audio_controls.ts @@ -0,0 +1,17 @@ +import { AudioControls } from '@votingworks/types'; +import { Mocked, vi } from 'vitest'; + +export function mockUseAudioControls(): Mocked { + return { + decreasePlaybackRate: vi.fn(), + decreaseVolume: vi.fn(), + increasePlaybackRate: vi.fn(), + increaseVolume: vi.fn(), + reset: vi.fn(), + replay: vi.fn(), + setControlsEnabled: vi.fn(), + setIsEnabled: vi.fn(), + toggleEnabled: vi.fn(), + togglePause: vi.fn(), + }; +} diff --git a/libs/test-utils-vitest/tsconfig.build.json b/libs/test-utils-vitest/tsconfig.build.json new file mode 100644 index 0000000000..5a67555159 --- /dev/null +++ b/libs/test-utils-vitest/tsconfig.build.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "include": ["src"], + "exclude": ["**/*.test.ts", "**/*.test.tsx"], + "compilerOptions": { + "noEmit": false, + "rootDir": "src", + "outDir": "build", + "declaration": true + }, + "references": [ + { "path": "../basics/tsconfig.build.json" }, + { "path": "../eslint-plugin-vx/tsconfig.build.json" }, + { "path": "../types/tsconfig.build.json" } + ] +} diff --git a/libs/test-utils-vitest/tsconfig.json b/libs/test-utils-vitest/tsconfig.json new file mode 100644 index 0000000000..4da5c13aaa --- /dev/null +++ b/libs/test-utils-vitest/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.shared.json", + "include": ["src"], + "exclude": [], + "compilerOptions": { + "module": "node16", + "composite": true, + "noEmit": true, + "esModuleInterop": true, + "lib": ["dom", "dom.iterable", "esnext"], + "skipLibCheck": true, + "noUncheckedIndexedAccess": true + }, + "references": [ + { "path": "../basics/tsconfig.build.json" }, + { "path": "../eslint-plugin-vx/tsconfig.build.json" }, + { "path": "../types/tsconfig.build.json" } + ] +} diff --git a/libs/test-utils-vitest/vitest.config.ts b/libs/test-utils-vitest/vitest.config.ts new file mode 100644 index 0000000000..d1ec6cc46f --- /dev/null +++ b/libs/test-utils-vitest/vitest.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + coverage: { + thresholds: { + lines: 84, + branches: 79, + }, + provider: 'istanbul', + include: ['src/**/*.ts'], + exclude: [ + 'src/**/*.test.ts', + 'src/advance_timers.ts', + 'src/mock_of.ts', + 'src/mock_kiosk.ts', + 'src/mock_use_audio_controls.ts', + ], + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1c7939026c..294ba6e6bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5241,6 +5241,55 @@ importers: specifier: 29.1.1 version: 29.1.1(@babel/core@7.22.9)(@jest/types@29.6.1)(esbuild@0.18.17)(jest@29.6.2)(typescript@5.6.2) + libs/test-utils-vitest: + dependencies: + '@testing-library/react': + specifier: ^15.0.7 + version: 15.0.7(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) + '@votingworks/basics': + specifier: workspace:* + version: link:../basics + '@votingworks/types': + specifier: workspace:* + version: link:../types + chalk: + specifier: 4.1.2 + version: 4.1.2 + jest-diff: + specifier: ^29.6.2 + version: 29.6.2 + react: + specifier: 18.3.1 + version: 18.3.1 + vitest: + specifier: ^2.1.8 + version: 2.1.8(@types/node@20.16.0) + devDependencies: + '@types/kiosk-browser': + specifier: workspace:* + version: link:../@types/kiosk-browser + '@types/node': + specifier: 20.16.0 + version: 20.16.0 + '@types/react': + specifier: 18.3.3 + version: 18.3.3 + '@vitest/coverage-istanbul': + specifier: ^2.1.8 + version: 2.1.8(vitest@2.1.8) + eslint-plugin-vx: + specifier: workspace:* + version: link:../eslint-plugin-vx + is-ci-cli: + specifier: 2.2.0 + version: 2.2.0 + lint-staged: + specifier: 11.0.0 + version: 11.0.0 + sort-package-json: + specifier: ^1.50.0 + version: 1.53.1 + libs/types: dependencies: '@antongolub/iso8601': @@ -5879,6 +5928,11 @@ packages: resolution: {integrity: sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==} engines: {node: '>=6.9.0'} + /@babel/compat-data@7.26.3: + resolution: {integrity: sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/core@7.20.12: resolution: {integrity: sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==} engines: {node: '>=6.9.0'} @@ -5924,6 +5978,29 @@ packages: transitivePeerDependencies: - supports-color + /@babel/core@7.26.0: + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} + engines: {node: '>=6.9.0'} + dependencies: + '@ampproject/remapping': 2.2.1 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helpers': 7.26.0 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/traverse': 7.23.2(supports-color@5.5.0) + '@babel/types': 7.26.0 + convert-source-map: 2.0.0 + debug: 4.3.4(supports-color@5.5.0) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/generator@7.22.9: resolution: {integrity: sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==} engines: {node: '>=6.9.0'} @@ -5973,6 +6050,17 @@ packages: lru-cache: 5.1.1 semver: 6.3.1 + /@babel/helper-compilation-targets@7.25.9: + resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/compat-data': 7.26.3 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.3 + lru-cache: 5.1.1 + semver: 6.3.1 + dev: true + /@babel/helper-create-class-features-plugin@7.22.10(@babel/core@7.22.9): resolution: {integrity: sha512-5IBb77txKYQPpOEdUdIhBx8VrZyDCQ+H82H0+5dX1TmuscP5vJKEE3cKurjtIw/vFwzbVH48VweE78kVDBrqjA==} engines: {node: '>=6.9.0'} @@ -6079,6 +6167,16 @@ packages: dependencies: '@babel/types': 7.22.10 + /@babel/helper-module-imports@7.25.9: + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/traverse': 7.23.2(supports-color@5.5.0) + '@babel/types': 7.26.0 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-module-transforms@7.22.9(@babel/core@7.20.12): resolution: {integrity: sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==} engines: {node: '>=6.9.0'} @@ -6106,6 +6204,20 @@ packages: '@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-validator-identifier': 7.22.5 + /@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0): + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.23.2(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-optimise-call-expression@7.22.5: resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} engines: {node: '>=6.9.0'} @@ -6195,6 +6307,11 @@ packages: resolution: {integrity: sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==} engines: {node: '>=6.9.0'} + /@babel/helper-validator-option@7.25.9: + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/helper-wrap-function@7.22.10: resolution: {integrity: sha512-OnMhjWjuGYtdoO3FmsEFWvBStBAe2QOgwOLsLNDjN+aaiMD8InJk1/O3HSD8lkqTjCgg5YI34Tz15KNNA3p+nQ==} engines: {node: '>=6.9.0'} @@ -6214,6 +6331,14 @@ packages: transitivePeerDependencies: - supports-color + /@babel/helpers@7.26.0: + resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + dev: true + /@babel/highlight@7.22.5: resolution: {integrity: sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==} engines: {node: '>=6.9.0'} @@ -7552,6 +7677,14 @@ packages: requiresBuild: true optional: true + /@esbuild/aix-ppc64@0.21.5: + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + optional: true + /@esbuild/android-arm64@0.17.11: resolution: {integrity: sha512-QnK4d/zhVTuV4/pRM4HUjcsbl43POALU2zvBynmrrqZt9LPcLA3x1fTZPBg2RRguBQnJcnU059yKr+bydkntjg==} engines: {node: '>=12'} @@ -7577,6 +7710,14 @@ packages: requiresBuild: true optional: true + /@esbuild/android-arm64@0.21.5: + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + optional: true + /@esbuild/android-arm@0.17.11: resolution: {integrity: sha512-CdyX6sRVh1NzFCsf5vw3kULwlAhfy9wVt8SZlrhQ7eL2qBjGbFhRBWkkAzuZm9IIEOCKJw4DXA6R85g+qc8RDw==} engines: {node: '>=12'} @@ -7602,6 +7743,14 @@ packages: requiresBuild: true optional: true + /@esbuild/android-arm@0.21.5: + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + optional: true + /@esbuild/android-x64@0.17.11: resolution: {integrity: sha512-3PL3HKtsDIXGQcSCKtWD/dy+mgc4p2Tvo2qKgKHj9Yf+eniwFnuoQ0OUhlSfAEpKAFzF9N21Nwgnap6zy3L3MQ==} engines: {node: '>=12'} @@ -7627,6 +7776,14 @@ packages: requiresBuild: true optional: true + /@esbuild/android-x64@0.21.5: + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + optional: true + /@esbuild/darwin-arm64@0.17.11: resolution: {integrity: sha512-pJ950bNKgzhkGNO3Z9TeHzIFtEyC2GDQL3wxkMApDEghYx5Qers84UTNc1bAxWbRkuJOgmOha5V0WUeh8G+YGw==} engines: {node: '>=12'} @@ -7652,6 +7809,14 @@ packages: requiresBuild: true optional: true + /@esbuild/darwin-arm64@0.21.5: + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true + /@esbuild/darwin-x64@0.17.11: resolution: {integrity: sha512-iB0dQkIHXyczK3BZtzw1tqegf0F0Ab5texX2TvMQjiJIWXAfM4FQl7D909YfXWnB92OQz4ivBYQ2RlxBJrMJOw==} engines: {node: '>=12'} @@ -7677,6 +7842,14 @@ packages: requiresBuild: true optional: true + /@esbuild/darwin-x64@0.21.5: + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true + /@esbuild/freebsd-arm64@0.17.11: resolution: {integrity: sha512-7EFzUADmI1jCHeDRGKgbnF5sDIceZsQGapoO6dmw7r/ZBEKX7CCDnIz8m9yEclzr7mFsd+DyasHzpjfJnmBB1Q==} engines: {node: '>=12'} @@ -7702,6 +7875,14 @@ packages: requiresBuild: true optional: true + /@esbuild/freebsd-arm64@0.21.5: + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + optional: true + /@esbuild/freebsd-x64@0.17.11: resolution: {integrity: sha512-iPgenptC8i8pdvkHQvXJFzc1eVMR7W2lBPrTE6GbhR54sLcF42mk3zBOjKPOodezzuAz/KSu8CPyFSjcBMkE9g==} engines: {node: '>=12'} @@ -7727,6 +7908,14 @@ packages: requiresBuild: true optional: true + /@esbuild/freebsd-x64@0.21.5: + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + optional: true + /@esbuild/linux-arm64@0.17.11: resolution: {integrity: sha512-Qxth3gsWWGKz2/qG2d5DsW/57SeA2AmpSMhdg9TSB5Svn2KDob3qxfQSkdnWjSd42kqoxIPy3EJFs+6w1+6Qjg==} engines: {node: '>=12'} @@ -7752,6 +7941,14 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-arm64@0.21.5: + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-arm@0.17.11: resolution: {integrity: sha512-M9iK/d4lgZH0U5M1R2p2gqhPV/7JPJcRz+8O8GBKVgqndTzydQ7B2XGDbxtbvFkvIs53uXTobOhv+RyaqhUiMg==} engines: {node: '>=12'} @@ -7777,6 +7974,14 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-arm@0.21.5: + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-ia32@0.17.11: resolution: {integrity: sha512-dB1nGaVWtUlb/rRDHmuDQhfqazWE0LMro/AIbT2lWM3CDMHJNpLckH+gCddQyhhcLac2OYw69ikUMO34JLt3wA==} engines: {node: '>=12'} @@ -7802,6 +8007,14 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-ia32@0.21.5: + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-loong64@0.17.11: resolution: {integrity: sha512-aCWlq70Q7Nc9WDnormntGS1ar6ZFvUpqr8gXtO+HRejRYPweAFQN615PcgaSJkZjhHp61+MNLhzyVALSF2/Q0g==} engines: {node: '>=12'} @@ -7827,6 +8040,14 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-loong64@0.21.5: + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-mips64el@0.17.11: resolution: {integrity: sha512-cGeGNdQxqY8qJwlYH1BP6rjIIiEcrM05H7k3tR7WxOLmD1ZxRMd6/QIOWMb8mD2s2YJFNRuNQ+wjMhgEL2oCEw==} engines: {node: '>=12'} @@ -7852,6 +8073,14 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-mips64el@0.21.5: + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-ppc64@0.17.11: resolution: {integrity: sha512-BdlziJQPW/bNe0E8eYsHB40mYOluS+jULPCjlWiHzDgr+ZBRXPtgMV1nkLEGdpjrwgmtkZHEGEPaKdS/8faLDA==} engines: {node: '>=12'} @@ -7877,6 +8106,14 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-ppc64@0.21.5: + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-riscv64@0.17.11: resolution: {integrity: sha512-MDLwQbtF+83oJCI1Cixn68Et/ME6gelmhssPebC40RdJaect+IM+l7o/CuG0ZlDs6tZTEIoxUe53H3GmMn8oMA==} engines: {node: '>=12'} @@ -7902,6 +8139,14 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-riscv64@0.21.5: + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-s390x@0.17.11: resolution: {integrity: sha512-4N5EMESvws0Ozr2J94VoUD8HIRi7X0uvUv4c0wpTHZyZY9qpaaN7THjosdiW56irQ4qnJ6Lsc+i+5zGWnyqWqQ==} engines: {node: '>=12'} @@ -7927,6 +8172,14 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-s390x@0.21.5: + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-x64@0.17.11: resolution: {integrity: sha512-rM/v8UlluxpytFSmVdbCe1yyKQd/e+FmIJE2oPJvbBo+D0XVWi1y/NQ4iTNx+436WmDHQBjVLrbnAQLQ6U7wlw==} engines: {node: '>=12'} @@ -7952,6 +8205,14 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-x64@0.21.5: + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/netbsd-x64@0.17.11: resolution: {integrity: sha512-4WaAhuz5f91h3/g43VBGdto1Q+X7VEZfpcWGtOFXnggEuLvjV+cP6DyLRU15IjiU9fKLLk41OoJfBFN5DhPvag==} engines: {node: '>=12'} @@ -7977,6 +8238,14 @@ packages: requiresBuild: true optional: true + /@esbuild/netbsd-x64@0.21.5: + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + optional: true + /@esbuild/openbsd-x64@0.17.11: resolution: {integrity: sha512-UBj135Nx4FpnvtE+C8TWGp98oUgBcmNmdYgl5ToKc0mBHxVVqVE7FUS5/ELMImOp205qDAittL6Ezhasc2Ev/w==} engines: {node: '>=12'} @@ -8002,6 +8271,14 @@ packages: requiresBuild: true optional: true + /@esbuild/openbsd-x64@0.21.5: + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + optional: true + /@esbuild/sunos-x64@0.17.11: resolution: {integrity: sha512-1/gxTifDC9aXbV2xOfCbOceh5AlIidUrPsMpivgzo8P8zUtczlq1ncFpeN1ZyQJ9lVs2hILy1PG5KPp+w8QPPg==} engines: {node: '>=12'} @@ -8027,6 +8304,14 @@ packages: requiresBuild: true optional: true + /@esbuild/sunos-x64@0.21.5: + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + optional: true + /@esbuild/win32-arm64@0.17.11: resolution: {integrity: sha512-vtSfyx5yRdpiOW9yp6Ax0zyNOv9HjOAw8WaZg3dF5djEHKKm3UnoohftVvIJtRh0Ec7Hso0RIdTqZvPXJ7FdvQ==} engines: {node: '>=12'} @@ -8052,6 +8337,14 @@ packages: requiresBuild: true optional: true + /@esbuild/win32-arm64@0.21.5: + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + /@esbuild/win32-ia32@0.17.11: resolution: {integrity: sha512-GFPSLEGQr4wHFTiIUJQrnJKZhZjjq4Sphf+mM76nQR6WkQn73vm7IsacmBRPkALfpOCHsopSvLgqdd4iUW2mYw==} engines: {node: '>=12'} @@ -8077,6 +8370,14 @@ packages: requiresBuild: true optional: true + /@esbuild/win32-ia32@0.21.5: + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + optional: true + /@esbuild/win32-x64@0.17.11: resolution: {integrity: sha512-N9vXqLP3eRL8BqSy8yn4Y98cZI2pZ8fyuHx6lKjiG2WABpT2l01TXdzq5Ma2ZUBzfB7tx5dXVhge8X9u0S70ZQ==} engines: {node: '>=12'} @@ -8102,6 +8403,14 @@ packages: requiresBuild: true optional: true + /@esbuild/win32-x64@0.21.5: + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true + /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -8502,7 +8811,7 @@ packages: resolution: {integrity: sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.25 callsites: 3.1.0 graceful-fs: 4.2.11 @@ -8619,6 +8928,9 @@ packages: /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + /@jridgewell/sourcemap-codec@1.5.0: + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + /@jridgewell/trace-mapping@0.3.18: resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} dependencies: @@ -9181,27 +9493,160 @@ packages: resolution: {integrity: sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==} dev: false - /@rollup/pluginutils@4.2.1: - resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} - engines: {node: '>= 8.0.0'} - dependencies: - estree-walker: 2.0.2 - picomatch: 2.3.1 - dev: true + /@rollup/pluginutils@4.2.1: + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} + engines: {node: '>= 8.0.0'} + dependencies: + estree-walker: 2.0.2 + picomatch: 2.3.1 + dev: true + + /@rollup/pluginutils@5.0.3: + resolution: {integrity: sha512-hfllNN4a80rwNQ9QCxhxuHCGHMAvabXqxNdaChUSSadMre7t4iEUI6fFAhBOn/eIYTgYVhBv7vCLsAJ4u3lf3g==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@types/estree': 1.0.5 + estree-walker: 2.0.2 + picomatch: 2.3.1 + dev: true + + /@rollup/rollup-android-arm-eabi@4.28.1: + resolution: {integrity: sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==} + cpu: [arm] + os: [android] + requiresBuild: true + optional: true + + /@rollup/rollup-android-arm64@4.28.1: + resolution: {integrity: sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==} + cpu: [arm64] + os: [android] + requiresBuild: true + optional: true + + /@rollup/rollup-darwin-arm64@4.28.1: + resolution: {integrity: sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true + + /@rollup/rollup-darwin-x64@4.28.1: + resolution: {integrity: sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true + + /@rollup/rollup-freebsd-arm64@4.28.1: + resolution: {integrity: sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + optional: true + + /@rollup/rollup-freebsd-x64@4.28.1: + resolution: {integrity: sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==} + cpu: [x64] + os: [freebsd] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.28.1: + resolution: {integrity: sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm-musleabihf@4.28.1: + resolution: {integrity: sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.28.1: + resolution: {integrity: sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.28.1: + resolution: {integrity: sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-loongarch64-gnu@4.28.1: + resolution: {integrity: sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==} + cpu: [loong64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-powerpc64le-gnu@4.28.1: + resolution: {integrity: sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.28.1: + resolution: {integrity: sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-s390x-gnu@4.28.1: + resolution: {integrity: sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==} + cpu: [s390x] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.28.1: + resolution: {integrity: sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.28.1: + resolution: {integrity: sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.28.1: + resolution: {integrity: sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.28.1: + resolution: {integrity: sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==} + cpu: [ia32] + os: [win32] + requiresBuild: true + optional: true - /@rollup/pluginutils@5.0.3: - resolution: {integrity: sha512-hfllNN4a80rwNQ9QCxhxuHCGHMAvabXqxNdaChUSSadMre7t4iEUI6fFAhBOn/eIYTgYVhBv7vCLsAJ4u3lf3g==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0 - peerDependenciesMeta: - rollup: - optional: true - dependencies: - '@types/estree': 1.0.5 - estree-walker: 2.0.2 - picomatch: 2.3.1 - dev: true + /@rollup/rollup-win32-x64-msvc@4.28.1: + resolution: {integrity: sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true /@rooks/use-interval@4.5.0(react@18.3.1): resolution: {integrity: sha512-As0DueIAGLJLYATKPPOCDGqoIlwbhPAcYP14TNTHaAj9/ODdvUYFXAP3jFCRzDNpjXCIgSe4oBuzVVmM526n+Q==} @@ -10249,7 +10694,7 @@ packages: resolution: {integrity: sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA==} engines: {node: '>=18'} dependencies: - '@babel/code-frame': 7.22.5 + '@babel/code-frame': 7.26.2 '@babel/runtime': 7.23.9 '@types/aria-query': 5.0.1 aria-query: 5.3.0 @@ -10673,7 +11118,9 @@ packages: /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: true + + /@types/estree@1.0.6: + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} /@types/express-serve-static-core@4.17.31: resolution: {integrity: sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==} @@ -11464,6 +11911,80 @@ packages: - supports-color dev: true + /@vitest/coverage-istanbul@2.1.8(vitest@2.1.8): + resolution: {integrity: sha512-cSaCd8KcWWvgDwEJSXm0NEWZ1YTiJzjicKHy+zOEbUm0gjbbkz+qJf1p8q71uBzSlS7vdnZA8wRLeiwVE3fFTA==} + peerDependencies: + vitest: 2.1.8 + dependencies: + '@istanbuljs/schema': 0.1.3 + debug: 4.3.7 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magicast: 0.3.5 + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.1.8(@types/node@20.16.0) + transitivePeerDependencies: + - supports-color + dev: true + + /@vitest/expect@2.1.8: + resolution: {integrity: sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==} + dependencies: + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 + tinyrainbow: 1.2.0 + + /@vitest/mocker@2.1.8(vite@5.4.11): + resolution: {integrity: sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + dependencies: + '@vitest/spy': 2.1.8 + estree-walker: 3.0.3 + magic-string: 0.30.17 + vite: 5.4.11(@types/node@20.16.0) + + /@vitest/pretty-format@2.1.8: + resolution: {integrity: sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==} + dependencies: + tinyrainbow: 1.2.0 + + /@vitest/runner@2.1.8: + resolution: {integrity: sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==} + dependencies: + '@vitest/utils': 2.1.8 + pathe: 1.1.2 + + /@vitest/snapshot@2.1.8: + resolution: {integrity: sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==} + dependencies: + '@vitest/pretty-format': 2.1.8 + magic-string: 0.30.17 + pathe: 1.1.2 + + /@vitest/spy@2.1.8: + resolution: {integrity: sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==} + dependencies: + tinyspy: 3.0.2 + + /@vitest/utils@2.1.8: + resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==} + dependencies: + '@vitest/pretty-format': 2.1.8 + loupe: 3.1.2 + tinyrainbow: 1.2.0 + /@votingworks/qrdetect@1.0.1: resolution: {integrity: sha512-Qk2i4NQoQy1tbkX1ADPZecXLDzBC0gaL2DcJUsOL90KXjP1lUE47UgmSWMRIlNNYu5iivpbwKA9Kl7sahgbSxg==} requiresBuild: true @@ -12046,6 +12567,10 @@ packages: object-is: 1.1.5 util: 0.12.4 + /assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + /assign-symbols@1.0.0: resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} engines: {node: '>=0.10.0'} @@ -12176,7 +12701,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/template': 7.22.5 - '@babel/types': 7.22.10 + '@babel/types': 7.26.0 '@types/babel__core': 7.20.1 '@types/babel__traverse': 7.20.1 @@ -12544,6 +13069,17 @@ packages: node-releases: 2.0.13 update-browserslist-db: 1.0.11(browserslist@4.21.10) + /browserslist@4.24.3: + resolution: {integrity: sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001689 + electron-to-chromium: 1.5.74 + node-releases: 2.0.19 + update-browserslist-db: 1.1.1(browserslist@4.24.3) + dev: true + /bs-logger@0.2.6: resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} engines: {node: '>= 6'} @@ -12639,6 +13175,10 @@ packages: yargs-parser: 20.2.9 dev: true + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + /cacache@12.0.4: resolution: {integrity: sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==} dependencies: @@ -12718,6 +13258,10 @@ packages: /caniuse-lite@1.0.30001519: resolution: {integrity: sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg==} + /caniuse-lite@1.0.30001689: + resolution: {integrity: sha512-CmeR2VBycfa+5/jOfnp/NpWPGd06nf1XYiefUvhXFfZE4GkRc9jv+eGPS4nT558WS/8lYCzV8SlANCIPvbWP1g==} + dev: true + /canvas@2.11.2: resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==} engines: {node: '>=6'} @@ -12736,6 +13280,16 @@ packages: hasBin: true dev: true + /chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} + engines: {node: '>=12'} + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.2 + pathval: 2.0.0 + /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -12771,6 +13325,10 @@ packages: resolution: {integrity: sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==} engines: {node: '>=12.20'} + /check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + /cheerio-select@2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} dependencies: @@ -13473,7 +14031,6 @@ packages: optional: true dependencies: ms: 2.1.3 - dev: false /decamelize-keys@1.1.0: resolution: {integrity: sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==} @@ -13533,6 +14090,10 @@ packages: type-detect: 4.0.8 dev: false + /deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + /deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -13833,6 +14394,10 @@ packages: /electron-to-chromium@1.4.485: resolution: {integrity: sha512-1ndQ5IBNEnFirPwvyud69GHL+31FkE09gH/CJ6m3KCbkx3i0EVOrjwz4UNxRmN9H8OVHbC6vMRZGN1yCvjSs9w==} + /electron-to-chromium@1.5.74: + resolution: {integrity: sha512-ck3//9RC+6oss/1Bh9tiAVFy5vfSKbRHAFh7Z3/eTRkEqJeWgymloShB17Vg3Z4nmDNp35vAd1BZ6CMW4Wt6Iw==} + dev: true + /elliptic@6.5.7: resolution: {integrity: sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==} dependencies: @@ -13985,6 +14550,9 @@ packages: resolution: {integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==} dev: true + /es-module-lexer@1.5.4: + resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + /es-set-tostringtag@2.0.1: resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} engines: {node: '>= 0.4'} @@ -14123,10 +14691,45 @@ packages: '@esbuild/win32-ia32': 0.21.2 '@esbuild/win32-x64': 0.21.2 + /esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} + /escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + dev: true + /escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} @@ -14632,6 +15235,11 @@ packages: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} dev: true + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + dependencies: + '@types/estree': 1.0.5 + /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -14721,6 +15329,10 @@ packages: resolution: {integrity: sha512-yWnriYB4e8G54M5/fAFj7rCIBiKs1HAACaY13kCz6Ku0dezjS9aMcfcdVK2X8Tv2tEV1BPz/wKfQ7WA4S/d8aA==} dev: true + /expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} + /expect@29.6.2: resolution: {integrity: sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -15465,6 +16077,18 @@ packages: path-scurry: 1.10.1 dev: true + /glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + dev: true + /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -16544,23 +17168,40 @@ packages: resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} engines: {node: '>=8'} + /istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + /istanbul-lib-instrument@5.2.1: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} dependencies: '@babel/core': 7.22.9 - '@babel/parser': 7.22.7 + '@babel/parser': 7.26.2 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 semver: 6.3.1 transitivePeerDependencies: - supports-color + /istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + dependencies: + '@babel/core': 7.26.0 + '@babel/parser': 7.26.2 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + dev: true + /istanbul-lib-report@3.0.1: resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} engines: {node: '>=10'} dependencies: - istanbul-lib-coverage: 3.2.0 + istanbul-lib-coverage: 3.2.2 make-dir: 4.0.0 supports-color: 7.2.0 @@ -16574,6 +17215,17 @@ packages: transitivePeerDependencies: - supports-color + /istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.3.4(supports-color@5.5.0) + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + dev: true + /istanbul-reports@3.1.6: resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} engines: {node: '>=8'} @@ -16581,6 +17233,14 @@ packages: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 + /istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + dev: true + /jackspeak@2.2.3: resolution: {integrity: sha512-pF0kfjmg8DJLxDrizHoCZGUFz4P4czQ3HyfW4BU0ffebYkzAVlBywp5zaxW/TM+r0sGbmrQdi8EQQVTJFxnGsQ==} engines: {node: '>=14'} @@ -16590,6 +17250,14 @@ packages: '@pkgjs/parseargs': 0.11.0 dev: true + /jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + dev: true + /jake@10.8.5: resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==} engines: {node: '>=10'} @@ -17403,7 +18071,7 @@ packages: execa: 5.1.1 listr2: 3.14.0(enquirer@2.3.6) log-symbols: 4.1.0 - micromatch: 4.0.5 + micromatch: 4.0.8 normalize-path: 3.0.0 please-upgrade-node: 3.2.0 string-argv: 0.3.1 @@ -17567,11 +18235,18 @@ packages: commander: 9.5.0 dev: true + /loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + /lru-cache@10.0.1: resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==} engines: {node: 14 || >=16.14} dev: true + /lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + dev: true + /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: @@ -17603,6 +18278,11 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + /magic-string@0.30.2: resolution: {integrity: sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==} engines: {node: '>=12'} @@ -17610,6 +18290,14 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + source-map-js: 1.2.0 + dev: true + /make-cancellable-promise@1.3.2: resolution: {integrity: sha512-GCXh3bq/WuMbS+Ky4JBPW1hYTOU+znU+Q5m9Pu+pI8EoUqIHk9+tviOKC6/qhHh8C4/As3tzJ69IF32kdz85ww==} dev: false @@ -17965,6 +18653,11 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dev: true + /minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + dev: true + /minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} @@ -18234,6 +18927,10 @@ packages: /node-releases@2.0.13: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} + /node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + dev: true + /nodemon@3.1.7: resolution: {integrity: sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==} engines: {node: '>=10'} @@ -18599,6 +19296,10 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + /package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + dev: true + /pako@0.2.9: resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} dev: true @@ -18717,6 +19418,14 @@ packages: minipass: 7.0.3 dev: true + /path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + dev: true + /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} @@ -18750,6 +19459,13 @@ packages: resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} dev: true + /pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + /pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + /pbkdf2@3.1.2: resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} engines: {node: '>=0.12'} @@ -18818,6 +19534,9 @@ packages: /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + /picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -18974,6 +19693,14 @@ packages: picocolors: 1.0.0 source-map-js: 1.2.0 + /postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.1.1 + source-map-js: 1.2.1 + /prebuild-install@7.1.1: resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} engines: {node: '>=10'} @@ -20267,6 +20994,34 @@ packages: fsevents: 2.3.3 dev: true + /rollup@4.28.1: + resolution: {integrity: sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.28.1 + '@rollup/rollup-android-arm64': 4.28.1 + '@rollup/rollup-darwin-arm64': 4.28.1 + '@rollup/rollup-darwin-x64': 4.28.1 + '@rollup/rollup-freebsd-arm64': 4.28.1 + '@rollup/rollup-freebsd-x64': 4.28.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.28.1 + '@rollup/rollup-linux-arm-musleabihf': 4.28.1 + '@rollup/rollup-linux-arm64-gnu': 4.28.1 + '@rollup/rollup-linux-arm64-musl': 4.28.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.28.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.28.1 + '@rollup/rollup-linux-riscv64-gnu': 4.28.1 + '@rollup/rollup-linux-s390x-gnu': 4.28.1 + '@rollup/rollup-linux-x64-gnu': 4.28.1 + '@rollup/rollup-linux-x64-musl': 4.28.1 + '@rollup/rollup-win32-arm64-msvc': 4.28.1 + '@rollup/rollup-win32-ia32-msvc': 4.28.1 + '@rollup/rollup-win32-x64-msvc': 4.28.1 + fsevents: 2.3.3 + /rope-sequence@1.3.4: resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} dev: false @@ -20542,6 +21297,9 @@ packages: object-inspect: 1.13.2 dev: false + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -20677,6 +21435,10 @@ packages: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} + /source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + /source-map-resolve@0.5.3: resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} deprecated: See https://github.com/lydell/source-map-resolve#deprecated @@ -20772,6 +21534,9 @@ packages: dependencies: escape-string-regexp: 2.0.0 + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + /standardized-audio-context@25.3.63: resolution: {integrity: sha512-6hB0GbZOx3Q0npxg1QMZTN/jioom3jMkVcr3EqZbwywytqykevtuCgZuaUJqntTzv7q2mEW9KsCUtFCpbWpCkw==} dependencies: @@ -20792,6 +21557,9 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} + /std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + /store2@2.14.2: resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==} dev: true @@ -21344,6 +22112,15 @@ packages: glob: 7.2.3 minimatch: 3.1.2 + /test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + dev: true + /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -21382,6 +22159,24 @@ packages: /tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + /tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + /tinyexec@0.3.1: + resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} + + /tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + + /tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + /tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + /tippy.js@6.3.7: resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} dependencies: @@ -21897,6 +22692,17 @@ packages: escalade: 3.1.1 picocolors: 1.0.0 + /update-browserslist-db@1.1.1(browserslist@4.24.3): + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.24.3 + escalade: 3.2.0 + picocolors: 1.1.1 + dev: true + /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: @@ -22062,7 +22868,7 @@ packages: resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} engines: {node: '>=10.12.0'} dependencies: - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.25 '@types/istanbul-lib-coverage': 2.0.4 convert-source-map: 1.9.0 @@ -22144,6 +22950,27 @@ packages: teex: 1.0.1 dev: false + /vite-node@2.1.8(@types/node@20.16.0): + resolution: {integrity: sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.7 + es-module-lexer: 1.5.4 + pathe: 1.1.2 + vite: 5.4.11(@types/node@20.16.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + /vite@4.5.2(@types/node@20.16.0): resolution: {integrity: sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==} engines: {node: ^14.18.0 || >=16.0.0} @@ -22180,6 +23007,101 @@ packages: fsevents: 2.3.3 dev: true + /vite@5.4.11(@types/node@20.16.0): + resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.16.0 + esbuild: 0.21.5 + postcss: 8.4.49 + rollup: 4.28.1 + optionalDependencies: + fsevents: 2.3.3 + + /vitest@2.1.8(@types/node@20.16.0): + resolution: {integrity: sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.8 + '@vitest/ui': 2.1.8 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/node': 20.16.0 + '@vitest/expect': 2.1.8 + '@vitest/mocker': 2.1.8(vite@5.4.11) + '@vitest/pretty-format': 2.1.8 + '@vitest/runner': 2.1.8 + '@vitest/snapshot': 2.1.8 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 + debug: 4.3.7 + expect-type: 1.1.0 + magic-string: 0.30.17 + pathe: 1.1.2 + std-env: 3.8.0 + tinybench: 2.9.0 + tinyexec: 0.3.1 + tinypool: 1.0.2 + tinyrainbow: 1.2.0 + vite: 5.4.11(@types/node@20.16.0) + vite-node: 2.1.8(@types/node@20.16.0) + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + /vm-browserify@1.1.2: resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} dev: false @@ -22413,6 +23335,14 @@ packages: dependencies: isexe: 2.0.0 + /why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + /wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} dependencies: diff --git a/vxsuite.code-workspace b/vxsuite.code-workspace index 081f6a2013..2897cec62e 100644 --- a/vxsuite.code-workspace +++ b/vxsuite.code-workspace @@ -192,6 +192,10 @@ "name": "libs/test-utils", "path": "libs/test-utils" }, + { + "name": "libs/test-utils-vitest", + "path": "libs/test-utils-vitest" + }, { "name": "libs/types", "path": "libs/types"