Files
pbakaus_impeccable/tests/live-browser-ignores.test.mjs
T
152d6940b0 Fix: harden live overlay detector waivers (#639 follow-up)
Read waiver config from every live root (appRoot, contextRoot, repoRoot),
so monorepo projects whose config lives at the repo root reach the
overlay; serialize served roots and page identities repo-relative there.

Resolve each page URL to its actual serving file via the inject config's
resolved page list before applying file-scoped waivers; ambiguous URLs
keep the conservative common-ancestor fallback (PR #645 review
discussion r3840011436).

Honour detector.ignoreFiles: a wholly waived page now scans to zero
findings in the overlay, matching the CLI and the edit hook.

Guard the resolver call so a throwing resolver degrades to an unfiltered
scan instead of breaking the detect toggle.

Match design-system-color waivers by color value across hex and rgb()
spellings, and stop extracting font values for bounce-easing findings,
mirroring extractFindingIgnoreValue. Regenerate the browser bundle.

AI-assisted change: reviewed, planned, and implemented with Claude Code
under maintainer direction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 18:38:36 +05:00

337 lines
14 KiB
JavaScript

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import vm from 'node:vm';
const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
const SCRIPT = join(REPO_ROOT, 'skill/scripts/live-browser-ignores.js');
// Evaluated in the test realm (not a vm context) so the arrays the resolver
// returns share this realm's prototypes and deepEqual compares them plainly.
function loadIgnoresApi() {
const source = readFileSync(SCRIPT, 'utf-8');
const factory = vm.runInThisContext(
`(function (window) {\n${source}\nreturn window.__IMPECCABLE_LIVE_IGNORES__;\n})`,
{ filename: SCRIPT },
);
return factory({});
}
const resolve = loadIgnoresApi().resolveDetectIgnores;
const EMPTY = { disabledRules: [], disabledValues: [], skipScan: false };
describe('live-browser-ignores resolver', () => {
it('registers a versioned API on the root', () => {
const api = loadIgnoresApi();
assert.equal(api.version, 1);
assert.equal(typeof api.resolveDetectIgnores, 'function');
});
it('degrades to an empty filter when the config global is missing or malformed', () => {
assert.deepEqual(resolve(), EMPTY);
assert.deepEqual(resolve({ ignores: undefined, pathname: '/index.html' }), EMPTY);
assert.deepEqual(resolve({ ignores: null, pathname: '/index.html' }), EMPTY);
assert.deepEqual(resolve({ ignores: 'nonsense', pathname: '/index.html' }), EMPTY);
assert.deepEqual(resolve({ ignores: {}, pathname: '/index.html' }), EMPTY);
});
it('does not spread a string ignoreRules into characters', () => {
// `"ignoreRules": "foo"` in a hand-edited config must disable nothing,
// not look like it disabled three one-letter rules.
const out = resolve({ ignores: { ignoreRules: 'foo' }, pathname: '/index.html' });
assert.deepEqual(out, EMPTY);
});
it('forwards ignoreRules normalized and deduplicated', () => {
const out = resolve({
ignores: { ignoreRules: ['Dark-Glow', 'dark-glow', ' gradient-text ', '', 42, null] },
pathname: '/index.html',
});
assert.deepEqual(out.disabledRules, ['dark-glow', 'gradient-text']);
});
it('forwards unscoped value entries and drops malformed ones', () => {
const out = resolve({
ignores: {
ignoreValues: [
{ rule: 'overused-font', value: 'Geist+Mono' },
null,
'not-an-entry',
{ rule: '', value: 'x' },
{ rule: 'overused-font', value: '' },
],
},
pathname: '/index.html',
});
assert.deepEqual(out.disabledValues, [{ rule: 'overused-font', value: 'geist mono' }]);
});
it('applies wildcard entries only on pages their globs name', () => {
const ignores = {
roots: ['prototype/'],
ignoreValues: [
{ rule: 'dark-glow', value: '*', files: ['prototype/attack-the-soc.html'] },
],
};
const onPage = resolve({ ignores, pathname: '/attack-the-soc.html' });
assert.deepEqual(onPage.disabledRules, ['dark-glow']);
const elsewhere = resolve({ ignores, pathname: '/index.html' });
assert.deepEqual(elsewhere.disabledRules, []);
});
it('never applies an unscoped wildcard entry, matching the CLI', () => {
// isIgnoredFindingValue in cli/lib/impeccable-config.mjs returns false
// for a wildcard entry with no files; project-wide suppression is
// ignoreRules' job.
const out = resolve({
ignores: { ignoreValues: [{ rule: 'dark-glow', value: '*' }] },
pathname: '/index.html',
});
assert.deepEqual(out, EMPTY);
});
it('drops scoped value entries on pages outside their globs', () => {
const ignores = {
roots: ['prototype/'],
ignoreValues: [
{ rule: 'overused-font', value: 'geist mono', files: ['prototype/mgmt-demo.html'] },
],
};
const onPage = resolve({ ignores, pathname: '/mgmt-demo.html' });
assert.deepEqual(onPage.disabledValues, [{ rule: 'overused-font', value: 'geist mono' }]);
const elsewhere = resolve({ ignores, pathname: '/index.html' });
assert.deepEqual(elsewhere.disabledValues, []);
});
it('does not lend one entry\'s glob prefix to other pages', () => {
// The trap from issue #639: prefixes come only from `roots`, never from
// the ignore globs themselves. An entry scoped to prototype/library/**
// must not suppress on prototype/index.html.
const ignores = {
roots: ['prototype/'],
ignoreValues: [
{ rule: 'em-dash-overuse', value: '*', files: ['prototype/library/**'] },
],
};
const inside = resolve({ ignores, pathname: '/library/buttons.html' });
assert.deepEqual(inside.disabledRules, ['em-dash-overuse']);
const outside = resolve({ ignores, pathname: '/index.html' });
assert.deepEqual(outside.disabledRules, []);
});
it('resolves root and directory URLs to their index file', () => {
const ignores = {
roots: ['prototype/'],
ignoreValues: [
{ rule: 'dark-glow', value: '*', files: ['prototype/index.html'] },
{ rule: 'gradient-text', value: '*', files: ['prototype/news/index.html'] },
],
};
assert.deepEqual(resolve({ ignores, pathname: '/' }).disabledRules, ['dark-glow']);
assert.deepEqual(resolve({ ignores, pathname: '/news/' }).disabledRules, ['gradient-text']);
});
it('matches path suffixes like the CLI scoped-file matcher', () => {
// findingMatchesScopedIgnoreFile tries every path suffix of the finding's
// file, so `library/**` written without the prototype/ prefix still
// scopes to the library pages.
const ignores = {
roots: ['prototype/'],
ignoreValues: [
{ rule: 'em-dash-overuse', value: '*', files: ['library/**'] },
{ rule: 'dark-glow', value: '*', files: ['buttons.html'] },
],
};
const out = resolve({ ignores, pathname: '/library/buttons.html' });
assert.deepEqual(out.disabledRules.sort(), ['dark-glow', 'em-dash-overuse']);
});
it('supports the CLI glob dialect, including alternation', () => {
const ignores = {
roots: ['prototype/'],
ignoreValues: [
{ rule: 'dark-glow', value: '*', files: ['prototype/{index,about}.html'] },
{ rule: 'gradient-text', value: '*', files: ['prototype/page-?.html'] },
],
};
assert.deepEqual(resolve({ ignores, pathname: '/about.html' }).disabledRules, ['dark-glow']);
assert.deepEqual(resolve({ ignores, pathname: '/page-3.html' }).disabledRules, ['gradient-text']);
assert.deepEqual(resolve({ ignores, pathname: '/page-33.html' }).disabledRules, []);
});
it('treats glob metacharacters in filenames literally', () => {
const ignores = {
ignoreValues: [
{ rule: 'dark-glow', value: '*', files: ['pricing (v2).html'] },
],
};
const out = resolve({ ignores, pathname: '/pricing (v2).html' });
assert.deepEqual(out.disabledRules, ['dark-glow']);
const near = resolve({ ignores, pathname: '/pricing xv2y.html' });
assert.deepEqual(near.disabledRules, []);
});
it('accepts a single `file` string alongside `files`', () => {
const out = resolve({
ignores: {
ignoreValues: [{ rule: 'dark-glow', value: '*', file: 'index.html' }],
},
pathname: '/index.html',
});
assert.deepEqual(out.disabledRules, ['dark-glow']);
});
it('asserts no prefix when the configured roots share no common ancestor', () => {
// With src/**/*.html and public/**/*.html both configured, no single
// document root maps /foo.html to a unique project file, so no prefix
// is asserted. A waiver naming src/foo.html must not hide a finding on
// a page served from public/foo.html; ambiguity resolves to showing
// the finding. Bare-path spellings still apply whichever root serves it.
const ignores = {
roots: ['src/', 'public/'],
ignoreValues: [
{ rule: 'dark-glow', value: '*', files: ['src/foo.html'] },
{ rule: 'clipped-overflow-container', value: '*', files: ['src/foo.html', 'public/foo.html'] },
{ rule: 'em-dash-overuse', value: '*', files: ['foo.html'] },
{ rule: 'gradient-text', value: '*', files: ['**/foo.html'] },
],
};
const out = resolve({ ignores, pathname: '/foo.html' });
assert.deepEqual(out.disabledRules.sort(), ['em-dash-overuse', 'gradient-text']);
});
it('reduces nested roots to their common ancestor so normal waivers keep applying', () => {
// Globs at two depths in one tree (prototype/*.html plus
// prototype/library/**/*.html) derive the prefixes prototype/ and
// prototype/library/. Those are not alternative identities: one server
// serves both, so the document root sits at their common ancestor and
// a project-relative waiver like prototype/index.html must apply.
const ignores = {
roots: ['prototype/', 'prototype/library/'],
ignoreValues: [
{ rule: 'dark-glow', value: '*', files: ['prototype/index.html'] },
{ rule: 'em-dash-overuse', value: '*', files: ['prototype/library/**'] },
],
};
assert.deepEqual(
resolve({ ignores, pathname: '/index.html' }).disabledRules,
['dark-glow'],
);
assert.deepEqual(
resolve({ ignores, pathname: '/library/buttons.html' }).disabledRules,
['em-dash-overuse'],
);
});
it('resolves a URL to its one served file when pageFiles knows it', () => {
// PR #645 review discussion r3840011436: with src/ and public/ both
// served, /foo.html used to borrow identities from every root. The
// served page list disambiguates: this URL serves public/foo.html, so
// src-scoped waivers must not apply.
const ignores = {
roots: ['src/', 'public/'],
pageFiles: ['src/other.html', 'public/foo.html'],
ignoreValues: [
{ rule: 'dark-glow', value: '*', files: ['src/foo.html'] },
{ rule: 'gradient-text', value: '*', files: ['public/foo.html'] },
],
};
const out = resolve({ ignores, pathname: '/foo.html' });
assert.deepEqual(out.disabledRules, ['gradient-text']);
});
it('keeps ambiguity conservative when served files share the URL suffix', () => {
const ignores = {
roots: ['src/', 'public/'],
pageFiles: ['src/foo.html', 'public/foo.html'],
ignoreValues: [
{ rule: 'dark-glow', value: '*', files: ['src/foo.html'] },
{ rule: 'gradient-text', value: '*', files: ['public/foo.html'] },
{ rule: 'em-dash-overuse', value: '*', files: ['foo.html'] },
],
};
const out = resolve({ ignores, pathname: '/foo.html' });
// Neither root-scoped waiver can claim the page; the bare spelling
// still applies whichever root serves it.
assert.deepEqual(out.disabledRules, ['em-dash-overuse']);
});
it('falls back to the common ancestor when index files collide across depths', () => {
// /index.html suffix-matches both served index files; the ambiguity
// resolves through the common ancestor, which still yields the correct
// shallow identity and never the deep one.
const ignores = {
roots: ['prototype/', 'prototype/library/'],
pageFiles: ['prototype/index.html', 'prototype/library/index.html'],
ignoreValues: [
{ rule: 'dark-glow', value: '*', files: ['prototype/index.html'] },
{ rule: 'gradient-text', value: '*', files: ['prototype/library/index.html'] },
],
};
const out = resolve({ ignores, pathname: '/index.html' });
assert.deepEqual(out.disabledRules, ['dark-glow']);
});
it('skips the scan on pages named by ignoreFiles', () => {
const ignores = {
roots: ['prototype/'],
ignoreFiles: ['prototype/library/**'],
ignoreRules: ['dark-glow'],
};
const waived = resolve({ ignores, pathname: '/library/buttons.html' });
assert.deepEqual(waived, { disabledRules: [], disabledValues: [], skipScan: true });
const scanned = resolve({ ignores, pathname: '/index.html' });
assert.equal(scanned.skipScan, false);
assert.deepEqual(scanned.disabledRules, ['dark-glow']);
});
it('matches ignoreFiles by basename like the CLI glob matcher', () => {
const out = resolve({
ignores: { ignoreFiles: ['buttons.html'], roots: ['prototype/'] },
pathname: '/library/buttons.html',
});
assert.equal(out.skipScan, true);
});
it('treats a malformed ignoreFiles value as no waiver at all', () => {
const out = resolve({
ignores: { ignoreFiles: 'prototype/**', roots: [] },
pathname: '/index.html',
});
assert.equal(out.skipScan, false);
});
it('drops entries scoped to source paths that no route URL can match', () => {
// Framework apps inject into source files while scans see route URLs; a
// source-scoped entry must fail conservative (finding shown), never
// suppress by accident. Pinned so a refactor cannot flip the direction.
const ignores = {
roots: ['src/'],
pageFiles: ['src/routes/about/+page.svelte'],
ignoreValues: [
{ rule: 'dark-glow', value: '*', files: ['src/routes/about/+page.svelte'] },
],
};
const out = resolve({ ignores, pathname: '/about' });
assert.deepEqual(out.disabledRules, []);
assert.equal(out.skipScan, false);
});
it('survives malformed roots and percent-escapes without throwing', () => {
const out = resolve({
ignores: {
roots: 7,
pageFiles: 'not-a-list',
ignoreFiles: [null, 42],
ignoreRules: ['dark-glow'],
ignoreValues: [{ rule: 'gradient-text', value: '*', files: ['broken%.html'] }],
},
pathname: '/broken%.html',
});
assert.deepEqual(out.disabledRules.sort(), ['dark-glow', 'gradient-text']);
});
});