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 viewportHeight = window.visualViewport?.height ?? window.innerHeight;
|
||||||
const destination = id === 'contact'
|
const destination = id === 'contact'
|
||||||
? document.documentElement.scrollHeight
|
? 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' });
|
window.scrollTo({ top: destination, behavior: 'instant' });
|
||||||
}, section.id);
|
}, section.id);
|
||||||
await waitForOneAnimationFrame(page);
|
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 headingTop = profile.getBoundingClientRect().top + window.scrollY;
|
||||||
const destination = Math.max(
|
const destination = Math.max(
|
||||||
1,
|
1,
|
||||||
headingTop - viewportHeight * (0.35 + candidateDistanceVh),
|
headingTop - viewportHeight * (0.6 + candidateDistanceVh),
|
||||||
);
|
);
|
||||||
return { destination, viewportHeight, expectedLabel: 'Profile' };
|
return { destination, viewportHeight, expectedLabel: 'Profile' };
|
||||||
}, triggerProbeDistanceVh(viewport.width, viewport.height));
|
}, 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 viewportHeight = window.visualViewport?.height ?? window.innerHeight;
|
||||||
const headingTop = profile.getBoundingClientRect().top + window.scrollY;
|
const headingTop = profile.getBoundingClientRect().top + window.scrollY;
|
||||||
return {
|
return {
|
||||||
destination: Math.max(1, headingTop - viewportHeight * (0.35 + 0.45)),
|
destination: Math.max(1, headingTop - viewportHeight * (0.6 + 0.45)),
|
||||||
viewportHeight,
|
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);
|
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,
|
page,
|
||||||
distUrl,
|
distUrl,
|
||||||
}) => {
|
}) => {
|
||||||
await page.setViewportSize({ width: 1_440, height: 900 });
|
await page.setViewportSize({ width: 1_440, height: 900 });
|
||||||
await page.goto(distUrl, { waitUntil: 'domcontentloaded' });
|
await page.goto(distUrl, { waitUntil: 'domcontentloaded' });
|
||||||
await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running');
|
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();
|
const viewport = page.viewportSize();
|
||||||
if (viewport === null) throw new Error('Missing browser viewport');
|
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(
|
return Math.max(
|
||||||
1,
|
1,
|
||||||
profile.getBoundingClientRect().top + window.scrollY
|
profile.getBoundingClientRect().top + window.scrollY
|
||||||
- viewportHeight * (0.35 + candidateDistanceVh),
|
- viewportHeight * (0.6 + candidateDistanceVh),
|
||||||
);
|
);
|
||||||
}, triggerProbeDistanceVh(viewport.width, viewport.height));
|
}, triggerProbeDistanceVh(viewport.width, viewport.height));
|
||||||
await page.mouse.wheel(0, 1);
|
await page.mouse.wheel(0, 1);
|
||||||
await page.evaluate((scrollY) => window.scrollTo(0, scrollY), destination);
|
await page.evaluate((scrollY) => window.scrollTo(0, scrollY), destination);
|
||||||
await expect.poll(async () => (
|
await expect.poll(async () => (
|
||||||
await readSceneInstrumentation(page)
|
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 expect.poll(async () => (
|
||||||
await readSceneInstrumentation(page)
|
await readSceneInstrumentation(page)
|
||||||
).signFrames.some((frame) => (
|
).signFrames.some((frame) => frame.targetId === 'profile' && frame.phase === 'depart')).toBe(true);
|
||||||
frame.targetId === 'profile'
|
|
||||||
&& frame.phase === 'depart'
|
|
||||||
&& frame.progress >= 0.52
|
|
||||||
)), { intervals: [16], timeout: 5_000 }).toBe(true);
|
|
||||||
|
|
||||||
const frames = (await readSceneInstrumentation(page)).signFrames;
|
const windowBox = await gantryWindowBox(page);
|
||||||
const dwell = frames.findLast((frame) => frame.targetId === 'profile' && frame.phase === 'dwell');
|
const frames = (await readSceneInstrumentation(page)).signFrames
|
||||||
const passing = frames.findLast((frame) => (
|
.filter((frame) => frame.targetId === 'profile');
|
||||||
frame.targetId === 'profile'
|
const approachPeak = Math.max(...frames
|
||||||
&& frame.phase === 'depart'
|
.filter((frame) => frame.phase === 'approach')
|
||||||
&& frame.progress >= 0.3
|
.map((frame) => frame.bounds.width));
|
||||||
&& frame.opacity >= 0.98
|
const dwell = frames.findLast((frame) => frame.phase === 'dwell');
|
||||||
));
|
const departs = frames.filter((frame) => frame.phase === 'depart');
|
||||||
if (dwell === undefined || passing === undefined) {
|
if (dwell === undefined || departs.length === 0) {
|
||||||
throw new Error('Missing comparable Profile dwell and pass-by frames');
|
throw new Error('Missing comparable Profile dwell and depart frames');
|
||||||
}
|
}
|
||||||
expect(passing.opacity).toBeGreaterThanOrEqual(0.98);
|
// The arc: the sign is read large over the road before it docks.
|
||||||
expect(passing.bounds.width).toBeGreaterThan(dwell.bounds.width * 1.18);
|
expect(approachPeak).toBeGreaterThan(dwell.bounds.width * 1.5);
|
||||||
expect(passing.bounds.left).toBeLessThan(dwell.bounds.left - 24);
|
// Docked: wholly inside the window, never across the reading column.
|
||||||
expect(passing.targetId).toBe(dwell.targetId);
|
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 ({
|
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(
|
return Math.max(
|
||||||
1,
|
1,
|
||||||
heading.getBoundingClientRect().top + window.scrollY
|
heading.getBoundingClientRect().top + window.scrollY
|
||||||
- viewportHeight * (0.35 + candidateDistanceVh),
|
- viewportHeight * (0.6 + candidateDistanceVh),
|
||||||
);
|
);
|
||||||
}, triggerProbeDistanceVh(viewport.width, viewport.height));
|
}, triggerProbeDistanceVh(viewport.width, viewport.height));
|
||||||
await page.mouse.wheel(0, 1);
|
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(
|
return Math.max(
|
||||||
1,
|
1,
|
||||||
profile.getBoundingClientRect().top + window.scrollY
|
profile.getBoundingClientRect().top + window.scrollY
|
||||||
- viewportHeight * (0.35 + 0.22),
|
- viewportHeight * (0.6 + 0.22),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
await page.mouse.wheel(0, 1);
|
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(
|
return Math.max(
|
||||||
1,
|
1,
|
||||||
profile.getBoundingClientRect().top + window.scrollY
|
profile.getBoundingClientRect().top + window.scrollY
|
||||||
- viewportHeight * (0.35 + candidateDistanceVh),
|
- viewportHeight * (0.6 + candidateDistanceVh),
|
||||||
);
|
);
|
||||||
}, triggerProbeDistanceVh(viewport.width, viewport.height));
|
}, triggerProbeDistanceVh(viewport.width, viewport.height));
|
||||||
await page.mouse.wheel(0, 1);
|
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');
|
if (!(target instanceof HTMLElement)) throw new Error('Missing selected builds heading');
|
||||||
const top = target.getBoundingClientRect().top + window.scrollY;
|
const top = target.getBoundingClientRect().top + window.scrollY;
|
||||||
const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
|
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)
|
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);
|
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,
|
page,
|
||||||
distUrl,
|
distUrl,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -530,7 +560,7 @@ test('keeps the readable Profile face outside its destination panel with verifie
|
|||||||
return Math.max(
|
return Math.max(
|
||||||
1,
|
1,
|
||||||
profile.getBoundingClientRect().top + window.scrollY
|
profile.getBoundingClientRect().top + window.scrollY
|
||||||
- viewportHeight * (0.35 + 0.45),
|
- viewportHeight * (0.6 + 0.45),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
await page.mouse.wheel(0, 1);
|
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'
|
candidate.targetId === 'profile' && candidate.phase === 'dwell'
|
||||||
));
|
));
|
||||||
if (frame === undefined) throw new Error('Missing readable Profile sign frame');
|
if (frame === undefined) throw new Error('Missing readable Profile sign frame');
|
||||||
const panel = await page.locator('#profile').evaluate((element) => {
|
// The face is read in the gantry window, which sits above the reading
|
||||||
const bounds = element.getBoundingClientRect();
|
// column, so it can never overlap the Profile panel below it.
|
||||||
return { left: bounds.left, top: bounds.top, right: bounds.right, bottom: bounds.bottom };
|
const windowBox = await gantryWindowBox(page);
|
||||||
});
|
expect(frame.bounds.left).toBeGreaterThanOrEqual(windowBox.left - 1);
|
||||||
expect(frame.bounds.right).toBeLessThanOrEqual(panel.left - 16);
|
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({
|
expect(frame.texture).toMatchObject({
|
||||||
width: 2_048,
|
width: 2_048,
|
||||||
height: 512,
|
height: 512,
|
||||||
@@ -583,7 +618,7 @@ test('keeps a fresh readable sign inside responsive world-safe corridors', async
|
|||||||
return Math.max(
|
return Math.max(
|
||||||
1,
|
1,
|
||||||
profile.getBoundingClientRect().top + window.scrollY
|
profile.getBoundingClientRect().top + window.scrollY
|
||||||
- viewportHeight * (0.35 + candidateDistanceVh),
|
- viewportHeight * (0.6 + candidateDistanceVh),
|
||||||
);
|
);
|
||||||
}, triggerProbeDistanceVh(viewport.width, viewport.height));
|
}, triggerProbeDistanceVh(viewport.width, viewport.height));
|
||||||
await page.mouse.wheel(0, 1);
|
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(
|
return Math.max(
|
||||||
1,
|
1,
|
||||||
heading.getBoundingClientRect().top + window.scrollY
|
heading.getBoundingClientRect().top + window.scrollY
|
||||||
- viewportHeight * (0.35 + 0.45),
|
- viewportHeight * (0.6 + 0.45),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
await page.mouse.wheel(0, 1);
|
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(
|
return Math.max(
|
||||||
1,
|
1,
|
||||||
heading.getBoundingClientRect().top + window.scrollY
|
heading.getBoundingClientRect().top + window.scrollY
|
||||||
- viewportHeight * (0.35 + 0.38),
|
- viewportHeight * (0.6 + 0.38),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
await page.mouse.wheel(0, 1);
|
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(
|
return Math.max(
|
||||||
1,
|
1,
|
||||||
heading.getBoundingClientRect().top + window.scrollY
|
heading.getBoundingClientRect().top + window.scrollY
|
||||||
- viewportHeight * (0.35 + candidateDistanceVh),
|
- viewportHeight * (0.6 + candidateDistanceVh),
|
||||||
);
|
);
|
||||||
}, {
|
}, {
|
||||||
targetId: next.id,
|
targetId: next.id,
|
||||||
|
|||||||
Reference in New Issue
Block a user