Initial Commit
This commit is contained in:
183
openspec/changes/enterprise-browser-test-vite/proposal.md
Normal file
183
openspec/changes/enterprise-browser-test-vite/proposal.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# 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
|
||||
|
||||
```ts
|
||||
// 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
|
||||
|
||||
```text
|
||||
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`.
|
||||
133
openspec/changes/enterprise-browser-test-vite/tasks.md
Normal file
133
openspec/changes/enterprise-browser-test-vite/tasks.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# Tasks: enterprise-browser-test-vite
|
||||
|
||||
**Change ID**: enterprise-browser-test-vite
|
||||
**Package**: `@suaveplan/test-vite`
|
||||
**Target path**: `browser/dev/test-vite`
|
||||
**Prerequisite packages on Verdaccio**: none (no workspace runtime deps)
|
||||
|
||||
---
|
||||
|
||||
## 0. Pre-flight (non-negotiable)
|
||||
|
||||
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
|
||||
- [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
|
||||
- [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
|
||||
|
||||
## 1. Scaffold
|
||||
|
||||
- [ ] Create directory tree per `proposal.md`.
|
||||
- [ ] `package.json` v0.0.0, `"sideEffects": false`.
|
||||
- `dependencies`: `@suaveplan/logger workspace:^`, `tslib catalog:build`
|
||||
- `peerDependencies`: `vite`, `vitest`
|
||||
- `devDependencies`: `vitest catalog:testing`, `@vitest/coverage-istanbul catalog:testing`, `vite catalog:build`, `@suaveplan/testing workspace:^`
|
||||
- Subpath exports: `"."`, `"./config/vite-library"`, `"./config/vite-app"`, `"./config/vitest"`, `"./config/vitest-browser"`, `"./setup/browser"`, `"./utils/tree-shake"`, `"./utils/bundle-size"`, `"./matchers"`, `"./ports"`
|
||||
- [ ] `tsconfig.json`, `vitest.config.ts` (pool: forks).
|
||||
- [ ] **Delete** `export const test = "hi"` placeholder from `src/index.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Types (`src/types/types.ts`)
|
||||
|
||||
- [ ] `ViteLibraryOptions`, `ViteAppOptions`, `VitestOptions`, `VitestBrowserOptions`, `BrowserEnvOptions`, `SetupResult`, `TreeShakeResult`, `BundleSizeResult`.
|
||||
- [ ] All option types validated by Zod with sensible defaults.
|
||||
- [ ] Write `types.md`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Scope split documentation
|
||||
|
||||
- [ ] Document formal scope split in this change's OpenSpec entry: `test-vite` = Vite/Vitest build config factories; `@suaveplan/testing` = polyfills + test utilities + fixtures. No overlap.
|
||||
|
||||
---
|
||||
|
||||
## 4. `createViteLibraryConfig` (`src/config/vite-library/`)
|
||||
|
||||
- [ ] `vite-library.ts` — `preserveModules: true`, `vite-plugin-dts`, external `@suaveplan/*` peers, ESM-first output, configurable `entry`.
|
||||
- [ ] Options validated by Zod.
|
||||
- [ ] Write `vite-library.test.ts` (options validation, externals list, DTS flag). Write `vite-library.md`.
|
||||
|
||||
---
|
||||
|
||||
## 5. `createViteAppConfig` (`src/config/vite-app/`)
|
||||
|
||||
- [ ] `vite-app.ts` — React/Vue plugin switch, env-var type declarations, hashing strategy.
|
||||
- [ ] Write `vite-app.test.ts`. Write `vite-app.md`.
|
||||
|
||||
---
|
||||
|
||||
## 6. `createVitestConfig` (`src/config/vitest/`)
|
||||
|
||||
- [ ] `vitest.ts` — defaults: `pool: 'forks'`, `setupFiles: ['@suaveplan/testing/polyfills']`, Istanbul provider, reporters tuned for CI.
|
||||
- [ ] Accepts `environment`, `coverage`, `pool` overrides.
|
||||
- [ ] Write `vitest.test.ts` (default pool is forks, Istanbul provider selected, polyfills in setupFiles). Write `vitest.md`.
|
||||
|
||||
---
|
||||
|
||||
## 7. `createVitestBrowserConfig` (`src/config/vitest-browser/`)
|
||||
|
||||
- [ ] `vitest-browser.ts` — Playwright provider; `fileParallelism: false`; configurable `browser` (chromium/firefox/webkit); `headless` toggle.
|
||||
- [ ] Write `vitest-browser.test.ts`. Write `vitest-browser.md`.
|
||||
|
||||
---
|
||||
|
||||
## 8. `setupBrowserEnvironment` (`src/setup/browser/`)
|
||||
|
||||
- [ ] `browser.ts` — happy-dom / jsdom helper with switches: `mockLocalStorage`, `mockServiceWorker`, `mockMatchMedia`, `mockResizeObserver`.
|
||||
- [ ] Implementations use typed adapters — no `vi.stubGlobal`.
|
||||
- [ ] Write `browser.test.ts` (each mock flag). Write `browser.md`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Tree-shake validator (`src/utils/tree-shake/`)
|
||||
|
||||
- [ ] `tree-shake.ts` — `verifyTreeShake(entry, expectedSymbols)` builds with Rollup, asserts only listed symbols in output chunk.
|
||||
- [ ] Write `tree-shake.test.ts` (synthetic package — verify dead code eliminated). Write `tree-shake.md`.
|
||||
|
||||
---
|
||||
|
||||
## 10. Bundle-size snapshot (`src/utils/bundle-size/`)
|
||||
|
||||
- [ ] `bundle-size.ts` — `snapshotBundleSize(entry, budgetBytes)` writes `.size.snap`; fails when budget exceeded.
|
||||
- [ ] Write `bundle-size.test.ts`. Write `bundle-size.md`.
|
||||
|
||||
---
|
||||
|
||||
## 11. Custom matchers (`src/matchers/`)
|
||||
|
||||
- [ ] `matchers.ts` — typed `expect.extend`: `toBeAccessible` (axe-core), `toMatchDataURL`, `toHaveValidCSSVars`.
|
||||
- [ ] Write `matchers.test.ts` (each matcher pass + fail case). Write `matchers.md`.
|
||||
|
||||
---
|
||||
|
||||
## 12. Port helpers (`src/ports/`)
|
||||
|
||||
- [ ] `ports.ts` — `allocatePort(label)` / `releasePort(label)` backed by a file lock; deterministic port assignment; `getPortMap()` for diagnostics.
|
||||
- [ ] Write `ports.test.ts` (allocate, release, conflict avoidance). Write `ports.md`.
|
||||
|
||||
---
|
||||
|
||||
## 13. Proof-of-concept consumer migration
|
||||
|
||||
- [ ] Migrate one browser consumer (e.g., `@suaveplan/react-tour`) off its handwritten `vite.config.ts` + `vitest.config.ts` onto the new factories.
|
||||
- [ ] Document the migration as an example in `README.md`.
|
||||
|
||||
---
|
||||
|
||||
## 14. Integration test (`tests/integration.test.ts`)
|
||||
|
||||
- [ ] Build a synthetic library package end-to-end using `createViteLibraryConfig`; assert ESM output, DTS files, externals.
|
||||
- [ ] Run `createVitestConfig` in a child process; assert `pool: forks` and Istanbul provider.
|
||||
- [ ] `verifyTreeShake` on a test module; assert dead export eliminated.
|
||||
|
||||
---
|
||||
|
||||
## 15. Docs + verify
|
||||
|
||||
- [ ] Write `.md` per module (≥200 words, API reference, options table, example).
|
||||
- [ ] Write `README.md` with scope-split table, subpath table, migration example.
|
||||
- [ ] `bunx turbo lint typecheck test --filter=@suaveplan/test-vite` — green.
|
||||
- [ ] `bunx turbo test:coverage` — 100% (currently near-zero).
|
||||
- [ ] No `@ts-expect-error` in `src/` or tests. No placeholder exports.
|
||||
- [ ] At least one consumer package migrated to new factories.
|
||||
- [ ] `bun scripts/publish.ts --package @suaveplan/test-vite`.
|
||||
- [ ] Archive to `openspec/archive/2026-04-24-enterprise-browser-test-vite/`.
|
||||
Reference in New Issue
Block a user