Skip to content

test(ui): computed-style smoke test — assert sidebar/topbar/chat layout properties on rendered DOM #2519

@alexey-pelykh

Description

@alexey-pelykh

Problem

The smoke test added in #2495 instantiates RemoteClawApp and asserts every required host-interface field is !== undefined on the class instance. This catches "production class missing initializers" (the original #2493 regression) but does NOT catch "production renders but layout is broken" — the regression class demonstrated by #2517 (sidebar wrappers missing → overflow: hidden clips content).

JSDOM doesn't apply most CSS, so layout assertions in JSDOM are mostly meaningless. But computed-style assertions ARE possible if we use a real browser harness (Playwright in headless mode) or the Vitest browser mode (already a dev dependency at @vitest/browser-playwright per ui/package.json).

Goal

Extend the existing class-instance smoke test to also assert key computed-style properties on the rendered DOM. Catch "rendered but visually clipped/hidden/wrong-sized" symptoms without requiring full visual regression baselines.

Concrete assertions to add

For the RemoteClawApp smoke test (running in browser-mode Vitest with real CSS applied):

describe("RemoteClawApp instance — computed-style smoke", () => {
  beforeEach(async () => {
    document.body.innerHTML = '<remoteclaw-app></remoteclaw-app>';
    await new Promise(r => requestAnimationFrame(r));
    await new Promise(r => requestAnimationFrame(r));
  });

  it("sidebar has non-zero height (not clipped)", () => {
    const sidebar = document.querySelector(".sidebar");
    expect(sidebar).not.toBeNull();
    const rect = sidebar!.getBoundingClientRect();
    expect(rect.height).toBeGreaterThan(300);  // empirical threshold; tune to viewport
    expect(rect.width).toBeGreaterThan(0);
  });

  it("topbar-status renders with non-zero width", () => {
    const status = document.querySelector(".topbar-status");
    expect(status).not.toBeNull();
    expect(status!.getBoundingClientRect().width).toBeGreaterThan(0);
  });

  it("nav-section group headers are all visible (not clipped)", () => {
    const groups = document.querySelectorAll(".nav-section");
    expect(groups.length).toBeGreaterThanOrEqual(4);
    for (const group of groups) {
      const rect = group.getBoundingClientRect();
      // Each group is at least partially visible
      expect(rect.height).toBeGreaterThan(0);
    }
  });

  it("chat panel renders with non-zero area when on chat tab", async () => {
    const app = document.querySelector("remoteclaw-app") as any;
    app.tab = "chat";
    await new Promise(r => requestAnimationFrame(r));
    const chat = document.querySelector("section.chat");
    expect(chat).not.toBeNull();
    const rect = chat!.getBoundingClientRect();
    expect(rect.height).toBeGreaterThan(100);
    expect(rect.width).toBeGreaterThan(100);
  });
});

Test runner choice

The project already has @vitest/browser-playwright and playwright as dev dependencies (ui/package.json), and a vitest.config.ts that supports browser mode. Two integration paths:

  1. Browser-mode Vitest in ui/src/ui/app.computed-style.test.ts — runs in a real browser instance via Playwright; CSS is applied; getBoundingClientRect returns real layout values
  2. Standalone Playwright test in ui/test/e2e/layout.spec.ts — heavier setup; better suited for full visual regression (separate work item P-4)

Recommend option 1 (browser-mode Vitest) — uses existing infra, fast iteration, runs in pnpm test.

Acceptance criteria

  • New test file at ui/src/ui/app.computed-style.test.ts (or co-located with existing smoke test)
  • Tests run via existing pnpm test (or a dedicated pnpm test:browser if browser-mode requires separate config)
  • All 4 example assertions above pass on a healthy build
  • Re-introducing the #2517 regression (removing .shell-nav wrapper) makes the sidebar-height test FAIL
  • CI runs the browser-mode tests on PRs (may require GitHub Actions runner adjustment)
  • Documented in CLAUDE.md § Testing or a new § Visual Smoke Tests subsection
  • Test runtime overhead < 30s on CI (acceptable for the prevention value)

Why this catches future variants

Unlike the class-existence check (#2503) which only sees "is this class defined?", computed-style assertions see "did the rendered DOM end up with the expected dimensions?". Any structural drift, CSS rename without markup update, missing wrapper, or computed-style regression will surface as a layout assertion failure.

Trade-off: tests need maintenance when intentional layout changes occur (height thresholds need tuning). Acceptable cost for the regression class it prevents.

Estimated effort

2-4 hours: ~30 min set up browser-mode test config, ~1 h write the assertion suite, ~30-60 min verify on local + CI, ~30 min documentation.

References

  • Catches the regression class of: #2493, #2501, #2517
  • Complements: #2495 (class-instance presence smoke test), #2503 (class-name lint)
  • Pattern: remoteclaw/hq#57 (post-mortem) — third confirmed instance via #2517
  • Personal Claude fork-sync skill: § "Definition-site sync without paired call-site update" → Mitigation Implement CLIRuntimeBase abstract class #6 (computed-style smoke tests)
  • Lighter-weight alternative to: P-4 (full visual regression with baselines)

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions