Files
Jyotisha/frontend/tests/css-contract-test-support.test.ts
T
Jesse_Chen 2d370f2e9d
Independent Staging Quality Gate / validate (push) Has been cancelled
Independent Staging Quality Gate / publish (push) Has been cancelled
fix(tests): read every CSS rule for a selector, not the first one
The sidebar contract read a selector with indexOf, so it returned whichever
rule appeared earliest in the file. A responsive override added above the base
rule made the gate report a missing min-height that was never removed, which
blocked staging on a change that was correct.

Share one helper that strips comments, parses rule by rule, accepts a whole
group as the query, and returns the declarations of every matching rule. The
union also makes a negative assertion mean no rule may declare the property,
which is what these contracts intend.

The helper was duplicated in two files and had no tests of its own; it now has
regressions for each way it read the wrong block.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-17 13:03:57 +08:00

58 lines
1.9 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { cssDeclarations } from "./css-contract-test-support.ts";
const source = `
@media (max-width: 640px) {
[data-sidebar="content"] { -webkit-overflow-scrolling: touch; }
}
[data-sidebar="content"] { min-height: 0; overflow-y: auto; }
.session-list { overflow-x: clip; }
.panel [data-sidebar="content"] { color: red; }
.a, .b { gap: 4px; }
/* ---- Documented section ---- */
.documented { padding: 8px; }
.wrapper
.multiline { margin: 0; }
`;
test("reads a base rule that a responsive override precedes", () => {
const declarations = cssDeclarations('[data-sidebar="content"]', source);
assert.match(declarations, /min-height:\s*0/);
assert.match(declarations, /overflow-y:\s*auto/);
});
test("includes every rule for the selector so a negative assertion cannot be evaded", () => {
const declarations = cssDeclarations('[data-sidebar="content"]', source);
assert.match(declarations, /-webkit-overflow-scrolling:\s*touch/);
assert.match(declarations, /color:\s*red/);
});
test("matches a selector inside a comma separated list", () => {
assert.match(cssDeclarations(".b", source), /gap:\s*4px/);
});
test("accepts a whole group as the query", () => {
assert.match(cssDeclarations(".a, .b", source), /gap:\s*4px/);
assert.match(cssDeclarations(".missing-one, .documented", source), /padding:\s*8px/);
});
test("does not match a different selector that shares a prefix", () => {
assert.doesNotMatch(cssDeclarations(".session-list", source), /min-height/);
});
test("reads a rule introduced by a comment", () => {
assert.match(cssDeclarations(".documented", source), /padding:\s*8px/);
});
test("reads a rule whose selector wraps across lines", () => {
assert.match(cssDeclarations(".multiline", source), /margin:\s*0/);
});
test("fails loudly when the selector is absent", () => {
assert.throws(() => cssDeclarations(".missing", source), /missing CSS selector: \.missing/);
});