← 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);
  }
})();