Add the approved name animation, rounded docking navigation, localized keyboard focus cue, unified builds card, and destination copy hierarchy.
Introduce lightweight one-shot meteors and a sun ring that repeats every three seconds at Contact. Open the email action in a separate browsing context. Preserve reduced-motion behavior and keep private handoff and visual-check artifacts out of version control.
Validation: 649 tests passed; all 20 failures in world-models and SceneCanvas reproduce on baseline 103427f. Typecheck, artifact verification, and transfer budgets passed. Client/SSR preview build passed before this commit. Existing baseline test failures remain unchanged.
77 lines
4.0 KiB
TypeScript
77 lines
4.0 KiB
TypeScript
import { expect, test } from '../helpers/serve-dist';
|
|
|
|
test('arrival draws six meteor triangles, uses warmed shaders, then stops drawing the meteors', async ({ page, distUrl }, testInfo) => {
|
|
test.setTimeout(45_000);
|
|
if (testInfo.project.name === 'desktop-chromium') await page.setViewportSize({ width: 1440, height: 900 });
|
|
await page.addInitScript((quality: string) => {
|
|
localStorage.setItem('portfolio.motion.maximum-quality', quality);
|
|
const probe = { draws: 0, maxVertices: 0, programs: 0, frames: 0, time: -1, ring: 0 };
|
|
(window as any).__arrivalProbe = probe;
|
|
const proto = WebGL2RenderingContext.prototype;
|
|
const meteorPrograms = new WeakSet<WebGLProgram>();
|
|
const locations = new WeakMap<WebGLUniformLocation, { program: WebGLProgram; name: string }>();
|
|
let activeProgram: WebGLProgram | null = null;
|
|
const meteorShaders = new WeakSet<WebGLShader>();
|
|
const source = proto.shaderSource;
|
|
proto.shaderSource = function (shader, text) {
|
|
if (text.includes('attribute vec2 aFlight;')) meteorShaders.add(shader);
|
|
return source.call(this, shader, text);
|
|
};
|
|
const attach = proto.attachShader;
|
|
proto.attachShader = function (program, shader) {
|
|
if (meteorShaders.has(shader) && !meteorPrograms.has(program)) {
|
|
meteorPrograms.add(program);
|
|
probe.programs++;
|
|
}
|
|
return attach.call(this, program, shader);
|
|
};
|
|
const getUniform = proto.getUniformLocation;
|
|
proto.getUniformLocation = function (program, name) {
|
|
const location = getUniform.call(this, program, name);
|
|
if (location) locations.set(location, { program, name });
|
|
return location;
|
|
};
|
|
const uniform = proto.uniform1f;
|
|
proto.uniform1f = function (location, value) {
|
|
const entry = location && locations.get(location);
|
|
if (entry?.name === 'uTime' && meteorPrograms.has(entry.program)) probe.time = value;
|
|
if (entry?.name === 'uRingOpacity') probe.ring = value;
|
|
return uniform.call(this, location, value);
|
|
};
|
|
const useProgram = proto.useProgram;
|
|
proto.useProgram = function (program) { activeProgram = program; return useProgram.call(this, program); };
|
|
const draw = proto.drawArrays;
|
|
proto.drawArrays = function (mode, first, count) {
|
|
if (activeProgram && meteorPrograms.has(activeProgram)) {
|
|
probe.draws++;
|
|
probe.maxVertices = Math.max(probe.maxVertices, count);
|
|
}
|
|
return draw.call(this, mode, first, count);
|
|
};
|
|
const clear = proto.clear;
|
|
proto.clear = function (mask) { probe.frames++; return clear.call(this, mask); };
|
|
}, process.env.ARRIVAL_TEST_QUALITY === 'high' ? 'high' : 'low');
|
|
await page.goto(distUrl);
|
|
await expect(page.locator('html')).toHaveAttribute('data-expressway', 'live');
|
|
const read = () => page.evaluate(() => ({ ...(window as any).__arrivalProbe }));
|
|
const baseline = await read();
|
|
expect(baseline.draws).toBe(1); // Transparent startup warm-up only.
|
|
expect(baseline.programs).toBe(1);
|
|
await page.locator('.hero-journey__action--primary').click();
|
|
await expect(page.locator('.journey-world')).toHaveAttribute('data-arrived', 'true');
|
|
await expect.poll(async () => (await read()).draws, { timeout: 12_000 }).toBeGreaterThan(baseline.draws);
|
|
await expect.poll(async () => (await read()).ring).toBeGreaterThan(0);
|
|
await expect.poll(async () => (await read()).time).toBeGreaterThan(1.2);
|
|
await page.screenshot({ path: testInfo.outputPath('arrival-active.png') });
|
|
await expect.poll(async () => (await read()).time, { timeout: 15_000 }).toBeGreaterThan(3.8);
|
|
const ending = await read();
|
|
await expect.poll(async () => (await read()).frames).toBeGreaterThan(ending.frames + 30);
|
|
const settled = await read();
|
|
expect(settled.maxVertices).toBe(18);
|
|
expect(settled.programs).toBe(baseline.programs);
|
|
await expect.poll(async () => (await read()).ring).toBeGreaterThan(0);
|
|
await expect.poll(async () => (await read()).frames).toBeGreaterThan(settled.frames + 30);
|
|
expect((await read()).draws).toBe(settled.draws);
|
|
await page.screenshot({ path: testInfo.outputPath('arrival-settled.png') });
|
|
});
|