diff --git a/e2e/breadcrumb.spec.ts b/e2e/breadcrumb.spec.ts new file mode 100644 index 0000000..5dfdc99 --- /dev/null +++ b/e2e/breadcrumb.spec.ts @@ -0,0 +1,106 @@ +import { test, expect, type Page } from '@playwright/test'; + +async function waitForGraph(page: Page) { + await page.waitForSelector('svg g.node', { timeout: 15000 }); +} + +function nodeByLabel(page: Page, label: string) { + return page.locator('g.node').filter({ hasText: label }); +} + +async function openSubgraph(page: Page, nodeLabel: string) { + await nodeByLabel(page, nodeLabel).click({ button: 'right' }); + await expect(page.locator('.ant-dropdown:visible')).toBeVisible({ timeout: 3000 }); + // Wait for the dropdown animation to fully settle before clicking + await page.waitForTimeout(300); + await page.locator('.ant-dropdown-menu-item').filter({ hasText: 'Subgraph' }).click(); + await waitForGraph(page); +} + +const breadcrumbLinks = (page: Page) => page.locator('.ant-breadcrumb-link'); + +test.describe('Breadcrumb navigation', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/'); + await waitForGraph(page); + }); + + test('shows "Main" as the only breadcrumb in the initial state', async ({ page }) => { + await expect(breadcrumbLinks(page)).toHaveCount(1); + await expect(page.locator('.ant-breadcrumb')).toContainText('Main'); + }); + + test('adds a breadcrumb segment when entering a subgraph', async ({ page }) => { + await openSubgraph(page, 'Start'); + + await expect(breadcrumbLinks(page)).toHaveCount(2); + await expect(page.locator('.ant-breadcrumb')).toContainText('Main'); + await expect(page.locator('.ant-breadcrumb')).toContainText('Start'); + }); + + test('breadcrumb grows for each additional level of nesting', async ({ page }) => { + // Level 1: enter Start's subgraph + await openSubgraph(page, 'Start'); + await expect(breadcrumbLinks(page)).toHaveCount(2); + + // Level 2: enter Start's subgraph again from within level 1 + await openSubgraph(page, 'Start'); + await expect(breadcrumbLinks(page)).toHaveCount(3); + }); + + test('clicking the root breadcrumb from two levels deep returns to main graph', async ({ page }) => { + // Navigate 2 levels deep + await openSubgraph(page, 'Start'); + await openSubgraph(page, 'Start'); + await expect(breadcrumbLinks(page)).toHaveCount(3); + + // Click "Main" (first item) + await breadcrumbLinks(page).first().click(); + + await expect(breadcrumbLinks(page)).toHaveCount(1, { timeout: 3000 }); + await expect(page.locator('.ant-breadcrumb')).toContainText('Main'); + await waitForGraph(page); + await expect(nodeByLabel(page, 'Start')).toBeVisible(); + await expect(nodeByLabel(page, 'End')).toBeVisible(); + }); + + test('clicking a middle breadcrumb navigates to that level and removes subsequent segments', async ({ page }) => { + // Navigate to level 1 (Start's subgraph) + await openSubgraph(page, 'Start'); + + // Add a node at level 1 so we can recognise it when we return + await nodeByLabel(page, 'End').click(); + await expect(page.locator('g.node')).toHaveCount(3, { timeout: 5000 }); + + // Navigate to level 2 + await openSubgraph(page, 'Start'); + await expect(breadcrumbLinks(page)).toHaveCount(3); + + // Click the level-1 breadcrumb (second item, "Start") + await breadcrumbLinks(page).nth(1).click(); + + // Breadcrumb should be truncated to 2 items + await expect(breadcrumbLinks(page)).toHaveCount(2, { timeout: 3000 }); + // The level-1 graph still has the extra node we added + await waitForGraph(page); + await expect(page.locator('g.node')).toHaveCount(3, { timeout: 5000 }); + }); + + test('navigating forward after going back starts a fresh path from that level', async ({ page }) => { + // Navigate to level 1 + await openSubgraph(page, 'Start'); + await expect(breadcrumbLinks(page)).toHaveCount(2); + + // Go back to main + await breadcrumbLinks(page).first().click(); + await expect(breadcrumbLinks(page)).toHaveCount(1, { timeout: 3000 }); + + // Navigate into End's subgraph (a different node than before) + await openSubgraph(page, 'End'); + + // Breadcrumb should be Main / End — no leftover "Start" segment + await expect(breadcrumbLinks(page)).toHaveCount(2, { timeout: 3000 }); + await expect(page.locator('.ant-breadcrumb')).toContainText('End'); + await expect(page.locator('.ant-breadcrumb')).not.toContainText('Start'); + }); +});