Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion web/src/components/sections/BuildPanel.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,10 @@
text-transform: uppercase;
}

.otherPaths {
/* Through .buildPanel, the same element as .panel-body: the global
`.panel-body p` (0,1,1) beat the bare class and set this line at 20px with
a 28px bottom margin. */
.buildPanel .otherPaths {
margin: 16px 0 0;
color: var(--pf-ink-muted);
font-size: var(--pf-fs-body);
Expand Down
8 changes: 6 additions & 2 deletions web/src/components/sections/PatternPanel.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,11 @@
line-height: 1.5;
}

.advancedNote {
/* .panel-body p is a global rule at (0,1,1), 20px (17px on a phone) with a
28px bottom margin, and it outranks a bare module class. Both notes are
scoped through .body to win it. At 1440 they were 20px under an 18px title,
and the To hardware block ran 572px. */
.body .advancedNote {
margin: 14px 0 0;
color: var(--pf-ink-muted);
font-size: var(--pf-fs-row-d);
Expand Down Expand Up @@ -341,7 +345,7 @@
border-color: var(--pf-cream-2);
}

.hardwareNote {
.body .hardwareNote {
margin: 0;
color: var(--pf-cream);
opacity: 0.75;
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/sections/PatternPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export default function PatternPanel({ content, isActive }: PatternPanelProps) {
<h1 className="pf-h2">{content.title}</h1>
<p className="pf-sub">{content.subtitle}</p>
</div>
<div className="panel-body">
<div className={`panel-body ${styles.body}`}>
{content.meta && content.meta.length > 0 && (
<div className={`pf-block ${styles.metaRows}`}>
<span className="pf-kicker">Details</span>
Expand Down
57 changes: 57 additions & 0 deletions web/src/components/sections/panelNotes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// jsdom applies none of the CSS modules, so the rules are read from the files.
// The global `.panel-body p` (app/globals.css) is (0,1,1): 20px, 17px under
// 900px, and a 28px bottom margin. A paragraph styled by one module class loses
// to it. On 24 October, at 1440, the Pattern tab's advanced note and both To
// hardware notes were 20px instead of 14px under an 18px title, the To hardware
// block ran 572px, and the Build tab's other-paths line was 20px instead of 15px.

import { readFileSync } from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";

const read = (name: string) => readFileSync(path.resolve(__dirname, name), "utf8");

/** Every selector in the file, comma lists split, comments dropped. */
function selectors(css: string): string[] {
const bare = css.replace(/\/\*[\s\S]*?\*\//g, "");
const found: string[] = [];
for (const m of bare.matchAll(/([^{}]+)\{/g)) {
for (const s of m[1].split(",")) found.push(s.trim().replace(/\s+/g, " "));
}
return found;
}

// Each one renders inside a `.panel-body`.
const panels = [
"PatternPanel",
"BuildPanel",
"InsidePanel",
"CasePicker",
"InsideGlobe/BuildCard",
];

describe("paragraphs styled by a module class under .panel-body", () => {
for (const panel of panels) {
const tsx = read(`${panel}.tsx`);
const rules = selectors(read(`${panel}.module.css`));
const names = [...new Set([...tsx.matchAll(/<p className=\{styles\.(\w+)\}/g)].map((m) => m[1]))];

for (const name of names) {
it(`${panel} .${name} is scoped through a second class`, () => {
expect(rules, `a bare .${name} loses to .panel-body p`).not.toContain(`.${name}`);
const scoped = rules.find((s) => new RegExp(`^\\.(\\w+) \\.${name}$`).test(s));
expect(scoped, `no ".scope .${name}" rule`).toBeDefined();
const scope = scoped!.split(" ")[0].slice(1);
expect(tsx, `.${scope} is not on an element in ${panel}.tsx`).toContain(`styles.${scope}`);
});
}
}

it("finds the four notes this guards", () => {
const all = panels.flatMap((panel) =>
[...read(`${panel}.tsx`).matchAll(/<p className=\{styles\.(\w+)\}/g)].map((m) => m[1]),
);
expect(all).toEqual(expect.arrayContaining(["advancedNote", "hardwareNote", "otherPaths"]));
expect(all.filter((n) => n === "hardwareNote")).toHaveLength(2);
});
});
Loading