test: assert window docking in the two pass-by journey tests
This commit is contained in:
@@ -51,7 +51,7 @@ test('keeps navigation, HUD, progress, chapter, and arrival on the same next-fra
|
||||
const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
|
||||
const destination = id === 'contact'
|
||||
? document.documentElement.scrollHeight
|
||||
: Math.max(0, top - viewportHeight * 0.35 + 1);
|
||||
: Math.max(0, top - viewportHeight * 0.6 + 1);
|
||||
window.scrollTo({ top: destination, behavior: 'instant' });
|
||||
}, section.id);
|
||||
await waitForOneAnimationFrame(page);
|
||||
@@ -102,7 +102,7 @@ test('draws only the independently eligible next roadside sign during passive fo
|
||||
const headingTop = profile.getBoundingClientRect().top + window.scrollY;
|
||||
const destination = Math.max(
|
||||
1,
|
||||
headingTop - viewportHeight * (0.35 + candidateDistanceVh),
|
||||
headingTop - viewportHeight * (0.6 + candidateDistanceVh),
|
||||
);
|
||||
return { destination, viewportHeight, expectedLabel: 'Profile' };
|
||||
}, triggerProbeDistanceVh(viewport.width, viewport.height));
|
||||
@@ -133,7 +133,7 @@ test('keeps a readable sign immutable through reversal, resize, and a multi-sect
|
||||
const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
|
||||
const headingTop = profile.getBoundingClientRect().top + window.scrollY;
|
||||
return {
|
||||
destination: Math.max(1, headingTop - viewportHeight * (0.35 + 0.45)),
|
||||
destination: Math.max(1, headingTop - viewportHeight * (0.6 + 0.45)),
|
||||
viewportHeight,
|
||||
};
|
||||
});
|
||||
@@ -176,13 +176,31 @@ test('keeps a readable sign immutable through reversal, resize, and a multi-sect
|
||||
expect((await readSceneInstrumentation(page)).maximumCanvasCount).toBeLessThanOrEqual(1);
|
||||
});
|
||||
|
||||
test('carries a departed sign past the car before its restrained edge fade', async ({
|
||||
/** The gantry window's box in CSS pixels, read from the live layout. */
|
||||
async function gantryWindowBox(page: import('@playwright/test').Page) {
|
||||
const box = await page.locator('.gantry-window').boundingBox();
|
||||
if (box === null) throw new Error('Missing gantry window');
|
||||
return { left: box.x, top: box.y, right: box.x + box.width, bottom: box.y + box.height, width: box.width };
|
||||
}
|
||||
|
||||
/**
|
||||
* Round 6 replaced the roadside pass-by with window docking: the sign arcs
|
||||
* in large over the road, docks into the gantry window, holds there while it
|
||||
* is read, then departs in place as the gantry reports it passing.
|
||||
*/
|
||||
test('docks a passing sign in the gantry window after its approach arc and lets it go in place', async ({
|
||||
page,
|
||||
distUrl,
|
||||
}) => {
|
||||
await page.setViewportSize({ width: 1_440, height: 900 });
|
||||
await page.goto(distUrl, { waitUntil: 'domcontentloaded' });
|
||||
await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running');
|
||||
await page.evaluate(() => {
|
||||
const states: string[] = [];
|
||||
(window as unknown as { __gantryStates: string[] }).__gantryStates = states;
|
||||
new MutationObserver(() => states.push(document.documentElement.dataset.gantry ?? ''))
|
||||
.observe(document.documentElement, { attributes: true, attributeFilter: ['data-gantry'] });
|
||||
});
|
||||
|
||||
const viewport = page.viewportSize();
|
||||
if (viewport === null) throw new Error('Missing browser viewport');
|
||||
@@ -193,37 +211,49 @@ test('carries a departed sign past the car before its restrained edge fade', asy
|
||||
return Math.max(
|
||||
1,
|
||||
profile.getBoundingClientRect().top + window.scrollY
|
||||
- viewportHeight * (0.35 + candidateDistanceVh),
|
||||
- viewportHeight * (0.6 + candidateDistanceVh),
|
||||
);
|
||||
}, triggerProbeDistanceVh(viewport.width, viewport.height));
|
||||
await page.mouse.wheel(0, 1);
|
||||
await page.evaluate((scrollY) => window.scrollTo(0, scrollY), destination);
|
||||
await expect.poll(async () => (
|
||||
await readSceneInstrumentation(page)
|
||||
).signFrames.some((frame) => frame.targetId === 'profile' && frame.phase === 'dwell')).toBe(true);
|
||||
).signFrames.some((frame) => frame.targetId === 'profile' && frame.phase === 'dwell'), { timeout: 10_000 }).toBe(true);
|
||||
await expect.poll(async () => page.evaluate(() => (
|
||||
(window as unknown as { __gantryStates: string[] }).__gantryStates.includes('passing')
|
||||
)), { timeout: 5_000 }).toBe(true);
|
||||
await expect.poll(async () => (
|
||||
await readSceneInstrumentation(page)
|
||||
).signFrames.some((frame) => (
|
||||
frame.targetId === 'profile'
|
||||
&& frame.phase === 'depart'
|
||||
&& frame.progress >= 0.52
|
||||
)), { intervals: [16], timeout: 5_000 }).toBe(true);
|
||||
).signFrames.some((frame) => frame.targetId === 'profile' && frame.phase === 'depart')).toBe(true);
|
||||
|
||||
const frames = (await readSceneInstrumentation(page)).signFrames;
|
||||
const dwell = frames.findLast((frame) => frame.targetId === 'profile' && frame.phase === 'dwell');
|
||||
const passing = frames.findLast((frame) => (
|
||||
frame.targetId === 'profile'
|
||||
&& frame.phase === 'depart'
|
||||
&& frame.progress >= 0.3
|
||||
&& frame.opacity >= 0.98
|
||||
));
|
||||
if (dwell === undefined || passing === undefined) {
|
||||
throw new Error('Missing comparable Profile dwell and pass-by frames');
|
||||
const windowBox = await gantryWindowBox(page);
|
||||
const frames = (await readSceneInstrumentation(page)).signFrames
|
||||
.filter((frame) => frame.targetId === 'profile');
|
||||
const approachPeak = Math.max(...frames
|
||||
.filter((frame) => frame.phase === 'approach')
|
||||
.map((frame) => frame.bounds.width));
|
||||
const dwell = frames.findLast((frame) => frame.phase === 'dwell');
|
||||
const departs = frames.filter((frame) => frame.phase === 'depart');
|
||||
if (dwell === undefined || departs.length === 0) {
|
||||
throw new Error('Missing comparable Profile dwell and depart frames');
|
||||
}
|
||||
expect(passing.opacity).toBeGreaterThanOrEqual(0.98);
|
||||
expect(passing.bounds.width).toBeGreaterThan(dwell.bounds.width * 1.18);
|
||||
expect(passing.bounds.left).toBeLessThan(dwell.bounds.left - 24);
|
||||
expect(passing.targetId).toBe(dwell.targetId);
|
||||
// The arc: the sign is read large over the road before it docks.
|
||||
expect(approachPeak).toBeGreaterThan(dwell.bounds.width * 1.5);
|
||||
// Docked: wholly inside the window, never across the reading column.
|
||||
expect(dwell.opacity).toBeGreaterThanOrEqual(0.98);
|
||||
expect(dwell.bounds.left).toBeGreaterThanOrEqual(windowBox.left - 1);
|
||||
expect(dwell.bounds.right).toBeLessThanOrEqual(windowBox.right + 1);
|
||||
expect(dwell.bounds.top).toBeGreaterThanOrEqual(windowBox.top - 1);
|
||||
expect(dwell.bounds.bottom).toBeLessThanOrEqual(windowBox.bottom + 1);
|
||||
// Departing: it fades where it docked instead of sweeping past the car.
|
||||
for (const depart of departs) {
|
||||
expect(Math.abs(depart.bounds.left - dwell.bounds.left)).toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(depart.bounds.width - dwell.bounds.width)).toBeLessThanOrEqual(1);
|
||||
}
|
||||
const states = await page.evaluate(() => (window as unknown as { __gantryStates: string[] }).__gantryStates);
|
||||
const order = ['incoming', 'docked', 'passing'].map((state) => states.indexOf(state));
|
||||
expect(order.every((index) => index >= 0)).toBe(true);
|
||||
expect(order).toEqual([...order].sort((a, b) => a - b));
|
||||
});
|
||||
|
||||
test('shows exactly one destination sign for a navigator click and suppresses history restoration', async ({
|
||||
@@ -274,7 +304,7 @@ test('keeps the non-navigator Explore work section jump sign-suppressed', async
|
||||
return Math.max(
|
||||
1,
|
||||
heading.getBoundingClientRect().top + window.scrollY
|
||||
- viewportHeight * (0.35 + candidateDistanceVh),
|
||||
- viewportHeight * (0.6 + candidateDistanceVh),
|
||||
);
|
||||
}, triggerProbeDistanceVh(viewport.width, viewport.height));
|
||||
await page.mouse.wheel(0, 1);
|
||||
@@ -414,7 +444,7 @@ test('moves an already-readable portrait sign into navigator-safe placement with
|
||||
return Math.max(
|
||||
1,
|
||||
profile.getBoundingClientRect().top + window.scrollY
|
||||
- viewportHeight * (0.35 + 0.22),
|
||||
- viewportHeight * (0.6 + 0.22),
|
||||
);
|
||||
});
|
||||
await page.mouse.wheel(0, 1);
|
||||
@@ -468,7 +498,7 @@ test('re-arms passive signage after a navigator round trip to Start', async ({
|
||||
return Math.max(
|
||||
1,
|
||||
profile.getBoundingClientRect().top + window.scrollY
|
||||
- viewportHeight * (0.35 + candidateDistanceVh),
|
||||
- viewportHeight * (0.6 + candidateDistanceVh),
|
||||
);
|
||||
}, triggerProbeDistanceVh(viewport.width, viewport.height));
|
||||
await page.mouse.wheel(0, 1);
|
||||
@@ -494,7 +524,7 @@ test('reconciles an idle rapid jump to the latest active route without flashing
|
||||
if (!(target instanceof HTMLElement)) throw new Error('Missing selected builds heading');
|
||||
const top = target.getBoundingClientRect().top + window.scrollY;
|
||||
const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
|
||||
window.scrollTo(0, Math.max(1, top - viewportHeight * 0.35 + 1));
|
||||
window.scrollTo(0, Math.max(1, top - viewportHeight * 0.6 + 1));
|
||||
});
|
||||
|
||||
await expect.poll(async () => (await readSceneInstrumentation(page)).routeLabelsDrawn)
|
||||
@@ -515,7 +545,7 @@ test('reconciles an idle rapid jump to the latest active route without flashing
|
||||
expect(dwell.bounds.right).toBeLessThanOrEqual(1_432);
|
||||
});
|
||||
|
||||
test('keeps the readable Profile face outside its destination panel with verified texel density', async ({
|
||||
test('docks the readable Profile face in the gantry window, clear of its panel, with verified texel density', async ({
|
||||
page,
|
||||
distUrl,
|
||||
}) => {
|
||||
@@ -530,7 +560,7 @@ test('keeps the readable Profile face outside its destination panel with verifie
|
||||
return Math.max(
|
||||
1,
|
||||
profile.getBoundingClientRect().top + window.scrollY
|
||||
- viewportHeight * (0.35 + 0.45),
|
||||
- viewportHeight * (0.6 + 0.45),
|
||||
);
|
||||
});
|
||||
await page.mouse.wheel(0, 1);
|
||||
@@ -545,11 +575,16 @@ test('keeps the readable Profile face outside its destination panel with verifie
|
||||
candidate.targetId === 'profile' && candidate.phase === 'dwell'
|
||||
));
|
||||
if (frame === undefined) throw new Error('Missing readable Profile sign frame');
|
||||
const panel = await page.locator('#profile').evaluate((element) => {
|
||||
const bounds = element.getBoundingClientRect();
|
||||
return { left: bounds.left, top: bounds.top, right: bounds.right, bottom: bounds.bottom };
|
||||
});
|
||||
expect(frame.bounds.right).toBeLessThanOrEqual(panel.left - 16);
|
||||
// The face is read in the gantry window, which sits above the reading
|
||||
// column, so it can never overlap the Profile panel below it.
|
||||
const windowBox = await gantryWindowBox(page);
|
||||
expect(frame.bounds.left).toBeGreaterThanOrEqual(windowBox.left - 1);
|
||||
expect(frame.bounds.right).toBeLessThanOrEqual(windowBox.right + 1);
|
||||
expect(frame.bounds.bottom).toBeLessThanOrEqual(windowBox.bottom + 1);
|
||||
const panelTop = await page.locator('#profile .chapter-body').evaluate((element) => (
|
||||
element.getBoundingClientRect().top
|
||||
));
|
||||
expect(frame.bounds.bottom).toBeLessThanOrEqual(panelTop);
|
||||
expect(frame.texture).toMatchObject({
|
||||
width: 2_048,
|
||||
height: 512,
|
||||
@@ -583,7 +618,7 @@ test('keeps a fresh readable sign inside responsive world-safe corridors', async
|
||||
return Math.max(
|
||||
1,
|
||||
profile.getBoundingClientRect().top + window.scrollY
|
||||
- viewportHeight * (0.35 + candidateDistanceVh),
|
||||
- viewportHeight * (0.6 + candidateDistanceVh),
|
||||
);
|
||||
}, triggerProbeDistanceVh(viewport.width, viewport.height));
|
||||
await page.mouse.wheel(0, 1);
|
||||
@@ -649,7 +684,7 @@ test('keeps the upcoming Experience sign clear of both outgoing and incoming pan
|
||||
return Math.max(
|
||||
1,
|
||||
heading.getBoundingClientRect().top + window.scrollY
|
||||
- viewportHeight * (0.35 + 0.45),
|
||||
- viewportHeight * (0.6 + 0.45),
|
||||
);
|
||||
});
|
||||
await page.mouse.wheel(0, 1);
|
||||
@@ -698,7 +733,7 @@ test('keeps the short-landscape Experience sign in the right roadside gap', asyn
|
||||
return Math.max(
|
||||
1,
|
||||
heading.getBoundingClientRect().top + window.scrollY
|
||||
- viewportHeight * (0.35 + 0.38),
|
||||
- viewportHeight * (0.6 + 0.38),
|
||||
);
|
||||
});
|
||||
await page.mouse.wheel(0, 1);
|
||||
@@ -776,7 +811,7 @@ test('keeps every passive transition sign clear of visible opaque panels and fix
|
||||
return Math.max(
|
||||
1,
|
||||
heading.getBoundingClientRect().top + window.scrollY
|
||||
- viewportHeight * (0.35 + candidateDistanceVh),
|
||||
- viewportHeight * (0.6 + candidateDistanceVh),
|
||||
);
|
||||
}, {
|
||||
targetId: next.id,
|
||||
|
||||
Reference in New Issue
Block a user