Skip to content

build: TypeScript test files are never type-checked (~55 latent type errors) #899

Description

@bpowers

Problem

No test file in the repo is type-checked.

tsconfig.base.json sets "isolatedModules": true, which made ts-jest run transpile-only for every package, so type errors under tests/ were silently ignored. The migration to Rstest (swc-based) does not change this -- swc also strips types without checking them -- so nothing was lost in that migration, but the hole is now explicit and worth closing.

Four packages (core, engine, diagram, app) explicitly exclude tests from their tsc program:

  • src/core/tsconfig.json -> "exclude": ["lib", "lib.module", "tests"]
  • src/engine/tsconfig.json -> "exclude": [..., "tests", ...] (and @simlin/engine is not even in the root tsc script's --filter list)
  • src/diagram/tsconfig.json -> "exclude": ["lib", "lib.browser", "tests"]
  • src/app/tsconfig.json -> "exclude": [..., "../**/tests/**", "../**/*.test.ts", ...]

Two packages (@simlin/server, @simlin/serve-web) do type-check their tests today, but only incidentally: their tsconfig include globs happen to cover the test files, so pnpm tsc picks them up.

Proof the hole is real

All of these currently compile clean, because nobody looks:

  • src/diagram/tests/editor-applyPatch.test.ts:11 imports JsonProjectPatch from '../json-types' -- that module does not exist. The import survives because it is type-only and gets elided.
  • src/diagram/tests/build-selection-map.test.ts:9 imports UID from '@simlin/core/common'; UID is actually declared in @simlin/core/datamodel.
  • src/diagram/tests/editor-details-remount.test.ts:33 (and editor-selection-changed, editor-selection-invariant): Module '"@simlin/core/datamodel"' declares 'JsonProject' locally, but it is not exported.
  • src/engine/tests/cleanup.test.ts:41: the fake EngineBackend is missing projectRenderPng -- the interface drifted and the fake never followed.

Why it matters

Type-checking tests catches real bugs. While preparing the Rstest migration, the two packages that do get checked immediately surfaced 9 wrong rs.fn<Return, Args>() calls in src/simlin-serve/web/src/ws.test.ts: jest's Mock generic is the return type, whereas rstest's is the whole function signature. Those mocks ran fine but were silently mistyped. The other four packages would have hidden the same class of error, and did hide the four above.

Beyond catching bugs, an unchecked test suite lets fixtures drift out of sync with the types they claim to construct -- see the TS2739 missing fixture props cluster below, where test fixtures no longer satisfy Aux/Stock/Flow/Module.

Components affected

src/core, src/engine, src/diagram, src/app (unchecked); src/server, src/simlin-serve/web (checked only incidentally); repo-root tsconfig.base.json and the root tsc script.

Scope: 55 errors across 25 files

Worst offenders:

file errors
src/diagram/tests/group-movement.test.ts 7
src/diagram/tests/build-selection-map.test.ts 7
src/diagram/tests/snackbar.test.tsx 4
src/diagram/tests/module-creation.test.ts 4
src/core/tests/datamodel.test.ts 4

By error kind: TS2322 (16), TS2345 (9), TS6133 unused (7), TS2739 missing fixture props (7), TS2352 bad casts (7), TS18048 possibly-undefined (5), TS2459/TS2305/TS2307 wrong imports (5), misc (rest).

Proposed fix

Add a repo-root tsconfig.tests.json and wire it into the root tsc script (pnpm exec tsc --noEmit -p tsconfig.tests.json), plus add @types/node to root devDependencies.

Key options and why:

  • extends ./tsconfig.base.json, noEmit: true
  • composite: false, declaration: false, declarationMap: false -- the base sets composite: true, which forbids disabling emit
  • module: "esnext" -- needed because import ... with { rstest: 'importActual' } import attributes are only allowed when targeting ES modules
  • lib: ["es2020","dom","dom.iterable","esnext.disposable"] -- the union of what the packages declare
  • types: ["node"]
  • paths mirroring each package's own mapping, so tests drive sources rather than built output
  • include must list the four ambient declaration files (src/app/css-modules.d.ts, src/app/globals.d.ts, src/diagram/css-modules.d.ts, src/diagram/globals.d.ts) plus each package's test globs -- a tests-only program does not otherwise pick up the ambient CSS-module shapes

This configuration is verified working: it is what produced the error list below.

Full proposed tsconfig.tests.json
{
  // Rstest compiles test files with swc, which strips types without checking
  // them -- ts-jest used to type-check them as a side effect of transpiling.
  // This program exists only to type-check every package's tests (and the
  // sources they pull in). It emits nothing; each package still builds with its
  // own tsconfig.
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "noEmit": true,
    // "composite" (inherited) forbids disabling emit, and declarations are
    // meaningless for a check-only pass.
    "composite": false,
    "declaration": false,
    "declarationMap": false,
    // Partial mocks reach the unmocked module through
    // `import ... with { rstest: 'importActual' }`, and import attributes are
    // only allowed when targeting ES modules. Nothing is emitted here, so this
    // affects checking only.
    "module": "esnext",
    // The union of what the packages declare: DOM for the React tests,
    // esnext.disposable for the engine's `using` blocks.
    "lib": ["es2020", "dom", "dom.iterable", "esnext.disposable"],
    "types": ["node"],
    // Mirrors each package's own resolution: tests drive engine/core sources
    // rather than built output, and the engine's two platform-specific
    // specifiers resolve to their Node flavors. Relative to this file.
    "paths": {
      "@simlin/engine/internal/wasm": ["./src/engine/src/internal/wasm.node.ts"],
      "@simlin/engine/internal/backend-factory": ["./src/engine/src/backend-factory.node.ts"],
      "@simlin/engine": ["./src/engine/src/index.ts"],
      "@simlin/engine/*": ["./src/engine/src/*"],
      "@simlin/*": ["./src/*"]
    }
  },
  "include": [
    // Ambient declarations the sources under test rely on (CSS-module shapes).
    // A tests-only program does not otherwise pick these up.
    "src/app/css-modules.d.ts",
    "src/app/globals.d.ts",
    "src/diagram/css-modules.d.ts",
    "src/diagram/globals.d.ts",

    "src/core/tests/**/*.ts",
    "src/engine/tests/**/*.ts",
    "src/server/tests/**/*.ts",
    "src/server/seshcookie/**/*.test.ts",
    "src/app/tests/**/*.ts",
    "src/app/tests/**/*.tsx",
    "src/diagram/tests/**/*.ts",
    "src/diagram/tests/**/*.tsx",
    "src/simlin-serve/web/src/**/*.test.ts",
    "src/simlin-serve/web/src/**/*.test.tsx",
    "src/simlin-serve/web/src/test-utils/**/*.ts",
    "src/simlin-serve/web/src/test-utils/**/*.tsx"
  ]
}
Full error list (55)
src/core/tests/datamodel.test.ts(2123,38): error TS2345: Argument of type '{ type: 'aux'; ident: string; equation: Equation; documentation: string; units: string; gf: GraphicalFunction | undefined; canBeModuleInput: boolean; isPublic: boolean; activeInitial: string | undefined; ... 5 more ...; connectorErrors: { ...; }[]; }' is not assignable to parameter of type 'Aux'.
src/core/tests/datamodel.test.ts(2127,40): error TS2345: Argument of type '{ type: 'stock'; ident: string; equation: Equation; documentation: string; units: string; inflows: readonly string[]; outflows: readonly string[]; nonNegative: boolean; canBeModuleInput: boolean; ... 7 more ...; connectorErrors: { ...; }[]; }' is not assignable to parameter of type 'Stock'.
src/core/tests/datamodel.test.ts(2127,59): error TS2739: Type '{ name: string; initialEquation: string; }' is missing the following properties from type 'JsonStock': inflows, outflows
src/core/tests/datamodel.test.ts(2131,39): error TS2345: Argument of type '{ type: 'flow'; ident: string; equation: Equation; documentation: string; units: string; gf: GraphicalFunction | undefined; nonNegative: boolean; canBeModuleInput: boolean; isPublic: boolean; ... 6 more ...; connectorErrors: { ...; }[]; }' is not assignable to parameter of type 'Flow'.
src/diagram/tests/build-selection-map.test.ts(9,10): error TS2305: Module '"@simlin/core/common"' has no exported member 'UID'.
src/diagram/tests/build-selection-map.test.ts(33,19): error TS2352: Conversion of type '{ selection: Set<number>; }' to type 'CanvasProps' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
src/diagram/tests/build-selection-map.test.ts(42,19): error TS2352: Conversion of type '{ selection: Set<number>; }' to type 'CanvasProps' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
src/diagram/tests/build-selection-map.test.ts(52,19): error TS2352: Conversion of type '{ selection: Set<number>; }' to type 'CanvasProps' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
src/diagram/tests/build-selection-map.test.ts(58,19): error TS2352: Conversion of type '{ selection: Set<number>; }' to type 'CanvasProps' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
src/diagram/tests/build-selection-map.test.ts(69,19): error TS2352: Conversion of type '{ selection: Set<number>; }' to type 'CanvasProps' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
src/diagram/tests/build-selection-map.test.ts(78,19): error TS2352: Conversion of type '{ selection: Set<number>; }' to type 'CanvasProps' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
src/diagram/tests/connector-routing.test.ts(33,9): error TS2322: Type '{ type: "aux"; ident: string; equation: { type: "applyToAll"; dimensionNames: string[]; equation: string; }; documentation: string; units: string; gf: undefined; data: undefined; errors: undefined; unitErrors: undefined; uid: undefined; } | { ...; }' is not assignable to type 'Aux'.
src/diagram/tests/connector-routing.test.ts(77,9): error TS2322: Type '{ type: "stock"; ident: string; equation: { type: "applyToAll"; dimensionNames: string[]; equation: string; }; documentation: string; units: string; inflows: never[]; outflows: never[]; nonNegative: false; data: undefined; errors: undefined; unitErrors: undefined; uid: undefined; } | { ...; }' is not assignable to type 'Stock'.
src/diagram/tests/connector-routing.test.ts(144,9): error TS2322: Type '{ type: "flow"; ident: string; equation: { type: "applyToAll"; dimensionNames: string[]; equation: string; }; documentation: string; units: string; gf: undefined; nonNegative: false; data: undefined; errors: undefined; unitErrors: undefined; uid: undefined; } | { ...; }' is not assignable to type 'Flow'.
src/diagram/tests/editor-applyPatch.test.ts(11,34): error TS2307: Cannot find module '../json-types' or its corresponding type declarations.
src/diagram/tests/editor-details-remount.test.ts(33,32): error TS2459: Module '"@simlin/core/datamodel"' declares 'JsonProject' locally, but it is not exported.
src/diagram/tests/editor-input.test.ts(187,11): error TS2367: This comparison appears to be unintentional because the types '"protobuf"' and '"json"' have no overlap.
src/diagram/tests/editor-input.test.ts(232,11): error TS2367: This comparison appears to be unintentional because the types '"protobuf"' and '"json"' have no overlap.
src/diagram/tests/editor-selection-changed.test.ts(31,32): error TS2459: Module '"@simlin/core/datamodel"' declares 'JsonProject' locally, but it is not exported.
src/diagram/tests/editor-selection-invariant.test.ts(29,32): error TS2459: Module '"@simlin/core/datamodel"' declares 'JsonProject' locally, but it is not exported.
src/diagram/tests/group-movement.test.ts(102,5): error TS2322: Type '{ type: "aux"; ident: string; equation: { type: "applyToAll"; dimensionNames: string[]; equation: string; }; documentation: string; units: string; gf: undefined; data: undefined; errors: undefined; unitErrors: undefined; uid: undefined; } | undefined' is not assignable to type 'Aux | undefined'.
src/diagram/tests/group-movement.test.ts(1591,21): error TS18048: 'newLink.arc' is possibly 'undefined'.
src/diagram/tests/group-movement.test.ts(1617,21): error TS18048: 'newLink.arc' is possibly 'undefined'.
src/diagram/tests/group-movement.test.ts(1618,21): error TS18048: 'newLink.arc' is possibly 'undefined'.
src/diagram/tests/group-movement.test.ts(1682,23): error TS18048: 'newLink.arc' is possibly 'undefined'.
src/diagram/tests/group-movement.test.ts(1718,23): error TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'.
src/diagram/tests/group-movement.test.ts(1743,23): error TS18048: 'newLink.arc' is possibly 'undefined'.
src/diagram/tests/hosted-web-editor-shell.test.tsx(141,60): error TS2493: Tuple type '[]' of length '0' has no element at index '1'.
src/diagram/tests/module-creation.test.ts(17,3): error TS2739: Type '{ type: "module"; ident: string; modelName: string; documentation: string; units: string; references: never[]; data: undefined; errors: undefined; unitErrors: undefined; uid: undefined; }' is missing the following properties from type 'Module': canBeModuleInput, isPublic, dataSource
src/diagram/tests/module-creation.test.ts(32,3): error TS2739: Type '{ type: "stock"; ident: string; equation: { type: "scalar"; equation: string; }; documentation: string; units: string; inflows: never[]; outflows: never[]; nonNegative: false; data: undefined; errors: undefined; unitErrors: undefined; uid: undefined; }' is missing the following properties from type 'Stock': canBeModuleInput, isPublic, activeInitial, dataSource
src/diagram/tests/module-creation.test.ts(49,3): error TS2739: Type '{ type: "aux"; ident: string; equation: { type: "scalar"; equation: string; }; documentation: string; units: string; gf: undefined; data: undefined; errors: undefined; unitErrors: undefined; uid: undefined; }' is missing the following properties from type 'Aux': canBeModuleInput, isPublic, activeInitial, dataSource
src/diagram/tests/module-creation.test.ts(64,3): error TS2739: Type '{ type: "flow"; ident: string; equation: { type: "scalar"; equation: string; }; documentation: string; units: string; gf: undefined; nonNegative: false; data: undefined; errors: undefined; unitErrors: undefined; uid: undefined; }' is missing the following properties from type 'Flow': canBeModuleInput, isPublic, activeInitial, dataSource
src/diagram/tests/module-details-utils.test.ts(21,3): error TS2322: Type '{ activeInitial?: string | undefined; dataSource?: DataSource | undefined; connectorErrors?: readonly ConnectorError[] | undefined; type: "aux"; ident: string; equation: Equation; ... 8 more ...; uid: number | undefined; }' is not assignable to type 'Aux'.
src/diagram/tests/module-details-utils.test.ts(39,3): error TS2322: Type '{ activeInitial?: string | undefined; dataSource?: DataSource | undefined; connectorErrors?: readonly ConnectorError[] | undefined; type: "stock"; ident: string; equation: Equation; ... 10 more ...; uid: number | undefined; }' is not assignable to type 'Stock'.
src/diagram/tests/module-wiring-ui.test.tsx(16,3): error TS2322: Type '{ activeInitial?: string | undefined; dataSource?: DataSource | undefined; connectorErrors?: readonly ConnectorError[] | undefined; type: "aux"; ident: string; equation: Equation; ... 8 more ...; uid: number | undefined; }' is not assignable to type 'Aux'.
src/diagram/tests/module-wiring-ui.test.tsx(34,3): error TS2322: Type '{ activeInitial?: string | undefined; dataSource?: DataSource | undefined; connectorErrors?: readonly ConnectorError[] | undefined; type: "stock"; ident: string; equation: Equation; ... 10 more ...; uid: number | undefined; }' is not assignable to type 'Stock'.
src/diagram/tests/module-wiring-ui.test.tsx(54,3): error TS2322: Type '{ activeInitial?: string | undefined; dataSource?: DataSource | undefined; connectorErrors?: readonly ConnectorError[] | undefined; type: "flow"; ident: string; equation: Equation; ... 9 more ...; uid: number | undefined; }' is not assignable to type 'Flow'.
src/diagram/tests/project-controller.test.ts(349,23): error TS2352: Conversion of type '{ elements: never[]; nextUid: number; viewBox: { x: number; y: number; width: number; height: number; }; zoom: number; }' to type 'StockFlowView' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
src/diagram/tests/snackbar.test.tsx(206,13): error TS2322: Type '{ ref: RefObject<ReRenderWrapperHandle | null>; }' is not assignable to type 'Omit<Record<string, never>, "ref">'.
src/diagram/tests/snackbar.test.tsx(263,13): error TS2322: Type '{ ref: RefObject<DurationWrapperHandle | null>; }' is not assignable to type 'Omit<Record<string, never>, "ref">'.
src/diagram/tests/snackbar.test.tsx(318,13): error TS2322: Type '{ ref: RefObject<MessageWrapperHandle | null>; }' is not assignable to type 'Omit<Record<string, never>, "ref">'.
src/diagram/tests/snackbar.test.tsx(422,35): error TS2322: Type '{ ref: RefObject<DupHostHandle | null>; }' is not assignable to type 'Omit<Record<string, never>, "ref">'.
src/diagram/tests/text-field.test.tsx(83,37): error TS2353: Object literal may only specify known properties, and ''data-testid'' does not exist in type 'InputHTMLAttributes<HTMLInputElement>'.
src/diagram/tests/variable-details-cancel.test.tsx(50,3): error TS2322: Type '{ canBeModuleInput?: boolean | undefined; isPublic?: boolean | undefined; activeInitial?: string | undefined; dataSource?: DataSource | undefined; connectorErrors?: readonly ConnectorError[] | undefined; ... 9 more ...; uid: number | undefined; }' is not assignable to type 'Aux'.
src/diagram/tests/variable-details-latex.test.tsx(31,3): error TS2739: Type '{ type: "aux"; ident: string; equation: { type: "scalar"; equation: string; }; documentation: string; units: string; gf: undefined; data: undefined; errors: undefined; unitErrors: undefined; uid: undefined; }' is missing the following properties from type 'Aux': canBeModuleInput, isPublic, activeInitial, dataSource
src/diagram/tests/variable-details-newline.test.tsx(39,3): error TS2322: Type '{ canBeModuleInput?: boolean | undefined; isPublic?: boolean | undefined; activeInitial?: string | undefined; dataSource?: DataSource | undefined; connectorErrors?: readonly ConnectorError[] | undefined; ... 9 more ...; uid: number | undefined; }' is not assignable to type 'Aux'.
src/diagram/tests/variable-details-preview.test.tsx(27,3): error TS2322: Type '{ canBeModuleInput?: boolean | undefined; isPublic?: boolean | undefined; activeInitial?: string | undefined; dataSource?: DataSource | undefined; connectorErrors?: readonly ConnectorError[] | undefined; ... 9 more ...; uid: number | undefined; }' is not assignable to type 'Aux'.
src/engine/tests/api.test.ts(797,27): error TS2739: Type '{ name: string; initialEquation: string; }' is missing the following properties from type 'JsonStock': inflows, outflows
src/engine/tests/cleanup.test.ts(41,3): error TS2741: Property 'projectRenderPng' is missing in type '{ init: () => Promise<void>; isInitialized: () => true; reset: () => Promise<void>; configureWasm: () => void; projectOpenXmile: () => Promise<ProjectHandle>; projectOpenProtobuf: () => Promise<...>; ... 33 more ...; simGetLinks: () => Promise<...>; }' but required in type 'EngineBackend'.
src/engine/tests/direct-backend.test.ts(492,63): error TS2345: Argument of type '{ models: { name: string; ops: { type: 'upsertView'; payload: { index: number; view: { elements: ({ type: string; uid: number; name: string; x: number; y: number; labelSide: string; points?: undefined; flowUid?: undefined; fromUid?: undefined; toUid?: undefined; arc?: undefined; } | { ...; } | { ...; } | { ...; })[]...' is not assignable to parameter of type 'JsonProjectPatch'.
src/engine/tests/integration.test.ts(13,27): error TS6133: 'SimlinJsonFormat' is declared but its value is never read.
src/engine/tests/race.test.ts(114,45): error TS2345: Argument of type '{ models: { name: string; ops: { type: string; payload: { aux: { name: string; equation: string; }; }; }[]; }[]; }' is not assignable to parameter of type 'JsonProjectPatch'.
src/engine/tests/wasm-model.test.ts(18,19): error TS6133: 'Model' is declared but its value is never read.
src/engine/tests/worker-backend.test.ts(13,43): error TS6196: 'SimHandle' is declared but never used.
src/engine/tests/worker-server.test.ts(166,9): error TS6133: 'server' is declared but its value is never read.

Sequencing note

Fixing the 55 errors is separable from landing the config: the config can go in first with the four packages' test globs added incrementally (server + serve-web are already clean), or the errors can be fixed first and the config landed as the guard that keeps them fixed. The latter is preferable -- a tsconfig.tests.json that is not wired into a green pnpm tsc provides no protection.

Related

  • core: tsconfig.json excludes test files, breaking eslint #441 (core: tsconfig.json excludes test files, breaking eslint) shares a root cause: src/core's tsconfig excludes tests. That issue is scoped to eslint's parserOptions.project failing to find test files; this one is about tsc never checking them. A repo-root tests program may resolve both, and they should be fixed together.
  • docs/tech-debt.md entry 57 (Stale @system-dynamics/* import path in build-selection-map.test.ts) is a single instance of this class -- a dangling type-only module specifier that survives because nothing resolves it. The import path has since been updated to @simlin/core/..., but the file still names a member (UID) the module does not export, so the underlying bug outlived the partial fix. This issue subsumes entry 57.

Context

Identified while preparing the Jest -> Rstest test-runner migration, when the two incidentally-checked packages surfaced mistyped rs.fn generics that the other four would have hidden.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions