In-source unit tests (#429)

Co-authored-by: Konsti Wohlwend <n2d4xc@gmail.com>
This commit is contained in:
CactusBlue 2025-02-14 11:47:52 -08:00 committed by GitHub
parent 1c1bc42b94
commit 816d64c850
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 235 additions and 27 deletions

View File

@ -8,7 +8,8 @@
"streetsidesoftware.code-spell-checker",
"YoavBls.pretty-ts-errors",
"mxsdev.typescript-explorer",
"github.vscode-github-actions"
"github.vscode-github-actions",
"fabiospampinato.vscode-highlight"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [

41
.vscode/settings.json vendored
View File

@ -103,5 +103,44 @@
"**/start-server.js",
"**/turbo/**"
],
"files.insertFinalNewline": true
"files.insertFinalNewline": true,
"highlight.regexes": {
"(import\\.meta\\.vitest\\?\\.test\\()[\"'`]([^\"'`]*)(.*)": [
{
"isWholeLine": true,
"before": {
"contentText": "test>  ",
"color": "#008080",
"fontStyle": "italic"
},
}
],
"((?<=(?:import\\.meta\\.vitest\\?\\.test\\(.*\\n)(?:(?!(\\n\\}?\\);))[\\s\\S])*))\\n": [
{
"isWholeLine": true,
"before": {
"contentText": "       ",
"color": "#008080",
"fontStyle": "italic"
},
},
],
"(import\\.meta\\.vitest\\?\\.test\\([\\s\\S]*?(\\n\\}?\\);))": [
{},
{
"isWholeLine": true,
"before": {
"contentText": "       ",
"color": "#008080",
"fontStyle": "italic"
},
},
],
// disable the default TODO highlighting
"((?:<!-- *)?(?:#|// @|//|./\\*+|<!--|--|\\* @|{!|{{!--|{{!) *TODO(?:\\s*\\([^)]+\\))?:?)((?!\\w)(?: *-->| *\\*/| *!}| *--}}| *}}|(?= *(?:[^:]//|/\\*+|<!--|@|--|{!|{{!--|{{!))|(?: +[^\\n@]*?)(?= *(?:[^:]//|/\\*+|<!--|@|--(?!>)|{!|{{!--|{{!))|(?: +[^@\\n]+)?))": [],
"((?:<!-- *)?(?:#|// @|//|./\\*+|<!--|--|\\* @|{!|{{!--|{{!) *(?:FIXME|FIX|BUG|UGLY|DEBUG|HACK)(?:\\s*\\([^)]+\\))?:?)((?!\\w)(?: *-->| *\\*/| *!}| *--}}| *}}|(?= *(?:[^:]//|/\\*+|<!--|@|--|{!|{{!--|{{!))|(?: +[^\\n@]*?)(?= *(?:[^:]//|/\\*+|<!--|@|--(?!>)|{!|{{!--|{{!))|(?: +[^@\\n]+)?))": [],
"((?:<!-- *)?(?:#|// @|//|./\\*+|<!--|--|\\* @|{!|{{!--|{{!) *(?:REVIEW|OPTIMIZE|TSC)(?:\\s*\\([^)]+\\))?:?)((?!\\w)(?: *-->| *\\*/| *!}| *--}}| *}}|(?= *(?:[^:]//|/\\*+|<!--|@|--|{!|{{!--|{{!))|(?: +[^\\n@]*?)(?= *(?:[^:]//|/\\*+|<!--|@|--(?!>)|{!|{{!--|{{!))|(?: +[^@\\n]+)?))": [],
"((?:<!-- *)?(?:#|// @|//|./\\*+|<!--|--|\\* @|{!|{{!--|{{!) *(?:IDEA)(?:\\s*\\([^)]+\\))?:?)((?!\\w)(?: *-->| *\\*/| *!}| *--}}| *}}|(?= *(?:[^:]//|/\\*+|<!--|@|--|{!|{{!--|{{!))|(?: +[^\\n@]*?)(?= *(?:[^:]//|/\\*+|<!--|@|--(?!>)|{!|{{!--|{{!))|(?: +[^@\\n]+)?))": [],
}
}

View File

@ -27,7 +27,10 @@
"./src/*"
]
},
"skipLibCheck": true
"skipLibCheck": true,
"types": [
"vitest/importMeta"
]
},
"include": [
"next-env.d.ts",

View File

@ -0,0 +1,7 @@
import { defineConfig, mergeConfig } from 'vitest/config'
import sharedConfig from '../../vitest.shared'
export default mergeConfig(
sharedConfig,
defineConfig({}),
)

View File

@ -27,7 +27,10 @@
"./src/*"
]
},
"skipLibCheck": true
"skipLibCheck": true,
"types": [
"vitest/importMeta"
]
},
"include": [
"next-env.d.ts",

View File

@ -0,0 +1,7 @@
import { defineConfig, mergeConfig } from 'vitest/config'
import sharedConfig from '../../vitest.shared'
export default mergeConfig(
sharedConfig,
defineConfig({}),
)

View File

@ -10,7 +10,10 @@
"esModuleInterop": true,
"noErrorTruncation": true,
"strict": true,
"skipLibCheck": true
"skipLibCheck": true,
"types": [
"vitest/importMeta"
]
},
"include": ["tests/**/*"],
"exclude": ["node_modules", "dist"]

View File

@ -1,15 +1,19 @@
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vitest/config'
import { defineConfig, mergeConfig } from 'vitest/config'
import sharedConfig from '../../vitest.shared'
export default defineConfig({
plugins: [react()],
test: {
environment: 'node',
testTimeout: 20_000,
globalSetup: './tests/global-setup.ts',
setupFiles: [
"./tests/setup.ts",
],
snapshotSerializers: ["./tests/snapshot-serializer.ts"],
},
})
export default mergeConfig(
sharedConfig,
defineConfig({
plugins: [react()],
test: {
environment: 'node',
testTimeout: 20_000,
globalSetup: './tests/global-setup.ts',
setupFiles: [
"./tests/setup.ts",
],
snapshotSerializers: ["./tests/snapshot-serializer.ts"],
},
}),
)

View File

@ -0,0 +1,7 @@
import { defineConfig, mergeConfig } from 'vitest/config'
import sharedConfig from '../../vitest.shared'
export default mergeConfig(
sharedConfig,
defineConfig({}),
)

View File

@ -39,6 +39,8 @@
"peek": "pnpm pre && pnpm release --peek",
"changeset": "pnpm pre && changeset",
"test": "pnpm pre && vitest",
"test:e2e": "pnpm pre && vitest e2e/tests",
"test:unit": "pnpm pre && vitest src",
"verify-data-integrity": "pnpm pre && pnpm -C apps/backend run verify-data-integrity",
"generate-docs": "pnpm pre && turbo run generate-docs",
"generate-keys": "pnpm pre && turbo run generate-keys",

View File

@ -0,0 +1,7 @@
import { defineConfig, mergeConfig } from 'vitest/config'
import sharedConfig from '../../vitest.shared'
export default mergeConfig(
sharedConfig,
defineConfig({}),
)

View File

@ -11,7 +11,10 @@
"esModuleInterop": true,
"noErrorTruncation": true,
"strict": true,
"skipLibCheck": true
"skipLibCheck": true,
"types": [
"vitest/importMeta"
]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]

View File

@ -0,0 +1,7 @@
import { defineConfig, mergeConfig } from 'vitest/config'
import sharedConfig from '../../vitest.shared'
export default mergeConfig(
sharedConfig,
defineConfig({}),
)

View File

@ -11,7 +11,10 @@
"esModuleInterop": true,
"noErrorTruncation": true,
"skipLibCheck": true,
"strict": true
"strict": true,
"types": [
"vitest/importMeta"
]
},
"include": ["src/**/*", "next-env.d.ts"],
"exclude": ["node_modules", "dist"]

View File

@ -0,0 +1,7 @@
import { defineConfig, mergeConfig } from 'vitest/config'
import sharedConfig from '../../vitest.shared'
export default mergeConfig(
sharedConfig,
defineConfig({}),
)

View File

@ -7,6 +7,7 @@
"scripts": {
"build": "tsc",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"clean": "rimraf dist && rimraf node_modules",
"dev": "tsc -w --preserveWatchOutput --declarationMap",
"lint": "eslint --ext .tsx,.ts ."

View File

@ -0,0 +1,33 @@
import { describe, expect, it } from "vitest";
import { templateIdentity } from "./strings";
describe("templateIdentity", () => {
it("should be equivalent to a regular template string", () => {
const adjective = "scientific";
const noun = "railgun";
expect(templateIdentity`a certain scientific railgun`).toBe("a certain scientific railgun");
expect(templateIdentity`a certain ${adjective} railgun`).toBe(`a certain scientific railgun`);
expect(templateIdentity`a certain ${adjective} ${noun}`).toBe(`a certain scientific railgun`);
expect(templateIdentity`${adjective}${noun}`).toBe(`scientificrailgun`);
});
it("should work with empty strings", () => {
expect(templateIdentity``).toBe("");
expect(templateIdentity`${""}`).toBe("");
expect(templateIdentity`${""}${""}`).toBe("");
});
it("should work with normal arrays", () => {
expect(templateIdentity(
["a ", " scientific ", "gun"],
"certain", "rail")
).toBe("a certain scientific railgun");
expect(templateIdentity(["a"])).toBe("a");
});
it("should throw an error with wrong number of value arguments", () => {
expect(() => templateIdentity([])).toThrow();
expect(() => templateIdentity(["a", "b"])).toThrow();
expect(() => templateIdentity(["a", "b", "c"], "a", "b", "c")).toThrow();
});
});

View File

@ -70,6 +70,19 @@ export function trimEmptyLinesEnd(s: string): string {
export function trimLines(s: string): string {
return trimEmptyLinesEnd(trimEmptyLinesStart(s));
}
import.meta.vitest?.test("trimLines", ({ expect }) => {
expect(trimLines("")).toBe("");
expect(trimLines(" ")).toBe("");
expect(trimLines(" \n ")).toBe("");
expect(trimLines(" abc ")).toBe(" abc ");
expect(trimLines("\n \nLine1\nLine2\n \n")).toBe("Line1\nLine2");
expect(trimLines("Line1\n \nLine2")).toBe("Line1\n \nLine2");
expect(trimLines(" \n \n\t")).toBe("");
expect(trimLines(" Hello World")).toBe(" Hello World");
expect(trimLines("\n")).toBe("");
expect(trimLines("\t \n\t\tLine1 \n \nLine2\t\t\n\t ")).toBe("\t\tLine1 \n \nLine2\t\t");
});
/**
* A template literal tag that returns the same string as the template literal without a tag.
@ -77,8 +90,26 @@ export function trimLines(s: string): string {
* Useful for implementing your own template literal tags.
*/
export function templateIdentity(strings: TemplateStringsArray | readonly string[], ...values: string[]): string {
if (values.length !== strings.length - 1) throw new StackAssertionError("Invalid number of values; must be one less than strings", { strings, values });
return strings.reduce((result, str, i) => result + str + (values[i] ?? ''), '');
}
import.meta.vitest?.test("templateIdentity", ({ expect }) => {
expect(templateIdentity`Hello World`).toBe("Hello World");
expect(templateIdentity`${"Hello"}`).toBe("Hello");
const greeting = "Hello";
const subject = "World";
expect(templateIdentity`${greeting}, ${subject}!`).toBe("Hello, World!");
expect(templateIdentity`${"A"}${"B"}${"C"}`).toBe("ABC");
expect(templateIdentity`Start${""}Middle${""}End`).toBe("StartMiddleEnd");
expect(templateIdentity``).toBe("");
expect(templateIdentity`Line1
Line2`).toBe("Line1\nLine2");
expect(templateIdentity(["a ", " scientific ", "gun"], "certain", "rail")).toBe("a certain scientific railgun");
expect(templateIdentity(["only one part"])).toBe("only one part");
expect(() => templateIdentity(["a ", "b", "c"], "only one")).toThrow("Invalid number of values");
expect(() => templateIdentity(["a", "b"], "x", "y")).toThrow("Invalid number of values");
});
export function deindent(code: string): string;
@ -86,7 +117,7 @@ export function deindent(strings: TemplateStringsArray | readonly string[], ...v
export function deindent(strings: string | readonly string[], ...values: any[]): string {
if (typeof strings === "string") return deindent([strings]);
if (strings.length === 0) return "";
if (values.length !== strings.length - 1) throw new Error("Invalid number of values; must be one less than strings");
if (values.length !== strings.length - 1) throw new StackAssertionError("Invalid number of values; must be one less than strings", { strings, values });
const trimmedStrings = [...strings];
trimmedStrings[0] = trimEmptyLinesStart(trimmedStrings[0]);

View File

@ -6,12 +6,15 @@
"declaration": true,
"target": "ES2021",
"lib": ["DOM", "DOM.Iterable", "ES2021", "ES2022.Error"],
"module": "ES2015",
"module": "ES2020",
"moduleResolution": "Bundler",
"esModuleInterop": true,
"noErrorTruncation": true,
"strict": true,
"skipLibCheck": true
"skipLibCheck": true,
"types": [
"vitest/importMeta"
]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]

View File

@ -0,0 +1,7 @@
import { defineConfig, mergeConfig } from 'vitest/config'
import sharedConfig from '../../vitest.shared'
export default mergeConfig(
sharedConfig,
defineConfig({}),
)

View File

@ -11,7 +11,10 @@
"esModuleInterop": true,
"noErrorTruncation": true,
"strict": true,
"skipLibCheck": true
"skipLibCheck": true,
"types": [
"vitest/importMeta"
]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]

View File

@ -0,0 +1,7 @@
import { defineConfig, mergeConfig } from 'vitest/config'
import sharedConfig from '../../vitest.shared'
export default mergeConfig(
sharedConfig,
defineConfig({}),
)

View File

@ -13,7 +13,10 @@
"skipLibCheck": true,
"strict": true,
"sourceMap": true,
"declarationMap": true
"declarationMap": true,
"types": [
"vitest/importMeta"
]
},
"include": ["next-env.d.ts", "src/**/*", ".next/types/**/*.ts"],
"exclude": ["node_modules", "dist"]

View File

@ -0,0 +1,7 @@
import { defineConfig, mergeConfig } from 'vitest/config'
import sharedConfig from '../../vitest.shared'
export default mergeConfig(
sharedConfig,
defineConfig({}),
)

8
vitest.shared.ts Normal file
View File

@ -0,0 +1,8 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
include: ['**/*.test.{js,ts,jsx,tsx}'],
includeSource: ['**/*.{js,ts,jsx,tsx}'],
},
})

View File

@ -1,6 +1,8 @@
export default [
import { defineWorkspace } from 'vitest/config';
export default defineWorkspace([
'packages/*',
'apps/*',
'examples/*',
'docs',
];
]);