mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-03 08:03:38 +08:00
fix(ui): hide stale focus warning while arranging graph
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { createRoot, type Root } from 'react-dom/client';
|
||||
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
|
||||
import GalaxyPanel, { type GalaxyPanelProps } from './GalaxyPanel';
|
||||
import type { GraphData, GraphNode } from './types';
|
||||
import type { OrganicLayoutRequest, OrganicLayoutResponse } from './organic-layout.worker';
|
||||
|
||||
/** The layout can be capped; focused relationships come independently from RPC. */
|
||||
function graphFetch(nodes: GraphNode[], edges: GraphData['edges'], total = nodes.length) {
|
||||
@@ -102,10 +103,24 @@ it('opens a file hierarchy without a caret symbol and follows literal marked ran
|
||||
expect(fetchLayout.mock.calls.filter(([url]) => String(url).includes('/api/layout'))).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('does not report a file missing from a capped layout while its indexed scope is loading', async () => {
|
||||
it.each([true, false])('does not report a file missing while its scope or layout is loading (indexed: %s)', async indexed => {
|
||||
let worker!: LayoutWorker;
|
||||
class LayoutWorker {
|
||||
onmessage: ((event: MessageEvent<OrganicLayoutResponse>) => void) | null = null;
|
||||
requests: OrganicLayoutRequest[] = [];
|
||||
constructor() { worker = this; }
|
||||
postMessage(request: OrganicLayoutRequest) { this.requests.push(request); }
|
||||
terminate() {}
|
||||
reply() {
|
||||
const { sequence, data } = this.requests.at(-1)!;
|
||||
this.onmessage?.(new MessageEvent<OrganicLayoutResponse>('message', { data: { sequence,
|
||||
result: { data, groups: [], groupByNode: new Map() } } }));
|
||||
}
|
||||
}
|
||||
vi.stubGlobal('Worker', LayoutWorker);
|
||||
const node: GraphNode = { id: 15, name: 'outside', qualified_name: 'sample.outside', label: 'Function',
|
||||
file_path: 'src/a.ts', start_line: 1, end_line: 10, x: 0, y: 0, z: 0, size: 1, color: '#999999' };
|
||||
const source = graphFetch([node], []);
|
||||
const source = graphFetch(indexed ? [node] : [], []);
|
||||
let release!: () => void;
|
||||
const pending = new Promise<void>(resolve => { release = resolve; });
|
||||
const fetchImpl = vi.fn(async (url: RequestInfo | URL, init?: RequestInit) => {
|
||||
@@ -114,15 +129,27 @@ it('does not report a file missing from a capped layout while its indexed scope
|
||||
return source(url, init);
|
||||
});
|
||||
await act(async () => root.render(<GalaxyPanel project="sample" visible focusFilePath="src/a.ts" onOpenNode={vi.fn()} fetch={fetchImpl} />));
|
||||
// Hand test K8: the status names the layer that is loading instead of an endless "Loading relationships".
|
||||
// It may first say "Checking index…" while the index generation is read; wait for the load itself.
|
||||
await vi.waitFor(() => expect(host.textContent).toContain('Loading layer 1…'));
|
||||
expect(host.textContent).toContain('Loading layer 1…');
|
||||
// Finish the empty capped-layout preview while the indexed scope is held.
|
||||
// This leaves a missing-file note in state, hidden until the scope finishes.
|
||||
expect(worker.requests).toHaveLength(1);
|
||||
expect(worker.requests[0]!.data.nodes).toEqual([]);
|
||||
await act(async () => worker.reply());
|
||||
expect(host.querySelector('[data-testid="atlas-galaxy-note"]')).toBeNull();
|
||||
await act(async () => { release(); });
|
||||
expect(host.textContent).toContain('All indexed direct dependencies included.');
|
||||
// The scope is complete, but its new picture has not been arranged yet.
|
||||
expect(worker.requests).toHaveLength(2);
|
||||
expect(host.querySelector('[role="status"][data-state="arranging"]')).not.toBeNull();
|
||||
expect(host.querySelector('[data-testid="atlas-galaxy-note"]')).toBeNull();
|
||||
expect(host.textContent).not.toContain('not in the loaded graph');
|
||||
await act(async () => worker.reply());
|
||||
expect(host.querySelector('[role="status"][data-state="complete"]')).not.toBeNull();
|
||||
expect(JSON.parse(host.querySelector('[data-testid="graph-selection"]')!.textContent!)).toEqual(indexed ? [15] : []);
|
||||
if (indexed) expect(host.querySelector('[data-testid="atlas-galaxy-note"]')).toBeNull();
|
||||
else expect(host.querySelector('[data-testid="atlas-galaxy-note"]')?.textContent).toBe('src/a.ts is not in the loaded graph layout.');
|
||||
});
|
||||
afterEach(async () => { await act(async () => root.unmount()); host.remove(); globalThis.__atlasGalaxy = undefined; });
|
||||
afterEach(async () => { await act(async () => root.unmount()); host.remove(); globalThis.__atlasGalaxy = undefined; vi.unstubAllGlobals(); });
|
||||
|
||||
it('selects the whole file, narrows only to marked code, and restores the file when the mark clears', async () => {
|
||||
const common = { file_path: 'src/a.ts', x: 0, y: 0, z: 0, size: 1, color: '#999999' };
|
||||
|
||||
@@ -3055,7 +3055,7 @@ export default function GalaxyPanel(props: GalaxyPanelProps): JSX.Element {
|
||||
<summary>Selection details</summary>
|
||||
<div className="atlas-galaxy-selection-details-body">{selectionContent}</div>
|
||||
</details>}
|
||||
{note.length > 0 && !scope.loading && (
|
||||
{note.length > 0 && !scope.loading && !organicTask.loading && (
|
||||
<p className="atlas-galaxy-note" data-testid="atlas-galaxy-note">
|
||||
{props.workspaceExpanded && note === GALAXY_NO_FOCUS_NOTE
|
||||
? 'Select a node or entry point to attach graph context to chat.' : note}
|
||||
|
||||
Reference in New Issue
Block a user