Gate.tsx (an island that rendered nothing) becomes scripts/gate.ts with its key handling and readout text as pure functions. With the switch already plain, the start page has no island and loads none of the React runtime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
84 lines
3.6 KiB
TypeScript
84 lines
3.6 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
import { blockThirdParty } from './helpers.ts';
|
|
|
|
// The gate's behaviour is a plain module script (spec 006); module scripts have run by the time page.goto
|
|
// resolves on `load`, so there is no island to wait for.
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await blockThirdParty(page);
|
|
});
|
|
|
|
test('clicking each side navigates', async ({ page }) => {
|
|
await page.goto('/');
|
|
await page.locator('a[data-enter="work"]').click();
|
|
await expect(page).toHaveURL(/\/work\/$/);
|
|
await page.goto('/');
|
|
await page.locator('a[data-enter="personal"]').click();
|
|
await expect(page).toHaveURL(/\/off-the-clock\/$/);
|
|
});
|
|
|
|
test('the everything pill is hidden for now, and G does nothing', async ({ page, isMobile }) => {
|
|
await page.goto('/');
|
|
await expect(page.locator('#garden-link')).toHaveCount(0);
|
|
test.skip(!!isMobile, 'keyboard shortcut');
|
|
await page.keyboard.press('g');
|
|
await page.waitForTimeout(300);
|
|
await expect(page).toHaveURL(/\/$/);
|
|
});
|
|
|
|
test('arrows lean and Enter enters', async ({ page, isMobile }) => {
|
|
test.skip(!!isMobile, 'keyboard shortcut');
|
|
await page.goto('/');
|
|
await expect(async () => {
|
|
await page.keyboard.press('ArrowRight');
|
|
await expect(page.locator('#gate')).toHaveClass(/lean-art/, { timeout: 500 });
|
|
}).toPass();
|
|
await page.keyboard.press('ArrowLeft');
|
|
await expect(page.locator('#gate')).toHaveClass(/lean-tech/);
|
|
await page.keyboard.press('Enter');
|
|
await expect(page).toHaveURL(/\/work\/$/);
|
|
});
|
|
|
|
test('nothing animates on load', async ({ page }) => {
|
|
// A pointer resting over a side would legitimately lean it; take the page out of hit testing
|
|
// from the first moment so only the page's own load-time motion is measured.
|
|
await page.addInitScript(() => {
|
|
new MutationObserver((_m, obs) => {
|
|
if (!document.documentElement) return;
|
|
const st = document.createElement('style');
|
|
st.textContent = '*{pointer-events:none!important}';
|
|
document.documentElement.append(st);
|
|
obs.disconnect();
|
|
}).observe(document, { childList: true });
|
|
});
|
|
await page.goto('/');
|
|
await page.waitForLoadState('load');
|
|
const running = await page.evaluate(() =>
|
|
document.getAnimations().map((a) => `${(a.effect as KeyframeEffect | null)?.target?.tagName ?? '?'} ${(a as CSSAnimation).animationName ?? (a as CSSTransition).transitionProperty}`),
|
|
);
|
|
expect(running).toEqual([]);
|
|
});
|
|
|
|
test('the gate fills the screen on tall and large displays', async ({ page, isMobile }) => {
|
|
test.skip(!!isMobile, 'phones stack the halves and scroll');
|
|
for (const [width, height] of [[1280, 720], [1920, 1080], [2560, 1440], [1440, 1600]]) {
|
|
await page.setViewportSize({ width, height });
|
|
await page.goto('/');
|
|
const m = await page.evaluate(() => ({
|
|
gate: document.querySelector('#gate')!.getBoundingClientRect().height,
|
|
foot: document.querySelector('.foot')!.getBoundingClientRect().bottom,
|
|
doc: document.documentElement.scrollHeight,
|
|
}));
|
|
expect(Math.round(m.foot), `${width}x${height}`).toBe(height); // footer sits on the bottom edge
|
|
expect(m.doc, `${width}x${height}`).toBe(height); // and nothing scrolls
|
|
// Scaled up or not, the "everything" pill (when shown) stays on the seam between the halves.
|
|
if (await page.locator('.garden-link').count()) await expect(async () => {
|
|
const seam = await page.evaluate(() => {
|
|
const r = document.querySelector('.garden-link')!.getBoundingClientRect();
|
|
return r.left + r.width / 2 - document.querySelector('#tech')!.getBoundingClientRect().right;
|
|
});
|
|
expect(Math.abs(seam)).toBeLessThan(2);
|
|
}).toPass({ timeout: 3000 });
|
|
}
|
|
});
|