From b377894c99e24394bd48e58b28b15546e9f14309 Mon Sep 17 00:00:00 2001 From: Noah Date: Tue, 21 Apr 2026 14:03:17 -0700 Subject: [PATCH] test(phase-0): add foundation_tokens browser test module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the Task 14 test-coverage gap from the Phase 0 foundation-shell plan (docs/plans/2026-04-19-ui-phase-0-foundation.md). Three tests: - foundation_palette_tokens_defined — asserts every palette/ink/state token on :root resolves to a non-empty computed value. - legacy_bg_main_aliases_bg_0 — asserts --bg-main (style.css) inherits the same value as --bg-0 (foundation.css), proving the reskin alias layer is live. - data_accent_swap_changes_moss_2 — toggles data-accent on #app-root and asserts the accent-controlled token updates. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/web/tests/browser.rs | 177 ++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/crates/web/tests/browser.rs b/crates/web/tests/browser.rs index e02918c6..b39b784a 100644 --- a/crates/web/tests/browser.rs +++ b/crates/web/tests/browser.rs @@ -9303,3 +9303,180 @@ mod phase_2a_message_row { ); } } + +// ── Foundation tokens (Phase 0) ───────────────────────────────────────────── +// +// Closes Task 14 of `docs/plans/2026-04-19-ui-phase-0-foundation.md`. +// Verifies the foundation design-token layer is live at the `:root` level +// and that the legacy `style.css` alias layer forwards to it correctly. +// +// The wasm-pack test harness does NOT pull in the app's stylesheets +// through Trunk, so each test injects `foundation.css` + `style.css` +// manually (dedupe-guarded via element ids) before reading computed +// styles on the document root. + +#[cfg(test)] +mod foundation_tokens { + use super::*; + + /// Inject `foundation.css` into the test document once per page load + /// so `:root` design tokens resolve under `getComputedStyle`. Dedupes + /// via a fixed element id. + fn ensure_foundation_css_loaded() { + const STYLE_ID: &str = "willow-test-foundation-css"; + let doc = web_sys::window().unwrap().document().unwrap(); + if doc.get_element_by_id(STYLE_ID).is_some() { + return; + } + let style = doc.create_element("style").unwrap(); + style.set_id(STYLE_ID); + style.set_text_content(Some(include_str!("../foundation.css"))); + let head = doc.head().expect("document has "); + head.append_child(&style).unwrap(); + } + + /// Inject `style.css` (legacy alias layer) into the test document. + /// Required for the `--bg-main` → `--bg-0` alias assertion. Dedupes + /// via a fixed element id. + fn ensure_style_css_loaded() { + const STYLE_ID: &str = "willow-test-style-css"; + let doc = web_sys::window().unwrap().document().unwrap(); + if doc.get_element_by_id(STYLE_ID).is_some() { + return; + } + let style = doc.create_element("style").unwrap(); + style.set_id(STYLE_ID); + style.set_text_content(Some(include_str!("../style.css"))); + let head = doc.head().expect("document has "); + head.append_child(&style).unwrap(); + } + + /// Read the computed value of `prop` on the document root (``). + fn computed_root_prop(prop: &str) -> String { + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let root: web_sys::Element = document.document_element().unwrap(); + let style = window.get_computed_style(&root).unwrap().unwrap(); + style.get_property_value(prop).unwrap_or_default() + } + + /// Set `data-accent=""` on the document root so the accent + /// override block in `foundation.css` takes effect. + fn set_data_accent(value: &str) { + let document = web_sys::window().unwrap().document().unwrap(); + let root: web_sys::Element = document.document_element().unwrap(); + root.set_attribute("data-accent", value).unwrap(); + } + + /// Clear `data-accent` from the document root so later tests start + /// from the inherited default. + fn clear_data_accent() { + let document = web_sys::window().unwrap().document().unwrap(); + let root: web_sys::Element = document.document_element().unwrap(); + let _ = root.remove_attribute("data-accent"); + } + + #[wasm_bindgen_test] + fn foundation_palette_tokens_defined() { + // Sanity — foundation.css is loaded and every palette/ink/state + // token the shell depends on resolves to a non-empty value. + ensure_foundation_css_loaded(); + for var in [ + "--bg-0", + "--bg-1", + "--bg-2", + "--bg-3", + "--bg-4", + "--ink-0", + "--ink-1", + "--ink-2", + "--ink-3", + "--ink-on-accent", + "--moss-2", + "--willow", + "--whisper", + "--amber", + "--ok", + "--warn", + "--err", + "--radius", + "--shadow-2", + "--focus-ring", + "--font-display", + "--font-ui", + "--font-mono", + "--motion", + "--motion-ease", + ] { + let v = computed_root_prop(var); + assert!( + !v.trim().is_empty(), + "foundation token {var} not defined on :root (computed value was empty)" + ); + } + } + + #[wasm_bindgen_test] + fn legacy_bg_main_aliases_bg_0() { + // style.css remaps --bg-main to var(--bg-0). Both must resolve to + // the same computed colour, proving the reskin alias layer is live. + ensure_foundation_css_loaded(); + ensure_style_css_loaded(); + let bg_main = computed_root_prop("--bg-main"); + let bg_0 = computed_root_prop("--bg-0"); + assert!( + !bg_0.trim().is_empty(), + "--bg-0 not defined (foundation.css not loaded?)" + ); + assert!( + !bg_main.trim().is_empty(), + "--bg-main not defined (style.css not loaded?)" + ); + assert_eq!( + bg_main.trim(), + bg_0.trim(), + "legacy --bg-main ({bg_main:?}) drifted from --bg-0 ({bg_0:?})" + ); + } + + #[wasm_bindgen_test] + fn data_accent_swap_changes_moss_2() { + // Swap the accent attribute on document element and verify + // --moss-2 updates synchronously (CSS-only, no Rust side effects). + // Moss is the default; willow is a distinct accent with a + // different --moss-2 value (see foundation.css accent block). + ensure_foundation_css_loaded(); + + set_data_accent("moss"); + let moss_default = computed_root_prop("--moss-2"); + assert!( + !moss_default.trim().is_empty(), + "--moss-2 undefined after data-accent=moss" + ); + + set_data_accent("willow"); + let moss_willow = computed_root_prop("--moss-2"); + assert!( + !moss_willow.trim().is_empty(), + "--moss-2 undefined after data-accent=willow" + ); + assert_ne!( + moss_default.trim(), + moss_willow.trim(), + "accent swap to willow did not change --moss-2 \ + (default {moss_default:?}, willow {moss_willow:?})" + ); + + // Revert to moss and confirm --moss-2 swaps back to the default. + set_data_accent("moss"); + let moss_reverted = computed_root_prop("--moss-2"); + assert_eq!( + moss_reverted.trim(), + moss_default.trim(), + "reverting to data-accent=moss did not restore original --moss-2" + ); + + // Leave the document root in a neutral state for later tests. + clear_data_accent(); + } +}