Files
ConceptSketch/e2e/breadcrumb.spec.ts
2026-03-06 07:20:24 +01:00

107 lines
4.2 KiB
TypeScript

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');
});
});