Files
codegraph/__tests__/store-binding-cache.test.ts
Colby Mchenry 4297b8e2ed fix: restore call graph relationships and Steps effects (#1862)
* docs: record regression audit and validation

* test: cover audited call and extraction regressions

* test: cover audited calls and selector guards

* test: cover audited calls and selector guards

* test: cover audited calls and selector guards

* test: cover audited calls and selector guards

* test: cover audited calls and selector guards

* fix: retain qualified call sites and restore language-specific getters

* fix: resolve typed calls and bound store actions

* fix: retain qualified call sites in the native kernel

* docs: explain restored call and Steps coverage

* test: isolate watchdog and cover imported store aliases

* refactor: expose import lookup through resolver context

* refactor: remove import resolver cycle

* test: canonicalize Claude config temp paths

* docs: record review fixes and large TypeScript timings

* test: cover store cache edits and document verification

* perf: cache per-file store eligibility

* fix: observe large benchmarks without a wall timeout

* docs: explain completed indexing benchmarks

docs: explain completed indexing benchmarks
2026-09-15 05:30:09 +00:00

87 lines
3.6 KiB
TypeScript

import { afterEach, describe, expect, it } from 'vitest';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { CodeGraph } from '../src';
const projects: { dir: string; cg: CodeGraph }[] = [];
afterEach(() => {
for (const { dir, cg } of projects.splice(0)) {
cg.close();
fs.rmSync(dir, { recursive: true, force: true });
}
});
function consumer(active: boolean): string {
return `import { useStore as current } from './store';
export function run() {
const { reset } = ${active ? 'current.getState()' : 'external()'};
reset();
reset();
}
export function effects(client: any) {
client.user.create();
client?.user?.create();
}
`;
}
async function project(active: boolean) {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-store-cache-'));
fs.writeFileSync(path.join(dir, 'store.ts'), `import { create } from 'zustand';
export const useStore = create((set) => ({ reset: () => set({}) }));
`);
fs.writeFileSync(path.join(dir, 'decoy.ts'), 'export function reset() { return 99; }');
fs.writeFileSync(path.join(dir, 'consumer.ts'), consumer(active));
const cg = CodeGraph.initSync(dir);
projects.push({ dir, cg });
const result = await cg.indexAll();
expect(result.success).toBe(true);
expect(result.filesErrored).toBe(0);
return { dir, cg };
}
function assertBindings(cg: CodeGraph, active: boolean) {
const functions = cg.getNodesByKind('function');
const run = functions.find(n => n.name === 'run' && n.filePath === 'consumer.ts')!;
const action = functions.find(n => n.name === 'reset' && n.filePath === 'store.ts')!;
const decoy = functions.find(n => n.name === 'reset' && n.filePath === 'decoy.ts')!;
const targets = cg.getOutgoingEdges(run.id).filter(e => e.kind === 'calls').map(e => e.target);
expect(targets.includes(action.id)).toBe(active);
expect(targets).not.toContain(decoy.id);
const pendingActions = cg.getUnresolvedReferencesFrom(run.id).filter(r => r.referenceName === 'reset');
expect(pendingActions).toHaveLength(active ? 0 : 2);
// Eligibility must not remove untyped qualified call-site evidence, including
// repeated/optional chains, or turn it into a guessed edge.
const effects = functions.find(n => n.name === 'effects' && n.filePath === 'consumer.ts')!;
expect(cg.getOutgoingEdges(effects.id).filter(e => e.kind === 'calls')).toEqual([]);
expect(cg.getUnresolvedReferencesFrom(effects.id).filter(r => r.referenceKind === 'calls')
.map(r => [r.referenceName, r.line, r.column])).toEqual([
['client.user.create', 8, 2], ['client.user.create', 9, 2],
]);
}
describe('store eligibility cache across edits and resolver contexts', () => {
it.each([false, true])('sync refreshes eligibility starting with getState=%s', async (initial) => {
const { dir, cg } = await project(initial);
assertBindings(cg, initial);
// Reuse the same CodeGraph/resolver and path in both directions. A cached
// negative must not mask a new store binding, and removing it must remove
// both action edges while preserving unresolved call-site evidence.
for (const active of [!initial, initial]) {
fs.writeFileSync(path.join(dir, 'consumer.ts'), consumer(active));
const result = await cg.sync();
expect(result.filesModified).toBe(1);
assertBindings(cg, active);
}
}, 60000);
it('does not share eligibility between projects with the same relative file path', async () => {
const absent = await project(false);
const present = await project(true);
assertBindings(absent.cg, false);
assertBindings(present.cg, true);
}, 60000);
});