Skip to content

Commit

Permalink
Create libs/test-utils-vitest (#5703)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jonahkagan authored and eventualbuddha committed Dec 11, 2024
1 parent 01150bc commit 99684b7
Show file tree
Hide file tree
Showing 21 changed files with 3,125 additions and 394 deletions.
24 changes: 24 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,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
Expand Down Expand Up @@ -1471,6 +1494,7 @@ workflows:
- test-libs-printing
- test-libs-res-to-ts
- test-libs-test-utils
- test-libs-test-utils-vitest
- test-libs-types
- test-libs-types-rs
- test-libs-ui
Expand Down
3 changes: 3 additions & 0 deletions libs/test-utils-vitest/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
build
coverage
vitest.config.ts
7 changes: 7 additions & 0 deletions libs/test-utils-vitest/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": ["plugin:vx/recommended"],
"rules": {
// Disable JSDOC rule as code is self-documenting.
"vx/gts-jsdoc": "off"
}
}
Empty file.
46 changes: 46 additions & 0 deletions libs/test-utils-vitest/package.json
Original file line number Diff line number Diff line change
@@ -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": "[email protected]"
}
25 changes: 25 additions & 0 deletions libs/test-utils-vitest/src/advance_timers.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
await waitFor(() => {
// Wait for promises.
});
}

export async function advanceTimersAndPromises(seconds = 0): Promise<void> {
advanceTimers(seconds);
await advancePromises();
}
109 changes: 109 additions & 0 deletions libs/test-utils-vitest/src/child_process.test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
Loading

0 comments on commit 99684b7

Please sign in to comment.