@@ -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 viewpor t' , async ( { page } ) = > {
test ( 'landing contains only the real filename tree and selected canonical conten t' , 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 summari es horizontally. Reset
// Keyboard selection can scroll long filenam es 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 accessi ble without JavaScript' , async ( { page } , testInfo ) = > {
test ( 'initial canonical article and every file link remain usa ble 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 st ate' , 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 immedi ately ' , 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 ( '通信の約束まで' ) ;
} ) ;