|
|
@@ -40,6 +40,28 @@ describe('codegraph_explore — blast radius', () => {
|
|
|
path.join(src, 'leaf.ts'),
|
|
|
`export function lonelyLeaf() { return 42; }\n`,
|
|
|
);
|
|
|
+ // `deepHelper` is only called by production code (`midCaller`), but the
|
|
|
+ // test file exercises it transitively — 2 caller hops up (#1475).
|
|
|
+ fs.writeFileSync(
|
|
|
+ path.join(src, 'util.ts'),
|
|
|
+ `export function deepHelper() { return 1; }\n`,
|
|
|
+ );
|
|
|
+ fs.writeFileSync(
|
|
|
+ path.join(src, 'mid.ts'),
|
|
|
+ `import { deepHelper } from './util';\n` +
|
|
|
+ `export function midCaller() { return deepHelper(); }\n`,
|
|
|
+ );
|
|
|
+ fs.writeFileSync(
|
|
|
+ path.join(src, 'mid.test.ts'),
|
|
|
+ `import { midCaller } from './mid';\n` +
|
|
|
+ `export function checkMid() { return midCaller(); }\n`,
|
|
|
+ );
|
|
|
+ // `untestedHelper` has a caller but no test anywhere up its caller chain.
|
|
|
+ fs.writeFileSync(
|
|
|
+ path.join(src, 'untested.ts'),
|
|
|
+ `export function untestedHelper() { return 3; }\n` +
|
|
|
+ `export function untestedCaller() { return untestedHelper(); }\n`,
|
|
|
+ );
|
|
|
|
|
|
cg = CodeGraph.initSync(testDir, { config: { include: ['**/*.ts'], exclude: [] } });
|
|
|
await cg.indexAll();
|
|
|
@@ -60,8 +82,28 @@ describe('codegraph_explore — blast radius', () => {
|
|
|
expect(text).toMatch(/caller/); // a caller count is reported
|
|
|
// It names WHERE (the caller file) — not the caller's source body.
|
|
|
expect(text).toContain('feature.ts');
|
|
|
- // Test coverage is surfaced (either the covering test file, or the warning).
|
|
|
- expect(text).toMatch(/tests:.*feature\.test\.ts|no covering tests/);
|
|
|
+ // The direct covering test file is surfaced.
|
|
|
+ expect(text).toMatch(/tests:.*feature\.test\.ts/);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('surfaces tests that cover a symbol transitively through its callers (#1475)', async () => {
|
|
|
+ const res = await handler.execute('codegraph_explore', { query: 'deepHelper' });
|
|
|
+ const text = res.content[0].text;
|
|
|
+
|
|
|
+ // deepHelper's only direct caller is production code, but mid.test.ts sits
|
|
|
+ // one more hop up — that must NOT read as "no tests".
|
|
|
+ expect(text).toMatch(/`deepHelper`[^\n]*tested via callers:[^\n]*mid\.test\.ts/);
|
|
|
+ const line = text.split('\n').find((l: string) => l.startsWith('- `deepHelper`'));
|
|
|
+ expect(line).not.toMatch(/no tests found|no covering tests/);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('states only what was measured when no test exists up the caller chain', async () => {
|
|
|
+ const res = await handler.execute('codegraph_explore', { query: 'untestedHelper' });
|
|
|
+ const text = res.content[0].text;
|
|
|
+
|
|
|
+ // Bounded claim, no warning glyph — the tool verified nothing beyond 3 hops.
|
|
|
+ expect(text).toMatch(/`untestedHelper`[^\n]*no tests found within 3 caller hops/);
|
|
|
+ expect(text).not.toContain('⚠️ no covering tests found');
|
|
|
});
|
|
|
|
|
|
it('omits symbols that have no dependents from the blast radius', async () => {
|