mirror of
https://github.com/affaan-m/ECC.git
synced 2026-11-15 12:32:11 +00:00
Carry forward AbhiramMandala’s canonical TypeScript guidance from PR #3142, reviewed source head 643f61d604. Rebuild current-main counts, Pi projection and trigger digest without replacing existing registry entries.
Use bracket access under the documented noPropertyAccessFromIndexSignature baseline. Type-check and execute the actual fixture, compare published function bodies, and preserve deterministic undefined, sparse-array and concurrency regressions.
Validation: 26 focused checks; strict TypeScript compile; all repository validators; affected ESLint and Markdownlint; deterministic Pi rebuild; real npm pack; disposable Claude, Codex and Copilot install/repair/uninstall with exact skill bytes and preserved user files. Parent independently reviewed the complete staged diff.
Co-authored-by: AbhiramMandala <abhirammandala02@gmail.com>
281 lines
11 KiB
JavaScript
281 lines
11 KiB
JavaScript
'use strict';
|
|
|
|
// Tests for the typescript-patterns skill: the SKILL.md examples must match
|
|
// their strict-TypeScript claims. Compile the real fixture and execute its
|
|
// emitted functions so the type checks and runtime regressions share one source.
|
|
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const ts = require('typescript');
|
|
|
|
const repoRoot = path.resolve(__dirname, '..', '..');
|
|
const skillPath = path.join(repoRoot, 'skills', 'typescript-patterns', 'SKILL.md');
|
|
const skill = fs.readFileSync(skillPath, 'utf8');
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
const pending = [];
|
|
|
|
function test(name, fn) {
|
|
try {
|
|
const result = fn();
|
|
if (result && typeof result.then === 'function') {
|
|
pending.push(result.then(
|
|
() => { passed += 1; console.log(` ✓ ${name}`); },
|
|
error => { failed += 1; console.log(` ✗ ${name}`); console.log(` ${error.message}`); }
|
|
));
|
|
return;
|
|
}
|
|
passed += 1;
|
|
console.log(` ✓ ${name}`);
|
|
} catch (error) {
|
|
failed += 1;
|
|
console.log(` ✗ ${name}`);
|
|
console.log(` ${error.message}`);
|
|
}
|
|
}
|
|
|
|
console.log('\n=== TypeScript patterns skill tests ===\n');
|
|
|
|
// ---- Skill document claims ---------------------------------------------
|
|
|
|
test('loadUser validates instead of asserting the payload', () => {
|
|
assert.doesNotMatch(skill, /response\.json\(\) as Promise<User>/);
|
|
assert.match(skill, /function isUser\(value: unknown\): value is User/);
|
|
});
|
|
|
|
test('config example validates every consumed field', () => {
|
|
assert.doesNotMatch(skill, /value as Config/);
|
|
assert.match(skill, /toPositiveInt\(raw\['timeout'\]/);
|
|
assert.match(skill, /toPositiveInt\(raw\['retries'\]/);
|
|
assert.match(skill, /DEFAULT_CONFIG/);
|
|
});
|
|
|
|
test('concurrency example narrows indexed access', () => {
|
|
assert.doesNotMatch(skill, /mapper\(items\[index\]\)/);
|
|
assert.match(skill, /Number\.isInteger\(limit\) \|\| limit < 1/);
|
|
assert.match(skill, /noUncheckedIndexedAccess/);
|
|
});
|
|
|
|
test('async error prose states the real semantics', () => {
|
|
assert.match(skill, /always surfaces throws as rejections/);
|
|
assert.doesNotMatch(skill, /lets them escape as rejected promises inconsistently/);
|
|
});
|
|
|
|
// ---- Compile and execute the actual strict TypeScript fixture ----------
|
|
|
|
const fixturePath = path.join(repoRoot, 'tests', 'fixtures', 'typescript-patterns', 'examples.ts');
|
|
const configPath = path.join(path.dirname(fixturePath), 'tsconfig.json');
|
|
const config = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
const parsedConfig = ts.parseJsonConfigFileContent(config.config, ts.sys, path.dirname(configPath));
|
|
const program = ts.createProgram(parsedConfig.fileNames, parsedConfig.options);
|
|
|
|
test('actual fixture compiles with the recommended index-signature baseline', () => {
|
|
assert.strictEqual(parsedConfig.options.noPropertyAccessFromIndexSignature, true);
|
|
const diagnostics = [config.error, ...parsedConfig.errors, ...ts.getPreEmitDiagnostics(program)].filter(Boolean);
|
|
assert.deepStrictEqual(diagnostics.map(d => ts.flattenDiagnosticMessageText(d.messageText, '\n')), []);
|
|
});
|
|
|
|
test('compiled config and concurrency bodies match the published examples', () => {
|
|
const printer = ts.createPrinter({ removeComments: true });
|
|
function bodies(source, filename) {
|
|
const file = ts.createSourceFile(filename, source, ts.ScriptTarget.Latest, true);
|
|
const found = new Map();
|
|
function visit(node) {
|
|
if (ts.isFunctionDeclaration(node) && node.name && node.body) {
|
|
found.set(node.name.text, printer.printNode(ts.EmitHint.Unspecified, node.body, file));
|
|
}
|
|
ts.forEachChild(node, visit);
|
|
}
|
|
visit(file);
|
|
return found;
|
|
}
|
|
const documented = [...skill.matchAll(/```typescript\n([\s\S]*?)```/g)].map(match => match[1]).join('\n');
|
|
const docs = bodies(documented, 'skill.ts');
|
|
const fixtures = bodies(fs.readFileSync(fixturePath, 'utf8'), fixturePath);
|
|
for (const name of ['parseConfig', 'mapWithConcurrency']) {
|
|
assert.ok(docs.has(name), `${name} is missing from the skill`);
|
|
assert.strictEqual(fixtures.get(name), docs.get(name), `${name} fixture drifted from the published example`);
|
|
}
|
|
});
|
|
|
|
const emitted = ts.transpileModule(fs.readFileSync(fixturePath, 'utf8'), {
|
|
compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS },
|
|
}).outputText;
|
|
const fixtureExports = {};
|
|
// Only checked-in fixture code is evaluated; no filesystem or network access.
|
|
new Function('exports', emitted)(fixtureExports);
|
|
const { parseConfig } = fixtureExports.ConfigExample;
|
|
const { loadUser } = fixtureExports.UserExample;
|
|
const { mapWithConcurrency } = fixtureExports.ConcurrencyExample;
|
|
|
|
const okFetch = payload => async () => ({ ok: true, status: 200, json: async () => payload });
|
|
const badFetch = async () => ({ ok: false, status: 404, json: async () => ({}) });
|
|
|
|
// ---- parseConfig --------------------------------------------------------
|
|
|
|
test('parseConfig accepts a valid configuration', () => {
|
|
assert.deepStrictEqual(parseConfig({ timeout: 1000, retries: 5 }), { timeout: 1000, retries: 5 });
|
|
});
|
|
|
|
test('parseConfig rejects a string-valued retries field', () => {
|
|
assert.throws(() => parseConfig({ timeout: 1000, retries: '3' }), TypeError);
|
|
});
|
|
|
|
test('parseConfig applies declared defaults for missing fields', () => {
|
|
assert.deepStrictEqual(parseConfig({}), { timeout: 30_000, retries: 3 });
|
|
assert.deepStrictEqual(parseConfig(null), { timeout: 30_000, retries: 3 });
|
|
});
|
|
|
|
test('parseConfig rejects zero and negative values', () => {
|
|
assert.throws(() => parseConfig({ timeout: 0, retries: 3 }), TypeError);
|
|
assert.throws(() => parseConfig({ timeout: 1000, retries: -1 }), TypeError);
|
|
assert.throws(() => parseConfig({ timeout: 1.5, retries: 3 }), TypeError);
|
|
});
|
|
|
|
// ---- loadUser / isUser ----------------------------------------------------
|
|
|
|
test('loadUser returns a valid user', async () => {
|
|
const user = await loadUser('u_1', okFetch({ id: 'u_1', email: 'a@example.com' }));
|
|
assert.deepStrictEqual(user, { id: 'u_1', email: 'a@example.com' });
|
|
});
|
|
|
|
test('loadUser rejects a response missing email', async () => {
|
|
await assert.rejects(loadUser('u_1', okFetch({ id: 'u_1' })), TypeError);
|
|
});
|
|
|
|
test('loadUser rejects fields with incorrect types', async () => {
|
|
await assert.rejects(loadUser('u_1', okFetch({ id: 1, email: 'a@example.com' })), TypeError);
|
|
await assert.rejects(loadUser('u_1', okFetch({ id: 'u_1', email: 'not-an-email' })), TypeError);
|
|
});
|
|
|
|
test('loadUser rejects null, arrays, and other malformed payloads', async () => {
|
|
await assert.rejects(loadUser('u_1', okFetch(null)), TypeError);
|
|
await assert.rejects(loadUser('u_1', okFetch([{ id: 'u_1', email: 'a@example.com' }])), TypeError);
|
|
await assert.rejects(loadUser('u_1', okFetch('oops')), TypeError);
|
|
});
|
|
|
|
test('loadUser rejects failed fetches and invalid JSON', async () => {
|
|
await assert.rejects(loadUser('u_1', badFetch), /Failed to load user/);
|
|
await assert.rejects(loadUser('u_1', async () => { throw new Error('bad json'); }), /bad json/);
|
|
});
|
|
|
|
// ---- mapWithConcurrency ----------------------------------------------------
|
|
|
|
test('mapWithConcurrency handles an empty input array', async () => {
|
|
assert.deepStrictEqual(await mapWithConcurrency([], 4, async x => x), []);
|
|
});
|
|
|
|
test('mapWithConcurrency maps with a valid limit', async () => {
|
|
assert.deepStrictEqual(await mapWithConcurrency([1, 2, 3], 2, async x => x * 2), [2, 4, 6]);
|
|
});
|
|
|
|
test('mapWithConcurrency accepts limits greater than the input length', async () => {
|
|
assert.deepStrictEqual(await mapWithConcurrency([1, 2], 10, async x => x + 1), [2, 3]);
|
|
});
|
|
|
|
test('mapWithConcurrency rejects zero and negative limits', async () => {
|
|
await assert.rejects(mapWithConcurrency([1], 0, async x => x), RangeError);
|
|
await assert.rejects(mapWithConcurrency([1], -2, async x => x), RangeError);
|
|
});
|
|
|
|
test('mapWithConcurrency rejects fractional and non-finite limits', async () => {
|
|
await assert.rejects(mapWithConcurrency([1], 1.5, async x => x), RangeError);
|
|
await assert.rejects(mapWithConcurrency([1], NaN, async x => x), RangeError);
|
|
await assert.rejects(mapWithConcurrency([1], Infinity, async x => x), RangeError);
|
|
});
|
|
|
|
test('mapWithConcurrency maps a present undefined element', async () => {
|
|
const seen = [];
|
|
const out = await mapWithConcurrency([undefined, 1], 2, async x => {
|
|
seen.push(x);
|
|
return x === undefined ? 0 : x;
|
|
});
|
|
assert.deepStrictEqual(seen, [undefined, 1]);
|
|
assert.deepStrictEqual(out, [0, 1]);
|
|
});
|
|
|
|
test('mapWithConcurrency skips only genuine sparse-array holes', async () => {
|
|
// Index 1 is a real hole; index 0 holds a present `undefined` value.
|
|
const input = new Array(3);
|
|
input[0] = undefined;
|
|
input[2] = 3;
|
|
const seen = [];
|
|
const out = await mapWithConcurrency(input, 3, async x => {
|
|
seen.push(x);
|
|
return x === undefined ? 'was-undefined' : x;
|
|
});
|
|
// The hole never reaches the mapper; the present undefined does.
|
|
assert.deepStrictEqual(seen, [undefined, 3]);
|
|
assert.strictEqual(out[0], 'was-undefined');
|
|
assert.strictEqual(out[2], 3);
|
|
assert.ok(!(1 in out), 'hole at index 1 should stay a hole in the result, not become a value');
|
|
assert.strictEqual(out.length, 3);
|
|
});
|
|
|
|
function deferred() {
|
|
let release;
|
|
const promise = new Promise(resolve => { release = resolve; });
|
|
return { promise, release };
|
|
}
|
|
|
|
test('mapWithConcurrency preserves order when mappers complete out of their input order', async () => {
|
|
const gates = [deferred(), deferred(), deferred(), deferred()];
|
|
const started = [];
|
|
const completed = [];
|
|
const resultPromise = mapWithConcurrency([0, 1, 2, 3], 4, async x => {
|
|
started.push(x);
|
|
await gates[x].promise;
|
|
completed.push(x);
|
|
return x * 10;
|
|
});
|
|
// No gate has been released yet, so having every mapper started proves
|
|
// concurrency happened before any completion.
|
|
await Promise.resolve();
|
|
assert.deepStrictEqual(started, [0, 1, 2, 3]);
|
|
// Release the gates in reverse input order and record the actual
|
|
// completion order; determinism comes from the gates, not timers.
|
|
for (let i = 3; i >= 0; i -= 1) {
|
|
gates[i].release();
|
|
}
|
|
const out = await resultPromise;
|
|
assert.deepStrictEqual(completed, [3, 2, 1, 0]);
|
|
// The completion order differs from input order, yet results still land
|
|
// in input order.
|
|
assert.deepStrictEqual(out, [0, 10, 20, 30]);
|
|
});
|
|
|
|
test('mapWithConcurrency propagates mapper rejection', async () => {
|
|
await assert.rejects(
|
|
mapWithConcurrency([1, 2, 3], 2, async x => { if (x === 2) throw new Error('boom'); return x; }),
|
|
/boom/
|
|
);
|
|
});
|
|
|
|
test('mapWithConcurrency never exceeds the configured limit', async () => {
|
|
let active = 0;
|
|
let maxActive = 0;
|
|
await mapWithConcurrency([1, 2, 3, 4, 5, 6], 2, async x => {
|
|
active += 1;
|
|
maxActive = Math.max(maxActive, active);
|
|
await new Promise(resolve => setTimeout(resolve, 5));
|
|
active -= 1;
|
|
return x;
|
|
});
|
|
assert.ok(maxActive <= 2, `peak concurrency ${maxActive} exceeded limit 2`);
|
|
assert.ok(maxActive > 1, 'workers never overlapped; pool may be serial');
|
|
});
|
|
|
|
test('async functions surface throws as rejections, sync returns do not', async () => {
|
|
const { asyncFails, lazyFails } = fixtureExports.AsyncErrorsExample;
|
|
await assert.rejects(asyncFails(), /becomes a rejection/);
|
|
assert.throws(() => lazyFails(), /throws synchronously/);
|
|
});
|
|
|
|
Promise.allSettled(pending).then(() => {
|
|
console.log(`\nPassed: ${passed}`);
|
|
console.log(`Failed: ${failed}`);
|
|
process.exit(failed > 0 ? 1 : 0);
|
|
});
|