[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M71G1ZMNGFD37RZYM620XM2C":3,"skill-same:01M71G1ZMNGFD37RZYM620XM2C":85},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":31,"linkOnly":32,"markdownContent":33,"markdownBody":34,"frontMatter":35,"refName":37,"commitSha":38,"skillDirectory":39,"forksCount":14,"repositoryHtmlUrl":40,"repositoryHomepageUrl":6,"ownerHtmlUrl":41,"ownerBlogUrl":42,"ownerDescription":43,"updatedAt":44,"install":45,"resources":58},"01M71G1ZMNGFD37RZYM620XM2C","typescript-test-standards",null,"TypeScript test standards enforced across all skills. Loaded by other skills, not invoked directly.","outcomeeng\u002Fplugins","outcomeeng","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F257335685?v=4","01M71FRVRF36XYR3HRSCGFHK2R","Outcome Engineering","01M71FRVS4BZG17DWANFJ7EPNY",1,"MIT","https:\u002F\u002Fgithub.com\u002Foutcomeeng\u002Fplugins\u002Fblob\u002Fmain\u002Fsrc\u002Fplugins\u002Ftypescript\u002Fskills\u002Ftypescript-test-standards\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[24,25,26,27,28,29,30],"standards","test","typescript test standards","typescript","false","read","across","2026-11-10 10:12:30",false,"---\nname: typescript-test-standards\nuser-invocable: false\ndescription: >-\n  TypeScript test standards enforced across all skills. Loaded by other skills, not invoked directly.\nallowed-tools: Read\n---\n\n\u003Cobjective>\nThe TypeScript-specific test standards every TypeScript skill enforces.\n\u003C\u002Fobjective>\n\n\u003Crepo_local_overlay>\nWhen another skill loads this reference inside a repository, it must also check for `spx\u002Flocal\u002Ftypescript-tests.md` at the repository root. Read that file after this reference if it exists and apply it as repo-local routing to the product's governing specs and decisions. A local overlay supplements skill behavior; it does not declare product truth.\n\u003C\u002Frepo_local_overlay>\n\n\u003Ccore_model>\nEvery TypeScript test file name encodes three independent axes:\n\n| Axis     | Tokens                                                         | Meaning                                    |\n| -------- | -------------------------------------------------------------- | ------------------------------------------ |\n| Evidence | `scenario`, `mapping`, `conformance`, `property`, `compliance` | What kind of evidence the test provides    |\n| Level    | `l1`, `l2`, `l3`                                               | How painful the test is to run             |\n| Runner   | optional token such as `playwright`                            | Which non-default runner executes the file |\n\nEvidence, level, and runner are orthogonal:\n\n- A Playwright test can be `l2` or `l3`\n- A filesystem test can be `l1` when it uses cheap temp dirs\n- A `scenario` test can run at any level\n- A runner token appears only when the runner is not the default\n\n\u003C\u002Fcore_model>\n\n\u003Cfile_naming>\nUse this canonical TypeScript pattern:\n\n```text\n\u003Csubject>.\u003Cevidence>.\u003Clevel>[.\u003Crunner>].test.ts\n```\n\nExamples:\n\n| Purpose                               | File                                      |\n| ------------------------------------- | ----------------------------------------- |\n| Cheap behavior scenario               | `config-loader.scenario.l1.test.ts`       |\n| Deterministic input-output mapping    | `route-parser.mapping.l1.test.ts`         |\n| Local browser flow through Playwright | `checkout.scenario.l2.playwright.test.ts` |\n| Live webhook contract                 | `stripe-webhook.conformance.l3.test.ts`   |\n| Safety boundary                       | `pii-redaction.compliance.l1.test.ts`     |\n| Generated invariant                   | `slug-roundtrip.property.l1.test.ts`      |\n\nDo not use legacy file suffixes such as `.unit.test.ts`, `.integration.test.ts`, `.e2e.test.ts`, or `.spec.ts` as the signal for evidence, level, or runner.\n\u003C\u002Ffile_naming>\n\n\u003Clevel_tooling>\nChoose the level from execution pain and dependency availability:\n\n| Level | Infrastructure                                     | Default runner | Typical runtime |\n| ----- | -------------------------------------------------- | -------------- | --------------- |\n| `l1`  | Node.js stdlib, temp dirs, repo-required dev tools | Vitest         | milliseconds    |\n| `l2`  | Docker, browsers, dev servers, product binaries    | Vitest         | seconds         |\n| `l3`  | Remote services, credentials, shared environments  | Vitest         | seconds\u002Fminutes |\n\nUse `playwright` as the runner token when Playwright is the non-default runner:\n\n- `browser-menu.scenario.l2.playwright.test.ts` for a local browser flow\n- `production-login.scenario.l3.playwright.test.ts` for a credentialed remote flow\n\nKeep runner configuration aligned with the filename pattern:\n\n```typescript\n\u002F\u002F vitest.config.ts\nimport { defineConfig } from \"vitest\u002Fconfig\";\n\nexport default defineConfig({\n  test: {\n    include: [\"spx\u002F**\u002F*.test.ts\"],\n    exclude: [\"**\u002F*.playwright.test.ts\"],\n  },\n});\n```\n\n```typescript\n\u002F\u002F playwright.config.ts\nimport { defineConfig } from \"@playwright\u002Ftest\";\n\nexport default defineConfig({\n  testDir: \".\u002Fspx\",\n  testMatch: \"**\u002F*.playwright.test.ts\",\n});\n```\n\n\u003C\u002Flevel_tooling>\n\n\u003Crouter_mapping>\nAfter `\u002Ftest` chooses the evidence and level, implement it with these TypeScript patterns:\n\n| Router Decision                            | TypeScript implementation                                  |\n| ------------------------------------------ | ---------------------------------------------------------- |\n| Stage 2 -> `l1`                            | Vitest, test harnesses, typed factories, temp dirs         |\n| Stage 2 -> `l2`                            | Vitest or Playwright with locally available real APIs      |\n| Stage 2 -> `l3`                            | Vitest or Playwright with credentials or remote real APIs  |\n| Stage 3A: pure computation                 | Direct tests of typed pure functions                       |\n| Stage 3B: extract pure part                | Pure function at `l1`, boundary at outer level             |\n| Stage 5 exception 1: failure simulation    | Interface implementation that throws\u002Ferrors                |\n| Stage 5 exception 2: interaction protocols | Spy function or class with typed call recording            |\n| Stage 5 exception 3: time\u002Fconcurrency      | Injected clock or `vi.useFakeTimers()`                     |\n| Stage 5 exception 4: safety                | Function or class that records intent without side effects |\n| Stage 5 exception 5: combinatorial cost    | Configurable fake with real-shaped behavior                |\n| Stage 5 exception 6: observability         | Spy that captures hidden boundary details                  |\n| Stage 5 exception 7: contract probes       | Stub validated against the contract schema                 |\n\n\u003C\u002Frouter_mapping>\n\n\u003Cl1_patterns>\nPure computation and filesystem tests at `l1` use direct function calls, typed factories, and Node.js temp dirs. Read `${CLAUDE_SKILL_DIR}\u002Freferences\u002Fl1-patterns.md` for full examples of each pattern.\n\u003C\u002Fl1_patterns>\n\n\u003Cexception_implementations>\nWhen `\u002Ftest` routes to Stage 5, implement the exception in TypeScript using the pattern from `\u003Crouter_mapping>`. Read `${CLAUDE_SKILL_DIR}\u002Freferences\u002Fexception-implementations.md` for full TypeScript examples of exceptions 1–4 and 6.\n\nException 5 (combinatorial cost: configurable fake with real-shaped behavior) is listed in `\u003Crouter_mapping>` but does not yet have a TypeScript example.\n\u003C\u002Fexception_implementations>\n\n\u003Cl2_patterns>\nUse typed harness factories when tests require real infrastructure (Docker, browsers, product binaries). Verify the binary is available at harness construction time, not inside each test. Throw with an installation hint so the developer knows immediately what is missing. Read `${CLAUDE_SKILL_DIR}\u002Freferences\u002Fl2-patterns.md` for the full harness factory pattern.\n\u003C\u002Fl2_patterns>\n\n\u003Cl3_patterns>\n`l3` tests require real credentials or remote services.\n\n**Credential policy: always fail loudly. Skipping is forbidden.**\n\n`it.skip`, `it.skipIf`, `test.skip`, and any other skip mechanism are forbidden for credentialed tests. A skipped test silently passes the suite while hiding missing evidence. When credentials are absent, the test must throw with a clear diagnostic.\n\n```typescript\n\u002F**\n * l3 tests require these environment variables:\n *\n *   LHCI_SERVER_URL  - LHCI server base URL\n *   LHCI_TOKEN       - build token\n *\n * Where to find: 1Password \"Engineering\u002FTest Credentials\"\n * Setup: cp .env.test.example .env.test and fill in values\n *\u002F\n\nimport { createLhciUploadRequest } from \"@testing\u002Fharnesses\u002Flhci\u002Fupload-request\";\n\ndescribe(\"LHCI\", () => {\n  it(\"uploads audit results to server\", async () => {\n    await expect(uploadAuditResults(createLhciUploadRequest())).resolves.toMatchObject({ success: true });\n  });\n});\n```\n\n\u003C\u002Fl3_patterns>\n\n\u003Cdependency_injection>\nTests verify behavior through real code paths. Avoid framework-level replacement of the dependency under test.\n\nForbidden patterns:\n\n- `vi.mock(...)` replacing the module that should provide evidence\n- `jest.mock(...)` replacing the module that should provide evidence\n- `vi.spyOn(...).mockReturnValue(...)` replacing behavior that the test claims to verify\n\nAllowed doubles are explicit objects or classes passed through dependency injection and mapped to a `\u002Ftest` Stage 5 exception, see `\u003Crouter_mapping>` above. Define controlled implementations and recording collaborators in `@testing\u002Fharnesses\u002F*`; the linked test imports their observations and owns the assertion.\n\n```typescript\nimport { createPaymentRecordingContext } from \"@testing\u002Fharnesses\u002Fpayments\";\n\ntest(\"records charge requests\", async () => {\n  const { processor, request, recordingGateway } = createPaymentRecordingContext(PaymentProcessor);\n\n  await processor.charge(request);\n\n  expect(recordingGateway.recordedCharges).toContainEqual(request);\n});\n```\n\n\u003C\u002Fdependency_injection>\n\n\u003Cpredicate_and_oracle_litmus>\n\nApply every question in `\u002Ftest-evidence-standards` `\u003Ccommon_litmus_questions>`, every per-assertion-type source-and-oracle rule in its `\u003Cassertion_type_litmus>`, every mutation in its `\u003Cmutation_litmus>`, and every field of its `\u003Cassertion_design_record>`. That shared set is the complete list; the items below render the ones whose form is TypeScript-specific and never replace or bound it.\n\n- Invert the `expect` matcher. Only the linked test changes; no harness or collaborator code changes.\n- Read the test callback alone. Every pass\u002Ffail predicate is visible there.\n- Trace each case to the spec scenario, complete source-owned mapping, `fast-check` arbitrary, external conformance oracle, governing compliance rule, or inert whole-payload fixture.\n- Trace each expected result to an oracle outside the production table, algorithm, parser, branch logic, or collaborator verdict method under test.\n- Mutate the assertion-relevant production behavior. The test fails.\n- Read each harness and factory. It returns observations, state, or handles — never a verdict, and never a `*Succeeds`, `isValid`, or `wasCalledWith` method.\n- Read a failure message. It reports actual against expected at the `expect` site, not `expect(helper()).toBe(true)`.\n- Ask whether the same harness could serve a test claiming the opposite about the same observation. It can when the predicate is test-owned.\n\n\u003C\u002Fpredicate_and_oracle_litmus>\n\n\u003Cproperty_based_testing>\nProperty assertions about parsers, serializers, mathematical operations, or invariant-preserving algorithms require `fast-check` and a meaningful property.\n\n| Code type               | Required property        | Pattern                                            |\n| ----------------------- | ------------------------ | -------------------------------------------------- |\n| Parsers                 | `parse(format(x)) == x`  | `assertProperty(parserRoundtripProperty())`        |\n| Serialization           | `decode(encode(x)) == x` | `assertProperty(serializationRoundtripProperty())` |\n| Mathematical operations | algebraic laws           | `assertProperty(algebraicLawProperty())`           |\n| Complex algorithms      | invariant preservation   | `assertProperty(invariantPreservationProperty())`  |\n\nAn assertion that only checks \"does not throw\" is insufficient. The property must fail when the requirement is broken.\nRoute property assertions through a harness or wrapper that owns seed selection, run count, and replay diagnostics; failure output must include the seed and replay path.\n\u003C\u002Fproperty_based_testing>\n\n\u003Ctest_data_policy>\n\u003Cexisting_code_stance>\nWhen writing tests for existing code, evaluate whether the source contract needs to change before writing around it. Maintainable evidence may require architecture improvements first: extract pure functions, inject side-effect dependencies, expose source-owned registries, split command boundaries from domain logic, or add typed constructors that make the behavior observable without copying internals.\n\nIf a test can only be written by copying source literals, pinning arbitrary example objects, mocking the behavior under test, or storing inert fixture strings, stop and improve the source contract first.\n\u003C\u002Fexisting_code_stance>\n\u003Cdata_ownership_decision>\nUse this decision table for every assertion in the spec file. Every test file can only cover assertions of the same assertion type: mapping goes in one file, compliance goes in another file. See `\u003Ccore_model>` above.\n\nExecuted TypeScript test functions and callbacks own every predicate and assertion API call. A `const`, `let`, `var`, framework fixture parameter, or property-generated parameter may receive an actual result, source-owned contract, generated value, harness observation, callback input, resource handle, or fixture path when it introduces no data or policy. Bindings that choose cases, expected results, configuration, setup policy, generator domains, fixture contents, or verdict rules belong in their semantic owner.\n\n1. **Data that the source imports or should import**\n   ALWAYS verify that the code under test imports routes, selectors, ids, feature flags, registry names, and all other public constants from the module that owns them.\n\nALWAYS verify that the code under test imports standard values like HTTP status codes from the canonical source of the runtime (Node) or framework (e.g., React or Next.js).\n\nReject test-local constants that rename runtime-owned values. Accept production-owned domain constants that add source-owned meaning.\n\n2. **Data that the code under test owns or should own**\n\nMost code under test Claude encounters will hardcode the same numbers and string literals several times in the source code.\n\nThis means the code is not testable in a maintainable way because any change to the source file will invalidate the test and lead to churn and extra work.\n\nALWAYS refactor source-owned domain constants, protocol tokens, command names, status values, rule ids, message ids, option labels, and other closed vocabulary into a semantically named registry, tuple, constructor, schema, or other suitable data structure.\n\nUse a source-owned registry or tuple for status tokens, command names, rule ids, message ids, option labels, and other closed vocabulary. Derive unions, schemas, and arrays from that one declaration.\n\nALWAYS make sure that these data structures reflect semantically what they represent.\n\nReject test-owned copies of source vocabulary.\n\n3. **Data that only the test needs**\n\nTests may need representative input domains that production code does not own. Those domains are still not a license to create shared constant bags.\n\nVariable domains move to generators; runner settings, seed policy, and replay diagnostics move to harnesses. Curated eval cases may live in eval case data when generating a JSONL case set would be wasteful and not tractable.\n\n\u003C\u002Fdata_ownership_decision>\n\n\u003Cvalid_test_data_sources>\n\n\u003Csource_imports>\n\nImport source-owned values and source-owned singleton constructors directly from the module that owns them. Do not wrap a source-owned singleton in `fc.constant(...)` or a generator merely to satisfy a \"generator\" rule.\n\nThere are no valid shared test-owned constant bags for protocol values, domain values, expected outputs, or edge-case sets. A value that belongs to production is imported from production. A value that varies is generated. A real payload whose whole shape matters is read as an inert fixture. Modules that collect \"typical\" or \"edge\" examples are literal laundering.\n\nReject test-owned protocol literals and shared constant bags:\n\n```typescript\n\u002F\u002F REJECTED: the test copies a source protocol shape\nconst ABSENT_CONFIG_READ = { kind: \"absent\" } as const;\n\nexpect(isAbsentConfigReadResult(ABSENT_CONFIG_READ)).toBe(true);\n```\n\n```typescript\n\u002F\u002F REJECTED: shared example bags preserve hand-picked values\nexport const EXAMPLE_SOURCE_PATHS = [\"src\u002Findex.ts\", \"src\u002Fconfig.ts\"];\nexport const BOUNDARY_SOURCE_PATHS = [\"\", \"..\u002Foutside.ts\"];\n```\n\nUse the source-owned constructor directly:\n\n```typescript\nimport { createAbsentConfigReadResult, isAbsentConfigReadResult } from \"@\u002Fconfig\u002Fread-result\";\n\nexpect(isAbsentConfigReadResult(createAbsentConfigReadResult())).toBe(true);\n```\n\nValid direct imports include:\n\n- Public registries, command tokens, status values, rule names, and message ids\n- Typed constructors or factory functions that production code owns\n- External standard values from the runtime, framework, or protocol package\n\n\u003C\u002Fsource_imports>\n\n\u003Cgenerators>\n\nUse generators for input domains that vary, compose, shrink, or explore more than one meaningful value. A generator is a pure function - it emits values, holds no state, and has no side effects. Use fast-check for randomized scalars and `fc.Arbitrary` for structured domain values.\n\n```typescript\n\u002F\u002F testing\u002Fgenerators\u002F{domain}.ts\nexport function arbitraryNodePath(config: Config): fc.Arbitrary\u003Cstring>;\nexport function arbitraryDecisionPath(config: Config): fc.Arbitrary\u003Cstring>;\nexport function arbitraryProductTree(config: Config): fc.Arbitrary\u003CProductTreeFixture>;\n```\n\nUse `arbitrary*()` generator functions for tests that should search a domain with `assertProperty`. Use `createGenerated*()` functions only as single-sample wrappers around the same arbitrary when a full property loop would make local infrastructure evidence too expensive.\n\nReject generators that only rename constants:\n\n```typescript\n\u002F\u002F REJECTED: this hides a singleton behind fast-check\nexport function arbitraryAbsentConfig(): fc.Arbitrary\u003CConfigReadResult> {\n  return fc.constant({ kind: CONFIG_FILE_READ_KIND.ABSENT });\n}\n```\n\nUse the source-owned constructor directly instead:\n\n```typescript\nimport { createAbsentConfigReadResult } from \"@\u002Fconfig\u002Fread-result\";\n```\n\nA constant branch inside a larger arbitrary is valid only when it expands boundary coverage and source-owned values still come from source APIs:\n\n```typescript\nimport { createEndOfInputToken, createIdentifierToken, type Token } from \"@\u002Flexer\u002Ftokens\";\n\nexport function arbitraryToken(): fc.Arbitrary\u003CToken> {\n  return fc.oneof(\n    fc.string({ minLength: 1 }).map(createIdentifierToken),\n    fc.constant(createEndOfInputToken()),\n  );\n}\n```\n\n\u003C\u002Fgenerators>\n\n\u003Charnesses>\n\nUse harnesses for tests that interact with external systems — filesystems, browsers, APIs, Docker. A harness manages setup and teardown of the external resource; it is not self-contained.\n\n```typescript\n\u002F\u002F testing\u002Fharnesses\u002F{domain}.ts\n\n\u002F\u002F Filesystem harness: manages a temp product directory for product-tree operations\nexport async function withTestEnv(\n  config: Config,\n  callback: (env: ProductTreeEnv) => Promise\u003Cvoid>,\n): Promise\u003Cvoid>;\n\n\u002F\u002F Browser harness: wraps Playwright with page lifecycle management\nexport function withPlaywright(testFn: BasePlaywrightTest): PlaywrightHarnessTest {\n  return testFn.extend\u003CPlaywrightHarnessFixtures>({\n    playwrightHarness: async ({ page }, provideFixture) => {\n      await provideFixture(createPlaywrightHarness(page));\n    },\n  });\n}\n\nexport const test = withPlaywright(baseTest);\n```\n\n\u003C\u002Fharnesses>\n\n\u003Cfixtures>\n\nFixtures are inert files. Use them for real-world data the code under test would encounter: a captured JSONL from a chat session, a saved API response payload, a document the parser must handle, a sample TypeScript source file for a linter, or a Product Tree copied into a temp directory.\n\nExecuted tests may read fixtures from disk, copy them into temp projects, or pass their paths to the code or program under test. Executed tests must never import fixture modules, require fixture files, or consume fixture exports. A fixture file can have a `.ts` extension when the test is verifying linters, parsers, pre-commit hooks, or scanners; it remains input data, not a test dependency.\n\nStrings and numbers are never valid fixtures by themselves. A string literal that represents a domain value belongs in the production module or a generator, not a static file or a test-file constant.\n\n\u003C\u002Ffixtures>\n\n- Keep descriptive test titles and assertion diagnostics inline; they are the only valid string literals in a test file.\n- Use aliases such as `@testing\u002F*` for shared test infrastructure\n- Keep `spx\u002F\u003Cnode>\u002Ftests\u002F` limited to typed assertion files; local test-adjacent modules that carry harness, generator, or fixture behavior are rejected\n\n\u003C\u002Fvalid_test_data_sources>\n\n\u003C\u002Ftest_data_policy>\n\n\u003Ctest_infrastructure>\nTest infrastructure (harnesses, generators, inert fixtures) is production code governed by the product's `test-infrastructure` PDR. In TypeScript, it lives in a `testing\u002F` directory at the product root, path-mapped to `@testing\u002F` via `tsconfig.json` and `vitest.config.ts`. The PDR mandates the three category subdirectories: `@testing\u002Fharnesses\u002F*`, `@testing\u002Fgenerators\u002F*`, `@testing\u002Ffixtures\u002F*`. This `testing\u002F` directory is sibling to product code (`src\u002F` or product root), never inside `spx\u002F` and never inside any `tests\u002F` directory. Each test-infrastructure artifact is governed by the naturally placed spec node whose assertions, child specs, or category contract cover the artifact's behavior, policy, lifecycle, or reusable semantics.\n\n```text\ntesting\u002F\n+-- harnesses\u002F\n|   +-- index.ts\n|   +-- hugo.ts          # Hugo build harness\n|   +-- postgres.ts      # PostgreSQL harness\n|   +-- factories.ts     # Harness factories\n+-- generators\u002F\n|   +-- paths.ts         # Variable input domains\n+-- fixtures\u002F\n    +-- sample-rule.ts   # Inert source file read by linter tests\n```\n\nConfigure the `@testing` alias in `tsconfig.json` and `vitest.config.ts`:\n\n```typescript\nimport { createAuditResult } from \"@testing\u002Fharnesses\u002Ffactories\";\nimport { createHugoHarness } from \"@testing\u002Fharnesses\u002Fhugo\";\n```\n\nReusable setup, context management, generated domains, and inert fixtures live under `testing\u002F`. Product Tree `tests\u002F` directories contain typed assertion files only.\n\n\u003C\u002Ftest_infrastructure>\n\n\u003Cscript_testing>\nCommitted `scripts\u002F` entrypoints get thin tests:\n\n- Argument parsing through the repository's canonical parser\n- Dispatch into the imported orchestrator\n- Exit-code mapping and observable terminal output\n\nThe orchestrator carries the main behavioral evidence. Script files should stay small and route to tested modules.\n\u003C\u002Fscript_testing>\n\n\u003Canti_patterns>\nReject or rewrite these patterns:\n\n- Legacy file suffixes: `.unit.test.ts`, `.integration.test.ts`, `.e2e.test.ts`, `.spec.ts`\n- Runner-level collapse: assuming Playwright means `l3`\n- Level-evidence collapse: assuming `scenario` means high-cost execution\n- Framework mocks replacing the dependency under test\n- Property claims implemented only with examples\n- Source-owned values copied into local constants\n- Production modules created only to aggregate values for tests\n- Deep relative imports into stable shared test infrastructure\n- Importing fixture files into executed tests instead of reading or copying inert files\n- Manual argument parsing in script tests when the repo has a canonical parser\n- `it.skip`, `it.skipIf`, and `test.skip` on credentialed evidence -- use a credential harness wrapper such as `withLhciCredentials()` that throws instead\n\nThe cross-file literal-reuse check (check IDs `L3`\u002F`L4`: literal in a test also present in `src\u002F`, or duplicated across test files) is not an ESLint rule — it runs as `spx validation literal` because cross-file analysis doesn't fit ESLint's per-file execution model.\n\n\u003Cplaywright_request_context>\n\nPlaywright's `{ request }` fixture uses its own `APIRequestContext` that does NOT share cookies with the `BrowserContext`. Cookies set via `context.addCookies(...)` do not reach `{ request }`.\n\n```typescript\n\u002F\u002F WRONG: request fixture -- no cookie inheritance\ntest(\"API returns flag-gated payload\", async ({ request }) => {\n  await expect(request.get(\"\u002Fapi\u002Fdata\").then((response) => response.json())).resolves.toContain(FLAGGED_ITEM); \u002F\u002F fails\n});\n\n\u002F\u002F RIGHT: context.request -- shares cookies with browser context\nimport { flagGatedApiPayload } from \"@testing\u002Fharnesses\u002Fbrowser\u002Fapi\";\n\ntest(\"API returns flag-gated payload\", async () => {\n  await expect(flagGatedApiPayload()).resolves.toContain(FLAGGED_ITEM);\n});\n```\n\n`page.request` also shares cookies with the browser context and works when a test already uses a page.\n\n\u003C\u002Fplaywright_request_context>\n\n\u003C\u002Fanti_patterns>\n\n\u003Creference_guides>\n\n| File                                                          | When to read                                                  |\n| ------------------------------------------------------------- | ------------------------------------------------------------- |\n| `${CLAUDE_SKILL_DIR}\u002Freferences\u002Fl1-patterns.md`               | Writing pure function, typed factory, or temp dir tests       |\n| `${CLAUDE_SKILL_DIR}\u002Freferences\u002Fexception-implementations.md` | Implementing a Stage 5 exception from `\u002Ftest`                 |\n| `${CLAUDE_SKILL_DIR}\u002Freferences\u002Fl2-patterns.md`               | Writing tests that require real infrastructure (Docker, etc.) |\n\n\u003C\u002Freference_guides>\n\n\u003Csuccess_criteria>\nTypeScript test guidance follows this standard when:\n\n- `\u002Ftest` determines the assertion type, execution level, and exception path before implementation\n- Test filenames use `\u003Csubject>.\u003Cevidence>.\u003Clevel>[.\u003Crunner>].test.ts`\n- Runner configuration uses explicit runner tokens instead of `.spec.ts`\n- Executed test functions and callbacks own every predicate and assertion API call\n- Test-file bindings introduce no case data, expectation, configuration, setup policy, generator domain, fixture content, or verdict rule\n- Doubles are passed through dependency injection and mapped to a Stage 5 exception\n- Property assertions use meaningful `fast-check` properties through a seed-reporting wrapper\n- Source-owned values come from the owning production module\n- Shared test infrastructure lives under `testing\u002F` as spec-governed harnesses, generators, or inert fixtures\n\n\u003C\u002Fsuccess_criteria>\n","\u003Cobjective>\nThe TypeScript-specific test standards every TypeScript skill enforces.\n\u003C\u002Fobjective>\n\n\u003Crepo_local_overlay>\nWhen another skill loads this reference inside a repository, it must also check for `spx\u002Flocal\u002Ftypescript-tests.md` at the repository root. Read that file after this reference if it exists and apply it as repo-local routing to the product's governing specs and decisions. A local overlay supplements skill behavior; it does not declare product truth.\n\u003C\u002Frepo_local_overlay>\n\n\u003Ccore_model>\nEvery TypeScript test file name encodes three independent axes:\n\n| Axis     | Tokens                                                         | Meaning                                    |\n| -------- | -------------------------------------------------------------- | ------------------------------------------ |\n| Evidence | `scenario`, `mapping`, `conformance`, `property`, `compliance` | What kind of evidence the test provides    |\n| Level    | `l1`, `l2`, `l3`                                               | How painful the test is to run             |\n| Runner   | optional token such as `playwright`                            | Which non-default runner executes the file |\n\nEvidence, level, and runner are orthogonal:\n\n- A Playwright test can be `l2` or `l3`\n- A filesystem test can be `l1` when it uses cheap temp dirs\n- A `scenario` test can run at any level\n- A runner token appears only when the runner is not the default\n\n\u003C\u002Fcore_model>\n\n\u003Cfile_naming>\nUse this canonical TypeScript pattern:\n\n```text\n\u003Csubject>.\u003Cevidence>.\u003Clevel>[.\u003Crunner>].test.ts\n```\n\nExamples:\n\n| Purpose                               | File                                      |\n| ------------------------------------- | ----------------------------------------- |\n| Cheap behavior scenario               | `config-loader.scenario.l1.test.ts`       |\n| Deterministic input-output mapping    | `route-parser.mapping.l1.test.ts`         |\n| Local browser flow through Playwright | `checkout.scenario.l2.playwright.test.ts` |\n| Live webhook contract                 | `stripe-webhook.conformance.l3.test.ts`   |\n| Safety boundary                       | `pii-redaction.compliance.l1.test.ts`     |\n| Generated invariant                   | `slug-roundtrip.property.l1.test.ts`      |\n\nDo not use legacy file suffixes such as `.unit.test.ts`, `.integration.test.ts`, `.e2e.test.ts`, or `.spec.ts` as the signal for evidence, level, or runner.\n\u003C\u002Ffile_naming>\n\n\u003Clevel_tooling>\nChoose the level from execution pain and dependency availability:\n\n| Level | Infrastructure                                     | Default runner | Typical runtime |\n| ----- | -------------------------------------------------- | -------------- | --------------- |\n| `l1`  | Node.js stdlib, temp dirs, repo-required dev tools | Vitest         | milliseconds    |\n| `l2`  | Docker, browsers, dev servers, product binaries    | Vitest         | seconds         |\n| `l3`  | Remote services, credentials, shared environments  | Vitest         | seconds\u002Fminutes |\n\nUse `playwright` as the runner token when Playwright is the non-default runner:\n\n- `browser-menu.scenario.l2.playwright.test.ts` for a local browser flow\n- `production-login.scenario.l3.playwright.test.ts` for a credentialed remote flow\n\nKeep runner configuration aligned with the filename pattern:\n\n```typescript\n\u002F\u002F vitest.config.ts\nimport { defineConfig } from \"vitest\u002Fconfig\";\n\nexport default defineConfig({\n  test: {\n    include: [\"spx\u002F**\u002F*.test.ts\"],\n    exclude: [\"**\u002F*.playwright.test.ts\"],\n  },\n});\n```\n\n```typescript\n\u002F\u002F playwright.config.ts\nimport { defineConfig } from \"@playwright\u002Ftest\";\n\nexport default defineConfig({\n  testDir: \".\u002Fspx\",\n  testMatch: \"**\u002F*.playwright.test.ts\",\n});\n```\n\n\u003C\u002Flevel_tooling>\n\n\u003Crouter_mapping>\nAfter `\u002Ftest` chooses the evidence and level, implement it with these TypeScript patterns:\n\n| Router Decision                            | TypeScript implementation                                  |\n| ------------------------------------------ | ---------------------------------------------------------- |\n| Stage 2 -> `l1`                            | Vitest, test harnesses, typed factories, temp dirs         |\n| Stage 2 -> `l2`                            | Vitest or Playwright with locally available real APIs      |\n| Stage 2 -> `l3`                            | Vitest or Playwright with credentials or remote real APIs  |\n| Stage 3A: pure computation                 | Direct tests of typed pure functions                       |\n| Stage 3B: extract pure part                | Pure function at `l1`, boundary at outer level             |\n| Stage 5 exception 1: failure simulation    | Interface implementation that throws\u002Ferrors                |\n| Stage 5 exception 2: interaction protocols | Spy function or class with typed call recording            |\n| Stage 5 exception 3: time\u002Fconcurrency      | Injected clock or `vi.useFakeTimers()`                     |\n| Stage 5 exception 4: safety                | Function or class that records intent without side effects |\n| Stage 5 exception 5: combinatorial cost    | Configurable fake with real-shaped behavior                |\n| Stage 5 exception 6: observability         | Spy that captures hidden boundary details                  |\n| Stage 5 exception 7: contract probes       | Stub validated against the contract schema                 |\n\n\u003C\u002Frouter_mapping>\n\n\u003Cl1_patterns>\nPure computation and filesystem tests at `l1` use direct function calls, typed factories, and Node.js temp dirs. Read `${CLAUDE_SKILL_DIR}\u002Freferences\u002Fl1-patterns.md` for full examples of each pattern.\n\u003C\u002Fl1_patterns>\n\n\u003Cexception_implementations>\nWhen `\u002Ftest` routes to Stage 5, implement the exception in TypeScript using the pattern from `\u003Crouter_mapping>`. Read `${CLAUDE_SKILL_DIR}\u002Freferences\u002Fexception-implementations.md` for full TypeScript examples of exceptions 1–4 and 6.\n\nException 5 (combinatorial cost: configurable fake with real-shaped behavior) is listed in `\u003Crouter_mapping>` but does not yet have a TypeScript example.\n\u003C\u002Fexception_implementations>\n\n\u003Cl2_patterns>\nUse typed harness factories when tests require real infrastructure (Docker, browsers, product binaries). Verify the binary is available at harness construction time, not inside each test. Throw with an installation hint so the developer knows immediately what is missing. Read `${CLAUDE_SKILL_DIR}\u002Freferences\u002Fl2-patterns.md` for the full harness factory pattern.\n\u003C\u002Fl2_patterns>\n\n\u003Cl3_patterns>\n`l3` tests require real credentials or remote services.\n\n**Credential policy: always fail loudly. Skipping is forbidden.**\n\n`it.skip`, `it.skipIf`, `test.skip`, and any other skip mechanism are forbidden for credentialed tests. A skipped test silently passes the suite while hiding missing evidence. When credentials are absent, the test must throw with a clear diagnostic.\n\n```typescript\n\u002F**\n * l3 tests require these environment variables:\n *\n *   LHCI_SERVER_URL  - LHCI server base URL\n *   LHCI_TOKEN       - build token\n *\n * Where to find: 1Password \"Engineering\u002FTest Credentials\"\n * Setup: cp .env.test.example .env.test and fill in values\n *\u002F\n\nimport { createLhciUploadRequest } from \"@testing\u002Fharnesses\u002Flhci\u002Fupload-request\";\n\ndescribe(\"LHCI\", () => {\n  it(\"uploads audit results to server\", async () => {\n    await expect(uploadAuditResults(createLhciUploadRequest())).resolves.toMatchObject({ success: true });\n  });\n});\n```\n\n\u003C\u002Fl3_patterns>\n\n\u003Cdependency_injection>\nTests verify behavior through real code paths. Avoid framework-level replacement of the dependency under test.\n\nForbidden patterns:\n\n- `vi.mock(...)` replacing the module that should provide evidence\n- `jest.mock(...)` replacing the module that should provide evidence\n- `vi.spyOn(...).mockReturnValue(...)` replacing behavior that the test claims to verify\n\nAllowed doubles are explicit objects or classes passed through dependency injection and mapped to a `\u002Ftest` Stage 5 exception, see `\u003Crouter_mapping>` above. Define controlled implementations and recording collaborators in `@testing\u002Fharnesses\u002F*`; the linked test imports their observations and owns the assertion.\n\n```typescript\nimport { createPaymentRecordingContext } from \"@testing\u002Fharnesses\u002Fpayments\";\n\ntest(\"records charge requests\", async () => {\n  const { processor, request, recordingGateway } = createPaymentRecordingContext(PaymentProcessor);\n\n  await processor.charge(request);\n\n  expect(recordingGateway.recordedCharges).toContainEqual(request);\n});\n```\n\n\u003C\u002Fdependency_injection>\n\n\u003Cpredicate_and_oracle_litmus>\n\nApply every question in `\u002Ftest-evidence-standards` `\u003Ccommon_litmus_questions>`, every per-assertion-type source-and-oracle rule in its `\u003Cassertion_type_litmus>`, every mutation in its `\u003Cmutation_litmus>`, and every field of its `\u003Cassertion_design_record>`. That shared set is the complete list; the items below render the ones whose form is TypeScript-specific and never replace or bound it.\n\n- Invert the `expect` matcher. Only the linked test changes; no harness or collaborator code changes.\n- Read the test callback alone. Every pass\u002Ffail predicate is visible there.\n- Trace each case to the spec scenario, complete source-owned mapping, `fast-check` arbitrary, external conformance oracle, governing compliance rule, or inert whole-payload fixture.\n- Trace each expected result to an oracle outside the production table, algorithm, parser, branch logic, or collaborator verdict method under test.\n- Mutate the assertion-relevant production behavior. The test fails.\n- Read each harness and factory. It returns observations, state, or handles — never a verdict, and never a `*Succeeds`, `isValid`, or `wasCalledWith` method.\n- Read a failure message. It reports actual against expected at the `expect` site, not `expect(helper()).toBe(true)`.\n- Ask whether the same harness could serve a test claiming the opposite about the same observation. It can when the predicate is test-owned.\n\n\u003C\u002Fpredicate_and_oracle_litmus>\n\n\u003Cproperty_based_testing>\nProperty assertions about parsers, serializers, mathematical operations, or invariant-preserving algorithms require `fast-check` and a meaningful property.\n\n| Code type               | Required property        | Pattern                                            |\n| ----------------------- | ------------------------ | -------------------------------------------------- |\n| Parsers                 | `parse(format(x)) == x`  | `assertProperty(parserRoundtripProperty())`        |\n| Serialization           | `decode(encode(x)) == x` | `assertProperty(serializationRoundtripProperty())` |\n| Mathematical operations | algebraic laws           | `assertProperty(algebraicLawProperty())`           |\n| Complex algorithms      | invariant preservation   | `assertProperty(invariantPreservationProperty())`  |\n\nAn assertion that only checks \"does not throw\" is insufficient. The property must fail when the requirement is broken.\nRoute property assertions through a harness or wrapper that owns seed selection, run count, and replay diagnostics; failure output must include the seed and replay path.\n\u003C\u002Fproperty_based_testing>\n\n\u003Ctest_data_policy>\n\u003Cexisting_code_stance>\nWhen writing tests for existing code, evaluate whether the source contract needs to change before writing around it. Maintainable evidence may require architecture improvements first: extract pure functions, inject side-effect dependencies, expose source-owned registries, split command boundaries from domain logic, or add typed constructors that make the behavior observable without copying internals.\n\nIf a test can only be written by copying source literals, pinning arbitrary example objects, mocking the behavior under test, or storing inert fixture strings, stop and improve the source contract first.\n\u003C\u002Fexisting_code_stance>\n\u003Cdata_ownership_decision>\nUse this decision table for every assertion in the spec file. Every test file can only cover assertions of the same assertion type: mapping goes in one file, compliance goes in another file. See `\u003Ccore_model>` above.\n\nExecuted TypeScript test functions and callbacks own every predicate and assertion API call. A `const`, `let`, `var`, framework fixture parameter, or property-generated parameter may receive an actual result, source-owned contract, generated value, harness observation, callback input, resource handle, or fixture path when it introduces no data or policy. Bindings that choose cases, expected results, configuration, setup policy, generator domains, fixture contents, or verdict rules belong in their semantic owner.\n\n1. **Data that the source imports or should import**\n   ALWAYS verify that the code under test imports routes, selectors, ids, feature flags, registry names, and all other public constants from the module that owns them.\n\nALWAYS verify that the code under test imports standard values like HTTP status codes from the canonical source of the runtime (Node) or framework (e.g., React or Next.js).\n\nReject test-local constants that rename runtime-owned values. Accept production-owned domain constants that add source-owned meaning.\n\n2. **Data that the code under test owns or should own**\n\nMost code under test Claude encounters will hardcode the same numbers and string literals several times in the source code.\n\nThis means the code is not testable in a maintainable way because any change to the source file will invalidate the test and lead to churn and extra work.\n\nALWAYS refactor source-owned domain constants, protocol tokens, command names, status values, rule ids, message ids, option labels, and other closed vocabulary into a semantically named registry, tuple, constructor, schema, or other suitable data structure.\n\nUse a source-owned registry or tuple for status tokens, command names, rule ids, message ids, option labels, and other closed vocabulary. Derive unions, schemas, and arrays from that one declaration.\n\nALWAYS make sure that these data structures reflect semantically what they represent.\n\nReject test-owned copies of source vocabulary.\n\n3. **Data that only the test needs**\n\nTests may need representative input domains that production code does not own. Those domains are still not a license to create shared constant bags.\n\nVariable domains move to generators; runner settings, seed policy, and replay diagnostics move to harnesses. Curated eval cases may live in eval case data when generating a JSONL case set would be wasteful and not tractable.\n\n\u003C\u002Fdata_ownership_decision>\n\n\u003Cvalid_test_data_sources>\n\n\u003Csource_imports>\n\nImport source-owned values and source-owned singleton constructors directly from the module that owns them. Do not wrap a source-owned singleton in `fc.constant(...)` or a generator merely to satisfy a \"generator\" rule.\n\nThere are no valid shared test-owned constant bags for protocol values, domain values, expected outputs, or edge-case sets. A value that belongs to production is imported from production. A value that varies is generated. A real payload whose whole shape matters is read as an inert fixture. Modules that collect \"typical\" or \"edge\" examples are literal laundering.\n\nReject test-owned protocol literals and shared constant bags:\n\n```typescript\n\u002F\u002F REJECTED: the test copies a source protocol shape\nconst ABSENT_CONFIG_READ = { kind: \"absent\" } as const;\n\nexpect(isAbsentConfigReadResult(ABSENT_CONFIG_READ)).toBe(true);\n```\n\n```typescript\n\u002F\u002F REJECTED: shared example bags preserve hand-picked values\nexport const EXAMPLE_SOURCE_PATHS = [\"src\u002Findex.ts\", \"src\u002Fconfig.ts\"];\nexport const BOUNDARY_SOURCE_PATHS = [\"\", \"..\u002Foutside.ts\"];\n```\n\nUse the source-owned constructor directly:\n\n```typescript\nimport { createAbsentConfigReadResult, isAbsentConfigReadResult } from \"@\u002Fconfig\u002Fread-result\";\n\nexpect(isAbsentConfigReadResult(createAbsentConfigReadResult())).toBe(true);\n```\n\nValid direct imports include:\n\n- Public registries, command tokens, status values, rule names, and message ids\n- Typed constructors or factory functions that production code owns\n- External standard values from the runtime, framework, or protocol package\n\n\u003C\u002Fsource_imports>\n\n\u003Cgenerators>\n\nUse generators for input domains that vary, compose, shrink, or explore more than one meaningful value. A generator is a pure function - it emits values, holds no state, and has no side effects. Use fast-check for randomized scalars and `fc.Arbitrary` for structured domain values.\n\n```typescript\n\u002F\u002F testing\u002Fgenerators\u002F{domain}.ts\nexport function arbitraryNodePath(config: Config): fc.Arbitrary\u003Cstring>;\nexport function arbitraryDecisionPath(config: Config): fc.Arbitrary\u003Cstring>;\nexport function arbitraryProductTree(config: Config): fc.Arbitrary\u003CProductTreeFixture>;\n```\n\nUse `arbitrary*()` generator functions for tests that should search a domain with `assertProperty`. Use `createGenerated*()` functions only as single-sample wrappers around the same arbitrary when a full property loop would make local infrastructure evidence too expensive.\n\nReject generators that only rename constants:\n\n```typescript\n\u002F\u002F REJECTED: this hides a singleton behind fast-check\nexport function arbitraryAbsentConfig(): fc.Arbitrary\u003CConfigReadResult> {\n  return fc.constant({ kind: CONFIG_FILE_READ_KIND.ABSENT });\n}\n```\n\nUse the source-owned constructor directly instead:\n\n```typescript\nimport { createAbsentConfigReadResult } from \"@\u002Fconfig\u002Fread-result\";\n```\n\nA constant branch inside a larger arbitrary is valid only when it expands boundary coverage and source-owned values still come from source APIs:\n\n```typescript\nimport { createEndOfInputToken, createIdentifierToken, type Token } from \"@\u002Flexer\u002Ftokens\";\n\nexport function arbitraryToken(): fc.Arbitrary\u003CToken> {\n  return fc.oneof(\n    fc.string({ minLength: 1 }).map(createIdentifierToken),\n    fc.constant(createEndOfInputToken()),\n  );\n}\n```\n\n\u003C\u002Fgenerators>\n\n\u003Charnesses>\n\nUse harnesses for tests that interact with external systems — filesystems, browsers, APIs, Docker. A harness manages setup and teardown of the external resource; it is not self-contained.\n\n```typescript\n\u002F\u002F testing\u002Fharnesses\u002F{domain}.ts\n\n\u002F\u002F Filesystem harness: manages a temp product directory for product-tree operations\nexport async function withTestEnv(\n  config: Config,\n  callback: (env: ProductTreeEnv) => Promise\u003Cvoid>,\n): Promise\u003Cvoid>;\n\n\u002F\u002F Browser harness: wraps Playwright with page lifecycle management\nexport function withPlaywright(testFn: BasePlaywrightTest): PlaywrightHarnessTest {\n  return testFn.extend\u003CPlaywrightHarnessFixtures>({\n    playwrightHarness: async ({ page }, provideFixture) => {\n      await provideFixture(createPlaywrightHarness(page));\n    },\n  });\n}\n\nexport const test = withPlaywright(baseTest);\n```\n\n\u003C\u002Fharnesses>\n\n\u003Cfixtures>\n\nFixtures are inert files. Use them for real-world data the code under test would encounter: a captured JSONL from a chat session, a saved API response payload, a document the parser must handle, a sample TypeScript source file for a linter, or a Product Tree copied into a temp directory.\n\nExecuted tests may read fixtures from disk, copy them into temp projects, or pass their paths to the code or program under test. Executed tests must never import fixture modules, require fixture files, or consume fixture exports. A fixture file can have a `.ts` extension when the test is verifying linters, parsers, pre-commit hooks, or scanners; it remains input data, not a test dependency.\n\nStrings and numbers are never valid fixtures by themselves. A string literal that represents a domain value belongs in the production module or a generator, not a static file or a test-file constant.\n\n\u003C\u002Ffixtures>\n\n- Keep descriptive test titles and assertion diagnostics inline; they are the only valid string literals in a test file.\n- Use aliases such as `@testing\u002F*` for shared test infrastructure\n- Keep `spx\u002F\u003Cnode>\u002Ftests\u002F` limited to typed assertion files; local test-adjacent modules that carry harness, generator, or fixture behavior are rejected\n\n\u003C\u002Fvalid_test_data_sources>\n\n\u003C\u002Ftest_data_policy>\n\n\u003Ctest_infrastructure>\nTest infrastructure (harnesses, generators, inert fixtures) is production code governed by the product's `test-infrastructure` PDR. In TypeScript, it lives in a `testing\u002F` directory at the product root, path-mapped to `@testing\u002F` via `tsconfig.json` and `vitest.config.ts`. The PDR mandates the three category subdirectories: `@testing\u002Fharnesses\u002F*`, `@testing\u002Fgenerators\u002F*`, `@testing\u002Ffixtures\u002F*`. This `testing\u002F` directory is sibling to product code (`src\u002F` or product root), never inside `spx\u002F` and never inside any `tests\u002F` directory. Each test-infrastructure artifact is governed by the naturally placed spec node whose assertions, child specs, or category contract cover the artifact's behavior, policy, lifecycle, or reusable semantics.\n\n```text\ntesting\u002F\n+-- harnesses\u002F\n|   +-- index.ts\n|   +-- hugo.ts          # Hugo build harness\n|   +-- postgres.ts      # PostgreSQL harness\n|   +-- factories.ts     # Harness factories\n+-- generators\u002F\n|   +-- paths.ts         # Variable input domains\n+-- fixtures\u002F\n    +-- sample-rule.ts   # Inert source file read by linter tests\n```\n\nConfigure the `@testing` alias in `tsconfig.json` and `vitest.config.ts`:\n\n```typescript\nimport { createAuditResult } from \"@testing\u002Fharnesses\u002Ffactories\";\nimport { createHugoHarness } from \"@testing\u002Fharnesses\u002Fhugo\";\n```\n\nReusable setup, context management, generated domains, and inert fixtures live under `testing\u002F`. Product Tree `tests\u002F` directories contain typed assertion files only.\n\n\u003C\u002Ftest_infrastructure>\n\n\u003Cscript_testing>\nCommitted `scripts\u002F` entrypoints get thin tests:\n\n- Argument parsing through the repository's canonical parser\n- Dispatch into the imported orchestrator\n- Exit-code mapping and observable terminal output\n\nThe orchestrator carries the main behavioral evidence. Script files should stay small and route to tested modules.\n\u003C\u002Fscript_testing>\n\n\u003Canti_patterns>\nReject or rewrite these patterns:\n\n- Legacy file suffixes: `.unit.test.ts`, `.integration.test.ts`, `.e2e.test.ts`, `.spec.ts`\n- Runner-level collapse: assuming Playwright means `l3`\n- Level-evidence collapse: assuming `scenario` means high-cost execution\n- Framework mocks replacing the dependency under test\n- Property claims implemented only with examples\n- Source-owned values copied into local constants\n- Production modules created only to aggregate values for tests\n- Deep relative imports into stable shared test infrastructure\n- Importing fixture files into executed tests instead of reading or copying inert files\n- Manual argument parsing in script tests when the repo has a canonical parser\n- `it.skip`, `it.skipIf`, and `test.skip` on credentialed evidence -- use a credential harness wrapper such as `withLhciCredentials()` that throws instead\n\nThe cross-file literal-reuse check (check IDs `L3`\u002F`L4`: literal in a test also present in `src\u002F`, or duplicated across test files) is not an ESLint rule — it runs as `spx validation literal` because cross-file analysis doesn't fit ESLint's per-file execution model.\n\n\u003Cplaywright_request_context>\n\nPlaywright's `{ request }` fixture uses its own `APIRequestContext` that does NOT share cookies with the `BrowserContext`. Cookies set via `context.addCookies(...)` do not reach `{ request }`.\n\n```typescript\n\u002F\u002F WRONG: request fixture -- no cookie inheritance\ntest(\"API returns flag-gated payload\", async ({ request }) => {\n  await expect(request.get(\"\u002Fapi\u002Fdata\").then((response) => response.json())).resolves.toContain(FLAGGED_ITEM); \u002F\u002F fails\n});\n\n\u002F\u002F RIGHT: context.request -- shares cookies with browser context\nimport { flagGatedApiPayload } from \"@testing\u002Fharnesses\u002Fbrowser\u002Fapi\";\n\ntest(\"API returns flag-gated payload\", async () => {\n  await expect(flagGatedApiPayload()).resolves.toContain(FLAGGED_ITEM);\n});\n```\n\n`page.request` also shares cookies with the browser context and works when a test already uses a page.\n\n\u003C\u002Fplaywright_request_context>\n\n\u003C\u002Fanti_patterns>\n\n\u003Creference_guides>\n\n| File                                                          | When to read                                                  |\n| ------------------------------------------------------------- | ------------------------------------------------------------- |\n| `${CLAUDE_SKILL_DIR}\u002Freferences\u002Fl1-patterns.md`               | Writing pure function, typed factory, or temp dir tests       |\n| `${CLAUDE_SKILL_DIR}\u002Freferences\u002Fexception-implementations.md` | Implementing a Stage 5 exception from `\u002Ftest`                 |\n| `${CLAUDE_SKILL_DIR}\u002Freferences\u002Fl2-patterns.md`               | Writing tests that require real infrastructure (Docker, etc.) |\n\n\u003C\u002Freference_guides>\n\n\u003Csuccess_criteria>\nTypeScript test guidance follows this standard when:\n\n- `\u002Ftest` determines the assertion type, execution level, and exception path before implementation\n- Test filenames use `\u003Csubject>.\u003Cevidence>.\u003Clevel>[.\u003Crunner>].test.ts`\n- Runner configuration uses explicit runner tokens instead of `.spec.ts`\n- Executed test functions and callbacks own every predicate and assertion API call\n- Test-file bindings introduce no case data, expectation, configuration, setup policy, generator domain, fixture content, or verdict rule\n- Doubles are passed through dependency injection and mapped to a Stage 5 exception\n- Property assertions use meaningful `fast-check` properties through a seed-reporting wrapper\n- Source-owned values come from the owning production module\n- Shared test infrastructure lives under `testing\u002F` as spec-governed harnesses, generators, or inert fixtures\n\n\u003C\u002Fsuccess_criteria>\n",{"name":5,"description":7,"allowed-tools":36,"user-invocable":32},"Read","main","978d51c51d68c99cffb2ba48f7cad9d2f07e5738","src\u002Fplugins\u002Ftypescript\u002Fskills\u002Ftypescript-test-standards","https:\u002F\u002Fgithub.com\u002Foutcomeeng\u002Fplugins","https:\u002F\u002Fgithub.com\u002Foutcomeeng","https:\u002F\u002Foutcome.engineering","Spec-driven development for AI-assisted engineering","2026-11-11 22:17:26",{"source":8,"skillName":5,"commands":46,"prompt":56,"zipAvailable":57},[47,50,53],{"tool":48,"command":49},"npx","npx skills add outcomeeng\u002Fplugins --skill typescript-test-standards",{"tool":51,"command":52},"bunx","bunx skills add outcomeeng\u002Fplugins --skill typescript-test-standards",{"tool":54,"command":55},"pnpm","pnpm dlx skills add outcomeeng\u002Fplugins --skill typescript-test-standards","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Foutcomeeng\u002Fplugins\u002Fblob\u002Fmain\u002Fsrc\u002Fplugins\u002Ftypescript\u002Fskills\u002Ftypescript-test-standards\u002FSKILL.md",true,[59,65,69,73,77,81],{"path":60,"resourceType":61,"resourceTypeDesc":62,"sizeBytes":63,"sourceHtmlUrl":64},"levels\u002Fl1-local-deterministic.md","REFERENCE","参考文档",3752,"https:\u002F\u002Fgithub.com\u002Foutcomeeng\u002Fplugins\u002Fblob\u002Fmain\u002Fsrc\u002Fplugins\u002Ftypescript\u002Fskills\u002Ftypescript-test-standards\u002Flevels\u002Fl1-local-deterministic.md",{"path":66,"resourceType":61,"resourceTypeDesc":62,"sizeBytes":67,"sourceHtmlUrl":68},"levels\u002Fl2-local-infrastructure.md",3140,"https:\u002F\u002Fgithub.com\u002Foutcomeeng\u002Fplugins\u002Fblob\u002Fmain\u002Fsrc\u002Fplugins\u002Ftypescript\u002Fskills\u002Ftypescript-test-standards\u002Flevels\u002Fl2-local-infrastructure.md",{"path":70,"resourceType":61,"resourceTypeDesc":62,"sizeBytes":71,"sourceHtmlUrl":72},"levels\u002Fl3-remote-credentialed.md",2537,"https:\u002F\u002Fgithub.com\u002Foutcomeeng\u002Fplugins\u002Fblob\u002Fmain\u002Fsrc\u002Fplugins\u002Ftypescript\u002Fskills\u002Ftypescript-test-standards\u002Flevels\u002Fl3-remote-credentialed.md",{"path":74,"resourceType":61,"resourceTypeDesc":62,"sizeBytes":75,"sourceHtmlUrl":76},"references\u002Fexception-implementations.md",2852,"https:\u002F\u002Fgithub.com\u002Foutcomeeng\u002Fplugins\u002Fblob\u002Fmain\u002Fsrc\u002Fplugins\u002Ftypescript\u002Fskills\u002Ftypescript-test-standards\u002Freferences\u002Fexception-implementations.md",{"path":78,"resourceType":61,"resourceTypeDesc":62,"sizeBytes":79,"sourceHtmlUrl":80},"references\u002Fl1-patterns.md",1590,"https:\u002F\u002Fgithub.com\u002Foutcomeeng\u002Fplugins\u002Fblob\u002Fmain\u002Fsrc\u002Fplugins\u002Ftypescript\u002Fskills\u002Ftypescript-test-standards\u002Freferences\u002Fl1-patterns.md",{"path":82,"resourceType":61,"resourceTypeDesc":62,"sizeBytes":83,"sourceHtmlUrl":84},"references\u002Fl2-patterns.md",684,"https:\u002F\u002Fgithub.com\u002Foutcomeeng\u002Fplugins\u002Fblob\u002Fmain\u002Fsrc\u002Fplugins\u002Ftypescript\u002Fskills\u002Ftypescript-test-standards\u002Freferences\u002Fl2-patterns.md",[86,92,127,143,156,170],{"skillNo":87,"name":88,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":89,"categories":90,"keywords":91,"publishedAt":31},"01M71G25RZTPQG9KJ00JCHC1F7","plugin","https:\u002F\u002Fgithub.com\u002Foutcomeeng\u002Fplugins\u002Fblob\u002Fmain\u002Fsrc\u002Ftemplates\u002Fplugin\u002FSKILL.md",[],[88],{"skillNo":93,"name":94,"nameZh":6,"description":95,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":96,"categories":97,"keywords":106,"publishedAt":31},"01M71G23DCX3SV60H63HBBKQ6J","sanitize-powerpoint","ALWAYS invoke this skill when sanitizing, cleaning up, auditing, or aligning a PowerPoint (.pptx) deck — slide-master and layout structure, layout type attributes, stray fonts, non-theme colors, or layout naming. NEVER hand-edit pptx XML without this skill.","https:\u002F\u002Fgithub.com\u002Foutcomeeng\u002Fplugins\u002Fblob\u002Fmain\u002Fsrc\u002Fplugins\u002Fwork\u002Fskills\u002Fsanitize-powerpoint\u002FSKILL.md",[98,101],{"categoryNo":99,"categoryName":100,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000003","演示文稿",{"categoryNo":102,"categoryName":103,"taxonomyType":104,"taxonomyTypeDesc":105},"01JBSEEDCAT000000000000034","行政与办公人员","PROFESSION","适用职业",[107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126],"sanitize","powerpoint","sanitize powerpoint","fonts","type","cleaning","structure","never","attributes","theme","pptx","naming","layout","deck","up","edit","xml","master","invoke","when",{"skillNo":128,"name":129,"nameZh":6,"description":130,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":131,"categories":132,"keywords":133,"publishedAt":31},"01M71G21PQKBCDECHGVYSKPJW5","draw-excalidraw","ALWAYS invoke this skill when creating Excalidraw diagrams, visualizing workflows, architectures, or concepts. NEVER generate Excalidraw JSON without this skill.","https:\u002F\u002Fgithub.com\u002Foutcomeeng\u002Fplugins\u002Fblob\u002Fmain\u002Fsrc\u002Fplugins\u002Fwork\u002Fskills\u002Fdraw-excalidraw\u002FSKILL.md",[],[134,135,136,125,114,137,138,126,139,140,141,142],"draw","excalidraw","draw excalidraw","generate","workflows","concepts","json","diagrams","architectures",{"skillNo":144,"name":145,"nameZh":6,"description":146,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":147,"categories":148,"keywords":152,"publishedAt":31},"01M71G1XBH0X1VCTR7269MDGSY","typescript-code-standards","TypeScript code standards enforced across all skills. Loaded by other skills, not invoked directly.","https:\u002F\u002Fgithub.com\u002Foutcomeeng\u002Fplugins\u002Fblob\u002Fmain\u002Fsrc\u002Fplugins\u002Ftypescript\u002Fskills\u002Ftypescript-code-standards\u002FSKILL.md",[149],{"categoryNo":150,"categoryName":151,"taxonomyType":104,"taxonomyTypeDesc":105},"01JBSEEDCAT000000000000030","开发工程师",[153,24,154,155,27,28,29,30],"code","typescript code standards","code standards",{"skillNo":157,"name":158,"nameZh":6,"description":159,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":160,"categories":161,"keywords":162,"publishedAt":31},"01M71G1TQ3VN45SJ75DFMJDB9W","typescript-architecture-standards","TypeScript ADR conventions enforced across architect and auditor skills. Loaded by other skills, not invoked directly.","https:\u002F\u002Fgithub.com\u002Foutcomeeng\u002Fplugins\u002Fblob\u002Fmain\u002Fsrc\u002Fplugins\u002Ftypescript\u002Fskills\u002Ftypescript-architecture-standards\u002FSKILL.md",[],[24,163,164,165,27,166,167,28,168,29,30,169],"architecture standards","typescript architecture standards","architecture","adr","conventions","auditor","architect",{"skillNo":171,"name":172,"nameZh":6,"description":173,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":174,"categories":175,"keywords":176,"publishedAt":31},"01M71G1RSFGDZR5XBXDX7E02B5","simplify-typescript-code","ALWAYS invoke this skill when simplifying TypeScript implementation while preserving behavior.","https:\u002F\u002Fgithub.com\u002Foutcomeeng\u002Fplugins\u002Fblob\u002Fmain\u002Fsrc\u002Fplugins\u002Ftypescript\u002Fskills\u002Fsimplify-typescript-code\u002FSKILL.md",[],[27,177,153,178,179,180,126,181,182,183,184,185,125,186,122,187,29,188,189,190],"simplify typescript code","simplify","head","git","use","implementation","rev","base","status","grep","tool","behavior","parse","diff"]