Skip to content

Commit efcc9cc

Browse files
committed
docs: update fuz-stack testing patterns
1 parent cc8af42 commit efcc9cc

3 files changed

Lines changed: 97 additions & 84 deletions

File tree

skills/fuz-stack/references/testing-patterns.md

Lines changed: 94 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ semantic precision:
7474
```typescript
7575
import {test, assert} from 'vitest';
7676

77-
assert.ok(value); // narrows away null/undefined
77+
assert.ok(value); // narrows away null/undefined
7878
assert.strictEqual(a, b);
7979
assert.deepStrictEqual(a, b);
8080
```
@@ -120,16 +120,20 @@ assert.throws(() => fn(), TypeError);
120120
assert.doesNotThrow(() => fn());
121121
```
122122

123-
`assert.throws()` returns `void`. To inspect the error, use try/catch:
123+
`assert.throws()` returns `void`. To inspect the error, place `assert.fail`
124+
**after** the catch block — never inside the try block, where it would be
125+
caught and swallowed:
124126

125127
```typescript
126128
try {
127129
fn();
128-
assert.fail('Expected error');
129-
} catch (e: any) {
130+
} catch (e) {
131+
assert(e instanceof Error);
130132
assert.include(e.message, 'expected substring');
131-
assert.strictEqual(e.code, 'EXPECTED_CODE');
133+
assert.strictEqual((e as any).code, 'EXPECTED_CODE');
134+
return;
132135
}
136+
assert.fail('Expected error');
133137
```
134138

135139
### Async Rejection Testing
@@ -148,8 +152,8 @@ await assert_rejects(
148152
);
149153

150154
// Pattern is optional — returns the Error for further assertions
151-
const err = await assert_rejects(
152-
() => local_repos_load({local_repo_paths: paths, git_ops, npm_ops}),
155+
const err = await assert_rejects(() =>
156+
local_repos_load({local_repo_paths: paths, git_ops, npm_ops}),
153157
);
154158
assert.include(err.message, 'repo-a');
155159
assert.include(err.message, 'repo-b');
@@ -359,28 +363,28 @@ fuz_ui's `test_helpers.ts` also provides generic fixture infrastructure
359363

360364
`{domain}_test_helpers.ts` pattern:
361365

362-
| File | Repo | Purpose |
363-
| ------------------------------------ | -------- | ---------------------------------------- |
364-
| `csp_test_helpers.ts` | fuz_ui | CSP test constants and source factories |
365-
| `contextmenu_test_helpers.ts` | fuz_ui | Contextmenu mounting and attachment setup |
366-
| `module_test_helpers.ts` | fuz_ui | Module analysis test options and program setup |
367-
| `deep_equal_test_helpers.ts` | fuz_util | Bidirectional equality assertions and batch helpers |
368-
| `log_test_helpers.ts` | fuz_util | Logger mock console with captured args |
369-
| `random_test_helpers.ts` | fuz_util | Custom PRNG factories for distribution testing |
370-
| `build_cache_test_helpers.ts` | gro | Build cache mock factories |
371-
| `build_task_test_helpers.ts` | gro | Build task context and mock plugins |
372-
| `deploy_task_test_helpers.ts` | gro | Deploy task context and git mock setup |
373-
| `css_class_extractor_test_helpers.ts`| fuz_css | Extractor assertion helpers |
366+
| File | Repo | Purpose |
367+
| ------------------------------------- | -------- | --------------------------------------------------- |
368+
| `csp_test_helpers.ts` | fuz_ui | CSP test constants and source factories |
369+
| `contextmenu_test_helpers.ts` | fuz_ui | Contextmenu mounting and attachment setup |
370+
| `module_test_helpers.ts` | fuz_ui | Module analysis test options and program setup |
371+
| `deep_equal_test_helpers.ts` | fuz_util | Bidirectional equality assertions and batch helpers |
372+
| `log_test_helpers.ts` | fuz_util | Logger mock console with captured args |
373+
| `random_test_helpers.ts` | fuz_util | Custom PRNG factories for distribution testing |
374+
| `build_cache_test_helpers.ts` | gro | Build cache mock factories |
375+
| `build_task_test_helpers.ts` | gro | Build task context and mock plugins |
376+
| `deploy_task_test_helpers.ts` | gro | Deploy task context and git mock setup |
377+
| `css_class_extractor_test_helpers.ts` | fuz_css | Extractor assertion helpers |
374378

375379
Fixture-specific helpers live inside the fixture directory:
376380

377-
| File | Repo | Purpose |
378-
| --------------------------------------------------------------------- | ------- | ---------------------------- |
379-
| `fixtures/mdz/mdz_test_helpers.ts` | fuz_ui | mdz fixture loading |
380-
| `fixtures/tsdoc/tsdoc_test_helpers.ts` | fuz_ui | tsdoc fixture loading |
381-
| `fixtures/ts/ts_test_helpers.ts` | fuz_ui | TypeScript fixture loading |
382-
| `fixtures/svelte/svelte_test_helpers.ts` | fuz_ui | Svelte fixture loading |
383-
| `fixtures/svelte_preprocess_mdz/svelte_preprocess_mdz_test_helpers.ts`| fuz_ui | Preprocessor fixture loading |
381+
| File | Repo | Purpose |
382+
| ---------------------------------------------------------------------- | ------ | ---------------------------- |
383+
| `fixtures/mdz/mdz_test_helpers.ts` | fuz_ui | mdz fixture loading |
384+
| `fixtures/tsdoc/tsdoc_test_helpers.ts` | fuz_ui | tsdoc fixture loading |
385+
| `fixtures/ts/ts_test_helpers.ts` | fuz_ui | TypeScript fixture loading |
386+
| `fixtures/svelte/svelte_test_helpers.ts` | fuz_ui | Svelte fixture loading |
387+
| `fixtures/svelte_preprocess_mdz/svelte_preprocess_mdz_test_helpers.ts` | fuz_ui | Preprocessor fixture loading |
384388

385389
### Svelte Component Test Helpers
386390

@@ -459,8 +463,8 @@ create_shared_core_tests(
459463
);
460464
```
461465

462-
fuz_ui uses this for contextmenu components with 8 factory modules
463-
(`contextmenu_test_{core,rendering,keyboard,nested,positioning,scoped,edge_cases,link_entries}.ts`).
466+
fuz*ui uses this for contextmenu components with 8 factory modules
467+
(`contextmenu_test*{core,rendering,keyboard,nested,positioning,scoped,edge_cases,link_entries}.ts`).
464468

465469
## Fixture-Based Testing
466470

@@ -619,8 +623,14 @@ See [dependency-injection.md](./dependency-injection.md) for the full pattern.
619623
// src/lib/operations.ts — interfaces for all side effects
620624
// each method uses options objects and returns Result
621625
export interface GitOperations {
622-
current_branch_name: (options?: {cwd?: string}) => Promise<Result<{value: string}, {message: string}>>;
623-
add_and_commit: (options: {files: string | Array<string>; message: string; cwd?: string}) => Promise<Result<object, {message: string}>>;
626+
current_branch_name: (options?: {
627+
cwd?: string;
628+
}) => Promise<Result<{value: string}, {message: string}>>;
629+
add_and_commit: (options: {
630+
files: string | Array<string>;
631+
message: string;
632+
cwd?: string;
633+
}) => Promise<Result<object, {message: string}>>;
624634
// ... ~15 more methods
625635
}
626636
export interface GitopsOperations {
@@ -656,7 +666,7 @@ fuz_gitops uses **zero vi.mock()** — all tests inject mock operations via DI.
656666
import {stub_app_deps} from '$lib/testing/stubs.js';
657667
import {create_mock_runtime} from '$lib/runtime/mock.js';
658668

659-
const deps = stub_app_deps; // throwing stubs for auth deps
669+
const deps = stub_app_deps; // throwing stubs for auth deps
660670
const runtime = create_mock_runtime(); // MockRuntime for CLI tests
661671
```
662672

@@ -719,9 +729,9 @@ describe.skipIf(SKIP)('vite plugin examples', () => {
719729
SKIP_EXAMPLE_TESTS=1 gro test
720730
```
721731

722-
| Flag | Repo | Purpose |
723-
| -------------------- | ------- | ----------------------------------- |
724-
| `SKIP_EXAMPLE_TESTS` | fuz_css | Skip slow Vite plugin integration tests |
732+
| Flag | Repo | Purpose |
733+
| -------------------- | ------- | -------------------------------------------- |
734+
| `SKIP_EXAMPLE_TESTS` | fuz_css | Skip slow Vite plugin integration tests |
725735
| `TEST_DATABASE_URL` | fuz_app | Enable PostgreSQL tests (PGlite always runs) |
726736

727737
## Test Structure
@@ -735,10 +745,13 @@ import {query_create_account} from '$lib/auth/account_queries.js';
735745
describe('account queries', () => {
736746
test('create returns an account with generated uuid', async () => {
737747
const db = get_db();
738-
const account = await query_create_account({db}, {
739-
username: 'alice',
740-
password_hash: 'hash123',
741-
});
748+
const account = await query_create_account(
749+
{db},
750+
{
751+
username: 'alice',
752+
password_hash: 'hash123',
753+
},
754+
);
742755

743756
assert.ok(account.id);
744757
assert.strictEqual(account.username, 'alice');
@@ -831,52 +844,52 @@ Tests with dynamic expected values or extra assertions should stay standalone.
831844

832845
### Composable Test Suites (fuz_app)
833846

834-
| Suite | Groups | Purpose |
835-
| ---------------------------------------------- | ------ | ---------------------------------------- |
836-
| `describe_standard_attack_surface_tests` | 5 | Snapshot, structure, adversarial auth/input/404 |
837-
| `describe_standard_integration_tests` | 10 | Login, cookies, sessions, bearer, passwords |
838-
| `describe_standard_admin_integration_tests` | 7 | Accounts, permits, sessions, audit log |
839-
| `describe_rate_limiting_tests` | 3 | IP, per-account, bearer rate limiting |
840-
| `describe_round_trip_validation` | varies | Schema-driven positive-path validation |
841-
| `describe_data_exposure_tests` | 6 | Schema-level + runtime field blocklists |
842-
| `describe_standard_adversarial_headers` | 7 | Header injection cases |
843-
| `describe_standard_tests` | - | Convenience wrapper: integration + admin |
847+
| Suite | Groups | Purpose |
848+
| ------------------------------------------- | ------ | ----------------------------------------------- |
849+
| `describe_standard_attack_surface_tests` | 5 | Snapshot, structure, adversarial auth/input/404 |
850+
| `describe_standard_integration_tests` | 10 | Login, cookies, sessions, bearer, passwords |
851+
| `describe_standard_admin_integration_tests` | 7 | Accounts, permits, sessions, audit log |
852+
| `describe_rate_limiting_tests` | 3 | IP, per-account, bearer rate limiting |
853+
| `describe_round_trip_validation` | varies | Schema-driven positive-path validation |
854+
| `describe_data_exposure_tests` | 6 | Schema-level + runtime field blocklists |
855+
| `describe_standard_adversarial_headers` | 7 | Header injection cases |
856+
| `describe_standard_tests` | - | Convenience wrapper: integration + admin |
844857

845858
Live in `fuz_app/src/lib/testing/` (library exports, not test files). Accept
846859
configuration with `session_options` and `create_route_specs`.
847860

848861
## Quick Reference
849862

850-
| Pattern | Purpose |
851-
| --------------------------------- | ------------------------------------------------ |
852-
| `src/test/` | All tests live here, not co-located |
853-
| `src/test/domain/` | Mirrors `src/lib/domain/` subdirectories |
854-
| `module.aspect.test.ts` | Split test suites by aspect |
855-
| `module.db.test.ts` | DB test — shared WASM worker via vitest projects |
856-
| `module.fixtures.test.ts` | Fixture-based test file |
857-
| `test_helpers.ts` | General shared test utilities (most repos) |
858-
| `{domain}_test_helpers.ts` | Domain-specific test utilities |
859-
| `{domain}_test_{aspect}.ts` | Shared test factory modules (not test files) |
860-
| `create_shared_*_tests()` | Factory function for reusable test suites |
861-
| `fixtures/feature/case/` | Subdirectory per fixture case |
862-
| `fixtures/update.task.ts` | Parent: runs all child update tasks |
863-
| `fixtures/feature/update.task.ts` | Child: regenerates one feature |
864-
| `assert` from vitest | Ecosystem-wide standard |
865-
| `assert.isDefined(x); x.prop` | Narrows to NonNullable — no `x!` needed |
866-
| `assert(x instanceof T); x.prop` | Narrows union types — the key advantage over `expect` |
867-
| `assert.throws(fn, /regex/)` | Returns void; second arg: constructor/string/RegExp (not function) |
868-
| `assert_rejects(fn, /regex?/)` | Shared — async rejection, optional pattern, returns Error |
869-
| `create_mock_logger()` | Shared — `vi.fn()` methods + tracking arrays |
870-
| try/catch + `assert.include` | For inspecting thrown errors when helper isn't enough |
871-
| `assert_*` (not `expect_*`) | Custom assertion helper naming convention |
872-
| `describe` + `test` (not `it`) | Default structure; 1-2 levels of `describe` typical |
873-
| `// @vitest-environment jsdom` | Pragma for UI tests needing DOM |
874-
| `vi.stubGlobal('ResizeObserver')` | Required in jsdom for components using ResizeObserver |
875-
| `describe_db(name, fn)` | DB test wrapper (fuz_app) |
876-
| `create_test_app()` | Full Hono app for integration tests (fuz_app) |
877-
| `stub_app_deps` | Throwing stub deps for unit tests (fuz_app) |
878-
| DI via `*Operations`/`*Deps` | Preferred over vi.mock() for side effects |
879-
| `create_mock_*()` | Factory functions for test data |
880-
| `SKIP_EXAMPLE_TESTS=1` | Skip slow fuz_css integration tests |
881-
| `TEST_DATABASE_URL` | Enable PostgreSQL tests alongside PGlite |
882-
| Never edit `expected.json` | Always regenerate via task |
863+
| Pattern | Purpose |
864+
| --------------------------------- | ------------------------------------------------------------------ |
865+
| `src/test/` | All tests live here, not co-located |
866+
| `src/test/domain/` | Mirrors `src/lib/domain/` subdirectories |
867+
| `module.aspect.test.ts` | Split test suites by aspect |
868+
| `module.db.test.ts` | DB test — shared WASM worker via vitest projects |
869+
| `module.fixtures.test.ts` | Fixture-based test file |
870+
| `test_helpers.ts` | General shared test utilities (most repos) |
871+
| `{domain}_test_helpers.ts` | Domain-specific test utilities |
872+
| `{domain}_test_{aspect}.ts` | Shared test factory modules (not test files) |
873+
| `create_shared_*_tests()` | Factory function for reusable test suites |
874+
| `fixtures/feature/case/` | Subdirectory per fixture case |
875+
| `fixtures/update.task.ts` | Parent: runs all child update tasks |
876+
| `fixtures/feature/update.task.ts` | Child: regenerates one feature |
877+
| `assert` from vitest | Ecosystem-wide standard |
878+
| `assert.isDefined(x); x.prop` | Narrows to NonNullable — no `x!` needed |
879+
| `assert(x instanceof T); x.prop` | Narrows union types — the key advantage over `expect` |
880+
| `assert.throws(fn, /regex/)` | Returns void; second arg: constructor/string/RegExp (not function) |
881+
| `assert_rejects(fn, /regex?/)` | Shared — async rejection, optional pattern, returns Error |
882+
| `create_mock_logger()` | Shared — `vi.fn()` methods + tracking arrays |
883+
| try/catch + `assert.include` | For inspecting thrown errors when helper isn't enough |
884+
| `assert_*` (not `expect_*`) | Custom assertion helper naming convention |
885+
| `describe` + `test` (not `it`) | Default structure; 1-2 levels of `describe` typical |
886+
| `// @vitest-environment jsdom` | Pragma for UI tests needing DOM |
887+
| `vi.stubGlobal('ResizeObserver')` | Required in jsdom for components using ResizeObserver |
888+
| `describe_db(name, fn)` | DB test wrapper (fuz_app) |
889+
| `create_test_app()` | Full Hono app for integration tests (fuz_app) |
890+
| `stub_app_deps` | Throwing stub deps for unit tests (fuz_app) |
891+
| DI via `*Operations`/`*Deps` | Preferred over vi.mock() for side effects |
892+
| `create_mock_*()` | Factory functions for test data |
893+
| `SKIP_EXAMPLE_TESTS=1` | Skip slow fuz_css integration tests |
894+
| `TEST_DATABASE_URL` | Enable PostgreSQL tests alongside PGlite |
895+
| Never edit `expected.json` | Always regenerate via task |

src/routes/libraries.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50966,7 +50966,7 @@
5096650966
"@fuzdev/fuz_code": "^0.45.1",
5096750967
"@fuzdev/fuz_css": "^0.58.0",
5096850968
"@fuzdev/fuz_ui": "^0.191.4",
50969-
"@fuzdev/fuz_util": "^0.55.0",
50969+
"@fuzdev/fuz_util": "^0.56.0",
5097050970
"@fuzdev/gro": "^0.197.3",
5097150971
"@jridgewell/trace-mapping": "^0.3.31",
5097250972
"@ryanatkn/eslint-config": "^0.10.1",
@@ -55585,7 +55585,7 @@
5558555585
"@fuzdev/fuz_code": "^0.45.1",
5558655586
"@fuzdev/fuz_css": "^0.58.0",
5558755587
"@fuzdev/fuz_ui": "^0.191.4",
55588-
"@fuzdev/fuz_util": "^0.55.0",
55588+
"@fuzdev/fuz_util": "^0.56.0",
5558955589
"@fuzdev/gro": "^0.197.3",
5559055590
"@jridgewell/trace-mapping": "^0.3.31",
5559155591
"@ryanatkn/eslint-config": "^0.10.1",

0 commit comments

Comments
 (0)