import { expect, test } from '@playwright/test'; import { bookURL, documentPaths, treeNodeCount } from './documents'; test('home and book share the usable tree without client JavaScript', async ({ page }, testInfo) => { const failures: string[] = []; page.on('response', (response) => { if (response.status() >= 400) failures.push(response.url()); }); await page.goto('./'); await expect(page.locator('.selected-content.prose[data-selected-content] .root-page[data-root-page]').getByRole('heading', { level: 1, name: 'WIP', exact: true })).toBeVisible(); await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-current-path', '/'); await expect(page.locator('.root-mark')).toHaveAttribute('aria-current', 'page'); await expect(page.locator('.root-mark')).toHaveAttribute('aria-selected', 'true'); await expect(page.locator('[data-selected-content]')).not.toContainText('1. コンセプト'); expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true); await expect(page.locator('.branch-prefix').first()).toHaveAttribute('data-prefix', '├─ '); const root = await page.locator('.root-mark').boundingBox(); const spine = await page.locator('.branch-vector').first().evaluate((element) => { const svg = element as SVGSVGElement; const top = new DOMPoint(Number(svg.dataset.cellWidth) / 2, 0).matrixTransform(svg.getScreenCTM()!); return { x: top.x, y: top.y }; }); expect(root).not.toBeNull(); const footX = await page.locator('.root-vector').evaluate((svg) => new DOMPoint(12, 60).matrixTransform((svg as SVGSVGElement).getScreenCTM()!).x); expect(Math.abs(footX - spine.x)).toBeLessThan(1); expect(Math.abs(root!.y + root!.height - spine.y)).toBeLessThan(1); await page.screenshot({ path: testInfo.outputPath('home.png'), fullPage: true }); await page.getByRole('navigation', { name: 'ルートページのナビゲーション', exact: true }).getByRole('link', { name: /^Book(?:\s|$)/ }).click(); await expect(page).toHaveURL(new URL('book/', testInfo.project.use.baseURL!).href); await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-current-path', '/book'); await expect(page.locator('.demo-node')).toHaveCount(treeNodeCount); await expect(page.locator('.sidebar, .mobile-chapters, .mobile-toc, .page-toc, .pagination')).toHaveCount(0); await expect(page.getByText('世界の見方から、', { exact: false })).toHaveCount(0); // The directory index lists all 14 physical documents, independent of logical chapter nesting. await expect(page.locator('.landing a:not(.demo-node)[href*="/book/"]')).toHaveCount(documentPaths.length); for (const path of documentPaths) { await expect(page.locator(`.landing a:not(.demo-node)[href="${new URL(bookURL(path, testInfo.project.use.baseURL!)).pathname}"]`)).toBeVisible(); } await expect(page.locator('.demo-node[data-path^="/draft"], .landing a[href*="/draft/"]')).toHaveCount(0); await page.locator('.landing a:not(.demo-node)[href$="/book/1-concept/"]').click(); await expect(page.locator('article.prose h1')).toHaveText('1. コンセプト'); await page.locator('.demo-node[data-path="/3-protocol.md"]').click(); await expect(page.locator('article.prose h1')).toHaveText('3. Protocol'); await expect(page.locator('.demo-node')).toHaveCount(treeNodeCount); await page.screenshot({ path: testInfo.outputPath('book.png'), fullPage: false }); expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true); // Cross-chapter Japanese fragment links must reach the matching heading. await page.locator('article.prose').getByRole('link', { name: '4.2. Text View' }).first().click(); await expect(page.locator('article.prose h1')).toHaveText('4.2. Text View'); await expect(page).toHaveURL(/4\.2-text-view\/#/); await expect(page.locator('a[href*="/draft/"]')).toHaveCount(0); await expect(page.locator('.draft-notice')).toHaveCount(0); expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true); expect(failures).toEqual([]); });