fix: reduce landing to existing documents and their tree
Reference site / build (push) Canceled after 0s
Reference site / build (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
// Existing Astro book collection filenames, not a second authored guide dataset.
|
||||
export const documentPaths = [
|
||||
'/1-concept.md',
|
||||
'/2-core-model.md',
|
||||
'/2.1-object-tree-schema.md',
|
||||
'/2.2-operation-and-interface.md',
|
||||
'/3-protocol.md',
|
||||
'/4-client.md',
|
||||
'/4.1-ai-tools.md',
|
||||
'/4.2-text-view.md',
|
||||
'/5-transports.md',
|
||||
'/5.1-wip-over-http.md',
|
||||
'/draft/dynamic-worldspace-projection.md',
|
||||
'/draft/filesystem-discovery.md',
|
||||
'/draft/rust-api-model.md',
|
||||
'/draft/typescript-api-model.md',
|
||||
'/draft/unresolved-issues.md',
|
||||
];
|
||||
export const treePaths = ['/', ...documentPaths.filter((path) => !path.startsWith('/draft/')), '/draft', ...documentPaths.filter((path) => path.startsWith('/draft/'))];
|
||||
export const treeNodeCount = treePaths.length;
|
||||
export const rootChildCount = treePaths.filter((path) => path !== '/' && path.lastIndexOf('/') === 0).length;
|
||||
export const bookURL = (path: string, base: string) => new URL(`book/${path.slice(1, -3).split('/').map(encodeURIComponent).join('/')}/`, base).href;
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { rootChildCount } from './documents';
|
||||
|
||||
test.use({ javaScriptEnabled: true });
|
||||
|
||||
@@ -30,10 +31,9 @@ test('self-hosted Lyth Mono and EB Garamond load with the configured base', asyn
|
||||
expect(external).toEqual([]);
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-phase', 'complete');
|
||||
await page.getByRole('button', { name: '本文を読む' }).click();
|
||||
await expect(page.locator('[data-result]')).toContainText('Unicode正規化');
|
||||
expect(await page.locator('[data-result]').evaluate((element) => getComputedStyle(element).fontFamily)).toContain('EB Garamond');
|
||||
await page.locator('.call-panel').screenshot({ path: testInfo.outputPath('eb-garamond-guide.png') });
|
||||
await expect(page.locator('[data-selected-content] h1')).toHaveText('1. コンセプト');
|
||||
expect(await page.locator('[data-selected-content]').evaluate((element) => getComputedStyle(element).fontFamily)).toContain('EB Garamond');
|
||||
await page.locator('[data-selected-content]').screenshot({ path: testInfo.outputPath('eb-garamond-selected-content.png') });
|
||||
await page.goto('./book/1-concept/');
|
||||
expect(await page.locator('.prose').evaluate((element) => getComputedStyle(element).fontFamily)).toContain('EB Garamond');
|
||||
await page.screenshot({ path: testInfo.outputPath('eb-garamond-book.png') });
|
||||
@@ -44,7 +44,7 @@ test.describe('static font and tree rendering', () => {
|
||||
test('glyph joins paint continuously across SVG cell boundaries', async ({ page }, testInfo) => {
|
||||
await page.goto('./');
|
||||
await page.evaluate(() => document.fonts.ready);
|
||||
// Sample all four joins without clipping the final branch in the scrollable tree.
|
||||
// Sample every root-child join without clipping the final branch in the scrollable tree.
|
||||
await page.setViewportSize({ width: page.viewportSize()!.width, height: 2000 });
|
||||
await page.locator('.tree-viewport').evaluate((el) => { (el as HTMLElement).style.height = 'auto'; });
|
||||
const joins = await page.locator('.demo-node[aria-level="2"]').evaluateAll((nodes) => nodes.map((node) => {
|
||||
@@ -52,7 +52,7 @@ test.describe('static font and tree rendering', () => {
|
||||
const p = new DOMPoint(Number(svg.dataset.cellWidth) / 2, 12).matrixTransform(svg.getScreenCTM()!);
|
||||
return { x: p.x, y: p.y };
|
||||
}));
|
||||
expect(joins).toHaveLength(4);
|
||||
expect(joins).toHaveLength(rootChildCount);
|
||||
for (const [index, join] of joins.entries()) {
|
||||
const clip = { x: Math.floor(join.x), y: Math.floor(join.y) - 1, width: 14, height: 3 };
|
||||
const png = (await page.screenshot({ clip, path: testInfo.outputPath(`glyph-join-${index}.png`) })).toString('base64');
|
||||
@@ -99,6 +99,6 @@ test('failed font downloads do not strand the animation or interactive demo', as
|
||||
await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-phase', 'armed');
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(page.locator('[data-landing-stage]')).toHaveAttribute('data-phase', 'complete');
|
||||
await page.getByRole('button', { name: '本文を読む' }).click();
|
||||
await expect(page.locator('[data-result]')).toContainText('Unicode正規化');
|
||||
await page.locator('.demo-node[data-path="/2-core-model.md"]').click();
|
||||
await expect(page.locator('[data-selected-content] h1')).toHaveText('2. コアモデル');
|
||||
});
|
||||
|
||||
+199
-260
@@ -1,12 +1,20 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { documentPaths, treePaths, treeNodeCount, bookURL } from './documents';
|
||||
|
||||
test.use({ javaScriptEnabled: true });
|
||||
|
||||
const stage = '[data-landing-stage]';
|
||||
const initialPath = '/design/object-tree/paths/names';
|
||||
const initialPath = '/1-concept.md';
|
||||
const selectedContent = '.selected-content.prose[data-selected-content]';
|
||||
const nodeAt = (page: import('@playwright/test').Page, path: string) => page.locator(`.demo-node[data-path="${path}"]`);
|
||||
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)
|
||||
))).toBe(true);
|
||||
}
|
||||
|
||||
// Ink sampling only: expose the last branch without scroll-container clipping.
|
||||
// Interaction tests retain the real scrollable viewport.
|
||||
async function exposeTree(page: import('@playwright/test').Page) {
|
||||
@@ -147,19 +155,14 @@ for (const trigger of ['click', 'wheel', 'key']) {
|
||||
const errors: string[] = [];
|
||||
page.on('pageerror', (error) => errors.push(error.message));
|
||||
await armed(page);
|
||||
if (trigger === 'click') await page.getByRole('treeitem', { name: 'Worldspaceを開く' }).click();
|
||||
if (trigger === 'click') await nodeAt(page, '/').click();
|
||||
if (trigger === 'wheel') await page.mouse.wheel(0, 240);
|
||||
if (trigger === 'key') await page.keyboard.press('a');
|
||||
await expect(page.locator('html')).toHaveAttribute('data-landing', 'playing');
|
||||
await complete(page);
|
||||
await nodeAt(page, initialPath).click();
|
||||
await expect(page.locator('[data-object-signature]')).toBeHidden();
|
||||
await page.getByText('Object signature', { exact: true }).click();
|
||||
await expect(page.locator('[data-object-signature]')).toContainText('object at "/design/object-tree/paths/names"');
|
||||
await expect(page.locator('[data-result]')).not.toContainText('Unicode正規化');
|
||||
await page.getByRole('button', { name: '本文を読む' }).click();
|
||||
await expect(page.locator('[data-result]')).toContainText('Unicode正規化');
|
||||
await expect(page.getByRole('status')).toContainText('read_body が完了');
|
||||
await expect(page.locator(selectedContent)).toBeVisible();
|
||||
await expect(page.locator(selectedContent + ' h1')).toHaveText('1. コンセプト');
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
}
|
||||
@@ -342,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('.call-panel')).toHaveAttribute('inert', '');
|
||||
await expect(page.locator(selectedContent)).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);
|
||||
@@ -410,7 +413,7 @@ test('continuous motion overlaps growth, travel and reveal without intermediate
|
||||
expect(extended.gap).toBeCloseTo(0, 1);
|
||||
expect(extended.bottom).toBeLessThan(page.viewportSize()!.height);
|
||||
await page.screenshot({ path: testInfo.outputPath('04-travelling-trunk.png') });
|
||||
await page.clock.runFor(920); // 2600ms: moving shared scene, persistent trunk AND panel onset.
|
||||
await page.clock.runFor(920); // 2600ms: moving shared scene, persistent trunk AND article onset.
|
||||
const overlap = await page.evaluate(() => {
|
||||
const anchor = document.querySelector<SVGGElement>('.trunk-anchor')!;
|
||||
const matrix = anchor.getScreenCTM()!;
|
||||
@@ -426,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),
|
||||
panelOpacity: Number(getComputedStyle(document.querySelector('.text-panel')!).opacity),
|
||||
articleOpacity: Number(getComputedStyle(document.querySelector('[data-selected-content]')!).opacity),
|
||||
sceneTransform: getComputedStyle(document.querySelector('.tree-scene')!).transform,
|
||||
rowTransforms: [...document.querySelectorAll('.node-row, .demo-node')].map((row) => getComputedStyle(row).transform),
|
||||
};
|
||||
@@ -436,7 +439,7 @@ test('continuous motion overlaps growth, travel and reveal without intermediate
|
||||
expect(overlap.spineOpacity).toBeGreaterThan(0);
|
||||
expect(overlap.trunkOpacity).toBe(1); // No opacity handoff or replacement text spine.
|
||||
expect(overlap.labelOpacity).toBeGreaterThan(0);
|
||||
expect(overlap.panelOpacity).toBeGreaterThan(0);
|
||||
expect(overlap.articleOpacity).toBeGreaterThan(0);
|
||||
expect(overlap.sceneTransform).not.toBe('none');
|
||||
expect(overlap.rowTransforms.every((transform) => transform === 'none')).toBe(true);
|
||||
const growingTreeRoot = (await root.boundingBox())!;
|
||||
@@ -444,8 +447,8 @@ test('continuous motion overlaps growth, travel and reveal without intermediate
|
||||
await page.screenshot({ path: testInfo.outputPath('05-moving-tree.png') });
|
||||
await page.clock.runFor(200);
|
||||
expect((await root.boundingBox())!.height).toBeLessThan(growingTreeRoot.height);
|
||||
await expect(page.locator('.call-panel')).toHaveAttribute('data-glitching');
|
||||
expect(await page.locator('.call-panel .box-fill').first().textContent()).not.toMatch(/^─+$/);
|
||||
await expectNodeGlitch(page);
|
||||
await expect(page.locator(selectedContent + ' [data-glitching]')).toHaveCount(0);
|
||||
await page.screenshot({ path: testInfo.outputPath('06-overlapping-glitch.png') });
|
||||
await page.clock.runFor(400);
|
||||
const chromeOpacity = await page.locator('.site-header').evaluate((el) => Number(getComputedStyle(el).opacity));
|
||||
@@ -455,25 +458,16 @@ 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('.call-panel')).not.toHaveAttribute('inert');
|
||||
await expect(page.locator(selectedContent)).not.toHaveAttribute('inert');
|
||||
await expect(page.locator('[data-glitching]')).toHaveCount(0);
|
||||
await expect(page.locator('[data-object-signature]')).toContainText('object at "/design/object-tree/paths/names"');
|
||||
for (const panel of await page.locator('.ascii-panel').all()) {
|
||||
await expect(panel).toHaveCSS('outline-style', 'none');
|
||||
await expect(panel).toHaveCSS('border-top-width', '0px');
|
||||
await expect(panel.locator('.box-corner')).toHaveText(['┌', '┐', '└', '┘']);
|
||||
for (const fill of await panel.locator('.box-fill').all()) await expect(fill).toHaveText(/^─+$/);
|
||||
for (const side of await panel.locator('.box-column').all()) await expect(side).toHaveText(/^[│\s]+$/);
|
||||
}
|
||||
await expect(page.locator(selectedContent + ' h1')).toHaveText('1. コンセプト');
|
||||
await expect(page.locator('.branch-lines')).toHaveCount(0);
|
||||
await expect(page.locator('.demo-node')).toHaveCount(42);
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(24);
|
||||
await expect(page.locator('[data-tree-count]')).toHaveText('24 / 42 objects');
|
||||
await expect(page.locator('.demo-badge')).toHaveText('4 AREAS / 26 GUIDES / 42 OBJECTS');
|
||||
await expect(page.locator('.demo-node')).toHaveCount(treeNodeCount);
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(treeNodeCount);
|
||||
await checkGlyphRefs(page);
|
||||
await expect(page.locator('.root-wire')).toHaveCount(0);
|
||||
await expect(page.locator(visiblePrefixes + ' svg')).toHaveCount(23);
|
||||
await expect(page.locator(visiblePrefixes + ' .branch-spine use')).toHaveCount(23);
|
||||
await expect(page.locator(visiblePrefixes + ' svg')).toHaveCount(treeNodeCount - 1);
|
||||
await expect(page.locator(visiblePrefixes + ' .branch-spine use')).toHaveCount(treeNodeCount - 1);
|
||||
await expect(page.locator('.tree-scene')).toHaveCSS('transform', 'none');
|
||||
const grid = await page.evaluate(() => {
|
||||
const root = document.querySelector('.root-mark')!.getBoundingClientRect();
|
||||
@@ -489,7 +483,6 @@ test('continuous motion overlaps growth, travel and reveal without intermediate
|
||||
frameCells: frame.width / cell,
|
||||
rowHeights: rows.map((row) => row.height),
|
||||
nodeColumns: nodes.map((node) => (node.left - frame.left) / cell),
|
||||
panels: [...document.querySelectorAll('.ascii-panel')].map((panel) => panel.getBoundingClientRect().toJSON()),
|
||||
font: getComputedStyle(prefix).fontFamily,
|
||||
};
|
||||
});
|
||||
@@ -498,9 +491,6 @@ test('continuous motion overlaps growth, travel and reveal without intermediate
|
||||
expect(Math.abs(grid.frameCells / 4 - Math.round(grid.frameCells / 4))).toBeLessThan(.02);
|
||||
expect(grid.rowHeights.every((height) => height === 24)).toBe(true);
|
||||
expect(grid.nodeColumns.every((col) => Math.abs(col - Math.round(col)) < .02)).toBe(true);
|
||||
// Prose determines panel height; the panels still must not overlap.
|
||||
expect(grid.panels[1].top - grid.panels[0].bottom).toBeCloseTo(24, 4);
|
||||
expect(grid.panels.every((panel) => panel.height > 0 && panel.width > 0)).toBe(true);
|
||||
expect(grid.font).toContain('Mono');
|
||||
await page.screenshot({ path: testInfo.outputPath('06-world.png'), fullPage: true, animations: 'disabled' });
|
||||
await page.keyboard.press('z');
|
||||
@@ -528,44 +518,155 @@ for (const time of [1500, 2600]) {
|
||||
});
|
||||
}
|
||||
|
||||
test('skipping an active glitch restores exact text and frames', async ({ page }) => {
|
||||
test('skipping an active node glitch restores exact filenames without altering document HTML', async ({ page }) => {
|
||||
await page.clock.install({ time: new Date('2026-01-01T00:00:00Z') });
|
||||
await page.clock.pauseAt(new Date('2026-01-01T00:00:01Z'));
|
||||
await armed(page);
|
||||
const html = await page.locator(selectedContent).innerHTML();
|
||||
await page.keyboard.press('a');
|
||||
await expect(page.locator(stage)).toHaveAttribute('data-phase', 'flow');
|
||||
await expect(page.locator('.tap-stream-a')).toHaveCSS('opacity', '0');
|
||||
await page.clock.runFor(3000);
|
||||
await expect(page.locator('.call-panel')).toHaveAttribute('data-glitching');
|
||||
await expectNodeGlitch(page);
|
||||
await expect(page.locator(selectedContent + ' [data-glitching]')).toHaveCount(0);
|
||||
expect(await page.locator(selectedContent).innerHTML()).toBe(html);
|
||||
await page.keyboard.press('Escape');
|
||||
await complete(page);
|
||||
await expect(page.locator('[data-glitching]')).toHaveCount(0);
|
||||
await expect(page.locator('#call-result-title')).toHaveText('03 / Read');
|
||||
await expect(page.locator('.call-panel .box-corner')).toHaveText(['┌', '┐', '└', '┘']);
|
||||
await expect(page.locator('.call-panel .box-fill').first()).toHaveText(/^─+$/);
|
||||
for (const path of treePaths.slice(1)) await expect(nodeAt(page, path).locator('.node-label')).toHaveText(path.split('/').at(-1)!);
|
||||
await page.clock.runFor(2000);
|
||||
await expect(page.locator('[data-object-signature]')).toContainText('object at "/design/object-tree/paths/names"');
|
||||
expect(await page.locator(selectedContent).innerHTML()).toBe(html);
|
||||
await page.locator('.root-mark').hover();
|
||||
await expect(page.locator(stage)).not.toHaveAttribute('data-hover');
|
||||
});
|
||||
|
||||
test('tree selection, collapse and reading are keyboard accessible in the scrollable viewport', async ({ page }) => {
|
||||
test('landing contains only the real filename tree and selected canonical content', async ({ page }, testInfo) => {
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' });
|
||||
await page.goto('./');
|
||||
await complete(page);
|
||||
await expect(page.locator([
|
||||
'.intro-invitation', '.tree-toolbar', '.tree-search', '[data-tree-count]', '.tree-help',
|
||||
'.tree-caption', '.root-caption', '.demo-badge', '.node-description', '.ascii-panel',
|
||||
'.box-frame', '.panel-title', '[data-object-signature]', '[data-interface-signature]',
|
||||
'[data-result]', '[data-sources]', '.demo-call', '.demo-status', '.static-guides',
|
||||
'.guide-body', '.no-script-note', '.brand-caption', '.site-footer',
|
||||
'main > :not(.landing):not(script):not(style)',
|
||||
].join(','))).toHaveCount(0);
|
||||
await expect(page.getByRole('status')).toHaveCount(0);
|
||||
await expect(page.getByRole('searchbox')).toHaveCount(0);
|
||||
await expect(page.getByRole('button', { name: '本文を読む' })).toHaveCount(0);
|
||||
await expect(page.getByRole('link', { name: 'Home', exact: true })).toHaveCount(0);
|
||||
await expect(page.locator('.site-header')).toContainText('WIP');
|
||||
await expect(page.getByRole('link', { name: 'Source', exact: true })).toBeVisible();
|
||||
await expect(page.locator('.demo-node')).toHaveCount(treeNodeCount);
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(treeNodeCount);
|
||||
expect(await page.locator('.demo-node').evaluateAll((nodes) => nodes.map((node) => (node as HTMLElement).dataset.path))).toEqual(treePaths);
|
||||
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.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');
|
||||
}
|
||||
for (const path of ['/', '/draft']) {
|
||||
const folder = nodeAt(page, path);
|
||||
expect(await folder.evaluate((node) => node.tagName)).toBe('BUTTON');
|
||||
await expect(folder).toHaveAttribute('aria-expanded', 'true');
|
||||
await expect(folder).toHaveAttribute('aria-level', path === '/' ? '1' : '2');
|
||||
}
|
||||
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!))
|
||||
))).toBe(true);
|
||||
const payload = page.locator('script#landing-documents');
|
||||
await expect(payload).toHaveCount(1);
|
||||
await expect(payload).toHaveAttribute('type', 'application/json');
|
||||
const serialized = (await payload.textContent())!;
|
||||
expect(serialized).not.toContain('<'); // Rendered HTML cannot terminate its JSON script element.
|
||||
const stored: { path: string; html?: string; title?: string; href?: string }[] = JSON.parse(serialized);
|
||||
expect(stored.map((document) => document.path)).toEqual(treePaths);
|
||||
for (const path of documentPaths) {
|
||||
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');
|
||||
}
|
||||
for (const path of ['/', '/draft']) {
|
||||
const folder = stored.find((document) => document.path === path)!;
|
||||
expect(folder.html).toBeUndefined();
|
||||
expect(folder.title).toBeUndefined();
|
||||
expect(folder.href).toBeUndefined();
|
||||
}
|
||||
await expect(nodeAt(page, initialPath)).toHaveAttribute('aria-selected', 'true');
|
||||
await expect(page.locator(selectedContent)).toBeVisible();
|
||||
await expect(page.locator(selectedContent + ' h1')).toHaveText('1. コンセプト');
|
||||
expect(await page.locator('.landing h1, .landing h2, .landing h3, .landing h4, .landing h5, .landing h6').evaluateAll((headings) => headings.every((heading) => heading.closest('[data-selected-content]')))).toBe(true);
|
||||
await checkGlyphRefs(page);
|
||||
});
|
||||
|
||||
test('each file immediately displays exactly its Book HTML, including links, without fetching or navigating', 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>();
|
||||
for (const path of documentPaths) {
|
||||
await book.goto(bookURL(path, testInfo.project.use.baseURL!));
|
||||
bodies.set(path, await book.locator('article.prose').innerHTML());
|
||||
}
|
||||
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()));
|
||||
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('');
|
||||
}
|
||||
});
|
||||
|
||||
test('file selection and folder collapse work with keyboard navigation in the scrollable viewport', async ({ page }) => {
|
||||
await armed(page);
|
||||
await page.keyboard.press('Escape');
|
||||
await complete(page);
|
||||
await expect(nodeAt(page, initialPath)).toBeFocused();
|
||||
await page.keyboard.press('ArrowUp');
|
||||
const paths = nodeAt(page, '/design/object-tree/paths');
|
||||
await expect(paths).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 page.keyboard.press('ArrowLeft');
|
||||
await expect(paths).toHaveAttribute('aria-expanded', 'false');
|
||||
await expect(nodeAt(page, initialPath)).toBeHidden();
|
||||
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 checkGlyphRefs(page);
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await expect(nodeAt(page, initialPath)).toBeVisible();
|
||||
await expect(draft).toHaveAttribute('aria-expanded', 'true');
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await expect(nodeAt(page, initialPath)).toBeFocused();
|
||||
await expect(nodeAt(page, documentPaths[10])).toBeFocused();
|
||||
await expect(page.locator(selectedContent)).toBeVisible();
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
await expect(paths).toBeFocused();
|
||||
await expect(draft).toBeFocused();
|
||||
await expect(page.locator(selectedContent)).toBeHidden();
|
||||
await page.keyboard.press('Home');
|
||||
const root = nodeAt(page, '/');
|
||||
await expect(root).toBeFocused();
|
||||
@@ -573,165 +674,31 @@ test('tree selection, collapse and reading are keyboard accessible in the scroll
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(1);
|
||||
expect((await trunkGeometry(page)).length).toBe(0);
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(24);
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(treeNodeCount);
|
||||
await checkGlyphRefs(page);
|
||||
await page.getByRole('button', { name: 'すべて展開', exact: true }).click();
|
||||
await root.focus();
|
||||
await page.keyboard.press('End');
|
||||
const finalLeaf = nodeAt(page, '/review/boundary-checks/failure-tests');
|
||||
const finalLeaf = nodeAt(page, documentPaths.at(-1)!);
|
||||
await expect(finalLeaf).toBeFocused();
|
||||
await expect(finalLeaf).toHaveAttribute('aria-selected', 'true');
|
||||
const scroll = await page.locator('.tree-viewport').evaluate((viewport) => {
|
||||
const node = document.activeElement!.getBoundingClientRect(), box = viewport.getBoundingClientRect();
|
||||
return { top: viewport.scrollTop, inside: node.top >= box.top && node.bottom <= box.bottom && node.left >= box.left && node.right <= box.right };
|
||||
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 };
|
||||
});
|
||||
expect(scroll.top).toBeGreaterThan(0);
|
||||
if (scroll.needsScroll) expect(scroll.top).toBeGreaterThan(0);
|
||||
expect(scroll.inside).toBe(true);
|
||||
await page.keyboard.press('ArrowUp');
|
||||
await expect(nodeAt(page, '/review/boundary-checks/validation')).toBeFocused();
|
||||
await expect(nodeAt(page, '/draft/typescript-api-model.md')).toBeFocused();
|
||||
await page.keyboard.press('ArrowDown');
|
||||
await expect(finalLeaf).toBeFocused();
|
||||
await expect(page.locator('.demo-node[tabindex="0"]')).toHaveCount(1);
|
||||
await page.keyboard.press('Tab');
|
||||
await expect(page.locator('.text-panel summary')).toBeFocused();
|
||||
await page.keyboard.press('Enter');
|
||||
await expect(page.locator('[data-object-signature]')).toBeVisible();
|
||||
await expect(finalLeaf).toHaveAttribute('aria-selected', 'true');
|
||||
await expect(page.locator(selectedContent)).toBeVisible();
|
||||
await page.keyboard.press('Space');
|
||||
await expect(finalLeaf).toHaveAttribute('aria-selected', 'true');
|
||||
await expect(page.locator(selectedContent)).toBeVisible();
|
||||
await page.keyboard.press('Tab');
|
||||
await expect(page.locator('.call-panel summary')).toBeFocused();
|
||||
await page.keyboard.press('Enter');
|
||||
await expect(page.locator('[data-interface-signature]')).toBeVisible();
|
||||
await page.keyboard.press('Tab');
|
||||
await expect(page.getByRole('button', { name: '本文を読む' })).toBeFocused();
|
||||
await page.keyboard.press('Enter');
|
||||
await expect(page.locator('[data-call-state]')).toHaveText('RETURNED');
|
||||
await expect(page.locator('[data-result]')).toContainText('結果不明やTransport不達でcallを自動再実行しない');
|
||||
});
|
||||
|
||||
test('expand/collapse all preserves visible selection and rehomes hidden selection, including a closed root', async ({ page }, testInfo) => {
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' });
|
||||
await page.goto('./');
|
||||
await complete(page);
|
||||
const expand = page.getByRole('button', { name: 'すべて展開', exact: true });
|
||||
const collapse = page.getByRole('button', { name: 'たたむ', exact: true });
|
||||
await expect(nodeAt(page, initialPath)).toHaveAttribute('aria-selected', 'true');
|
||||
await expand.click();
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(42);
|
||||
await expect(page.locator('[data-tree-count]')).toHaveText('42 / 42 objects');
|
||||
const labelsOnly = 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 ?? '')
|
||||
));
|
||||
expect(labelsOnly).toBe(true);
|
||||
await expect(page.locator('.demo-node:not([aria-expanded])')).toHaveCount(26);
|
||||
await expect(nodeAt(page, initialPath)).toHaveAttribute('aria-selected', 'true');
|
||||
await checkGlyphRefs(page);
|
||||
await collapse.click();
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(5);
|
||||
await expect(page.locator('[data-tree-count]')).toHaveText('5 / 42 objects');
|
||||
await expect(nodeAt(page, '/')).toHaveAttribute('aria-selected', 'true');
|
||||
await expect(page.locator('[data-target]')).toHaveText('/');
|
||||
await expect(page.getByRole('button', { name: '本文を読む' })).toBeDisabled();
|
||||
await checkGlyphRefs(page);
|
||||
await nodeAt(page, '/build').click();
|
||||
await expand.click();
|
||||
await expect(nodeAt(page, '/build')).toHaveAttribute('aria-selected', 'true');
|
||||
await collapse.click();
|
||||
await expect(nodeAt(page, '/build')).toHaveAttribute('aria-selected', 'true');
|
||||
await nodeAt(page, '/').focus();
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(1);
|
||||
await collapse.click(); // Regression: collapse-all must reopen a keyboard-collapsed root.
|
||||
await expect(nodeAt(page, '/')).toHaveAttribute('aria-expanded', 'true');
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(5);
|
||||
await checkGlyphRefs(page);
|
||||
await page.locator('.tree-pane').screenshot({ path: testInfo.outputPath('guide-collapse-all-root-reopened.png') });
|
||||
});
|
||||
|
||||
test('body search exposes ancestors, preserves or rehomes selection, and handles no results', async ({ page }, testInfo) => {
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' });
|
||||
await page.goto('./');
|
||||
await complete(page);
|
||||
const search = page.getByRole('searchbox', { name: 'コンテンツを検索' });
|
||||
const expand = page.getByRole('button', { name: 'すべて展開', exact: true });
|
||||
const collapse = page.getByRole('button', { name: 'たたむ', exact: true });
|
||||
await search.fill('Unicode正規化'); // Body only, not a name/title/description.
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(5);
|
||||
await expect(page.locator('[data-tree-count]')).toHaveText('5 / 42 objects');
|
||||
await expect(nodeAt(page, initialPath)).toHaveAttribute('aria-selected', 'true');
|
||||
await expect(expand).toBeDisabled();
|
||||
await expect(collapse).toBeDisabled();
|
||||
await checkGlyphRefs(page);
|
||||
await expect(nodeAt(page, '/design').locator('..').locator('.branch-spine use')).toHaveAttribute('href', '#tree-glyph-9492');
|
||||
const shortTrunk = (await trunkGeometry(page)).length;
|
||||
await page.locator('.tree-pane').screenshot({ path: testInfo.outputPath('guide-search-body-ancestors.png') });
|
||||
await search.fill('疑似ID'); // Initially visible identity, also body-only.
|
||||
await expect(nodeAt(page, '/design/object-tree/paths/identity')).toHaveAttribute('aria-selected', 'true');
|
||||
await expect(page.locator('[data-target]')).toHaveText('/design/object-tree/paths/identity');
|
||||
await checkGlyphRefs(page);
|
||||
await search.fill('観測cache'); // A leaf behind an initially collapsed parent.
|
||||
await expect(nodeAt(page, '/build/client/cache')).toBeVisible();
|
||||
await expect(nodeAt(page, '/build/client/cache')).toHaveAttribute('aria-selected', 'true');
|
||||
await expect(nodeAt(page, '/build/client')).toHaveAttribute('aria-expanded', 'true');
|
||||
await checkGlyphRefs(page);
|
||||
await search.press('Enter');
|
||||
await expect(nodeAt(page, '/build/client/cache')).toBeFocused();
|
||||
await page.keyboard.press('Home');
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await expect(nodeAt(page, '/build')).toBeFocused();
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await expect(nodeAt(page, '/build/client')).toBeFocused();
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await expect(nodeAt(page, '/build/client/cache')).toBeFocused();
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
await expect(nodeAt(page, '/build/client')).toBeFocused();
|
||||
await expect(nodeAt(page, '/build/client')).toHaveAttribute('aria-expanded', 'true');
|
||||
await search.fill('no-guide-can-match-this-98371');
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(1);
|
||||
await expect(page.locator('[data-tree-count]')).toHaveText('一致する項目がありません');
|
||||
await expect(nodeAt(page, '/')).toHaveAttribute('aria-selected', 'true');
|
||||
await expect(page.locator('[data-target]')).toHaveText('/');
|
||||
expect((await trunkGeometry(page)).length).toBe(0);
|
||||
await expect(page.getByRole('button', { name: '本文を読む' })).toBeDisabled();
|
||||
await page.locator('.tree-pane').screenshot({ path: testInfo.outputPath('guide-search-no-results.png') });
|
||||
await search.fill('');
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(24);
|
||||
await expect(expand).toBeEnabled();
|
||||
await expect(collapse).toBeEnabled();
|
||||
await expect(nodeAt(page, '/build/client')).toHaveAttribute('aria-expanded', 'false');
|
||||
await expect(nodeAt(page, '/')).toHaveAttribute('aria-selected', 'true');
|
||||
expect((await trunkGeometry(page)).length).toBeGreaterThan(shortTrunk);
|
||||
await checkGlyphRefs(page);
|
||||
});
|
||||
|
||||
test('reading uses explicit operations, disclosure signatures, and navigable SITE_BASE-aware sources', async ({ page }, testInfo) => {
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' });
|
||||
await page.goto('./');
|
||||
await complete(page);
|
||||
await expect(page.locator('[data-target]')).toHaveText(initialPath);
|
||||
await expect(page.locator('[data-object-signature]')).toBeHidden();
|
||||
await expect(page.locator('[data-interface-signature]')).toBeHidden();
|
||||
await expect(page.locator('[data-sources]')).toBeHidden();
|
||||
await expect(page.locator('[data-result]')).not.toContainText('Unicode正規化');
|
||||
await page.getByText('Object signature', { exact: true }).click();
|
||||
await expect(page.locator('[data-object-signature]')).toBeVisible();
|
||||
await expect(page.locator('[data-object-signature]')).toContainText('object at "' + initialPath + '"');
|
||||
await page.getByText('Interface signature', { exact: true }).click();
|
||||
await expect(page.locator('[data-interface-signature]')).toBeVisible();
|
||||
await expect(page.locator('[data-interface-signature]')).toContainText('operation read_body() -> { body: string }');
|
||||
await page.getByRole('button', { name: '本文を読む' }).click();
|
||||
await expect(page.locator('[data-result]')).toContainText('Unicode正規化');
|
||||
await expect(page.locator('[data-call-state]')).toHaveText('RETURNED');
|
||||
await expect(page.getByRole('status')).toContainText('read_body が完了');
|
||||
await expect(page.locator('[data-sources]')).toBeVisible();
|
||||
const links = page.locator('[data-sources] a');
|
||||
await expect(links).toHaveCount(2);
|
||||
const base = testInfo.project.use.baseURL!;
|
||||
const expected = ['book/2.1-object-tree-schema/', 'book/3-protocol/'].map((path) => new URL(path, base).href);
|
||||
expect(await links.evaluateAll((anchors) => anchors.map((a) => (a as HTMLAnchorElement).href))).toEqual(expected);
|
||||
await page.locator('.call-panel').screenshot({ path: testInfo.outputPath('guide-read-signatures-sources.png') });
|
||||
await links.first().click();
|
||||
await expect(page).toHaveURL(expected[0]);
|
||||
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
|
||||
await expect(page.locator('.prose')).toContainText('Object');
|
||||
await expect(page.locator(selectedContent)).toBeFocused();
|
||||
});
|
||||
|
||||
test.describe('glyph trunk regressions', () => {
|
||||
@@ -773,7 +740,7 @@ test.describe('glyph trunk regressions', () => {
|
||||
await complete(page);
|
||||
await exposeTree(page);
|
||||
const checkEnd = async (name: string) => {
|
||||
// Keyboard selection can scroll wide prose summaries horizontally. Reset
|
||||
// Keyboard selection can scroll long filenames horizontally. Reset
|
||||
// the sampling column before measuring, never after computing screen points.
|
||||
await page.locator('.tree-viewport').evaluate((el) => { el.scrollTop = el.scrollLeft = 0; });
|
||||
await page.evaluate(() => scrollTo(0, 0));
|
||||
@@ -821,68 +788,55 @@ test.describe('glyph trunk regressions', () => {
|
||||
}
|
||||
return trunk.length;
|
||||
};
|
||||
let previous = await checkEnd('expanded');
|
||||
for (const name of ['design', 'build', 'operate']) {
|
||||
const node = page.getByRole('treeitem', { name, exact: true });
|
||||
await node.focus();
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
await expect(node).toHaveAttribute('aria-expanded', 'false');
|
||||
const length = await checkEnd(name);
|
||||
expect(length).toBeLessThan(previous);
|
||||
previous = length;
|
||||
}
|
||||
const review = page.getByRole('treeitem', { name: 'review', exact: true });
|
||||
await review.focus();
|
||||
const expanded = await checkEnd('expanded');
|
||||
const draft = nodeAt(page, '/draft');
|
||||
await draft.focus();
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
expect(await checkEnd('review')).toBe(previous); // Last child never extends the first-column spine.
|
||||
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 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(previous);
|
||||
for (const name of ['design', 'build', 'operate', 'review']) {
|
||||
await page.getByRole('treeitem', { name, exact: true }).focus();
|
||||
await page.keyboard.press('ArrowRight');
|
||||
}
|
||||
expect(await checkEnd('all-reopened')).toBeGreaterThan(previous);
|
||||
expect(await checkEnd('root-reopened')).toBe(expanded);
|
||||
await draft.focus();
|
||||
await page.keyboard.press('ArrowRight');
|
||||
expect(await checkEnd('all-reopened')).toBe(expanded);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('JavaScript-free glyph tree', () => {
|
||||
test.use({ javaScriptEnabled: false });
|
||||
|
||||
test('all 26 authored bodies are accessible without JavaScript', async ({ page }, testInfo) => {
|
||||
test('initial canonical article and every file link remain usable without JavaScript', async ({ page }, testInfo) => {
|
||||
await page.goto('./');
|
||||
const articles = page.locator('.static-guides details');
|
||||
await expect(articles).toHaveCount(26);
|
||||
await expect(page.locator('.static-guides .guide-body')).toHaveCount(26);
|
||||
const bodies = await page.locator('.static-guides .guide-body').allTextContents();
|
||||
expect(new Set(bodies).size).toBe(26);
|
||||
for (const article of await articles.all()) {
|
||||
await article.locator('summary').click();
|
||||
await expect(article).toHaveAttribute('open', '');
|
||||
await expect(article.locator('.guide-body')).toBeVisible();
|
||||
expect((await article.locator('.guide-body').textContent())!.length).toBeGreaterThan(200);
|
||||
const sources = await article.locator('a').evaluateAll((links) => links.map((a) => (a as HTMLAnchorElement).href));
|
||||
expect(sources.length).toBeGreaterThan(0);
|
||||
for (const source of sources) expect(source.startsWith(new URL('book/', testInfo.project.use.baseURL!).href)).toBe(true);
|
||||
await article.locator('summary').click();
|
||||
await expect(article.locator('.guide-body')).toBeHidden();
|
||||
await expect(page.locator(selectedContent)).toBeVisible();
|
||||
await expect(page.locator(selectedContent + ' h1')).toHaveText('1. コンセプト');
|
||||
const initialHTML = await page.locator(selectedContent).innerHTML();
|
||||
await expect(page.locator('.static-guides, .guide-body, .ascii-panel')).toHaveCount(0);
|
||||
await expect(page.getByRole('treeitem')).toHaveCount(treeNodeCount);
|
||||
for (const path of documentPaths) {
|
||||
await page.goto('./');
|
||||
const node = nodeAt(page, path);
|
||||
expect(await node.evaluate((node) => (node as HTMLAnchorElement).href)).toBe(bookURL(path, testInfo.project.use.baseURL!));
|
||||
await node.click();
|
||||
await expect(page).toHaveURL(bookURL(path, testInfo.project.use.baseURL!));
|
||||
await expect(page.locator('article.prose h1')).toBeVisible();
|
||||
if (path === initialPath) expect(await page.locator('article.prose').innerHTML()).toBe(initialHTML);
|
||||
}
|
||||
await articles.last().locator('summary').click();
|
||||
await articles.last().screenshot({ path: testInfo.outputPath('no-js-final-article-body.png') });
|
||||
});
|
||||
|
||||
test('SSR paints one filled trunk and SVG branch outlines without animation code', async ({ page }, testInfo) => {
|
||||
await page.goto('./');
|
||||
await expect(page.locator('html')).not.toHaveAttribute('data-landing');
|
||||
await expect(page.locator('.no-script-note')).toBeVisible();
|
||||
await expect(page.locator(selectedContent)).toBeVisible();
|
||||
expect((await page.locator('.root-shape').boundingBox())!.height).toBeCloseTo(12.25, 4);
|
||||
await expect(page.locator('.trunk-line')).toHaveCount(1);
|
||||
await expect(page.locator('.trunk-line')).toHaveCSS('opacity', '1');
|
||||
await expect(page.locator('.trunk-line')).toHaveCSS('stroke', 'none');
|
||||
await expect(page.locator(visiblePrefixes + ' svg')).toHaveCount(23);
|
||||
await expect(page.locator(visiblePrefixes + ' svg')).toHaveCount(treeNodeCount - 1);
|
||||
expect(await page.locator('.branch-prefix').evaluateAll((prefixes) => prefixes.every((prefix) => !prefix.textContent!.trim()))).toBe(true);
|
||||
const trunk = await trunkGeometry(page);
|
||||
expect(trunk.width).toBeCloseTo(1.33, 5);
|
||||
@@ -895,35 +849,14 @@ test.describe('JavaScript-free glyph tree', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('selecting another guide cancels a read reveal and resets its state', async ({ page }) => {
|
||||
await page.clock.install({ time: new Date('2026-01-01T00:00:00Z') });
|
||||
await page.clock.pauseAt(new Date('2026-01-01T00:00:01Z'));
|
||||
await armed(page);
|
||||
await page.keyboard.press('Escape');
|
||||
await complete(page);
|
||||
await expect(page.locator('[data-call-state]')).toHaveText('READY');
|
||||
await page.getByRole('button', { name: '本文を読む' }).click();
|
||||
await page.clock.runFor(32);
|
||||
await expect(page.locator('[data-call-state]')).toHaveText('RETURNED');
|
||||
await nodeAt(page, '/design/object-tree/paths/identity').click();
|
||||
await expect(page.locator('[data-call-state]')).toHaveText('READY');
|
||||
await expect(page.locator('[data-result]')).toHaveCSS('opacity', '1');
|
||||
await expect(page.locator('[data-result]')).toHaveCSS('transform', 'none');
|
||||
await expect(page.locator('[data-sources]')).toBeHidden();
|
||||
await expect(page.locator('[data-result]')).not.toContainText('Unicode正規化');
|
||||
await page.clock.runFor(500);
|
||||
await expect(page.locator('[data-call-state]')).toHaveText('READY');
|
||||
await page.getByRole('button', { name: '本文を読む' }).click();
|
||||
await expect(page.locator('[data-result]')).toContainText('疑似ID');
|
||||
});
|
||||
|
||||
test('reduced motion opens the working demo immediately', async ({ page }) => {
|
||||
test('reduced motion opens the real tree and article immediately', async ({ page }) => {
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' });
|
||||
await page.goto('./');
|
||||
await complete(page);
|
||||
await expect(page.locator('.intro-skip')).toBeHidden();
|
||||
await page.getByRole('button', { name: '本文を読む' }).click();
|
||||
await expect(page.locator('[data-result]')).toContainText('Unicode正規化');
|
||||
await expect(page.locator(selectedContent)).toHaveCSS('opacity', '1');
|
||||
await nodeAt(page, '/2-core-model.md').click();
|
||||
await expect(page.locator(selectedContent + ' h1')).toHaveText('2. コアモデル');
|
||||
});
|
||||
|
||||
test('changing motion preference and resizing cannot strand the intro', async ({ page }) => {
|
||||
@@ -941,7 +874,13 @@ test('blocked animation module fails open to readable content', async ({ page })
|
||||
await page.route('**/*.js', (route) => route.abort());
|
||||
await page.goto('./');
|
||||
await expect(page.locator('html')).not.toHaveAttribute('data-landing', { timeout: 6000 });
|
||||
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
|
||||
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 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('通信の約束まで');
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ test('home and book are usable without client JavaScript', async ({ page }, test
|
||||
const failures: string[] = [];
|
||||
page.on('response', (response) => { if (response.status() >= 400) failures.push(response.url()); });
|
||||
await page.goto('./');
|
||||
await expect(page.getByRole('heading', { level: 1 })).toContainText('探索できる世界へ');
|
||||
await expect(page.locator('[data-selected-content] h1')).toHaveText('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();
|
||||
|
||||
Reference in New Issue
Block a user