2d370f2e9d
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>
58 lines
1.9 KiB
TypeScript
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/);
|
|
});
|