From ef95339154f77b5e32cbe67c2c6d7802e8ea1005 Mon Sep 17 00:00:00 2001 From: Bernhard Jackiewicz Date: Sun, 4 Oct 2026 14:49:51 +0200 Subject: [PATCH] fix(ui): say why Expand +1 is blocked while a layer loads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While a layer loads, Expand +1 is blocked (aria-disabled) and still takes the pointer and the focus, but it showed no tooltip: there was no text for that state, and an empty hint shows nothing. It now reads "Layer 3 is loading; "−" cancels it." on hover and on focus. Signed-off-by: Bernhard Jackiewicz --- .../GalaxyPanel.expand-loading.test.tsx | 73 +++++++++++++++++++ graph-ui/src/galaxy/GalaxyPanel.tsx | 4 +- graph-ui/src/galaxy/galaxy-strings.ts | 2 + 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 graph-ui/src/galaxy/GalaxyPanel.expand-loading.test.tsx diff --git a/graph-ui/src/galaxy/GalaxyPanel.expand-loading.test.tsx b/graph-ui/src/galaxy/GalaxyPanel.expand-loading.test.tsx new file mode 100644 index 000000000..2448bc790 --- /dev/null +++ b/graph-ui/src/galaxy/GalaxyPanel.expand-loading.test.tsx @@ -0,0 +1,73 @@ +// @vitest-environment jsdom +/* + * Review zu K31: waehrend eine Ebene laedt, ist Expand +1 gesperrt + * (aria-disabled), nimmt Zeiger und Fokus, zeigte aber keinen Tooltip: der + * Text fuer diesen Zustand fehlte, und ein leerer Hinweis zeigt nichts. Jetzt + * sagt der Knopf, welche Ebene laedt und dass "−" sie abbricht. + */ +import { act } from 'react'; +import { createRoot, type Root } from 'react-dom/client'; +import { afterEach, beforeEach, expect, it, vi } from 'vitest'; +import GalaxyPanel from './GalaxyPanel'; +import { galaxyLayerText } from './galaxy-strings'; +import { scopeFetch, scopeNode } from './test-scope-fetch'; +import type { GraphEdge } from './types'; + +vi.mock('./GraphScene', async importOriginal => ({ + ...await importOriginal(), + GraphScene: () => , +})); + +let host: HTMLDivElement, root: Root; +beforeEach(() => { + (globalThis as unknown as Record).IS_REACT_ACT_ENVIRONMENT = true; + host = document.createElement('div'); document.body.append(host); root = createRoot(host); +}); +afterEach(async () => { + await act(async () => root.unmount()); host.remove(); globalThis.__atlasGalaxy = undefined; + window.localStorage.clear(); +}); + +const settle = (check: () => void) => vi.waitFor(async () => { + await act(async () => { await new Promise(resolve => setTimeout(resolve, 0)); }); + check(); +}); +const status = () => host.querySelector('.atlas-graph-scope-count'); +const expand = () => [...host.querySelectorAll('button')].find(entry => entry.getAttribute('aria-label') === 'Expand +1')!; +const shown = () => document.querySelector('[data-testid="atlas-hint"][data-hint-for="galaxy-expand"]'); + +it('K31: while a layer loads, the blocked Expand +1 says which layer loads and that "−" cancels it, on hover and on focus', async () => { + const nodes = [1, 2, 3, 4, 5, 6].map(id => scopeNode(id)); + const edges: GraphEdge[] = [{ id: 1, source: 1, target: 2, type: 'CALLS' }, { id: 2, source: 2, target: 3, type: 'CALLS' }, + { id: 3, source: 4, target: 2, type: 'CALLS' }, { id: 4, source: 3, target: 5, type: 'CALLS' }, { id: 5, source: 6, target: 1, type: 'TESTS' }]; + let block = false, release: () => void = () => {}, queries = 0; + const { fetch } = scopeFetch({ nodes, edges, gate: async () => { + queries += 1; + if (block && queries > 1) await new Promise(resolve => { release = resolve; }); + } }); + await act(async () => root.render()); + await settle(() => expect(globalThis.__atlasGalaxy?.nodes).toBe(6)); + await act(async () => { globalThis.__atlasGalaxy!.clickNode('sample.n1'); }); + await settle(() => expect(status()?.textContent).toBe('3 nodes · 2 edges')); + + block = true; queries = 0; + await act(async () => expand().click()); + await settle(() => expect(status()?.getAttribute('data-state')).toBe('loading')); + expect(expand().getAttribute('aria-disabled')).toBe('true'); + expect(expand().getAttribute('data-hint')).toBe('Layer 2 is loading; "−" cancels it.'); + expect(galaxyLayerText.expandLoading(2)).toBe('Layer 2 is loading; "−" cancels it.'); + + await act(async () => { expand().dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); }); + expect(shown()?.textContent).toBe('Layer 2 is loading; "−" cancels it.'); + await act(async () => { expand().dispatchEvent(new MouseEvent('mouseout', { bubbles: true })); }); + expect(shown()).toBeNull(); + await act(async () => { expand().focus(); }); + expect(shown()?.textContent).toBe('Layer 2 is loading; "−" cancels it.'); + // A press while it loads still does nothing. + await act(async () => expand().click()); + expect([...host.querySelectorAll('.atlas-graph-exploration span')].some(entry => entry.textContent === '2 layers')).toBe(true); + await act(async () => { expand().blur(); release(); }); + await settle(() => expect(status()?.getAttribute('data-state')).not.toBe('loading')); + // Loaded: the tooltip speaks about the next layer again. + expect(expand().getAttribute('data-hint') ?? '').not.toContain('is loading'); +}); diff --git a/graph-ui/src/galaxy/GalaxyPanel.tsx b/graph-ui/src/galaxy/GalaxyPanel.tsx index 872429e67..9395595a9 100644 --- a/graph-ui/src/galaxy/GalaxyPanel.tsx +++ b/graph-ui/src/galaxy/GalaxyPanel.tsx @@ -2113,7 +2113,9 @@ export default function GalaxyPanel(props: GalaxyPanelProps): JSX.Element { const expandPast = expandOutlook && expandPastLimit(expandOutlook); const expandWarning = Boolean(expandPast); const expandBlocked = Boolean(scope.loading || scope.result?.exhausted || scope.result?.partial); - const expandTitle = partial ? galaxyLayerText.expandPartial : scope.result?.exhausted ? galaxyLayerText.expandEnd + // Review zu K31: gesperrt heisst immer mit Grund, auch waehrend eine Ebene laedt. + const expandTitle = scope.loading ? galaxyLayerText.expandLoading(scope.depth) + : partial ? galaxyLayerText.expandPartial : scope.result?.exhausted ? galaxyLayerText.expandEnd : expandOutlook ? galaxyLayerText.expandHint(expandOutlook, expandPast) : undefined; /* diff --git a/graph-ui/src/galaxy/galaxy-strings.ts b/graph-ui/src/galaxy/galaxy-strings.ts index 5e428b301..ec6bf5e1a 100644 --- a/graph-ui/src/galaxy/galaxy-strings.ts +++ b/graph-ui/src/galaxy/galaxy-strings.ts @@ -171,6 +171,8 @@ export const galaxyLayerText = { }, expandPartial: 'This layer stopped loading after it passed the render limit, so there is no complete edge to grow from. Raise the limit under Limits first.', expandEnd: 'End of trace: no relationship leads further.', + /** Review of K31: Expand +1 is blocked while a layer loads, and says why. */ + expandLoading: (layer: number) => `Layer ${layer} is loading; "−" cancels it.`, }; /** Compact toolbar words, so the scoped toolbar keeps to one row at 1600 px. */