levkin.ca/scripts/test-stack-scroll.mjs
ilia 21c75cdcba Rebuild stack-folder with sticky tab rail and site previews.
L0–L7 folders stack on scroll with aligned max depth, labeled tabs that
stay consistent when L7 joins the rail, Cal embeds, preview screenshots,
Playwright tests, and updated README.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-21 21:30:05 -04:00

119 lines
3.7 KiB
JavaScript

/**
* Automated scroll/blur tests for stack-folder.
* Run: node scripts/test-stack-scroll.mjs
*/
import { chromium } from 'playwright';
const URL = process.env.STACK_URL || 'http://localhost:5173/stack-folder/';
const VIEWPORT = { width: 1280, height: 800 };
function fail(msg) {
console.error('FAIL:', msg);
process.exitCode = 1;
}
function pass(msg) {
console.log('PASS:', msg);
}
async function readState(page) {
return page.evaluate(() => {
const stick =
parseFloat(getComputedStyle(document.documentElement).fontSize) * 3;
const l7Tab = document.querySelector('.f7 .tab');
const l0 = document.querySelector('.f0');
const l0Body = document.querySelector('.f0 .body');
const folderBlur = l0?.style.getPropertyValue('--stack-blur') || '';
const filter = l0Body ? getComputedStyle(l0Body).filter : '';
const blurMatch = filter.match(/blur\(([\d.]+)px\)/);
const blurPx = blurMatch ? parseFloat(blurMatch[1]) : 0;
return {
scrollY: window.scrollY,
docMax: document.documentElement.scrollHeight - innerHeight,
l7TabTop: l7Tab?.getBoundingClientRect().top ?? null,
stick,
l0Covered: l0?.classList.contains('is-covered'),
l0BlurPx: blurPx,
folderBlur,
l0Filter: filter,
runway: getComputedStyle(document.querySelector('.mount')).getPropertyValue(
'--stack-runway',
),
};
});
}
async function main() {
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: VIEWPORT });
await page.goto(URL, { waitUntil: 'networkidle' });
await page.waitForTimeout(400);
const top = await readState(page);
if (top.l0Covered || top.l0BlurPx > 0.1) {
fail(`L0 blurred at top (covered=${top.l0Covered}, blur=${top.l0BlurPx})`);
} else {
pass('L0 clear at scroll top');
}
await page.evaluate(() => window.scrollTo(0, 999999));
await page.waitForTimeout(350);
const end = await readState(page);
if (end.l7TabTop === null) fail('L7 tab missing');
else if (Math.abs(end.l7TabTop - end.stick) > 8) {
fail(`L7 tab not on stick: top=${end.l7TabTop} stick=${end.stick} scrollY=${end.scrollY}`);
} else {
pass(`L7 on stick at max scroll (y=${end.scrollY}, tabTop=${end.l7TabTop.toFixed(1)})`);
}
if (end.l0BlurPx < 2) {
fail(`L0 not blurred when stacked (blur=${end.l0BlurPx})`);
} else {
pass(`L0 blurred when stacked (blur=${end.l0BlurPx}px)`);
}
const midY = Math.floor(end.scrollY * 0.45);
await page.evaluate((y) => window.scrollTo(0, y), midY);
await page.waitForTimeout(200);
const mid = await readState(page);
if (mid.l0BlurPx > 2) {
fail(`L0 still heavily blurred mid-scroll out (blur=${mid.l0BlurPx} at y=${mid.scrollY})`);
} else {
pass(`L0 unfades when scrolling out (blur=${mid.l0BlurPx}px at y=${mid.scrollY})`);
}
await page.evaluate(() => window.scrollTo(0, 0));
await page.waitForTimeout(350);
const back = await readState(page);
if (back.l0Covered || back.l0BlurPx > 0.1) {
fail(`L0 still blurred after scroll to top (covered=${back.l0Covered}, blur=${back.l0BlurPx})`);
} else {
pass('L0 unfades after scroll back to top');
}
const maxY = end.scrollY;
await page.evaluate((y) => window.scrollTo(0, y), maxY);
await page.waitForTimeout(200);
await page.evaluate(() => window.scrollTo(0, 0));
await page.waitForTimeout(350);
const back2 = await readState(page);
if (back2.l0Covered || back2.l0BlurPx > 0.1) {
fail(`L0 stuck after down-up cycle (blur=${back2.l0BlurPx})`);
} else {
pass('L0 unfades after full down-up cycle');
}
await browser.close();
if (process.exitCode) {
console.log('\nTests failed.');
process.exit(1);
}
console.log('\nAll tests passed.');
}
main().catch((e) => {
console.error(e);
process.exit(1);
});