murashit_codex-panel/tests/styles.test.ts

198 lines
9.3 KiB
TypeScript
Raw Normal View History

2026-05-25 14:06:25 +00:00
import { readFileSync } from "node:fs";
import path from "node:path";
2026-05-25 14:06:25 +00:00
import { describe, expect, it } from "vitest";
const sourceDir = path.join("src", "styles");
2026-05-31 11:26:11 +00:00
const sourceFiles = JSON.parse(readFileSync(path.join(sourceDir, "order.json"), "utf8")) as string[];
const styles = `${sourceFiles.map((file) => readFileSync(path.join(sourceDir, file), "utf8").trimEnd()).join("\n\n")}\n`;
2026-05-25 14:06:25 +00:00
2026-07-04 10:05:14 +00:00
function ruleBody(selector: string): string {
const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
return new RegExp(`(?:^|\\n)${escapedSelector} \\{(?<body>[^}]+)\\}`).exec(styles)?.groups?.["body"] ?? "";
}
function standaloneRuleBody(selector: string): string {
const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
return new RegExp(`(?:^|\\n\\n)${escapedSelector} \\{(?<body>[^}]+)\\}`).exec(styles)?.groups?.["body"] ?? "";
}
describe("panel CSS boundaries", () => {
2026-05-25 14:06:25 +00:00
it("defines design tokens on every standalone UI root", () => {
const tokenScopeEnd = styles.indexOf(" {");
expect(tokenScopeEnd).toBeGreaterThan(0);
const tokenScope = styles.slice(0, tokenScopeEnd);
2026-05-25 14:06:25 +00:00
expect(tokenScope).toContain(".codex-panel");
2026-06-27 12:49:26 +00:00
expect(tokenScope).toContain(".codex-panel-turn-diff");
2026-05-25 14:06:25 +00:00
expect(tokenScope).toContain(".codex-panel-settings");
expect(tokenScope).toContain(".codex-panel-threads");
expect(tokenScope).toContain(".codex-panel-selection-rewrite");
});
2026-07-04 10:05:14 +00:00
it("keeps selectors shallow enough for Obsidian theme compatibility", () => {
expect(styles).not.toContain(":has(");
expect(styles).not.toMatch(/:where\([^)]*[.#[]/);
expect(styles).not.toMatch(/(^|[\s,{])#[\w-]+/);
});
2026-05-25 14:06:25 +00:00
});
2026-05-28 01:29:21 +00:00
2026-07-04 10:05:14 +00:00
describe("panel CSS layout invariants", () => {
2026-07-08 21:52:19 +00:00
it("lets the thread stream scroll inside the shell grid", () => {
const threadStream = ruleBody(".codex-panel__thread-stream");
2026-07-08 21:52:19 +00:00
expect(threadStream).toContain("overflow-y: auto");
expect(threadStream).not.toMatch(/^\s+height:/m);
});
2026-07-04 10:05:14 +00:00
it("keeps icon-only toolbar actions on Obsidian toolbar geometry", () => {
const toolbar = ruleBody(".codex-panel__toolbar");
const toolbarAction = ruleBody(".codex-panel-ui__toolbar-action");
expect(toolbarAction).toContain("--icon-size: var(--codex-panel-control-icon-size)");
expect(toolbarAction).toContain("--icon-stroke: var(--icon-m-stroke-width, 1.75px)");
expect(toolbarAction).not.toContain("width:");
expect(toolbarAction).not.toContain("height:");
expect(toolbarAction).not.toContain("padding:");
expect(toolbarAction).not.toContain("border-radius:");
expect(toolbar).not.toContain("padding:");
});
2026-07-04 10:05:14 +00:00
it("keeps shared nav rows aligned with Obsidian nav item geometry", () => {
const navItem = ruleBody(".codex-panel-ui__nav-item");
const navRow = ruleBody(".codex-panel-ui__nav-row");
2026-07-04 10:05:14 +00:00
expect(navItem).toContain("min-height: var(--nav-item-size");
expect(navItem).toContain("padding: var(--nav-item-padding");
expect(navItem).toContain("border-radius: var(--nav-item-radius");
expect(navRow).toContain("padding-inline-end:");
2026-05-29 01:59:58 +00:00
});
2026-07-04 10:05:14 +00:00
it("keeps selected nav items stable when hovered or focused", () => {
const selectedNavItem = ruleBody(".codex-panel-ui__nav-item.is-selected:where(:hover, :focus, :focus-visible, :active, :focus-within)");
const selectedNavRow = ruleBody(
[
".codex-panel-ui__nav-row.is-selected,",
".codex-panel-ui__nav-row.is-selected:hover,",
".codex-panel-ui__nav-row.is-selected:focus-within",
].join("\n"),
);
expect(selectedNavItem).toContain("background: var(--nav-item-background-active, var(--background-modifier-active))");
expect(selectedNavRow).toContain("background: var(--nav-item-background-active, var(--background-modifier-active))");
expect(selectedNavItem).not.toContain("nav-item-background-active-hover");
expect(selectedNavRow).not.toContain("nav-item-background-active-hover");
});
2026-06-03 01:57:31 +00:00
2026-07-04 10:05:14 +00:00
it("keeps nav inline inputs as native inline editors", () => {
const navInlineInput = ruleBody(".codex-panel-ui__nav-inline-input.codex-panel-ui__nav-inline-input");
const navInlineInputFocus = ruleBody(
[
".codex-panel-ui__nav-inline-input.codex-panel-ui__nav-inline-input:focus,",
".codex-panel-ui__nav-inline-input.codex-panel-ui__nav-inline-input:focus-visible,",
".codex-panel-ui__nav-inline-input.codex-panel-ui__nav-inline-input:hover,",
".codex-panel-ui__nav-inline-input.codex-panel-ui__nav-inline-input:active",
].join("\n"),
);
2026-07-04 10:05:14 +00:00
expect(navInlineInput).toContain("appearance: none");
expect(navInlineInput).toContain("border: 0");
expect(navInlineInput).toContain("background: transparent");
expect(navInlineInputFocus).toContain("background: transparent");
});
2026-06-03 01:57:31 +00:00
it("keeps framed text inputs native across panel surfaces", () => {
const frame = ruleBody(".codex-panel-ui__text-input-frame");
const frameFocus = ruleBody(".codex-panel-ui__text-input-frame:focus-within");
const input = ruleBody(".codex-panel-ui__text-input");
const inputInteraction = ruleBody(
[
".codex-panel-ui__text-input.codex-panel-ui__text-input:hover,",
".codex-panel-ui__text-input.codex-panel-ui__text-input:focus,",
".codex-panel-ui__text-input.codex-panel-ui__text-input:focus-visible,",
".codex-panel-ui__text-input.codex-panel-ui__text-input:active",
].join("\n"),
);
expect(frame).toContain("background: var(--background-modifier-form-field)");
expect(frameFocus).toContain("border-color: var(--background-modifier-border-focus)");
expect(input).toContain("border: 0");
expect(inputInteraction).toContain("background: transparent");
expect(inputInteraction).toContain("outline: none");
});
it("shares activity dot timing across active surfaces", () => {
const activityDots = ruleBody(".codex-panel-ui__activity-dots span");
const secondDot = ruleBody(".codex-panel-ui__activity-dots span:where(:nth-child(2))");
const thirdDot = ruleBody(".codex-panel-ui__activity-dots span:where(:nth-child(3))");
expect(activityDots).toContain("animation: codex-panel-activity-dot 1.2s infinite ease-in-out");
expect(secondDot).toContain("animation-delay: 0.18s");
expect(thirdDot).toContain("animation-delay: 0.36s");
});
2026-07-04 10:05:14 +00:00
it("keeps compact status rows from wrapping or stealing pointer interaction", () => {
const contextMeter = ruleBody(".codex-panel__composer-meta-context");
const summary = ruleBody(".codex-panel__composer-meta-summary");
2026-06-04 12:10:19 +00:00
2026-06-05 00:31:11 +00:00
expect(contextMeter).toContain("white-space: nowrap");
2026-07-04 10:05:14 +00:00
expect(contextMeter).toContain("font-variant-numeric: tabular-nums");
2026-06-05 02:31:24 +00:00
expect(summary).toContain("position: absolute");
2026-06-05 06:27:39 +00:00
expect(summary).toContain("pointer-events: none");
2026-06-05 02:31:24 +00:00
});
2026-07-04 10:05:14 +00:00
it("lets usage limit meters absorb available width", () => {
const limitMeterCell = ruleBody(".codex-panel__limit-panel-meter-cell");
const limitValue = ruleBody(".codex-panel__limit-panel-value");
const limitReset = standaloneRuleBody(".codex-panel__limit-panel-reset");
expect(limitMeterCell).toContain("width: 100%");
expect(limitMeterCell).toContain("vertical-align: middle");
2026-06-04 01:09:02 +00:00
expect(limitValue).toContain("font-variant-numeric: tabular-nums");
2026-07-04 10:05:14 +00:00
expect(limitReset).toContain("white-space: nowrap");
});
it("keeps threads view row titles ellipsized while actions stay collapsed until interaction", () => {
const rowTitleBase = ruleBody(".codex-panel-threads__row-title");
const rowTitleDisplay = standaloneRuleBody(".codex-panel-threads__row-title");
const actions = ruleBody(".codex-panel-threads__actions");
const actionReveal = ruleBody(
[
".codex-panel-threads__row:hover .codex-panel-threads__actions,",
".codex-panel-threads__row:focus-within .codex-panel-threads__actions",
].join("\n"),
);
2026-06-04 13:08:48 +00:00
2026-07-04 10:05:14 +00:00
expect(rowTitleDisplay).toContain("display: block");
expect(rowTitleBase).toContain("white-space: nowrap");
expect(rowTitleBase).toContain("text-overflow: ellipsis");
expect(actions).toContain("width: 0");
expect(actions).toContain("pointer-events: none");
expect(actions).toContain("opacity: 0");
expect(actionReveal).toContain("width: auto");
expect(actionReveal).toContain("pointer-events: auto");
expect(actionReveal).toContain("opacity: 1");
2026-06-04 13:08:48 +00:00
});
2026-07-04 10:05:14 +00:00
it("keeps threads view inline rename aligned to nav row height", () => {
const renameForm = ruleBody(".codex-panel-threads__rename-form");
const renameField = ruleBody(".codex-panel-threads__rename-field");
const renameInput = ruleBody(".codex-panel-threads__rename-input");
2026-05-28 01:29:21 +00:00
2026-07-04 10:05:14 +00:00
expect(renameForm).toContain("min-height: var(--nav-item-size");
expect(renameField).toContain("min-height: var(--nav-item-size");
expect(renameInput).toContain("line-height: var(--line-height-tight)");
2026-05-28 01:29:21 +00:00
});
2026-07-04 10:05:14 +00:00
it("keeps selection rewrite controls framed as a compact edit-and-review surface", () => {
const composerFrame = ruleBody(".codex-panel-selection-rewrite__composer-frame");
const sharedFrame = ruleBody(".codex-panel-ui__text-input-frame");
2026-07-04 10:05:14 +00:00
const result = ruleBody(".codex-panel-selection-rewrite__result");
2026-06-02 21:07:19 +00:00
2026-07-04 10:05:14 +00:00
expect(composerFrame).toContain("grid-template-columns:");
expect(sharedFrame).toContain("background: var(--background-modifier-form-field)");
2026-07-04 10:05:14 +00:00
expect(result).toContain("grid-template-columns:");
expect(result).toContain("border: var(--codex-panel-border)");
2026-06-03 01:57:31 +00:00
});
2026-05-28 01:29:21 +00:00
});