diff --git a/apps/frontend/e2e/architecture-visual.spec.ts b/apps/frontend/e2e/architecture-visual.spec.ts index 597ac153..302f90f2 100644 --- a/apps/frontend/e2e/architecture-visual.spec.ts +++ b/apps/frontend/e2e/architecture-visual.spec.ts @@ -51,7 +51,7 @@ async function openSurface(page: Page, name: string) { async function selectRepository(page: Page, fixture: Fixture) { const trigger = page.locator('header button').filter({ - hasText: /^(No repository|small|medium|large-multi|large-single|long-labels|disconnected-unresolved|unanalysed)$/, + hasText: /^(No repository|small|medium|large-multi|large-single|long-labels|disconnected-unresolved|scale-over-500|unanalysed)$/, }).first(); await trigger.click(); await page.locator('button').filter({ hasText: new RegExp(`^${fixture.name}$`) }).last().click(); @@ -329,3 +329,31 @@ test.describe('architecture graph visual acceptance', () => { await capture(manifest, 'revision-manifest', testInfo); }); }); + + +test('over-500 relationship fixture remains usable without discarding evidence', async ({page}, testInfo) => { + const fixture = byLabel('scale-over-500'); + expect(fixture.nodes).toBeGreaterThanOrEqual(80); + expect(fixture.edges).toBeGreaterThan(500); + await login(page); + const started = performance.now(); + await openArchitecture(page, fixture); + await waitForGraph(page); + const renderedMs = performance.now() - started; + await expect(page.getByTestId('large-graph-notice')).toBeVisible(); + expect(await page.locator('.react-flow__edge').count()).toBe(0); + const visibleNodeId = await graphNodes(page).evaluateAll(elements => { + const canvas = document.querySelector('.react-flow')!.getBoundingClientRect(); + return elements.find(element => { + const box = element.getBoundingClientRect(); + return box.x > canvas.x + 60 && box.right < canvas.right - 60 && box.y > canvas.y + 80 && box.bottom < canvas.bottom - 80; + })?.getAttribute('data-id'); + }); + expect(visibleNodeId).toBeTruthy(); + await page.locator(`.react-flow__node[data-id="${visibleNodeId}"]`).click(); + await expect.poll(() => page.locator('.react-flow__edge').count()).toBeGreaterThan(0); + await page.getByRole('button', {name:'Zoom In',exact:true}).click(); + expect(renderedMs).toBeLessThan(20_000); + await testInfo.attach('scale-measurement', {body:JSON.stringify({nodes:fixture.nodes,edges:fixture.edges,renderedMs}),contentType:'application/json'}); + await capture(page,'architecture-over-500',testInfo); +}); diff --git a/scripts/seed-e2e-fixtures.mjs b/scripts/seed-e2e-fixtures.mjs index 1e012233..cb24336f 100644 --- a/scripts/seed-e2e-fixtures.mjs +++ b/scripts/seed-e2e-fixtures.mjs @@ -23,6 +23,13 @@ const moduleSource = (name, imports = []) => [ ].join('\n'); const FIXTURES = [ + { + label: 'scale-over-500', + files: Object.fromEntries([ + ['package.json', JSON.stringify({name:'fixture-scale',dependencies:Object.fromEntries(Array.from({length:8},(_,i)=>[`fixture-package-${i}`,'1.0.0']))})], + ...Array.from({length:80},(_,i)=>[`src/feature-${i}/index.ts`,moduleSource(`Module${i}`,Array.from({length:8},(_,j)=>`fixture-package-${j}`))]), + ]), + }, { label: 'small', files: {