feat: add persistent book navigation and public-only pages
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { documentPaths, treePaths, treeNodeCount, bookURL } from './documents';
|
||||
import { documentPaths, treePaths, treeNodeCount, bookURL, chapterSections, treeParents } from './documents';
|
||||
|
||||
test.use({ javaScriptEnabled: true });
|
||||
|
||||
@@ -11,7 +11,7 @@ const visiblePrefixes = '.node-row:not([hidden]) .branch-prefix';
|
||||
|
||||
async function expectNodeGlitch(page: import('@playwright/test').Page) {
|
||||
expect(await page.locator('.demo-node:not(.root-mark)').evaluateAll((nodes) => nodes.some((node) =>
|
||||
node.querySelector('.node-label')!.textContent !== (node as HTMLElement).dataset.path!.split('/').at(-1)
|
||||
node.querySelector('.node-label')!.textContent !== ((node as HTMLElement).dataset.path === '/book' ? 'book/' : (node as HTMLElement).dataset.path!.split('/').at(-1))
|
||||
))).toBe(true);
|
||||
}
|
||||
|
||||
@@ -27,16 +27,16 @@ async function exposeTree(page: import('@playwright/test').Page) {
|
||||
}
|
||||
|
||||
async function checkGlyphRefs(page: import('@playwright/test').Page) {
|
||||
const rendered = await page.locator(visiblePrefixes).evaluateAll((prefixes) => {
|
||||
const rendered = await page.locator(visiblePrefixes).evaluateAll((prefixes, parents) => {
|
||||
const paths = [...document.querySelectorAll<HTMLElement>('.node-row:not([hidden]) .demo-node')].map((node) => node.dataset.path!);
|
||||
const parent = (path: string) => path.slice(0, path.lastIndexOf('/')) || '/';
|
||||
const parent = (path: string) => parents[path];
|
||||
const last = (path: string) => paths.filter((other) => other !== '/' && parent(other) === parent(path)).at(-1) === path;
|
||||
return prefixes.map((prefix) => {
|
||||
const path = prefix.parentElement!.querySelector<HTMLElement>('.demo-node')!.dataset.path!;
|
||||
let expected = last(path) ? '└── ' : '├── ';
|
||||
let expected = last(path) ? '└─ ' : '├─ ';
|
||||
let ancestor = parent(path);
|
||||
while (ancestor !== '/') {
|
||||
expected = (last(ancestor) ? ' ' : '│ ') + expected;
|
||||
expected = (last(ancestor) ? ' ' : '│ ') + expected;
|
||||
ancestor = parent(ancestor);
|
||||
}
|
||||
const refs = [...prefix.querySelectorAll<SVGUseElement>('use[data-cell]')].map((use) => ({
|
||||
@@ -45,7 +45,7 @@ async function checkGlyphRefs(page: import('@playwright/test').Page) {
|
||||
}));
|
||||
return { actual: prefix.getAttribute('data-prefix'), expected, refs };
|
||||
});
|
||||
});
|
||||
}, treeParents);
|
||||
expect(rendered.length).toBeGreaterThan(0);
|
||||
for (const { actual, expected, refs } of rendered) {
|
||||
expect(actual).toBe(expected);
|
||||
@@ -345,7 +345,7 @@ test('continuous motion overlaps growth, travel and reveal without intermediate
|
||||
const root = page.locator('.root-mark');
|
||||
await expect(root.locator('svg .root-shape')).toHaveCount(1);
|
||||
await expect(root.locator('.root-shape')).toHaveAttribute('d', 'M31 4H41L17 60H7Z'); // The slash is geometry, not a font glyph.
|
||||
await expect(page.locator(selectedContent)).toHaveAttribute('inert', '');
|
||||
await expect(page.locator('.document-pane[data-reveal]')).toHaveAttribute('inert', '');
|
||||
const initial = await root.boundingBox();
|
||||
expect(initial).not.toBeNull();
|
||||
expect(Math.abs(initial!.x + initial!.width / 2 - page.viewportSize()!.width / 2)).toBeLessThan(2);
|
||||
@@ -429,7 +429,7 @@ test('continuous motion overlaps growth, travel and reveal without intermediate
|
||||
spineOpacity: Number(getComputedStyle(first.querySelector('.branch-spine')!).opacity),
|
||||
trunkOpacity: Number(getComputedStyle(document.querySelector('.trunk-line')!).opacity),
|
||||
labelOpacity: Number(getComputedStyle(document.querySelector('.demo-node:not(.root-mark)')!).opacity),
|
||||
articleOpacity: Number(getComputedStyle(document.querySelector('[data-selected-content]')!).opacity),
|
||||
articleOpacity: Number(getComputedStyle(document.querySelector('.document-pane[data-reveal]')!).opacity),
|
||||
sceneTransform: getComputedStyle(document.querySelector('.tree-scene')!).transform,
|
||||
rowTransforms: [...document.querySelectorAll('.node-row, .demo-node')].map((row) => getComputedStyle(row).transform),
|
||||
};
|
||||
@@ -458,7 +458,7 @@ test('continuous motion overlaps growth, travel and reveal without intermediate
|
||||
await page.clock.runFor(800);
|
||||
await complete(page);
|
||||
await expect(root).toHaveCount(1);
|
||||
await expect(page.locator(selectedContent)).not.toHaveAttribute('inert');
|
||||
await expect(page.locator('.document-pane[data-reveal]')).not.toHaveAttribute('inert');
|
||||
await expect(page.locator('[data-glitching]')).toHaveCount(0);
|
||||
await expect(page.locator(selectedContent + ' h1')).toHaveText('1. コンセプト');
|
||||
await expect(page.locator('.branch-lines')).toHaveCount(0);
|
||||
@@ -472,7 +472,7 @@ test('continuous motion overlaps growth, travel and reveal without intermediate
|
||||
const grid = await page.evaluate(() => {
|
||||
const root = document.querySelector('.root-mark')!.getBoundingClientRect();
|
||||
const prefix = document.querySelector<HTMLElement>('.branch-prefix')!;
|
||||
const cell = prefix.getBoundingClientRect().width / 4;
|
||||
const cell = prefix.getBoundingClientRect().width / prefix.dataset.prefix!.length;
|
||||
const frame = document.querySelector('.landing-content')!.getBoundingClientRect();
|
||||
const rows = [...document.querySelectorAll<HTMLElement>('.node-row:not(.root-slot):not([hidden])')].map((row) => row.getBoundingClientRect());
|
||||
const nodes = [...document.querySelectorAll<HTMLElement>('.node-row:not([hidden]) .demo-node:not(.root-mark)')].map((node) => node.getBoundingClientRect());
|
||||
@@ -511,7 +511,7 @@ for (const time of [1500, 2600]) {
|
||||
for (const row of await page.locator('.node-row').all()) await expect(row).toHaveCSS('transform', 'none');
|
||||
await expect(page.locator('.root-mark')).toHaveCSS('transform', 'none');
|
||||
await expect(page.locator('.site-header')).toHaveCSS('opacity', '1');
|
||||
await expect(page.locator('.branch-prefix').first()).toHaveAttribute('data-prefix', '├── ');
|
||||
await expect(page.locator('.branch-prefix').first()).toHaveAttribute('data-prefix', '└─ ');
|
||||
await expect(page.locator('.intro-art')).toBeHidden();
|
||||
await page.clock.runFor(4000);
|
||||
await expect(page.locator(stage)).toHaveAttribute('data-phase', 'complete');
|
||||
@@ -533,7 +533,7 @@ test('skipping an active node glitch restores exact filenames without altering d
|
||||
await page.keyboard.press('Escape');
|
||||
await complete(page);
|
||||
await expect(page.locator('[data-glitching]')).toHaveCount(0);
|
||||
for (const path of treePaths.slice(1)) await expect(nodeAt(page, path).locator('.node-label')).toHaveText(path.split('/').at(-1)!);
|
||||
for (const path of treePaths.slice(1)) await expect(nodeAt(page, path).locator('.node-label')).toHaveText(path === '/book' ? 'book/' : path.split('/').at(-1)!);
|
||||
await page.clock.runFor(2000);
|
||||
expect(await page.locator(selectedContent).innerHTML()).toBe(html);
|
||||
await page.locator('.root-mark').hover();
|
||||
@@ -564,22 +564,32 @@ test('landing contains only the real filename tree and selected canonical conten
|
||||
for (const path of documentPaths) {
|
||||
const node = nodeAt(page, path);
|
||||
await expect(node).toHaveAttribute('role', 'treeitem');
|
||||
await expect(node).toHaveAttribute('aria-level', path.startsWith('/draft/') ? '3' : '2');
|
||||
await expect(node).toHaveAttribute('aria-level', treeParents[path] === '/book' ? '3' : '4');
|
||||
await expect(node.locator('.node-label')).toHaveText(path.split('/').at(-1)!);
|
||||
expect(await node.evaluate((node) => node.tagName)).toBe('A');
|
||||
expect(await node.evaluate((node) => (node as HTMLAnchorElement).href)).toBe(bookURL(path, testInfo.project.use.baseURL!));
|
||||
await expect(node).not.toHaveAttribute('aria-expanded');
|
||||
if (chapterSections[path]) await expect(node).toHaveAttribute('aria-expanded', 'true');
|
||||
else await expect(node).not.toHaveAttribute('aria-expanded');
|
||||
}
|
||||
for (const path of ['/', '/draft']) {
|
||||
for (const path of ['/', '/book']) {
|
||||
const folder = nodeAt(page, path);
|
||||
expect(await folder.evaluate((node) => node.tagName)).toBe('BUTTON');
|
||||
expect(await folder.evaluate((node) => node.tagName)).toBe('A');
|
||||
await expect(folder).toHaveAttribute('href', new URL(path === '/' ? './' : 'book/', testInfo.project.use.baseURL!).pathname);
|
||||
await expect(folder).toHaveAttribute('aria-expanded', 'true');
|
||||
await expect(folder).toHaveAttribute('aria-level', path === '/' ? '1' : '2');
|
||||
if (path === '/book') {
|
||||
await expect(folder.locator('.node-label')).toHaveText('book/');
|
||||
const toggle = page.locator('.node-toggle[data-toggle-path="/book"]');
|
||||
await expect(toggle).toHaveText('−');
|
||||
expect(await toggle.evaluate((button) => button.tagName)).toBe('BUTTON');
|
||||
expect(await toggle.evaluate((button) => button.parentElement === document.querySelector('.demo-node[data-path="/book"]')!.parentElement)).toBe(true);
|
||||
await expect(folder.locator('button')).toHaveCount(0);
|
||||
}
|
||||
}
|
||||
await expect(page.locator('.demo-node[data-path^="/draft"], .landing a[href*="/draft/"]')).toHaveCount(0);
|
||||
expect(await page.locator('.demo-node:not(.root-mark)').evaluateAll((nodes) => nodes.every((node) =>
|
||||
node.textContent === node.querySelector('.node-label')!.textContent + (node.querySelector('.node-indicator')?.textContent ?? '')
|
||||
&& [...node.children].every((child) => child.matches('.node-label, .node-indicator'))
|
||||
&& [...node.querySelectorAll('.node-indicator')].every((indicator) => /^[+−-]$/.test(indicator.textContent!))
|
||||
node.textContent === node.querySelector('.node-label')!.textContent
|
||||
&& [...node.children].every((child) => child.matches('.node-label'))
|
||||
))).toBe(true);
|
||||
const payload = page.locator('script#landing-documents');
|
||||
await expect(payload).toHaveCount(1);
|
||||
@@ -592,14 +602,14 @@ test('landing contains only the real filename tree and selected canonical conten
|
||||
const document = stored.find((document) => document.path === path)!;
|
||||
expect(document.href).toBe(new URL(bookURL(path, testInfo.project.use.baseURL!)).pathname);
|
||||
expect(document.title).toBeTruthy();
|
||||
expect(document.html).toContain('<h1');
|
||||
expect(document).not.toHaveProperty('html');
|
||||
}
|
||||
for (const path of ['/', '/draft']) {
|
||||
for (const path of ['/', '/book']) {
|
||||
const folder = stored.find((document) => document.path === path)!;
|
||||
expect(folder.html).toBeUndefined();
|
||||
expect(folder.title).toBeUndefined();
|
||||
expect(folder.href).toBeUndefined();
|
||||
expect(folder).not.toHaveProperty('html');
|
||||
expect(folder.href).toBe(new URL(path === '/' ? './' : 'book/', testInfo.project.use.baseURL!).pathname);
|
||||
}
|
||||
await expect(page.locator(stage)).toHaveAttribute('data-current-path', initialPath);
|
||||
await expect(nodeAt(page, initialPath)).toHaveAttribute('aria-selected', 'true');
|
||||
await expect(page.locator(selectedContent)).toBeVisible();
|
||||
await expect(page.locator(selectedContent + ' h1')).toHaveText('1. コンセプト');
|
||||
@@ -607,79 +617,123 @@ test('landing contains only the real filename tree and selected canonical conten
|
||||
await checkGlyphRefs(page);
|
||||
});
|
||||
|
||||
test('each file immediately displays exactly its Book HTML, including links, without fetching or navigating', async ({ page }, testInfo) => {
|
||||
test('numbered chapters nest visually, fold independently, and retain section URLs', async ({ page }, testInfo) => {
|
||||
const section = '/1.1-interaction-abstraction.md';
|
||||
await page.goto(bookURL(section, testInfo.project.use.baseURL!));
|
||||
await complete(page);
|
||||
const chapter = nodeAt(page, initialPath);
|
||||
const child = nodeAt(page, section);
|
||||
await expect(chapter).toHaveAttribute('aria-level', '3');
|
||||
await expect(child).toHaveAttribute('aria-level', '4');
|
||||
const prefix = child.locator('..').locator('.branch-prefix');
|
||||
await expect(prefix).toHaveAttribute('data-prefix', ' │ ├─ ');
|
||||
await expect(nodeAt(page, '/1.4-concepts-and-scope.md').locator('..').locator('.branch-prefix')).toHaveAttribute('data-prefix', ' │ └─ ');
|
||||
const before = await page.locator(selectedContent).innerHTML();
|
||||
await page.locator('.node-toggle[data-toggle-path="/1-concept.md"]').click();
|
||||
for (const path of chapterSections[initialPath]) await expect(nodeAt(page, path)).toBeHidden();
|
||||
await expect(nodeAt(page, '/2-core-model.md')).toBeVisible();
|
||||
await expect(page).toHaveURL(bookURL(section, testInfo.project.use.baseURL!));
|
||||
expect(await page.locator(selectedContent).innerHTML()).toBe(before);
|
||||
await expect(child).toHaveAttribute('aria-current', 'page');
|
||||
await checkGlyphRefs(page);
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await expect(child).toBeVisible();
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await expect(child).toBeFocused();
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
await expect(chapter).toBeFocused();
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
await expect(child).toBeHidden();
|
||||
// Other navigation preserves the collapsed chapter; returning to a child reveals it.
|
||||
await nodeAt(page, '/2-core-model.md').click();
|
||||
await expect(nodeAt(page, '/2-core-model.md')).toHaveAttribute('aria-current', 'page');
|
||||
await expect(chapter).toHaveAttribute('aria-expanded', 'false');
|
||||
await page.goBack();
|
||||
await expect(child).toHaveAttribute('aria-current', 'page');
|
||||
await expect(chapter).toHaveAttribute('aria-expanded', 'true');
|
||||
await expect(child).toBeVisible();
|
||||
await expect(child).toHaveAttribute('href', new URL(bookURL(section, testInfo.project.use.baseURL!)).pathname);
|
||||
await checkGlyphRefs(page);
|
||||
});
|
||||
|
||||
test('each file navigates to exactly its Book HTML and title, including links', async ({ page }, testInfo) => {
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' });
|
||||
await page.goto('./');
|
||||
await complete(page);
|
||||
const book = await page.context().newPage();
|
||||
const bodies = new Map<string, string>();
|
||||
const documents = new Map<string, { html: string; title: string }>();
|
||||
for (const path of documentPaths) {
|
||||
await book.goto(bookURL(path, testInfo.project.use.baseURL!));
|
||||
bodies.set(path, await book.locator('article.prose').innerHTML());
|
||||
documents.set(path, { html: await book.locator('article.prose').innerHTML(), title: await book.title() });
|
||||
}
|
||||
await book.close();
|
||||
const landingURL = page.url();
|
||||
expect(await page.locator(selectedContent).innerHTML()).toBe(bodies.get(initialPath));
|
||||
const requests: string[] = [];
|
||||
page.on('request', (request) => requests.push(request.url()));
|
||||
expect(await page.locator(selectedContent).innerHTML()).toBe(documents.get(initialPath)!.html);
|
||||
for (const path of documentPaths) {
|
||||
// Observe the body in the same task as the click, not after waiting for a read/reveal.
|
||||
const displayed = await nodeAt(page, path).evaluate((node) => {
|
||||
(node as HTMLElement).click();
|
||||
const article = document.querySelector<HTMLElement>('[data-selected-content]')!;
|
||||
return { html: article.innerHTML, hidden: article.hidden };
|
||||
});
|
||||
expect(displayed).toEqual({ html: bodies.get(path), hidden: false });
|
||||
await expect(nodeAt(page, path)).toHaveAttribute('aria-selected', 'true');
|
||||
expect(page.url()).toBe(landingURL);
|
||||
}
|
||||
expect(requests).toEqual([]);
|
||||
for (const path of ['/draft', '/']) {
|
||||
await nodeAt(page, path).click();
|
||||
await expect(page.locator(selectedContent)).toBeHidden();
|
||||
expect(await page.locator(selectedContent).innerHTML()).toBe('');
|
||||
await expect(page).toHaveURL(bookURL(path, testInfo.project.use.baseURL!));
|
||||
await expect(page.locator(stage)).toHaveAttribute('data-current-path', path);
|
||||
await expect(page).toHaveTitle(documents.get(path)!.title);
|
||||
await expect.poll(() => page.locator(selectedContent).innerHTML()).toBe(documents.get(path)!.html);
|
||||
await expect(page.locator('article.prose')).toHaveCount(1);
|
||||
await expect(nodeAt(page, path)).toHaveAttribute('aria-selected', 'true');
|
||||
await expect(nodeAt(page, path)).toHaveAttribute('aria-current', 'page');
|
||||
await expect(page.locator('.demo-node[aria-current="page"]')).toHaveCount(1);
|
||||
await expect(page.locator(stage)).toHaveAttribute('data-phase', 'complete');
|
||||
await expect(page.locator('html')).not.toHaveAttribute('data-landing');
|
||||
}
|
||||
});
|
||||
|
||||
test('file selection and folder collapse work with keyboard navigation in the scrollable viewport', async ({ page }) => {
|
||||
test('keyboard arrows only move focus or fold; Enter and Space activate route links', async ({ page }, testInfo) => {
|
||||
await armed(page);
|
||||
await page.keyboard.press('Escape');
|
||||
await complete(page);
|
||||
const originalURL = page.url();
|
||||
const originalHTML = await page.locator(selectedContent).innerHTML();
|
||||
const unchanged = async () => {
|
||||
expect(page.url()).toBe(originalURL);
|
||||
expect(await page.locator(selectedContent).innerHTML()).toBe(originalHTML);
|
||||
await expect(nodeAt(page, initialPath)).toHaveAttribute('aria-selected', 'true');
|
||||
};
|
||||
await expect(nodeAt(page, initialPath)).toBeFocused();
|
||||
await page.keyboard.press('ArrowDown');
|
||||
await expect(nodeAt(page, '/2-core-model.md')).toBeFocused();
|
||||
await expect(nodeAt(page, '/2-core-model.md')).toHaveAttribute('aria-selected', 'true');
|
||||
await expect(page.locator(selectedContent + ' h1')).toHaveText('2. コアモデル');
|
||||
const draft = nodeAt(page, '/draft');
|
||||
await draft.focus();
|
||||
await expect(nodeAt(page, '/1.1-interaction-abstraction.md')).toBeFocused();
|
||||
await expect(nodeAt(page, '/1.1-interaction-abstraction.md')).toHaveAttribute('aria-selected', 'false');
|
||||
await unchanged();
|
||||
const book = nodeAt(page, '/book');
|
||||
await book.focus();
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
await expect(draft).toHaveAttribute('aria-expanded', 'false');
|
||||
for (const path of documentPaths.filter((path) => path.startsWith('/draft/'))) await expect(nodeAt(page, path)).toBeHidden();
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(treeNodeCount - 5);
|
||||
await expect(page.locator(selectedContent)).toBeHidden();
|
||||
expect(await page.locator(selectedContent).innerHTML()).toBe('');
|
||||
await expect(book).toHaveAttribute('aria-expanded', 'false');
|
||||
for (const path of documentPaths) await expect(nodeAt(page, path)).toBeHidden();
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(2);
|
||||
await unchanged();
|
||||
await checkGlyphRefs(page);
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await expect(draft).toHaveAttribute('aria-expanded', 'true');
|
||||
await expect(book).toHaveAttribute('aria-expanded', 'true');
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await expect(nodeAt(page, documentPaths[10])).toBeFocused();
|
||||
await expect(page.locator(selectedContent)).toBeVisible();
|
||||
await expect(nodeAt(page, initialPath)).toBeFocused();
|
||||
await unchanged();
|
||||
await page.keyboard.press('ArrowLeft'); // Fold the chapter before moving to its parent.
|
||||
await expect(nodeAt(page, initialPath)).toHaveAttribute('aria-expanded', 'false');
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
await expect(draft).toBeFocused();
|
||||
await expect(page.locator(selectedContent)).toBeHidden();
|
||||
await expect(book).toBeFocused();
|
||||
await unchanged();
|
||||
await nodeAt(page, initialPath).focus();
|
||||
await page.keyboard.press('ArrowRight'); // Restore all descendants for root collapse/reopen.
|
||||
await page.keyboard.press('Home');
|
||||
const root = nodeAt(page, '/');
|
||||
await expect(root).toBeFocused();
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(1);
|
||||
expect((await trunkGeometry(page)).length).toBe(0);
|
||||
await unchanged();
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(treeNodeCount);
|
||||
await checkGlyphRefs(page);
|
||||
await page.keyboard.press('End');
|
||||
const finalLeaf = nodeAt(page, documentPaths.at(-1)!);
|
||||
const finalPath = documentPaths.at(-1)!;
|
||||
const finalLeaf = nodeAt(page, finalPath);
|
||||
await expect(finalLeaf).toBeFocused();
|
||||
await expect(finalLeaf).toHaveAttribute('aria-selected', 'true');
|
||||
await expect(finalLeaf).toHaveAttribute('aria-selected', 'false');
|
||||
const scroll = await page.locator('.tree-viewport').evaluate((viewport) => {
|
||||
const node = document.activeElement!.getBoundingClientRect(), box = viewport.getBoundingClientRect();
|
||||
return { top: viewport.scrollTop, needsScroll: viewport.scrollHeight > viewport.clientHeight, inside: node.top >= box.top && node.bottom <= box.bottom && node.left >= box.left && node.right <= box.right };
|
||||
@@ -687,18 +741,21 @@ test('file selection and folder collapse work with keyboard navigation in the sc
|
||||
if (scroll.needsScroll) expect(scroll.top).toBeGreaterThan(0);
|
||||
expect(scroll.inside).toBe(true);
|
||||
await page.keyboard.press('ArrowUp');
|
||||
await expect(nodeAt(page, '/draft/typescript-api-model.md')).toBeFocused();
|
||||
const previousPath = '/5-transports.md';
|
||||
await expect(nodeAt(page, previousPath)).toBeFocused();
|
||||
await page.keyboard.press('ArrowDown');
|
||||
await expect(finalLeaf).toBeFocused();
|
||||
await unchanged();
|
||||
await expect(page.locator('.demo-node[tabindex="0"]')).toHaveCount(1);
|
||||
await page.keyboard.press('Enter');
|
||||
await expect(finalLeaf).toHaveAttribute('aria-selected', 'true');
|
||||
await expect(page).toHaveURL(bookURL(finalPath, testInfo.project.use.baseURL!));
|
||||
await expect(finalLeaf).toHaveAttribute('aria-current', 'page');
|
||||
await expect(page.locator(selectedContent)).toBeVisible();
|
||||
await nodeAt(page, previousPath).focus();
|
||||
await page.keyboard.press('Space');
|
||||
await expect(finalLeaf).toHaveAttribute('aria-selected', 'true');
|
||||
await expect(page).toHaveURL(bookURL(previousPath, testInfo.project.use.baseURL!));
|
||||
await expect(nodeAt(page, previousPath)).toHaveAttribute('aria-current', 'page');
|
||||
await expect(page.locator(selectedContent)).toBeVisible();
|
||||
await page.keyboard.press('Tab');
|
||||
await expect(page.locator(selectedContent)).toBeFocused();
|
||||
});
|
||||
|
||||
test.describe('glyph trunk regressions', () => {
|
||||
@@ -789,19 +846,19 @@ test.describe('glyph trunk regressions', () => {
|
||||
return trunk.length;
|
||||
};
|
||||
const expanded = await checkEnd('expanded');
|
||||
const draft = nodeAt(page, '/draft');
|
||||
await draft.focus();
|
||||
const book = nodeAt(page, '/book');
|
||||
await book.focus();
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
await expect(draft).toHaveAttribute('aria-expanded', 'false');
|
||||
// Draft is the last root child: its descendants never extend the first-column spine.
|
||||
expect(await checkEnd('draft')).toBe(expanded);
|
||||
await expect(book).toHaveAttribute('aria-expanded', 'false');
|
||||
// Book is the sole root child: its descendants never extend the first-column spine.
|
||||
expect(await checkEnd('book')).toBe(expanded);
|
||||
await page.keyboard.press('Home');
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(1);
|
||||
await checkEnd('root');
|
||||
await page.keyboard.press('ArrowRight');
|
||||
expect(await checkEnd('root-reopened')).toBe(expanded);
|
||||
await draft.focus();
|
||||
await book.focus();
|
||||
await page.keyboard.press('ArrowRight');
|
||||
expect(await checkEnd('all-reopened')).toBe(expanded);
|
||||
});
|
||||
@@ -824,6 +881,9 @@ test.describe('JavaScript-free glyph tree', () => {
|
||||
await node.click();
|
||||
await expect(page).toHaveURL(bookURL(path, testInfo.project.use.baseURL!));
|
||||
await expect(page.locator('article.prose h1')).toBeVisible();
|
||||
await expect(page.locator('.demo-node')).toHaveCount(treeNodeCount);
|
||||
await expect(nodeAt(page, path)).toHaveAttribute('aria-current', 'page');
|
||||
await expect(page.locator(stage)).toHaveAttribute('data-current-path', path);
|
||||
if (path === initialPath) expect(await page.locator('article.prose').innerHTML()).toBe(initialHTML);
|
||||
}
|
||||
});
|
||||
@@ -845,7 +905,9 @@ test.describe('JavaScript-free glyph tree', () => {
|
||||
expect(trunk.gapRatio).toBeCloseTo(1 / 14, 5);
|
||||
await page.locator('.demo-tree').screenshot({ path: testInfo.outputPath('no-js-glyph-tree.png') });
|
||||
await page.getByRole('link', { name: 'Book', exact: true }).click();
|
||||
await expect(page.getByRole('heading', { level: 1 })).toContainText('通信の約束まで');
|
||||
await expect(page).toHaveURL(/\/book\/$/);
|
||||
await expect(nodeAt(page, '/book')).toHaveAttribute('aria-current', 'page');
|
||||
await expect(page.locator('.landing a[href$="/book/1-concept/"]').last()).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -876,13 +938,15 @@ test('blocked animation module fails open to readable content', async ({ page })
|
||||
await expect(page.locator('html')).not.toHaveAttribute('data-landing', { timeout: 6000 });
|
||||
await expect(page.locator(selectedContent + ' h1')).toHaveText('1. コンセプト');
|
||||
await expect(page.locator(selectedContent)).toBeVisible();
|
||||
await nodeAt(page, '/draft/rust-api-model.md').click();
|
||||
await expect(page).toHaveURL(/\/book\/draft\/rust-api-model\/$/);
|
||||
await nodeAt(page, '/2-core-model.md').click();
|
||||
await expect(page).toHaveURL(/\/book\/2-core-model\/$/);
|
||||
await expect(page.locator('article.prose h1')).toBeVisible();
|
||||
await page.goto('./');
|
||||
await expect(page.locator(selectedContent)).toBeVisible();
|
||||
await page.getByRole('link', { name: 'Book', exact: true }).click();
|
||||
await expect(page.getByRole('heading', { level: 1 })).toContainText('通信の約束まで');
|
||||
await expect(page).toHaveURL(/\/book\/$/);
|
||||
await expect(nodeAt(page, '/book')).toHaveAttribute('aria-current', 'page');
|
||||
await expect(page.locator('.landing a[href$="/book/1-concept/"]').last()).toBeVisible();
|
||||
});
|
||||
|
||||
test('touch swipe starts the uninterrupted timeline', async ({ page }, testInfo) => {
|
||||
|
||||
Reference in New Issue
Block a user