Files
codegraph/__tests__/namespace-object-resolution.test.ts
T
Colby McHenry 873f133c96 feat(expo-router): add Expo Router support for Screens and navigations and introduce Steps API
Introduce Expo Router integration with a new Screens view and API to surface screens and transitions, plus a new Steps API and UI to depict typed steps from anchors or symbols. Extend codegraph’s extraction and resolution to handle namespace objects (export default NAME, two-statement forms, and default bindings) and React hook bindings for handlers, improving accuracy of flows across JS ↔ native boundaries. Add Swift/React Native bridge receiver evidence (RCT_EXTERN_MODULE, RCT_EXTERN_METHOD) and related resolution logic, with tests covering namespace-object resolution, useCallback-driven handlers, and inline RN event listeners. Update UI to include a Steps tab and associated components (StepsView, StepNode, ScreenEdge) and wire navigation to expose steps-based exploration via /api/steps and UI routes. Documentation and changelog reflect the new Expo Router integration and steps surface capabilities.
2026-08-28 09:46:50 -05:00

79 lines
3.3 KiB
TypeScript

/**
* The default-export namespace object — `const UploadApi = { uploadARCapture };
* export default UploadApi` — and a call through it from another file. Two
* things have to hold for `handleZipComplete → uploadARCapture` to exist:
* the default import must find the constant the `export default` statement
* names (it is not exported at its declaration), and the member must resolve
* to the binding the shorthand property carries, through the object's own
* imports.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as os from 'node:os';
import { CodeGraph } from '../src';
describe('namespace object default exports', () => {
let dir: string;
beforeEach(() => {
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-namespace-object-'));
});
afterEach(() => {
fs.rmSync(dir, { recursive: true, force: true });
});
function write(rel: string, content: string): void {
const full = path.join(dir, rel);
fs.mkdirSync(path.dirname(full), { recursive: true });
fs.writeFileSync(full, content);
}
it('resolves Api.member() to the function the shorthand property names', async () => {
write('package.json', '{"name":"app"}');
write('src/api/frames.ts', 'export async function uploadARCapture(uri: string) {\n return uri\n}\n');
write('src/api/folders.ts', 'export function createFolder(name: string) {\n return name\n}\n');
write(
'src/api/index.ts',
"import { uploadARCapture } from './frames'\n" +
"import { createFolder } from './folders'\n" +
'function localHelper() {\n return 1\n}\n' +
'const UploadApi = {\n uploadARCapture,\n makeFolder: createFolder,\n localHelper,\n}\n' +
'export default UploadApi\n'
);
write(
'src/hooks.ts',
"import UploadApi from './api'\n" +
'export function handleZipComplete(uri: string) {\n' +
' UploadApi.makeFolder(uri)\n' +
' UploadApi.localHelper()\n' +
' return UploadApi.uploadARCapture(uri)\n' +
'}\n'
);
const cg = await CodeGraph.init(dir, { silent: true });
await cg.indexAll();
const handler = cg.getNodesByName('handleZipComplete')[0]!;
const callees = cg.getCallees(handler.id).map((c) => c.node.name).sort();
cg.close();
expect(callees).toEqual(['createFolder', 'localHelper', 'uploadARCapture']);
});
it('a default import of a later-exported const finds that const, and a method inside it', async () => {
write('package.json', '{"name":"app"}');
write(
'src/store.ts',
'const useStore = {\n read() {\n return 1\n },\n}\nexport function unrelated() {\n return 2\n}\nexport default useStore\n'
);
write('src/use.ts', "import store from './store'\nexport function consume() {\n return store.read()\n}\n");
const cg = await CodeGraph.init(dir, { silent: true });
await cg.indexAll();
const consume = cg.getNodesByName('consume')[0]!;
const callees = cg.getCallees(consume.id).map((c) => c.node.name);
cg.close();
// Without the `export default NAME` binding the default import guessed the
// first exported function (`unrelated`); now it is the object, and the
// member resolves inside it.
expect(callees).toEqual(['read']);
});
});