Repository navigation
test(gui): guard the Codex Set page-head wrap and the Codex nav mark #3468
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| # 040 — Regression coverage for the two chrome fixes | ||
|
|
||
| ## Why this exists | ||
|
|
||
| Both fixes in #3465 shipped with live proof and zero committed guard. Two | ||
| independent reviewers flagged the same hole from opposite directions: | ||
|
|
||
| - The plan auditor found `gui/tests/sidebar-codex-set.test.ts:28` pinning | ||
| `Icon: IconKey` and required it be unpinned. It was. | ||
| - CodeRabbit then observed that unpinning removed the *only* thing tying that | ||
| nav row to any icon at all, and asked for focused coverage of the new mapping. | ||
|
|
||
| Both are right, and they are not in conflict — the mistake would be to answer | ||
| the second by undoing the first. The comment already in that file says why: | ||
| pinning "the shape of one line" made the test fail "the moment an entry gained a | ||
| field — a change it was never written to catch". Re-pinning the whole literal | ||
| with a new icon name recreates exactly that. | ||
|
|
||
| The CSS fix has no guard either. `rg -l codex-auth-page-head gui/tests` returns | ||
| one file, about toast tone, not layout. A future `flex-wrap` removal would | ||
| restore the clip silently. | ||
|
|
||
| ## What to assert, and where | ||
|
|
||
| A sibling file, `gui/tests/codex-set-chrome.test.ts`, not an extension of the | ||
| sidebar test. That file's subject is routing identity — "always present, never | ||
| filtered by view mode", plus the legacy `#codex-auth` bookmark. Icon identity | ||
| and page-head layout are a different contract, and `anthropic-pool-card-layout.test.ts` | ||
| is the precedent for giving a visual contract its own file with its own | ||
| explanation of why each value is what it is. | ||
|
|
||
| Instrument: source-text assertions, matching this suite's convention for | ||
| anything layout-shaped. `anthropic-pool-card-layout.test.ts` states the reason | ||
| outright — happy-dom performs no layout, so `getBoundingClientRect()` returns | ||
| zeros and would prove nothing. The rendered proof for this unit lives in `030` | ||
| as real-browser measurements; these tests guard the source that produced it. | ||
|
|
||
| Borrow that file's two safeguards: | ||
|
|
||
| - `withoutComments()` before matching CSS, so an assertion can never pass on | ||
| prose quoting the value. This unit's fix ships with a long explanatory comment | ||
| that literally contains the words `flex-wrap`, so without stripping, the CSS | ||
| assertions would be self-satisfying. This is the single most important detail | ||
| here. | ||
| - `ruleBody()` anchored at line start, so a selector that also appears indented | ||
| inside `@media` is not read off the wrong rule. | ||
|
|
||
| ### wp4-a/b — the row wears the mark, asserted through a render | ||
|
|
||
| The first draft of this doc proposed `toContain('Icon: IconCodex')`. A reviewer | ||
| rejected it, correctly: that is the *same* fragility the sidebar test documents | ||
| removing, one rename later. Renaming the symbol is not a regression; the string | ||
| breaks anyway. What must not change is the glyph. | ||
|
|
||
| So the name is read from the NAV row only to *resolve* the component, and every | ||
| assertion lands on rendered output: | ||
|
|
||
| 1. Strip comments from `App.tsx`, slice the `NAV` table, and capture the | ||
| `Icon:` identifier for the `codex-set` row. Source text is forced here — | ||
| `NAV` is not exported, so no other instrument can observe the mapping. | ||
| 2. Resolve that name against the `icons` module namespace and fail with a | ||
| readable message if it is not exported. | ||
| 3. `renderToStaticMarkup` it and assert the geometry. | ||
|
|
||
| Rendering is the right instrument for the second half because the JSX is | ||
| multi-line and attribute-ordered — `toContain('strokeWidth={2.484}')` would | ||
| break on a reformat. `renderToStaticMarkup` normalizes to | ||
| `stroke-width="2.484"` regardless of authoring style. It is also the suite's | ||
| *lighter* render instrument: happy-dom is installed but needs five global swaps | ||
| plus teardown, and no layout, event, or lifecycle behavior is under test here. | ||
|
|
||
| Geometry is asserted as tokens from the `d` attribute — the ring's radius and | ||
| its extreme points, the underscore, the chevron — with whitespace collapsed | ||
| first, so a decimal-preserving reflow passes while a redraw fails. | ||
|
|
||
| The specific silent-revert this guards, which the first draft failed to name: | ||
| folding the mark back through `S()`. That helper hardcodes a 24-unit box and | ||
| stroke 2, which rescales a path drawn for 32 units and still renders something | ||
| icon-shaped — review does not catch it, and `stroke-width="2.484"` does not | ||
| contain `stroke-width="2"`. Hence the negative assertions on | ||
| `viewBox="0 0 24 24"` and `stroke-width="2"`. | ||
|
|
||
| File: `gui/tests/sidebar-codex-mark.test.tsx`, a sibling rather than an | ||
| extension. `sidebar-codex-set.test.ts`'s declared subject is the row surviving | ||
| the removed viewMode filter; mark identity is a different contract, and this | ||
| repository already keeps mark-identity tests in their own files | ||
| (`provider-icons`, `client-marks-assets`, `integration-marks`). | ||
|
|
||
| ### wp4-c — the page head still wraps | ||
|
|
||
| ```ts | ||
| expect(ruleBody(css, ".codex-auth-page-head")).toContain("flex-wrap: wrap"); | ||
| expect(ruleBody(css, ".codex-auth-page-head__actions")).toContain("flex-wrap: wrap"); | ||
| ``` | ||
|
|
||
| on comment-stripped CSS. | ||
|
|
||
| ## What this does NOT catch | ||
|
|
||
| Worth stating so nobody reads more into a green run than is there: | ||
|
|
||
| - It does not catch upstream drift. Nothing re-fetches `success.html`; this pins | ||
| "matches what was copied on 2026-09-04", not "matches upstream today". | ||
| - It does not catch a mangled path that happens to retain the asserted tokens. | ||
| Closing that needs a full normalized-`d` equality, at real formatting cost — | ||
| a deliberate trade. | ||
| - It does not prove the sidebar renders the icon at all: the row is read as | ||
| source text, so deleting `<Icon />` from the nav JSX would leave this green. | ||
| - It does not prove the head *renders* unclipped. No layout engine runs. A | ||
| change to `.page-head`, `.btn`, or the container width could reintroduce a clip | ||
| with both `flex-wrap` declarations still present. Only `030`'s browser | ||
| measurements prove the rendered outcome, and only for the moment they were taken. | ||
| - It does not cover 17px sizing, `currentColor` inheritance, or theme contrast. | ||
| - It does not cover the `embedded` variant, which never had the defect. | ||
| - It does not cover the other eight nav rows, whose glyphs stay freely swappable. | ||
|
|
||
| ## Verification | ||
|
|
||
| `bun test --isolate tests/sidebar-codex-mark.test.tsx tests/codex-set-chrome.test.ts` | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The documented verification command references Useful? React with 👍 / 👎. |
||
| plus the existing `tests/sidebar-codex-set.test.ts`, and both new files driven | ||
| red once — the mark test by pointing the row at a neighbouring glyph, the CSS | ||
| test by removing a `flex-wrap` — because an assertion never seen to fail is not | ||
| known to be load-bearing. No repository-wide suite. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import { expect, test } from "bun:test"; | ||
|
|
||
| /** | ||
| * The Codex Set page head must be able to wrap. | ||
| * | ||
| * Its action cluster is four nowrap items — the Spark switch, two labelled | ||
| * buttons, and the feedback slot. While the head was a single nowrap flex row, | ||
| * a narrow viewport pushed the trailing button past its own container, and | ||
| * `overflow-x: hidden` on html/body turned that into a clip rather than a | ||
| * scrollbar: measured at 850px, "Refresh quotas" ran to x=944 against a | ||
| * container ending at 804. | ||
| * | ||
| * Source-text assertions, not measurements: happy-dom performs no layout, so a | ||
| * getBoundingClientRect() here returns zeros and would prove nothing. The | ||
| * rendered proof was captured in a real browser and lives in | ||
| * devlog/_plan/260904_codex_set_head_and_logo/030_live_verification_record.md. | ||
| * What this file guards is the declaration that produced it. | ||
| */ | ||
| const cssUrl = new URL("../src/styles.css", import.meta.url); | ||
|
|
||
| /** Strip comments so no assertion can pass on prose that quotes a value. */ | ||
| function withoutComments(css: string): string { | ||
| return css.replace(/\/\*[\s\S]*?\*\//g, ""); | ||
| } | ||
|
|
||
| /** | ||
| * Body of a top-level rule. Anchored with no leading whitespace on purpose, so a | ||
| * selector that also appears indented inside an `@media` block is not read off | ||
| * the wrong rule. | ||
| */ | ||
| function ruleBody(css: string, selector: string): string { | ||
| const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | ||
| const match = css.match(new RegExp(`(^|\\n)${escaped}\\s*\\{([^}]*)\\}`)); | ||
| if (!match) throw new Error(`rule not found: ${selector}`); | ||
| return match[2]!; | ||
| } | ||
|
|
||
| test("the Codex Set page head and its action cluster both wrap", async () => { | ||
| const css = withoutComments(await Bun.file(cssUrl).text()); | ||
|
|
||
| // The head wraps so the cluster can drop below the title instead of competing | ||
| // with it for one line. | ||
| expect(ruleBody(css, ".codex-auth-page-head")).toContain("flex-wrap: wrap"); | ||
|
|
||
| // The cluster wraps so it can break internally when even a full row is not | ||
| // enough, staying right-aligned as it does at wide widths. | ||
| const actions = ruleBody(css, ".codex-auth-page-head__actions"); | ||
| expect(actions).toContain("flex-wrap: wrap"); | ||
| expect(actions).toContain("justify-content: flex-end"); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import { expect, test } from "bun:test"; | ||
| import { renderToStaticMarkup } from "react-dom/server"; | ||
| import { createElement, type FC, type SVGProps } from "react"; | ||
| import * as icons from "../src/icons"; | ||
|
|
||
| /** | ||
| * The codex-set nav row wears the Codex mark. | ||
| * | ||
| * Deliberately not in `sidebar-codex-set.test.ts`: that file's subject is the row | ||
| * surviving the removed viewMode filter, and it dropped its own `Icon: IconKey` | ||
| * pin for failing on a change it was never written to catch. This file's subject | ||
| * IS the glyph, so it is supposed to fail when the glyph changes. | ||
| * | ||
| * It still does not pin the symbol NAME. A rename is not a regression; wearing a | ||
| * key again is. The name is read from the row only to resolve the component, and | ||
| * every assertion lands on rendered geometry. | ||
| */ | ||
| function iconNameForNavRow(src: string, id: string): string { | ||
| // Comments naming the icon are prose, not evidence: icons.tsx carries a long | ||
| // block comment naming this mark, and App.tsx has comment prose inside <nav>. | ||
| const code = src.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, ""); | ||
| const at = code.indexOf("const NAV: NavEntry[] = ["); | ||
| expect(at, "NAV table not found in App.tsx").toBeGreaterThan(-1); | ||
| const nav = code.slice(at, code.indexOf("];", at)); | ||
| const row = nav.match(new RegExp(`\\{[^{}]*id: "${id}"[^{}]*\\}`)); | ||
| expect(row, `NAV has no ${id} row`).not.toBeNull(); | ||
| const name = row![0].match(/Icon:\s*(\w+)/); | ||
| expect(name, `the ${id} row declares no Icon`).not.toBeNull(); | ||
| return name![1]!; | ||
| } | ||
|
|
||
| test("the codex-set nav row renders the upstream Codex mark, under any symbol name", async () => { | ||
| const src = await Bun.file(new URL("../src/App.tsx", import.meta.url)).text(); | ||
| const name = iconNameForNavRow(src, "codex-set"); | ||
|
|
||
| const Icon = (icons as Record<string, FC<SVGProps<SVGSVGElement>> | undefined>)[name]; | ||
| expect(Icon, `App.tsx points codex-set at ${name}, which icons.tsx does not export`) | ||
| .toBeTypeOf("function"); | ||
|
|
||
| const svg = renderToStaticMarkup(createElement(Icon!)); | ||
| // Attribute order and source formatting are not the subject; the drawn path is. | ||
| const d = (svg.match(/\sd="[^"]*"/g) ?? []).join(" ").replace(/\s+/g, " "); | ||
|
|
||
| /* | ||
| * Provenance, by the numbers only the openai/codex `svg.codex-mark` carries: a | ||
| * ring of r=14.758 about (16,16) on a 32-unit box, enclosing a `>` and a `_`. | ||
| * Asserted as tokens rather than as the whole `d` string, so a reflow or a | ||
| * decimal-preserving reformat does not fail it while a redraw does. | ||
| */ | ||
| expect(svg).toContain('viewBox="0 0 32 32"'); | ||
| expect(d).toContain("30.758 16"); // ring, rightmost point | ||
| expect(d).toContain("14.758"); // ring radius | ||
| expect(d).toContain("16 1.242"); // ring, top | ||
| expect(d).toContain("22.356 19.797H17.17"); // the underscore | ||
| expect(d).toContain("9.662 12.29"); // the chevron | ||
|
|
||
| /* | ||
| * The reversion this is really guarding: folding the mark back through `S()`, | ||
| * which hardcodes a 24-unit box and stroke 2. That rescales a path drawn for 32 | ||
| * units and still renders something icon-shaped, so review does not catch it. | ||
| * `stroke-width="2.484"` does not contain `stroke-width="2"`, hence the | ||
| * negative assertions rather than only the positive ones. | ||
| */ | ||
| expect(svg).toContain('stroke-width="2.484"'); | ||
| expect(svg).toContain('stroke="currentColor"'); | ||
| expect(svg).toContain('fill="none"'); | ||
| expect(svg).not.toContain('viewBox="0 0 24 24"'); | ||
| expect(svg).not.toContain('stroke-width="2"'); | ||
| expect((svg.match(/<path/g) ?? []).length).toBe(1); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Update the regression-test references.
Line 25 names
gui/tests/codex-set-chrome.test.ts, but this cohort addsgui/tests/sidebar-codex-mark.test.tsxandgui/tests/codex-set-page-head-wrap.test.ts. Lines 119-120 invoke the obsolete name, so the documented verification command does not run the new page-head wrapping test.devlog/_plan/260904_codex_set_head_and_logo/040_wp4_regression_coverage.md#L25-L30: Replace the obsolete single-file plan with the two delivered test files and their separate contracts.devlog/_plan/260904_codex_set_head_and_logo/040_wp4_regression_coverage.md#L119-L123: Invokegui/tests/sidebar-codex-mark.test.tsxandgui/tests/codex-set-page-head-wrap.test.ts; retain the existing sidebar test using its repository-relative path.📍 Affects 1 file
devlog/_plan/260904_codex_set_head_and_logo/040_wp4_regression_coverage.md#L25-L30(this comment)devlog/_plan/260904_codex_set_head_and_logo/040_wp4_regression_coverage.md#L119-L123🤖 Prompt for AI Agents