TypeScript Test

This documentation was generated with the assistance of AI. Please report any inaccuracies.

Run a TypeScript/npm project’s unit test suite through whichever test runner it actually has configured — Vitest, Jest, or the Angular CLI’s Vitest-backed ng test builder — scoping the run to whatever the caller asked for and reporting a compact pass/fail summary parsed from the runner’s own JSON reporter. This is a read-only reporting skill: it never fixes a failing test or modifies source/test code unless asked to as an explicit follow-up.

Purpose

iru-typescript-test turns a loosely-specified request ("run everything", "just this file", "tests matching this name") into the correct invocation for whichever runner a TypeScript project actually has wired, and returns a concise pass/fail report instead of a raw console dump. It detects the runner and package manager from the project’s own manifests rather than assuming one, and distinguishes a genuinely clean run from a selector that silently matched nothing — a distinction that looks identical in a broken CI job if conflated.

When .ui-quality.yml says accessibility: none (opted out entirely) or off (rules apply, checks not run), accessibility checks are not applicable: they are skipped and reported as "accessibility: opted out" (none) or "accessibility checks off" (off) — never "unverified" and never a failure.

Plugin

Shipped by the iru-typescript plugin of the ai-catalog marketplace (install with /plugin install iru-typescript@ai-catalog; see Plugins).

Invocation

/iru-typescript-test
/iru-typescript-test <selector>
/iru-typescript-test e2e [<selector>]
/iru-typescript-test ui [<selector>]
/iru-typescript-test e2e
lock: held
configuration: <detox-configuration>

Inputs

Argument Required Description Default

selector

No

A file path, a directory, or a -t "<name pattern>" filter, combinable as a file/directory plus a name pattern. Mapped to the matching runner’s scoping flags per the table below.

None — omitting it runs the whole unit suite.

e2e / ui / unit keyword

No

Leading keyword (NestJS, Next.js, React Native and Playwright-wired web UIs only) that picks the suite. e2e runs the end-to-end suite instead of the unit suite and scopes it with the rest of the selector; ui means exactly the same as e2e (so callers can say "the UI tests" for a web or React Native app); unit means the same as no keyword.

unit

lock: held

No

key: value line from a calling skill: the caller already holds the repository-wide .integration-test.lock, so a React Native e2e run must not acquire or release it. Ignored for every other selector.

Not set — React Native e2e takes the lock itself.

configuration: <name>

No

React Native e2e with Detox only: the .detoxrc.js configuration to run (ios.sim.debug, android.emu.debug, …​).

Derived from .detoxrc.js (single configuration, else a *.debug one for the current platform); asked when still ambiguous.

The runner and its invocation are resolved as follows:

Runner Detected via Full-suite command

Vitest

vitest in dependencies/devDependencies plus a vitest.config.* file (or a test/vitest block in vite.config.ts)

vitest run

Jest

jest/jest-expo in dependencies/devDependencies plus a jest.config.* file or a "jest" key in package.json

jest

Angular CLI (ng test, Vitest runner)

@angular/build devDependency with the project’s test architect target using builder @angular/build:unit-test (its runner option defaults to "vitest")

ng test --watch=false

If none of the three is found, the skill stops and reports "no test runner wired" rather than reporting a misleadingly clean "0 tests". If ng test’s target has `options.runner explicitly set to "karma", the skill’s Vitest-shaped commands don’t apply and it reports that instead of guessing at Karma’s CLI. The package manager (npm/pnpm/yarn) is read from packageManager in package.json or the lockfile present, and the runner’s binary is invoked directly (npx/pnpm exec/yarn) rather than through the project’s own test npm script, since that script often omits flags the skill needs to forward predictably.

Selector scoping per runner:

What the user wants Vitest Jest ng test (Vitest runner)

One file

vitest run path/to/file.test.ts

jest path/to/file.test.ts

ng test --watch=false --include=path/to/file.spec.ts

A directory

vitest run path/to/dir

jest path/to/dir

ng test --watch=false --include="path/to/dir/**"

Name pattern

vitest run -t "<pattern>"

jest -t "<pattern>"

ng test --watch=false --filter="<pattern>"

File/dir + name pattern

vitest run path/to/file.test.ts -t "<pattern>"

jest path/to/file.test.ts -t "<pattern>"

ng test --watch=false --include=path/to/file.spec.ts --filter="<pattern>"

An unmatched file/directory selector is a hard failure (non-zero exit) for all three runners; an unmatched -t/--filter name pattern against files that do match is not a failure — every runner reports the matched tests as skipped and exits 0.

NestJS, Next.js and React Native

@nestjs/core in dependencies marks a NestJS project, next a Next.js project, and react-native or expo a React Native project; all still run on Vitest or Jest, detected as above, and the framework only changes which suite counts as "unit", how failures are explained, and how e2e resolves:

  • NestJS — unit tests are .spec.ts next to the source and e2e tests are test/.e2e-spec.ts, kept apart by two configs so the default run never picks up e2e files: ESM/Vitest (vitest.config.ts plus vitest.config.e2e.ts) or CommonJS/Jest (jest.config.ts plus test/jest-e2e.json). The e2e config path is read from the project’s own test:e2e script; with no such script or config, e2e is reported as "NestJS e2e suite not wired". Relative .js imports in the ESM flavor are correct and never reported.

  • Next.js — unit/component tests run on Vitest (vitest.config.mts) or Jest via next/jest; e2e is Playwright (@playwright/test plus playwright.config.*), which builds and starts the app, so it only runs on an explicit e2e selector and never by default. Cypress-only projects are reported as "Cypress e2e present, not run".

  • React Native — always Jest (never Vitest), with the toolchain resolved first: expo in dependencies is expo (even with prebuild android//ios/), otherwise metro (bare CLI app). The preset is read from the Jest config: jest-expo (or a platform variant; jest-expo/universal multiplies test counts per platform project and the report says so) for expo, @react-native/jest-preset or the legacy react-native preset for metro. A preset contradicting the toolchain still runs but is flagged. Unit tests live in tests/ or beside the source; .maestro/ and e2e/ flows are never matched by the default run. Failures are reported by cause: an untranspiled ESM/Flow package needing transformIgnorePatterns, a native module without a Jest mock, or React Native Testing Library 14’s un-awaited render.

React Native e2e picks Detox (.detoxrc.* plus the detox devDependency) or else Maestro (.maestro/ flows), runs it through its own JUnit/JSON reporter, and reports "React Native e2e suite not wired" when neither exists. Preconditions are checked in order (tool installed, a booted simulator/emulator, the repository lock, Metro answering on port 8081, started and later stopped by the skill when it wasn’t running) and any miss is reported as "unverified (reason)", never as a pass. Unlike NestJS/Next.js, whose callers take .integration-test.lock, React Native e2e acquires and releases that lock itself unless lock: held is passed.

Web UI (React/Vite, Angular CLI, Ionic) — @playwright/test plus a playwright.config.* makes the project a web-ui one: unit/component tests stay on the unit runner (jsdom/happy-dom runs never produce screenshots) and the browser UI tests are Playwright, resolved through the e2e selector’s Playwright column and every config the project’s own scripts name (for example a separate Playwright component-testing config). Cypress is reported as "present, not run".

UI runs

Every Playwright, Maestro or Detox e2e/ui run is also a UI run, wrapping the command with the catalog’s UI contract (see Building user interfaces):

  • The settings are resolved at the repository root: UI_SCREENSHOTS_DIR (absolute, from the environment) or .ui-screenshots/, and the accessibility (enforce/report/off/none), directions and themes keys of .ui-quality.yml (missing file or key means enforce, left-to-right and right-to-left, light and dark).

  • A full UI run (no file, directory, name or tag) clears the screenshot directory’s contents first, only when the path is .ui-screenshots or came from UI_SCREENSHOTS_DIR; a scoped run adds to it. A missing .gitignore entry is reported, never edited.

  • The directory is exported to the runner. Playwright’s helper writes straight into it and the axe results come from accessibility-scan-results attachments in the JSON report; Maestro gets UI_SCREENSHOTS_DIR and UI_THEME, with the device appearance recorded, switched through each theme for ui-flow flows and restored on every exit path; Detox’s helper copies its screenshots in. Stray PNGs are moved into the <kind>/<Id>/ layout.

  • Every file name is validated against the contract’s naming pattern (malformed names and captures outside the configured matrix are listed), React Native accessibility is checked through eslint-plugin-react-native-a11y lint counts, and the level is applied: enforce makes violations a failed run, report only lists them, off skips the check.

An e2e selector on any other project is reported as unsupported and the skill stops. A failure caused by a missing database, Docker/Testcontainers daemon, Playwright browsers, or a failed next build is reported as an environment failure ("e2e not run/aborted: cause"), never as failing tests; the skill checks docker info first when the project uses Testcontainers.

Outputs

  • A plain-text pass/fail summary in the conversation — never a written file.

  • Total, passed, and failed test counts parsed from each runner’s JSON reporter output (--reporter=json for Vitest, --json for Jest, --reporters=json for ng test), written to a scratch file and deleted after parsing.

  • On failures: each failing test’s fullName and the first line of its assertion message — no stack traces, no passing-test noise.

  • A selector that matched no files is reported plainly as "matched no test files", distinct from a genuinely clean 0-failure run; a name pattern that matched no individual tests is reported plainly as "0 of N tests matched the filter", distinct from "all passed".

  • For every UI run: the screenshot directory used (cleared or added to), screenshot counts per kind (component/screen/ flow) and distinct ids, strays collected, malformed names, captures outside the configured matrix, the .ui-quality.yml level applied, accessibility violations as a count plus rule ids, and for Maestro the themes switched through and confirmation that the original appearance was restored; a run that could not start is "UI run unverified" with the reason.

  • For NestJS, Next.js and React Native, the report states which suite ran (unit or e2e), so a green unit run is never read as e2e coverage. Playwright runs are parsed from its own JSON reporter shape (stats and nested suites[].specs[]).

  • No source or test code is ever modified.

Execution flow

flowchart TD A["Start /iru-typescript-test\n(optional selector, lock / configuration lines)"] --> A0["Step 0: parse selector and\nkey: value lines"] A0 --> B["Read package.json / angular.json\nto detect runner + package manager"] B --> C{Runner detected?} C -- no --> D["Stop: report\n"no test runner wired""] C -- yes, ng test with\nrunner=karma --> E["Stop: report Karma\nnot supported by this skill"] C -- yes --> C2{"NestJS, Next.js or React Native\nwith e2e keyword?"} C2 -- "e2e, env missing\n(Docker / browsers / build /\ndevice / Metro / lock)" --> C3["Report e2e not run:\nenvironment failure / unverified"] C2 -- "e2e, ready" --> F C2 -- "no" --> F{Selector given?} F -- no --> G["Full-suite command\n(vitest run / jest / ng test --watch=false)"] F -- yes --> H["Map selector to file/dir\nand/or -t / --filter syntax"] H --> I["Compose scoped command"] G --> J["Run via Bash with JSON reporter\n(--reporter=json / --json / --reporters=json)"] I --> J J --> K["Parse JSON: totals,\nfailed assertionResults"] K --> L["Delete scratch JSON file"] L --> M{Selector matched\nno files?} M -- yes --> N["Report: selector matched\nno test files"] M -- no --> O{Name pattern matched\nno tests within matched files?} O -- yes --> P["Report: 0 of N tests\nmatched the filter"] O -- no --> Q{Any failures?} Q -- yes --> R["List failing test names +\nfirst line of each assertion message"] Q -- no --> S["Report: all passed, briefly"]

Dependencies

Invokes

None — iru-typescript-test is a leaf skill; it only runs Vitest/Jest/ng test commands and reports on their output, it does not call any other skill in this catalog.

Invoked by

  • iru-gate-runner — both calling skills above invoke this skill from inside a iru-gate-runner sub-agent they spawn, not inline.

Source

SKILL.md on GitHub — the file this page was generated from.