Files
gstack/test/typecheck-test-ratchet.test.ts
T
garrytan 42e53413da feat: require a zero-error product typecheck and a test type-debt ratchet
Adds tsconfig.json (strict) over product code, fixes its remaining 90
diagnostics (type-only, interface corrections, and explicit narrowing),
and adds a typecheck job to the required free-tests aggregate running
bun run typecheck, the test-code ratchet (identity -> count baseline, fails
on new, repeated, or unlocked fixed diagnostics), and the lib/cso format
check. Reuses fixes from #2447 where they still applied.
2026-09-29 14:34:02 +00:00

63 lines
3.1 KiB
TypeScript

import { describe, expect, test } from 'bun:test';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { compareDiagnostics, parseDiagnostics, readBaseline } from '../scripts/typecheck-test';
const output = [
"test/a.test.ts(3,5): error TS2339: Property 'questions' does not exist on type 'Plan'.",
"test/a.test.ts(9,5): error TS2339: Property 'questions' does not exist on type 'Plan'.",
'test/b.test.ts(1,1): error TS2345: Argument of type \'string\' is not assignable to parameter of type \'number\'.',
" Type 'string' is not assignable to type 'number'.",
'Found 3 errors.',
].join('\n');
describe('test typecheck ratchet', () => {
test('counts repeated identical diagnostics instead of collapsing them', () => {
expect(parseDiagnostics(output)).toEqual({
"test/a.test.ts\tTS2339\tProperty 'questions' does not exist on type 'Plan'.": 2,
"test/b.test.ts\tTS2345\tArgument of type 'string' is not assignable to parameter of type 'number'. Type 'string' is not assignable to type 'number'.": 1,
});
});
test('ignores line and column so moving code does not churn the baseline', () => {
const moved = output.replace('(3,5)', '(30,7)').replace('(9,5)', '(90,1)');
expect(parseDiagnostics(moved)).toEqual(parseDiagnostics(output));
});
test('a duplicate of an existing diagnostic and a new identity both fail', () => {
const baseline = parseDiagnostics(output);
const current = parseDiagnostics(output + "\ntest/a.test.ts(12,5): error TS2339: Property 'questions' does not exist on type 'Plan'.\ntest/c.test.ts(1,1): error TS2304: Cannot find name 'join'.");
const { added, fixed } = compareDiagnostics(baseline, current);
expect(added.map(d => [d.identity.split('\t')[0], d.baseline, d.current])).toEqual([
['test/a.test.ts', 2, 3],
['test/c.test.ts', 0, 1],
]);
expect(fixed).toEqual([]);
});
test('a fixed diagnostic is reported so the smaller allowance gets locked in', () => {
const baseline = parseDiagnostics(output);
const { added, fixed } = compareDiagnostics(baseline, parseDiagnostics(output.split('\n').slice(1).join('\n')));
expect(added).toEqual([]);
expect(fixed).toHaveLength(1);
expect(fixed[0]).toMatchObject({ baseline: 2, current: 1 });
});
test('a missing or malformed baseline fails closed with the regeneration command', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'typecheck-baseline-'));
try {
expect(() => readBaseline(path.join(dir, 'missing.json'))).toThrow('bun run typecheck:test --write-baseline');
fs.writeFileSync(path.join(dir, 'bad.json'), JSON.stringify({ version: 1, diagnostics: { x: 0 } }));
expect(() => readBaseline(path.join(dir, 'bad.json'))).toThrow('malformed');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('the committed baseline is readable and non-empty', () => {
const baseline = readBaseline(path.join(import.meta.dir, '..', 'scripts', 'typecheck-test-baseline.json'));
expect(Object.keys(baseline).length).toBeGreaterThan(0);
});
});