Files
suaveplan/openspec/changes/enterprise-browser-test-vite/proposal.md
2026-05-27 23:29:53 +01:00

6.0 KiB

OpenSpec Change: enterprise-browser-test-vite

  • Change ID: enterprise-browser-test-vite
  • Package: @suaveplan/test-vite
  • Category: browser
  • Target path: browser/dev/test-vite
  • Status: IN PROGRESS
  • Date: 2026-04-24
  • Reference spec: /mnt/s/web/suaveplan/enterprise/test-vite.md

Summary

Deliver @suaveplan/test-vite on the genesis branch at enterprise quality. The package is currently a stub (exports const test = "hi"). Genesis replaces the stub with real Vite + Vitest configuration factory functions: createViteLibraryConfig, createViteAppConfig, createVitestConfig, createVitestBrowserConfig, setupBrowserEnvironment, a tree-shake validator, a bundle-size snapshot helper, and typed custom browser matchers. All WSL2-safe defaults pre-applied. Scope is split from @suaveplan/testing: test-vite owns Vite/Vitest build config; @suaveplan/testing owns polyfills, test utilities, and fixtures.


Current-state audit (genesis)

Package exists as a stub only — exports export const test = "hi". No real functionality.


Available workspace deps on genesis

Package Role
@suaveplan/logger (workspace:^) Diagnostics logging inside factory helpers
tslib (catalog:build) Runtime helpers

peerDependencies: vite, vitest — consumers must have their own versions. The package exports factories that accept user config — it does not re-export vite/vitest APIs.

Dev: vitest, @vitest/coverage-istanbul, vite, @suaveplan/testing.


Preserved public API surface

// Vite config factories
function createViteLibraryConfig(options: ViteLibraryOptions): UserConfig
function createViteAppConfig(options: ViteAppOptions): UserConfig

// Vitest config factories
function createVitestConfig(options?: VitestOptions): UserConfigExport
function createVitestBrowserConfig(options?: VitestBrowserOptions): UserConfigExport

// Environment setup
function setupBrowserEnvironment(options?: BrowserEnvOptions): SetupResult

// Validation utilities
function verifyTreeShake(entry: string, expectedSymbols: string[]): Promise<void>
function snapshotBundleSize(entry: string, budgetBytes: number): Promise<void>

// Custom matchers (typed expect.extend)
// expect(element).toBeAccessible()
// expect(dataUrl).toMatchDataURL(expected)
// expect(element).toHaveValidCSSVars(vars)

// Port utilities
function allocatePort(label: string): Promise<number>
function releasePort(label: string): Promise<void>

// Types
interface ViteLibraryOptions { entry: string | Record<string, string>; external?: string[]; dts?: boolean; outDir?: string }
interface ViteAppOptions { root?: string; envDir?: string; plugins?: Plugin[] }
interface VitestOptions { environment?: 'happy-dom' | 'jsdom' | 'node'; coverage?: boolean; pool?: 'forks' | 'vmForks'; setupFiles?: string[] }
interface VitestBrowserOptions { browser?: 'chromium' | 'firefox' | 'webkit'; headless?: boolean }
interface BrowserEnvOptions { mockLocalStorage?: boolean; mockServiceWorker?: boolean; mockMatchMedia?: boolean; mockResizeObserver?: boolean }

Subpath exports: ".", "./config/vite-library", "./config/vite-app", "./config/vitest", "./config/vitest-browser", "./setup/browser", "./utils/tree-shake", "./utils/bundle-size", "./matchers", "./ports".


New additions for genesis

1. Replace stub

Delete export const test = "hi". Implement all documented factories.

2. WSL2-safe Vitest defaults

createVitestConfig defaults: pool: 'forks', setupFiles: ['@suaveplan/testing/polyfills'], Istanbul coverage provider. createVitestBrowserConfig adds fileParallelism: false.

3. Scope split vs @suaveplan/testing

Formal decision documented in this OpenSpec change: test-vite owns Vite build config + Vitest config factories; @suaveplan/testing retains polyfills, FakeClock, Factory, MemoryStore, etc. No overlap.

4. Tree-shake validator

verifyTreeShake(entry, expectedSymbols) builds with Rollup, asserts only listed symbols in output. Catches accidental barrel-import pollution.

5. Bundle-size snapshot

snapshotBundleSize(entry, budgetBytes) writes <entry>.size.snap; fails CI when budget exceeded.

6. Custom matchers

toBeAccessible (axe-core), toMatchDataURL, toHaveValidCSSVars — typed expect.extend for browser API assertions.

7. Port helpers

allocatePort(label) / releasePort(label) backed by a file lock (/tmp/vite-ports.lock) so parallel Storybook/Playwright runs don't collide on WSL2.


File layout

browser/dev/test-vite/
  package.json
  tsconfig.json
  vitest.config.ts
  src/
    index.ts
    config/
      vite-library/
        vite-library.ts
        vite-library.test.ts
        vite-library.md
        index.ts
      vite-app/
        vite-app.ts
        vite-app.test.ts
        vite-app.md
        index.ts
      vitest/
        vitest.ts
        vitest.test.ts
        vitest.md
        index.ts
      vitest-browser/
        vitest-browser.ts
        vitest-browser.test.ts
        vitest-browser.md
        index.ts
    setup/
      browser/
        browser.ts
        browser.test.ts
        browser.md
        index.ts
    utils/
      tree-shake/
        tree-shake.ts
        tree-shake.test.ts
        tree-shake.md
        index.ts
      bundle-size/
        bundle-size.ts
        bundle-size.test.ts
        bundle-size.md
        index.ts
    matchers/
      matchers.ts
      matchers.test.ts
      matchers.md
      index.ts
    ports/
      ports.ts
      ports.test.ts
      ports.md
      index.ts
    types/
      types.ts
      types.md
    constants/
      index.ts
  tests/
    integration.test.ts

Impact

Downstream: every @suaveplan/* browser package that ships its own vite.config.ts / vitest.config.ts. Migration is opt-in; packages can adopt on their own schedule.

Breaking changes: removes the test = "hi" placeholder; any consumer importing it (unlikely) must remove the import.

Prerequisites: None at runtime. vite and vitest are peerDependencies.