fix: make nightdrive signage reliable and legible
This commit is contained in:
@@ -88,7 +88,7 @@ test('draws only the independently eligible next roadside sign during passive fo
|
||||
if (!(profile instanceof HTMLElement)) throw new Error('Missing profile heading');
|
||||
const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
|
||||
const headingTop = profile.getBoundingClientRect().top + window.scrollY;
|
||||
const destination = Math.max(1, headingTop - viewportHeight * (0.35 + 0.8));
|
||||
const destination = Math.max(1, headingTop - viewportHeight * (0.35 + 0.45));
|
||||
return { destination, viewportHeight, expectedLabel: 'Profile' };
|
||||
});
|
||||
await page.evaluate(() => window.dispatchEvent(new WheelEvent('wheel', { deltaY: 1 })));
|
||||
@@ -118,7 +118,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.8)),
|
||||
destination: Math.max(1, headingTop - viewportHeight * (0.35 + 0.45)),
|
||||
viewportHeight,
|
||||
};
|
||||
});
|
||||
@@ -163,7 +163,159 @@ test('suppresses roadside signs for explicit navigation and history restoration'
|
||||
expect((await readSceneInstrumentation(page)).routeLabelsDrawn).toEqual([]);
|
||||
});
|
||||
|
||||
test('reconciles an idle rapid jump to the latest active route without flashing intermediates', async ({
|
||||
page,
|
||||
distUrl,
|
||||
}) => {
|
||||
await page.goto(distUrl, { waitUntil: 'domcontentloaded' });
|
||||
await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running');
|
||||
|
||||
await page.mouse.wheel(0, 1);
|
||||
await page.evaluate(() => {
|
||||
const target = document.getElementById('selected-builds-heading');
|
||||
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));
|
||||
});
|
||||
|
||||
await expect.poll(async () => (await readSceneInstrumentation(page)).routeLabelsDrawn)
|
||||
.toContain('Selected builds');
|
||||
expect([...new Set((await readSceneInstrumentation(page)).routeLabelsDrawn)])
|
||||
.toEqual(['Selected builds']);
|
||||
});
|
||||
|
||||
test('keeps the readable Profile face outside its destination panel with verified texel density', async ({
|
||||
page,
|
||||
distUrl,
|
||||
}) => {
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await page.goto(distUrl, { waitUntil: 'domcontentloaded' });
|
||||
await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running');
|
||||
|
||||
const destination = await page.evaluate(() => {
|
||||
const profile = document.getElementById('profile-heading');
|
||||
if (!(profile instanceof HTMLElement)) throw new Error('Missing profile heading');
|
||||
const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
|
||||
return Math.max(
|
||||
1,
|
||||
profile.getBoundingClientRect().top + window.scrollY
|
||||
- viewportHeight * (0.35 + 0.45),
|
||||
);
|
||||
});
|
||||
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);
|
||||
|
||||
const instrumentation = await readSceneInstrumentation(page);
|
||||
const frame = instrumentation.signFrames.findLast((candidate) => (
|
||||
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);
|
||||
expect(frame.texture).toMatchObject({
|
||||
width: 2_048,
|
||||
height: 512,
|
||||
fontReadyWhenDrawn: true,
|
||||
});
|
||||
expect(frame.texture.width / (frame.bounds.width * frame.rendererPixelRatio))
|
||||
.toBeGreaterThanOrEqual(4);
|
||||
});
|
||||
|
||||
test('keeps a fresh readable sign inside responsive world-safe corridors', async ({
|
||||
page,
|
||||
distUrl,
|
||||
}, testInfo) => {
|
||||
test.skip(testInfo.project.name !== 'desktop-chromium', 'One exact responsive matrix is sufficient.');
|
||||
|
||||
for (const viewport of [
|
||||
{ width: 390, height: 844, profile: 'tall', minimumFaceWidth: 120 },
|
||||
{ width: 844, height: 390, profile: 'short', minimumFaceWidth: 130 },
|
||||
{ width: 768, height: 1_024, profile: 'tall', minimumFaceWidth: 150 },
|
||||
{ width: 1_440, height: 900, profile: 'wide', minimumFaceWidth: 180 },
|
||||
{ width: 2_560, height: 1_440, profile: 'wide', minimumFaceWidth: 240 },
|
||||
] as const) {
|
||||
await page.setViewportSize(viewport);
|
||||
await page.goto(distUrl, { waitUntil: 'domcontentloaded' });
|
||||
await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running');
|
||||
const destination = await page.evaluate(() => {
|
||||
const profile = document.getElementById('profile-heading');
|
||||
if (!(profile instanceof HTMLElement)) throw new Error('Missing profile heading');
|
||||
const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
|
||||
return Math.max(
|
||||
1,
|
||||
profile.getBoundingClientRect().top + window.scrollY
|
||||
- viewportHeight * (0.35 + 0.45),
|
||||
);
|
||||
});
|
||||
await page.mouse.wheel(0, 1);
|
||||
await page.evaluate((scrollY) => window.scrollTo(0, scrollY), destination);
|
||||
await expect.poll(async () => (
|
||||
await readSceneInstrumentation(page)
|
||||
).signFrames.at(-1)?.phase, { intervals: [16], timeout: 5_000 }).toBe('dwell');
|
||||
|
||||
const frame = (await readSceneInstrumentation(page)).signFrames.findLast((candidate) => (
|
||||
candidate.targetId === 'profile' && candidate.phase === 'dwell'
|
||||
));
|
||||
if (frame === undefined) throw new Error(`Missing Profile dwell at ${viewport.width}x${viewport.height}`);
|
||||
const geometry = await page.evaluate(() => {
|
||||
const boundsFor = (selector: string) => {
|
||||
const element = document.querySelector(selector);
|
||||
if (!(element instanceof HTMLElement)) return null;
|
||||
const bounds = element.getBoundingClientRect();
|
||||
return { left: bounds.left, top: bounds.top, right: bounds.right, bottom: bounds.bottom };
|
||||
};
|
||||
return {
|
||||
panel: boundsFor('#profile'),
|
||||
adjacentPanels: [boundsFor('.hero-journey__content')].filter((bounds) => bounds !== null),
|
||||
fixedZones: [
|
||||
boundsFor('.site-navigation'),
|
||||
boundsFor('.drive-controls'),
|
||||
boundsFor('.journey-hud'),
|
||||
].filter((bounds) => bounds !== null),
|
||||
};
|
||||
});
|
||||
if (geometry.panel === null) throw new Error('Missing Profile panel bounds');
|
||||
expect(frame.viewportProfile).toBe(viewport.profile);
|
||||
expect(frame.bounds.left).toBeGreaterThanOrEqual(8);
|
||||
expect(frame.bounds.right).toBeLessThanOrEqual(viewport.width - 8);
|
||||
expect(frame.bounds.top).toBeGreaterThanOrEqual(8);
|
||||
expect(frame.bounds.bottom).toBeLessThanOrEqual(viewport.height - 8);
|
||||
expect(frame.bounds.width).toBeGreaterThanOrEqual(viewport.minimumFaceWidth);
|
||||
expect(rectanglesOverlap(frame.bounds, geometry.panel, 16)).toBe(false);
|
||||
for (const adjacentPanel of geometry.adjacentPanels) {
|
||||
expect(rectanglesOverlap(frame.bounds, adjacentPanel, 16)).toBe(false);
|
||||
}
|
||||
for (const fixedZone of geometry.fixedZones) {
|
||||
expect(rectanglesOverlap(frame.bounds, fixedZone, 8)).toBe(false);
|
||||
}
|
||||
expect(frame.texture.fontReadyWhenDrawn).toBe(true);
|
||||
expect(frame.texture.width / (frame.bounds.width * frame.rendererPixelRatio))
|
||||
.toBeGreaterThanOrEqual(4);
|
||||
}
|
||||
});
|
||||
|
||||
function colorEnergy(color: string): number {
|
||||
const value = Number.parseInt(color.slice(1), 16);
|
||||
return ((value >> 16) & 0xff) + ((value >> 8) & 0xff) + (value & 0xff);
|
||||
}
|
||||
|
||||
function rectanglesOverlap(
|
||||
first: { left: number; top: number; right: number; bottom: number },
|
||||
second: { left: number; top: number; right: number; bottom: number },
|
||||
gap: number,
|
||||
): boolean {
|
||||
return !(
|
||||
first.right <= second.left - gap
|
||||
|| first.left >= second.right + gap
|
||||
|| first.bottom <= second.top - gap
|
||||
|| first.top >= second.bottom + gap
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user