98 lines
3.9 KiB
TypeScript
98 lines
3.9 KiB
TypeScript
/**
|
|
* nav-arrow-consistency-smoke — every directional arrow uses the shared
|
|
* .nav-arrow affordance, so all arrows slide in their pointing direction and
|
|
* turn emerald on hover/focus, and arrow-bearing links never underline (cp339,
|
|
* the homepage "Learn more" effect, applied site-wide for consistency).
|
|
*
|
|
* WHY: arrows were scattered — tiny "→ / ←", bespoke inline SVGs, and one-off
|
|
* Tailwind `rtl:-scale-x-100` + `group-hover:translate-x-1` classes. They must
|
|
* all be `<span class="nav-arrow nav-arrow-{left|right}">⇦/⇨</span>`, with the
|
|
* slide + color + RTL mirror + no-underline centralized in app.css. This smoke
|
|
* pins:
|
|
* - no element still carries the old per-arrow rtl:-scale-x-100 class
|
|
* - the global .nav-arrow rules (slide, emerald, RTL mirror, no-underline)
|
|
* exist in app.css
|
|
* - the homepage cards no longer use the bespoke priorities-card-cta-arrow
|
|
*
|
|
* Usage (from apps/web): tsx scripts/nav-arrow-consistency-smoke.ts
|
|
*/
|
|
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
|
|
const SRC = join(import.meta.dirname, '..', 'src');
|
|
|
|
function walkSvelte(dir: string, out: string[] = []): string[] {
|
|
for (const e of readdirSync(dir)) {
|
|
const p = join(dir, e);
|
|
if (statSync(p).isDirectory()) walkSvelte(p, out);
|
|
else if (p.endsWith('.svelte')) out.push(p);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
let failures = 0;
|
|
let checks = 0;
|
|
function check(name: string, cond: boolean, detail = ''): void {
|
|
checks++;
|
|
if (cond) {
|
|
console.log(` ✓ ${name}`);
|
|
} else {
|
|
failures++;
|
|
console.log(` ✗ ${name}${detail ? ' — ' + detail : ''}`);
|
|
}
|
|
}
|
|
|
|
console.log('\n── arrows use the shared .nav-arrow affordance ────────');
|
|
|
|
const offenders = walkSvelte(SRC).filter((f) => /rtl:-scale-x-100/.test(readFileSync(f, 'utf-8')));
|
|
check(
|
|
'no element still uses the old rtl:-scale-x-100 arrow class',
|
|
offenders.length === 0,
|
|
offenders.map((f) => f.replace(SRC, 'src')).join(', ')
|
|
);
|
|
|
|
const css = readFileSync(join(SRC, 'app.css'), 'utf-8');
|
|
check('app.css defines .nav-arrow', /\.nav-arrow\s*\{/.test(css));
|
|
check('app.css slides nav-arrow-right on hover', /:hover\s+\.nav-arrow-right/.test(css));
|
|
check('app.css slides nav-arrow-left on hover', /:hover\s+\.nav-arrow-left/.test(css));
|
|
check('app.css greens the arrow on hover', /:hover\s+\.nav-arrow\b/.test(css) && /--morphit-emerald/.test(css));
|
|
check('app.css removes underline on links carrying an arrow', /:has\(\.nav-arrow\)/.test(css));
|
|
check("app.css mirrors the arrow under RTL", /\[dir='rtl'\]\s*\.nav-arrow/.test(css));
|
|
|
|
const priorities = readFileSync(join(SRC, 'lib', 'components', 'PrioritiesSection.svelte'), 'utf-8');
|
|
check('homepage cards use nav-arrow', /nav-arrow nav-arrow-right/.test(priorities));
|
|
check(
|
|
'homepage cards no longer use the bespoke priorities-card-cta-arrow',
|
|
!priorities.includes('priorities-card-cta-arrow')
|
|
);
|
|
|
|
// cp411 — no link/button uses a BARE → (U+2192) as its affordance. Such an
|
|
// arrow neither slides nor greens and is the smaller, inconsistent glyph. The
|
|
// affordance idioms: a translated label `{$_(…)}` immediately followed by a
|
|
// trailing →, a label `{…}` ending a line with a bare →, or a → right before a
|
|
// closing </a>/</button>. Plain arrows in prose/diagrams/comments don't match.
|
|
const BARE_ARROW_AFFORDANCE = [/\)\}\s*→/, /\}\s*→\s*$/, /→\s*<\/(?:a|button)>/];
|
|
const bareArrowOffenders: string[] = [];
|
|
for (const f of walkSvelte(SRC)) {
|
|
readFileSync(f, 'utf-8')
|
|
.split('\n')
|
|
.forEach((line, i) => {
|
|
if (BARE_ARROW_AFFORDANCE.some((re) => re.test(line))) {
|
|
bareArrowOffenders.push(`${f.replace(SRC, 'src')}:${i + 1}`);
|
|
}
|
|
});
|
|
}
|
|
check(
|
|
'no link/button uses a bare → affordance (use nav-arrow ⇨)',
|
|
bareArrowOffenders.length === 0,
|
|
bareArrowOffenders.join(', ')
|
|
);
|
|
|
|
console.log('');
|
|
if (failures === 0) {
|
|
console.log(`✓ all ${checks} nav-arrow-consistency scenarios passed`);
|
|
process.exit(0);
|
|
} else {
|
|
console.log(`✗ ${failures} check(s) failed`);
|
|
process.exit(1);
|
|
}
|