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.
79 lines
3.3 KiB
TypeScript
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']);
|
|
});
|
|
});
|