← back to NationalPaperHangers
tests/e2e-find-sort-density.js
84 lines
// e2e: /find page must have sort + density controls per CLAUDE.md standing rule.
// Verifies presence, sort actually re-orders, density actually changes --card-min,
// and both persist to localStorage.
const { chromium } = require('playwright-core');
const BASE = process.env.BASE || 'http://localhost:9765';
function findChromium() {
const paths = [
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'/Applications/Chromium.app/Contents/MacOS/Chromium',
process.env.CHROME_PATH,
].filter(Boolean);
for (const p of paths) { try { require('fs').accessSync(p); return p; } catch {} }
return null;
}
(async () => {
const exe = findChromium();
if (!exe) { console.error('FAIL: no Chromium'); process.exit(2); }
const browser = await chromium.launch({ executablePath: exe, headless: true });
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
const page = await ctx.newPage();
let pass = 0, fail = 0;
const assert = (c, m) => { if (c) { console.log(` ✓ ${m}`); pass++; } else { console.log(` ✗ ${m}`); fail++; } };
try {
console.log('[setup] GET /find');
await page.goto(`${BASE}/find`, { waitUntil: 'domcontentloaded' });
assert(!!(await page.$('#sort-select')), 'sort select present');
assert(!!(await page.$('#density-slider')), 'density slider present');
const cardCount = await page.$$eval('.installer-card', els => els.length);
if (cardCount < 2) {
console.log(' ⊘ skipped sort assertions — fewer than 2 cards in grid');
} else {
// Capture default first-card name
const firstName0 = await page.$eval('.installer-card:first-child', el => el.dataset.name);
// Switch to A→Z — wait for the DOM mutation to actually finish (522
// cards on prod take much longer to sort than ~10 locally).
await page.selectOption('#sort-select', 'az');
await page.waitForFunction(prevFirst => {
const f = document.querySelector('.installer-card:first-child');
return f && f.dataset.name && f.dataset.name !== prevFirst;
}, firstName0, { timeout: 5000 }).catch(() => {});
const allNames = await page.$$eval('.installer-card', els => els.map(e => e.dataset.name || ''));
// Match the page's sort impl, which uses localeCompare (handles punctuation
// like `.` vs `&` differently from default codepoint sort).
const sortedAsc = [...allNames].sort((a, b) => a.localeCompare(b));
assert(JSON.stringify(allNames) === JSON.stringify(sortedAsc), `A→Z sort orders cards alphabetically (${allNames.length} cards)`);
// Switch to Z→A
const firstAZ = allNames[0];
await page.selectOption('#sort-select', 'za');
await page.waitForFunction(prevFirst => {
const f = document.querySelector('.installer-card:first-child');
return f && f.dataset.name && f.dataset.name !== prevFirst;
}, firstAZ, { timeout: 5000 }).catch(() => {});
const allNamesZA = await page.$$eval('.installer-card', els => els.map(e => e.dataset.name || ''));
const sortedDesc = [...allNamesZA].sort((a, b) => b.localeCompare(a));
assert(JSON.stringify(allNamesZA) === JSON.stringify(sortedDesc), `Z→A sort orders cards reverse-alphabetically (${allNamesZA.length} cards)`);
}
// Density slider — set min to 400, verify CSS var updates
await page.fill('#density-slider', '400');
await page.dispatchEvent('#density-slider', 'input');
await page.waitForTimeout(100);
const cardMin = await page.$eval('#installerGrid', el => el.style.getPropertyValue('--card-min'));
assert(cardMin === '400px', `--card-min CSS var updated (got "${cardMin}")`);
// localStorage persistence
const lsSort = await page.evaluate(() => localStorage.getItem('nph-find-sort'));
const lsDens = await page.evaluate(() => localStorage.getItem('nph-find-density'));
assert(lsSort && ['az','za','default','tier','verified_first','newest_claimed','creds'].includes(lsSort), `sort persisted to localStorage (got "${lsSort}")`);
assert(lsDens === '400', `density persisted to localStorage (got "${lsDens}")`);
} catch (err) {
console.error('FATAL:', err.message); fail++;
} finally {
await browser.close();
console.log(`[result] ${pass} pass · ${fail} fail`);
process.exit(fail ? 1 : 0);
}
})();