Files
webstudio/apps/builder/app/shared/error/error-message.browser.test.tsx
Oleg Isonen d4b1994a6a fix: Improve editor selection and project tag contrast (#6302)
Closes #6301

## Summary

- Restore Dashboard project tags to the pre-color-refactor treatment:
fixed white text over a 30% black scrim in both application themes.
- Use one high-contrast text-selection treatment across native inputs,
textareas, contenteditable fields, and every shared CodeMirror editor.
- Preserve the existing subtle selection treatment for Navigator,
Project Settings, command menus, Dashboard navigation, and other
selected controls.
- Add direct Storybook coverage for the CSS fragment editor.
- Simplify chromatic color derivation from 571 to 329 lines without
changing computed colors.

## Technical choices

- Keep project tags independent from application-theme polarity because
they overlay arbitrary thumbnail artwork.
- Keep the existing `selectionBackground` control-state export unchanged
at its original 12% light / 20% dark accent blend.
- Define separate `--background-text-selection` and
`--foreground-on-text-selection` tokens exclusively for selected text.
- Use the bounded accent in light mode and a luminance-lifted accent in
dark mode so selected text remains distinct even with extreme custom
theme colors.
- Style CodeMirror through its existing selection layer and
`::selection` CSS hooks; no selection tracking or editor-state logic is
added.
- Leave canvas rich-text selection independent: authored canvas
documents do not load the Builder UI theme stylesheet.
- Replace five duplicated XYZ clamp formulas with the exact
upper-luminance cap they reduce to. The removed lower-bound and
white-mixing branches were unreachable because the minimum was fixed at
zero.

## Todo

- [x] Restore the original project-tag scrim and foreground in both
themes.
- [x] Inventory native, CodeMirror, and canvas rich-text selection
paths.
- [x] Unify native and shared CodeMirror text-selection colors.
- [x] Keep text selection independent from selected control and
navigation states.
- [x] Add the missing CSS fragment editor story.
- [x] Add light- and dark-mode browser regressions and confirm they fail
before the fix.
- [x] Enforce 4.5:1 selected-text contrast and 3:1 highlight-to-surface
contrast across supported theme inputs.
- [x] Remove duplicated and unreachable chromatic color derivation while
preserving outputs.
- [x] Review documentation impact; the design-system color-token
reference is updated, with no user-facing product documentation change
required.
- [x] Verify fixture impact; no fixture inputs or generated outputs are
affected.

## Verification

- `pnpm --filter @webstudio-is/design-system test` — 227 tests passed,
including the control/text separation regression, Navigator tree
interaction, default-palette comparisons, and the full supported-theme
contrast sweep
- `pnpm --filter @webstudio-is/design-system typecheck`
- `pnpm --filter @webstudio-is/builder exec vitest run
app/shared/code-editor-base.browser.test.tsx --project
app-browser-shared`
- `pnpm --filter @webstudio-is/builder exec vitest run
app/dashboard/projects/project-card.browser.test.tsx --project
app-browser-canvas-dashboard`
- `pnpm --filter @webstudio-is/builder typecheck`
- Targeted `oxlint`, formatting, and `git diff --check`
- Live dark-mode Dashboard verification of the restored subtle
navigation selection
- Storybook visual checks for the full color system and input/CodeMirror
selection in light and dark modes
2026-09-01 17:05:14 +02:00

65 lines
1.9 KiB
TypeScript

import { act } from "react-dom/test-utils";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, expect, test } from "vitest";
import { ErrorMessage } from "./error-message.client";
(
globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
).IS_REACT_ACT_ENVIRONMENT = true;
let container: HTMLDivElement;
let root: Root;
beforeEach(() => {
container = document.createElement("div");
document.body.appendChild(container);
root = createRoot(container);
});
afterEach(() => {
act(() => root.unmount());
container.remove();
});
test("renders without design-system global color variables", () => {
act(() => {
root.render(
<ErrorMessage
error={{
status: 500,
message: "Canvas failed to render",
}}
/>
);
});
const page = container.firstElementChild;
const link = container.querySelector("a");
const panel = link?.previousElementSibling;
if (
page instanceof HTMLElement === false ||
panel instanceof HTMLElement === false ||
link instanceof HTMLElement === false
) {
throw new Error("Expected the error page, panel, and link");
}
expect(getComputedStyle(page).color).toBe("rgb(17, 24, 28)");
expect(getComputedStyle(panel).backgroundColor).toBe("rgb(255, 255, 255)");
expect(getComputedStyle(link).backgroundColor).toBe("rgb(9, 108, 255)");
expect(getComputedStyle(link).color).toBe("rgb(255, 255, 255)");
for (const state of ["hover", "pressed"]) {
link.dataset.state = state;
const style = getComputedStyle(link);
expect(style.backgroundImage, state).not.toBe("none");
expect(style.color, state).toBe("rgb(255, 255, 255)");
}
link.dataset.state = "focus";
const focusStyle = getComputedStyle(link);
expect(focusStyle.outlineColor).toBe("rgb(41, 122, 255)");
expect(focusStyle.outlineStyle).not.toBe("none");
expect(focusStyle.color).toBe("rgb(255, 255, 255)");
});