From c8de59d81e4028588a6fd8ffe76035c53c2cbf00 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Fri, 24 Apr 2026 23:37:04 -0700 Subject: [PATCH] test(live): add full-cycle E2E framework-fixture suite with pluggable agent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 19 fixtures (11 styling/build variants + 4 conditional-render scenarios + 4 meta-frameworks) drive the entire user flow end-to-end: handshake, pick, configure, Go, cycle, accept, carbonize cleanup. Each fixture installs real deps, boots the framework dev server, and runs Playwright Chromium against a deterministic fake agent that produces realistic variants (colocated style with @scope rules, full data-impeccable-params manifests covering range + steps + toggle, JSX/HTML/Svelte syntax-aware rendering). The agent is pluggable via a one-method interface — generateVariants(event) — so a future LLM-backed agent slots in by implementing the same shape. The orchestrator handles wrap, file write, accept, and carbonize cleanup deterministically regardless of which agent is plugged in. Schema extensions (tests/framework-fixtures/README.md): runtime block adds preActions / reloadProbe / pickSelector / scheme / ignoreHTTPSErrors so fixtures can drive conditional UI (modal, tab, route) before pick and verify the carbonized variant survives a reload. Static fixture suite filtered to skip dirs without fixture.json so empty scaffold dirs no longer break discovery. Total: 178 static checks, 19 E2E full cycles, ~107s wall clock for the E2E suite. Co-Authored-By: Claude Opus 4.7 (1M context) --- package.json | 1 + tests/framework-fixtures.test.mjs | 3 +- tests/framework-fixtures/README.md | 33 ++ .../astro-vite7/files/astro.config.mjs | 5 + .../astro-vite7/files/package.json | 14 + .../files/src/layouts/Layout.astro | 13 + .../astro-vite7/files/src/pages/index.astro | 16 + .../astro-vite7/fixture.json | 28 + .../astro-vite7/gitignore.txt | 4 + .../nextjs-app-router/files/app/layout.jsx | 11 + .../nextjs-app-router/files/app/page.jsx | 10 + .../nextjs-app-router/files/next.config.js | 3 + .../nextjs-app-router/files/package.json | 15 + .../nextjs-app-router/fixture.json | 28 + .../nextjs-app-router/gitignore.txt | 5 + .../nuxt-vite7/files/app.vue | 10 + .../nuxt-vite7/files/nuxt.config.ts | 4 + .../nuxt-vite7/files/package.json | 15 + .../nuxt-vite7/files/pages/index.vue | 12 + .../nuxt-vite7/fixture.json | 18 + .../nuxt-vite7/gitignore.txt | 5 + .../vite8-https/files/index.html | 11 + .../vite8-https/files/package.json | 20 + .../vite8-https/files/src/App.jsx | 12 + .../vite8-https/files/src/main.jsx | 10 + .../vite8-https/files/src/styles.css | 6 + .../vite8-https/files/vite.config.js | 8 + .../vite8-https/fixture.json | 30 ++ .../vite8-https/gitignore.txt | 4 + .../vite8-react-base-path/files/index.html | 11 + .../vite8-react-base-path/files/package.json | 19 + .../vite8-react-base-path/files/src/App.jsx | 8 + .../vite8-react-base-path/files/src/main.jsx | 10 + .../files/src/styles.css | 4 + .../files/vite.config.js | 8 + .../vite8-react-base-path/fixture.json | 29 + .../vite8-react-base-path/gitignore.txt | 4 + .../vite8-react-csp-meta/files/index.html | 15 + .../vite8-react-csp-meta/files/package.json | 19 + .../vite8-react-csp-meta/files/src/App.jsx | 12 + .../vite8-react-csp-meta/files/src/main.jsx | 10 + .../vite8-react-csp-meta/files/src/styles.css | 6 + .../vite8-react-csp-meta/files/vite.config.js | 7 + .../vite8-react-csp-meta/fixture.json | 33 ++ .../vite8-react-csp-meta/gitignore.txt | 4 + .../vite8-react-css-modules/files/index.html | 11 + .../files/package.json | 19 + .../vite8-react-css-modules/files/src/App.jsx | 14 + .../files/src/App.module.css | 5 + .../files/src/main.jsx | 9 + .../files/vite.config.js | 7 + .../vite8-react-css-modules/fixture.json | 28 + .../vite8-react-css-modules/gitignore.txt | 4 + .../vite8-react-emotion/files/index.html | 11 + .../vite8-react-emotion/files/package.json | 21 + .../vite8-react-emotion/files/src/App.jsx | 21 + .../vite8-react-emotion/files/src/main.jsx | 9 + .../vite8-react-emotion/files/vite.config.js | 7 + .../vite8-react-emotion/fixture.json | 28 + .../vite8-react-emotion/gitignore.txt | 4 + .../vite8-react-mapped-list/files/index.html | 11 + .../files/package.json | 19 + .../vite8-react-mapped-list/files/src/App.jsx | 21 + .../files/src/main.jsx | 10 + .../files/src/styles.css | 6 + .../files/vite.config.js | 7 + .../vite8-react-mapped-list/fixture.json | 28 + .../vite8-react-mapped-list/gitignore.txt | 4 + .../vite8-react-modal/files/index.html | 11 + .../vite8-react-modal/files/package.json | 19 + .../vite8-react-modal/files/src/App.jsx | 24 + .../vite8-react-modal/files/src/main.jsx | 10 + .../vite8-react-modal/files/src/styles.css | 7 + .../vite8-react-modal/files/vite.config.js | 7 + .../vite8-react-modal/fixture.json | 39 ++ .../vite8-react-modal/gitignore.txt | 4 + .../vite8-react-plain/files/index.html | 11 + .../vite8-react-plain/files/package.json | 19 + .../vite8-react-plain/files/src/App.jsx | 12 + .../vite8-react-plain/files/src/main.jsx | 10 + .../vite8-react-plain/files/src/styles.css | 6 + .../vite8-react-plain/files/vite.config.js | 10 + .../vite8-react-plain/fixture.json | 28 + .../vite8-react-plain/gitignore.txt | 4 + .../vite8-react-router-spa/files/index.html | 11 + .../vite8-react-router-spa/files/package.json | 20 + .../vite8-react-router-spa/files/src/main.jsx | 21 + .../files/src/routes/About.jsx | 8 + .../files/src/routes/Home.jsx | 8 + .../files/src/styles.css | 6 + .../files/vite.config.js | 7 + .../vite8-react-router-spa/fixture.json | 46 ++ .../vite8-react-router-spa/gitignore.txt | 4 + .../files/index.html | 11 + .../files/package.json | 20 + .../files/src/App.jsx | 19 + .../files/src/main.jsx | 9 + .../files/vite.config.js | 7 + .../fixture.json | 28 + .../gitignore.txt | 4 + .../vite8-react-tabs/files/index.html | 11 + .../vite8-react-tabs/files/package.json | 19 + .../vite8-react-tabs/files/src/App.jsx | 41 ++ .../vite8-react-tabs/files/src/main.jsx | 10 + .../vite8-react-tabs/files/src/styles.css | 8 + .../vite8-react-tabs/files/vite.config.js | 7 + .../vite8-react-tabs/fixture.json | 39 ++ .../vite8-react-tabs/gitignore.txt | 4 + .../vite8-react-tailwindv3/files/index.html | 11 + .../vite8-react-tailwindv3/files/package.json | 22 + .../files/postcss.config.js | 6 + .../vite8-react-tailwindv3/files/src/App.jsx | 12 + .../vite8-react-tailwindv3/files/src/main.jsx | 10 + .../files/src/styles.css | 3 + .../files/tailwind.config.js | 5 + .../files/vite.config.js | 7 + .../vite8-react-tailwindv3/fixture.json | 28 + .../vite8-react-tailwindv3/gitignore.txt | 4 + .../vite8-react-tailwindv4/files/index.html | 11 + .../vite8-react-tailwindv4/files/package.json | 21 + .../vite8-react-tailwindv4/files/src/App.jsx | 12 + .../vite8-react-tailwindv4/files/src/main.jsx | 10 + .../files/src/styles.css | 1 + .../files/vite.config.js | 8 + .../vite8-react-tailwindv4/fixture.json | 28 + .../vite8-react-tailwindv4/gitignore.txt | 4 + .../vite8-react-ts/files/index.html | 11 + .../vite8-react-ts/files/package.json | 22 + .../vite8-react-ts/files/src/App.tsx | 12 + .../vite8-react-ts/files/src/main.tsx | 10 + .../vite8-react-ts/files/src/styles.css | 6 + .../vite8-react-ts/files/tsconfig.json | 15 + .../vite8-react-ts/files/vite.config.ts | 7 + .../vite8-react-ts/fixture.json | 28 + .../vite8-react-ts/gitignore.txt | 4 + .../vite8-react-unocss/files/index.html | 11 + .../vite8-react-unocss/files/package.json | 21 + .../vite8-react-unocss/files/src/App.jsx | 12 + .../vite8-react-unocss/files/src/main.jsx | 10 + .../vite8-react-unocss/files/uno.config.js | 5 + .../vite8-react-unocss/files/vite.config.js | 8 + .../vite8-react-unocss/fixture.json | 28 + .../vite8-react-unocss/gitignore.txt | 4 + .../files/index.html | 11 + .../files/package.json | 21 + .../files/src/App.jsx | 14 + .../files/src/main.jsx | 9 + .../files/src/styles.css.js | 20 + .../files/vite.config.js | 8 + .../vite8-react-vanilla-extract/fixture.json | 28 + .../vite8-react-vanilla-extract/gitignore.txt | 4 + .../vite8-sveltekit/files/package.json | 18 + .../vite8-sveltekit/files/src/app.html | 11 + .../files/src/routes/+layout.svelte | 5 + .../files/src/routes/+page.svelte | 8 + .../vite8-sveltekit/files/svelte.config.js | 7 + .../vite8-sveltekit/files/vite.config.js | 7 + .../vite8-sveltekit/fixture.json | 28 + .../vite8-sveltekit/gitignore.txt | 5 + tests/live-e2e.test.mjs | 405 ++++++++++++++ tests/live-e2e/agent.mjs | 496 ++++++++++++++++++ tests/live-e2e/session.mjs | 250 +++++++++ tests/live-e2e/ui.mjs | 175 ++++++ 163 files changed, 3392 insertions(+), 1 deletion(-) create mode 100644 tests/framework-fixtures/astro-vite7/files/astro.config.mjs create mode 100644 tests/framework-fixtures/astro-vite7/files/package.json create mode 100644 tests/framework-fixtures/astro-vite7/files/src/layouts/Layout.astro create mode 100644 tests/framework-fixtures/astro-vite7/files/src/pages/index.astro create mode 100644 tests/framework-fixtures/astro-vite7/fixture.json create mode 100644 tests/framework-fixtures/astro-vite7/gitignore.txt create mode 100644 tests/framework-fixtures/nextjs-app-router/files/app/layout.jsx create mode 100644 tests/framework-fixtures/nextjs-app-router/files/app/page.jsx create mode 100644 tests/framework-fixtures/nextjs-app-router/files/next.config.js create mode 100644 tests/framework-fixtures/nextjs-app-router/files/package.json create mode 100644 tests/framework-fixtures/nextjs-app-router/fixture.json create mode 100644 tests/framework-fixtures/nextjs-app-router/gitignore.txt create mode 100644 tests/framework-fixtures/nuxt-vite7/files/app.vue create mode 100644 tests/framework-fixtures/nuxt-vite7/files/nuxt.config.ts create mode 100644 tests/framework-fixtures/nuxt-vite7/files/package.json create mode 100644 tests/framework-fixtures/nuxt-vite7/files/pages/index.vue create mode 100644 tests/framework-fixtures/nuxt-vite7/fixture.json create mode 100644 tests/framework-fixtures/nuxt-vite7/gitignore.txt create mode 100644 tests/framework-fixtures/vite8-https/files/index.html create mode 100644 tests/framework-fixtures/vite8-https/files/package.json create mode 100644 tests/framework-fixtures/vite8-https/files/src/App.jsx create mode 100644 tests/framework-fixtures/vite8-https/files/src/main.jsx create mode 100644 tests/framework-fixtures/vite8-https/files/src/styles.css create mode 100644 tests/framework-fixtures/vite8-https/files/vite.config.js create mode 100644 tests/framework-fixtures/vite8-https/fixture.json create mode 100644 tests/framework-fixtures/vite8-https/gitignore.txt create mode 100644 tests/framework-fixtures/vite8-react-base-path/files/index.html create mode 100644 tests/framework-fixtures/vite8-react-base-path/files/package.json create mode 100644 tests/framework-fixtures/vite8-react-base-path/files/src/App.jsx create mode 100644 tests/framework-fixtures/vite8-react-base-path/files/src/main.jsx create mode 100644 tests/framework-fixtures/vite8-react-base-path/files/src/styles.css create mode 100644 tests/framework-fixtures/vite8-react-base-path/files/vite.config.js create mode 100644 tests/framework-fixtures/vite8-react-base-path/fixture.json create mode 100644 tests/framework-fixtures/vite8-react-base-path/gitignore.txt create mode 100644 tests/framework-fixtures/vite8-react-csp-meta/files/index.html create mode 100644 tests/framework-fixtures/vite8-react-csp-meta/files/package.json create mode 100644 tests/framework-fixtures/vite8-react-csp-meta/files/src/App.jsx create mode 100644 tests/framework-fixtures/vite8-react-csp-meta/files/src/main.jsx create mode 100644 tests/framework-fixtures/vite8-react-csp-meta/files/src/styles.css create mode 100644 tests/framework-fixtures/vite8-react-csp-meta/files/vite.config.js create mode 100644 tests/framework-fixtures/vite8-react-csp-meta/fixture.json create mode 100644 tests/framework-fixtures/vite8-react-csp-meta/gitignore.txt create mode 100644 tests/framework-fixtures/vite8-react-css-modules/files/index.html create mode 100644 tests/framework-fixtures/vite8-react-css-modules/files/package.json create mode 100644 tests/framework-fixtures/vite8-react-css-modules/files/src/App.jsx create mode 100644 tests/framework-fixtures/vite8-react-css-modules/files/src/App.module.css create mode 100644 tests/framework-fixtures/vite8-react-css-modules/files/src/main.jsx create mode 100644 tests/framework-fixtures/vite8-react-css-modules/files/vite.config.js create mode 100644 tests/framework-fixtures/vite8-react-css-modules/fixture.json create mode 100644 tests/framework-fixtures/vite8-react-css-modules/gitignore.txt create mode 100644 tests/framework-fixtures/vite8-react-emotion/files/index.html create mode 100644 tests/framework-fixtures/vite8-react-emotion/files/package.json create mode 100644 tests/framework-fixtures/vite8-react-emotion/files/src/App.jsx create mode 100644 tests/framework-fixtures/vite8-react-emotion/files/src/main.jsx create mode 100644 tests/framework-fixtures/vite8-react-emotion/files/vite.config.js create mode 100644 tests/framework-fixtures/vite8-react-emotion/fixture.json create mode 100644 tests/framework-fixtures/vite8-react-emotion/gitignore.txt create mode 100644 tests/framework-fixtures/vite8-react-mapped-list/files/index.html create mode 100644 tests/framework-fixtures/vite8-react-mapped-list/files/package.json create mode 100644 tests/framework-fixtures/vite8-react-mapped-list/files/src/App.jsx create mode 100644 tests/framework-fixtures/vite8-react-mapped-list/files/src/main.jsx create mode 100644 tests/framework-fixtures/vite8-react-mapped-list/files/src/styles.css create mode 100644 tests/framework-fixtures/vite8-react-mapped-list/files/vite.config.js create mode 100644 tests/framework-fixtures/vite8-react-mapped-list/fixture.json create mode 100644 tests/framework-fixtures/vite8-react-mapped-list/gitignore.txt create mode 100644 tests/framework-fixtures/vite8-react-modal/files/index.html create mode 100644 tests/framework-fixtures/vite8-react-modal/files/package.json create mode 100644 tests/framework-fixtures/vite8-react-modal/files/src/App.jsx create mode 100644 tests/framework-fixtures/vite8-react-modal/files/src/main.jsx create mode 100644 tests/framework-fixtures/vite8-react-modal/files/src/styles.css create mode 100644 tests/framework-fixtures/vite8-react-modal/files/vite.config.js create mode 100644 tests/framework-fixtures/vite8-react-modal/fixture.json create mode 100644 tests/framework-fixtures/vite8-react-modal/gitignore.txt create mode 100644 tests/framework-fixtures/vite8-react-plain/files/index.html create mode 100644 tests/framework-fixtures/vite8-react-plain/files/package.json create mode 100644 tests/framework-fixtures/vite8-react-plain/files/src/App.jsx create mode 100644 tests/framework-fixtures/vite8-react-plain/files/src/main.jsx create mode 100644 tests/framework-fixtures/vite8-react-plain/files/src/styles.css create mode 100644 tests/framework-fixtures/vite8-react-plain/files/vite.config.js create mode 100644 tests/framework-fixtures/vite8-react-plain/fixture.json create mode 100644 tests/framework-fixtures/vite8-react-plain/gitignore.txt create mode 100644 tests/framework-fixtures/vite8-react-router-spa/files/index.html create mode 100644 tests/framework-fixtures/vite8-react-router-spa/files/package.json create mode 100644 tests/framework-fixtures/vite8-react-router-spa/files/src/main.jsx create mode 100644 tests/framework-fixtures/vite8-react-router-spa/files/src/routes/About.jsx create mode 100644 tests/framework-fixtures/vite8-react-router-spa/files/src/routes/Home.jsx create mode 100644 tests/framework-fixtures/vite8-react-router-spa/files/src/styles.css create mode 100644 tests/framework-fixtures/vite8-react-router-spa/files/vite.config.js create mode 100644 tests/framework-fixtures/vite8-react-router-spa/fixture.json create mode 100644 tests/framework-fixtures/vite8-react-router-spa/gitignore.txt create mode 100644 tests/framework-fixtures/vite8-react-styled-components/files/index.html create mode 100644 tests/framework-fixtures/vite8-react-styled-components/files/package.json create mode 100644 tests/framework-fixtures/vite8-react-styled-components/files/src/App.jsx create mode 100644 tests/framework-fixtures/vite8-react-styled-components/files/src/main.jsx create mode 100644 tests/framework-fixtures/vite8-react-styled-components/files/vite.config.js create mode 100644 tests/framework-fixtures/vite8-react-styled-components/fixture.json create mode 100644 tests/framework-fixtures/vite8-react-styled-components/gitignore.txt create mode 100644 tests/framework-fixtures/vite8-react-tabs/files/index.html create mode 100644 tests/framework-fixtures/vite8-react-tabs/files/package.json create mode 100644 tests/framework-fixtures/vite8-react-tabs/files/src/App.jsx create mode 100644 tests/framework-fixtures/vite8-react-tabs/files/src/main.jsx create mode 100644 tests/framework-fixtures/vite8-react-tabs/files/src/styles.css create mode 100644 tests/framework-fixtures/vite8-react-tabs/files/vite.config.js create mode 100644 tests/framework-fixtures/vite8-react-tabs/fixture.json create mode 100644 tests/framework-fixtures/vite8-react-tabs/gitignore.txt create mode 100644 tests/framework-fixtures/vite8-react-tailwindv3/files/index.html create mode 100644 tests/framework-fixtures/vite8-react-tailwindv3/files/package.json create mode 100644 tests/framework-fixtures/vite8-react-tailwindv3/files/postcss.config.js create mode 100644 tests/framework-fixtures/vite8-react-tailwindv3/files/src/App.jsx create mode 100644 tests/framework-fixtures/vite8-react-tailwindv3/files/src/main.jsx create mode 100644 tests/framework-fixtures/vite8-react-tailwindv3/files/src/styles.css create mode 100644 tests/framework-fixtures/vite8-react-tailwindv3/files/tailwind.config.js create mode 100644 tests/framework-fixtures/vite8-react-tailwindv3/files/vite.config.js create mode 100644 tests/framework-fixtures/vite8-react-tailwindv3/fixture.json create mode 100644 tests/framework-fixtures/vite8-react-tailwindv3/gitignore.txt create mode 100644 tests/framework-fixtures/vite8-react-tailwindv4/files/index.html create mode 100644 tests/framework-fixtures/vite8-react-tailwindv4/files/package.json create mode 100644 tests/framework-fixtures/vite8-react-tailwindv4/files/src/App.jsx create mode 100644 tests/framework-fixtures/vite8-react-tailwindv4/files/src/main.jsx create mode 100644 tests/framework-fixtures/vite8-react-tailwindv4/files/src/styles.css create mode 100644 tests/framework-fixtures/vite8-react-tailwindv4/files/vite.config.js create mode 100644 tests/framework-fixtures/vite8-react-tailwindv4/fixture.json create mode 100644 tests/framework-fixtures/vite8-react-tailwindv4/gitignore.txt create mode 100644 tests/framework-fixtures/vite8-react-ts/files/index.html create mode 100644 tests/framework-fixtures/vite8-react-ts/files/package.json create mode 100644 tests/framework-fixtures/vite8-react-ts/files/src/App.tsx create mode 100644 tests/framework-fixtures/vite8-react-ts/files/src/main.tsx create mode 100644 tests/framework-fixtures/vite8-react-ts/files/src/styles.css create mode 100644 tests/framework-fixtures/vite8-react-ts/files/tsconfig.json create mode 100644 tests/framework-fixtures/vite8-react-ts/files/vite.config.ts create mode 100644 tests/framework-fixtures/vite8-react-ts/fixture.json create mode 100644 tests/framework-fixtures/vite8-react-ts/gitignore.txt create mode 100644 tests/framework-fixtures/vite8-react-unocss/files/index.html create mode 100644 tests/framework-fixtures/vite8-react-unocss/files/package.json create mode 100644 tests/framework-fixtures/vite8-react-unocss/files/src/App.jsx create mode 100644 tests/framework-fixtures/vite8-react-unocss/files/src/main.jsx create mode 100644 tests/framework-fixtures/vite8-react-unocss/files/uno.config.js create mode 100644 tests/framework-fixtures/vite8-react-unocss/files/vite.config.js create mode 100644 tests/framework-fixtures/vite8-react-unocss/fixture.json create mode 100644 tests/framework-fixtures/vite8-react-unocss/gitignore.txt create mode 100644 tests/framework-fixtures/vite8-react-vanilla-extract/files/index.html create mode 100644 tests/framework-fixtures/vite8-react-vanilla-extract/files/package.json create mode 100644 tests/framework-fixtures/vite8-react-vanilla-extract/files/src/App.jsx create mode 100644 tests/framework-fixtures/vite8-react-vanilla-extract/files/src/main.jsx create mode 100644 tests/framework-fixtures/vite8-react-vanilla-extract/files/src/styles.css.js create mode 100644 tests/framework-fixtures/vite8-react-vanilla-extract/files/vite.config.js create mode 100644 tests/framework-fixtures/vite8-react-vanilla-extract/fixture.json create mode 100644 tests/framework-fixtures/vite8-react-vanilla-extract/gitignore.txt create mode 100644 tests/framework-fixtures/vite8-sveltekit/files/package.json create mode 100644 tests/framework-fixtures/vite8-sveltekit/files/src/app.html create mode 100644 tests/framework-fixtures/vite8-sveltekit/files/src/routes/+layout.svelte create mode 100644 tests/framework-fixtures/vite8-sveltekit/files/src/routes/+page.svelte create mode 100644 tests/framework-fixtures/vite8-sveltekit/files/svelte.config.js create mode 100644 tests/framework-fixtures/vite8-sveltekit/files/vite.config.js create mode 100644 tests/framework-fixtures/vite8-sveltekit/fixture.json create mode 100644 tests/framework-fixtures/vite8-sveltekit/gitignore.txt create mode 100644 tests/live-e2e.test.mjs create mode 100644 tests/live-e2e/agent.mjs create mode 100644 tests/live-e2e/session.mjs create mode 100644 tests/live-e2e/ui.mjs diff --git a/package.json b/package.json index ee253fc6e..44805b2ed 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "preview": "bun run build && wrangler pages dev", "deploy": "bun run build && wrangler pages deploy build/", "test": "bun test tests/build.test.js tests/detect-antipatterns.test.js && node --test tests/detect-antipatterns-fixtures.test.mjs && node --test tests/detect-antipatterns-browser.test.mjs && node --test tests/cleanup-deprecated.test.mjs && node --test tests/live-wrap.test.mjs && node --test tests/live-accept.test.mjs && node --test tests/live-inject.test.mjs && node --test tests/live-server.test.mjs && node --test tests/framework-fixtures.test.mjs", + "test:live-e2e": "node --test --test-timeout=600000 tests/live-e2e.test.mjs", "prepack": "cp README.md README.repo.md && cp README.npm.md README.md", "postpack": "cp README.repo.md README.md && rm README.repo.md", "screenshot": "bun run scripts/screenshot-antipatterns.js", diff --git a/tests/framework-fixtures.test.mjs b/tests/framework-fixtures.test.mjs index d447be059..84bb699ff 100644 --- a/tests/framework-fixtures.test.mjs +++ b/tests/framework-fixtures.test.mjs @@ -12,7 +12,7 @@ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { execFileSync } from 'node:child_process'; -import { cpSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { cpSync, existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -27,6 +27,7 @@ const FIXTURES_DIR = join(__dirname, 'framework-fixtures'); function listFixtures() { return readdirSync(FIXTURES_DIR, { withFileTypes: true }) .filter((e) => e.isDirectory()) + .filter((e) => existsSync(join(FIXTURES_DIR, e.name, 'fixture.json'))) .map((e) => e.name); } diff --git a/tests/framework-fixtures/README.md b/tests/framework-fixtures/README.md index ad953f3d8..7f7865fcf 100644 --- a/tests/framework-fixtures/README.md +++ b/tests/framework-fixtures/README.md @@ -2,6 +2,8 @@ Representative project shapes for exercising live mode against different framework conventions. Each fixture is a small directory tree that the test harness copies into a temp git repo, then drives `live-inject.mjs`, `live-wrap.mjs`, `live-accept.mjs`, and `is-generated.mjs` against. +Fixtures can also opt into a **runtime E2E** pass that actually installs dependencies, boots the framework dev server, and drives a Playwright browser to verify the live handshake. See the `runtime` block below. + ## Layout ``` @@ -32,12 +34,43 @@ Representative project shapes for exercising live mode against different framewo "signals": ["diagnostic hints — paths where CSP was detected"], "patchTarget": "which file the agent should modify", "expectedAfter": "filename of the reference post-patch output inside this fixture" + }, + "runtime": { + "styling": "plain-css | tailwind-v4 | styled-components | ...", + "install": ["npm", "install"], + "devCommand": ["npm", "run", "dev"], + "scheme": "http", + "ignoreHTTPSErrors": false, + "readyPattern": "Local:\\s+https?://[^:]+:(\\d+)", + "readyTimeoutMs": 120000, + "pickSelector": "h1.hero-title", + "preActions": [ + { "type": "click", "selector": "[data-testid='open-modal']" }, + { "type": "goto", "path": "/about" } + ], + "reloadProbe": { + "preActions": [{ "type": "click", "selector": "[data-testid='open-modal']" }], + "expectSelector": "h1.hero-title" + }, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } } } ``` The `expectedAfter` file lives alongside `fixture.json` (not inside `files/`) and is a human/agent-review reference — tests don't auto-apply the patch. +The `runtime` block is optional. Fixtures without it only run the static unit checks (is-generated, inject, wrap, csp-detect). Fixtures *with* it additionally run the E2E suite in `tests/live-e2e.test.mjs` (`bun run test:live-e2e`), which: + +1. Stages the fixture into a tmp repo. +2. Runs `runtime.install` to install real deps. +3. Starts `live-server.mjs --background` and runs `live-inject.mjs --port` against it. +4. Spawns `runtime.devCommand` and scrapes the port from stdout using `runtime.readyPattern` (the first capture group must be the port). +5. Opens Playwright Chromium at the dev URL and asserts `window.__IMPECCABLE_LIVE_INIT__ === true` (the browser-side handshake oracle) within `runtime.readyTimeoutMs`. +6. Tears everything down (Playwright close, dev server SIGTERM, live-server stop, tmp rm). + ## Current fixtures | Fixture | Shape | diff --git a/tests/framework-fixtures/astro-vite7/files/astro.config.mjs b/tests/framework-fixtures/astro-vite7/files/astro.config.mjs new file mode 100644 index 000000000..fa5bcb799 --- /dev/null +++ b/tests/framework-fixtures/astro-vite7/files/astro.config.mjs @@ -0,0 +1,5 @@ +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + server: { host: '127.0.0.1' }, +}); diff --git a/tests/framework-fixtures/astro-vite7/files/package.json b/tests/framework-fixtures/astro-vite7/files/package.json new file mode 100644 index 000000000..33ba1a69d --- /dev/null +++ b/tests/framework-fixtures/astro-vite7/files/package.json @@ -0,0 +1,14 @@ +{ + "name": "astro-vite7-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "astro dev --host 127.0.0.1", + "build": "astro build", + "preview": "astro preview" + }, + "devDependencies": { + "astro": "^6.0.0" + } +} diff --git a/tests/framework-fixtures/astro-vite7/files/src/layouts/Layout.astro b/tests/framework-fixtures/astro-vite7/files/src/layouts/Layout.astro new file mode 100644 index 000000000..1c5b88e85 --- /dev/null +++ b/tests/framework-fixtures/astro-vite7/files/src/layouts/Layout.astro @@ -0,0 +1,13 @@ +--- +const { title } = Astro.props; +--- + + + + + {title} + + + + + diff --git a/tests/framework-fixtures/astro-vite7/files/src/pages/index.astro b/tests/framework-fixtures/astro-vite7/files/src/pages/index.astro new file mode 100644 index 000000000..f77a49d83 --- /dev/null +++ b/tests/framework-fixtures/astro-vite7/files/src/pages/index.astro @@ -0,0 +1,16 @@ +--- +import Layout from '../layouts/Layout.astro'; +--- + +
+

Astro + Vite 7 Fixture

+

Static Astro page rendered server-side, hydrated nothing.

+
+
+ + diff --git a/tests/framework-fixtures/astro-vite7/fixture.json b/tests/framework-fixtures/astro-vite7/fixture.json new file mode 100644 index 000000000..977d0b8cd --- /dev/null +++ b/tests/framework-fixtures/astro-vite7/fixture.json @@ -0,0 +1,28 @@ +{ + "name": "Astro 6 + Vite 7", + "config": { + "files": ["src/layouts/Layout.astro"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": ["src/layouts/Layout.astro", "src/pages/index.astro", "astro.config.mjs"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps hero in pages/index.astro", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "src/pages/index.astro" + } + ], + "runtime": { + "styling": "plain-css", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "astro", "dev", "--host", "127.0.0.1"], + "readyPattern": "Local\\s+https?://[^:\\s]+:(\\d+)", + "readyTimeoutMs": 180000, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/astro-vite7/gitignore.txt b/tests/framework-fixtures/astro-vite7/gitignore.txt new file mode 100644 index 000000000..84856e988 --- /dev/null +++ b/tests/framework-fixtures/astro-vite7/gitignore.txt @@ -0,0 +1,4 @@ +node_modules/ +.astro/ +dist/ +package-lock.json diff --git a/tests/framework-fixtures/nextjs-app-router/files/app/layout.jsx b/tests/framework-fixtures/nextjs-app-router/files/app/layout.jsx new file mode 100644 index 000000000..8be733679 --- /dev/null +++ b/tests/framework-fixtures/nextjs-app-router/files/app/layout.jsx @@ -0,0 +1,11 @@ +export const metadata = { title: 'Next.js App Router Fixture' }; + +export default function RootLayout({ children }) { + return ( + + + {children} + + + ); +} diff --git a/tests/framework-fixtures/nextjs-app-router/files/app/page.jsx b/tests/framework-fixtures/nextjs-app-router/files/app/page.jsx new file mode 100644 index 000000000..648e5c2ae --- /dev/null +++ b/tests/framework-fixtures/nextjs-app-router/files/app/page.jsx @@ -0,0 +1,10 @@ +'use client'; + +export default function Page() { + return ( +
+

Next.js App Router Fixture

+

Client component under Turbopack HMR.

+
+ ); +} diff --git a/tests/framework-fixtures/nextjs-app-router/files/next.config.js b/tests/framework-fixtures/nextjs-app-router/files/next.config.js new file mode 100644 index 000000000..d918f8040 --- /dev/null +++ b/tests/framework-fixtures/nextjs-app-router/files/next.config.js @@ -0,0 +1,3 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = {}; +module.exports = nextConfig; diff --git a/tests/framework-fixtures/nextjs-app-router/files/package.json b/tests/framework-fixtures/nextjs-app-router/files/package.json new file mode 100644 index 000000000..4190c7b7e --- /dev/null +++ b/tests/framework-fixtures/nextjs-app-router/files/package.json @@ -0,0 +1,15 @@ +{ + "name": "nextjs-app-router-fixture", + "private": true, + "version": "0.0.0", + "scripts": { + "dev": "next dev --hostname 127.0.0.1", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "next": "^16.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } +} diff --git a/tests/framework-fixtures/nextjs-app-router/fixture.json b/tests/framework-fixtures/nextjs-app-router/fixture.json new file mode 100644 index 000000000..263c037dc --- /dev/null +++ b/tests/framework-fixtures/nextjs-app-router/fixture.json @@ -0,0 +1,28 @@ +{ + "name": "Next.js 16 + App Router (Turbopack)", + "config": { + "files": ["app/layout.jsx"], + "insertBefore": "", + "commentSyntax": "jsx" + }, + "sourceFiles": ["app/layout.jsx", "app/page.jsx", "next.config.js"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps hero title in source JSX page", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "app/page.jsx" + } + ], + "runtime": { + "styling": "plain-css", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "next", "dev", "--hostname", "127.0.0.1"], + "readyPattern": "(?:Local|Network):?\\s+https?://[^:\\s]+:(\\d+)", + "readyTimeoutMs": 180000, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/nextjs-app-router/gitignore.txt b/tests/framework-fixtures/nextjs-app-router/gitignore.txt new file mode 100644 index 000000000..068ae1818 --- /dev/null +++ b/tests/framework-fixtures/nextjs-app-router/gitignore.txt @@ -0,0 +1,5 @@ +node_modules/ +.next/ +out/ +dist/ +package-lock.json diff --git a/tests/framework-fixtures/nuxt-vite7/files/app.vue b/tests/framework-fixtures/nuxt-vite7/files/app.vue new file mode 100644 index 000000000..80fbb63be --- /dev/null +++ b/tests/framework-fixtures/nuxt-vite7/files/app.vue @@ -0,0 +1,10 @@ + diff --git a/tests/framework-fixtures/nuxt-vite7/files/nuxt.config.ts b/tests/framework-fixtures/nuxt-vite7/files/nuxt.config.ts new file mode 100644 index 000000000..e00a9dc6e --- /dev/null +++ b/tests/framework-fixtures/nuxt-vite7/files/nuxt.config.ts @@ -0,0 +1,4 @@ +export default defineNuxtConfig({ + devtools: { enabled: false }, + ssr: false, +}); diff --git a/tests/framework-fixtures/nuxt-vite7/files/package.json b/tests/framework-fixtures/nuxt-vite7/files/package.json new file mode 100644 index 000000000..a9c64226f --- /dev/null +++ b/tests/framework-fixtures/nuxt-vite7/files/package.json @@ -0,0 +1,15 @@ +{ + "name": "nuxt-vite7-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "nuxt dev --host 127.0.0.1", + "build": "nuxt build", + "preview": "nuxt preview" + }, + "devDependencies": { + "nuxt": "^4.0.0", + "vue": "^3.5.0" + } +} diff --git a/tests/framework-fixtures/nuxt-vite7/files/pages/index.vue b/tests/framework-fixtures/nuxt-vite7/files/pages/index.vue new file mode 100644 index 000000000..a9fe1951e --- /dev/null +++ b/tests/framework-fixtures/nuxt-vite7/files/pages/index.vue @@ -0,0 +1,12 @@ + + + diff --git a/tests/framework-fixtures/nuxt-vite7/fixture.json b/tests/framework-fixtures/nuxt-vite7/fixture.json new file mode 100644 index 000000000..556521993 --- /dev/null +++ b/tests/framework-fixtures/nuxt-vite7/fixture.json @@ -0,0 +1,18 @@ +{ + "name": "Nuxt 4 + Vue 3 (static fixture only — runtime inject unsupported)", + "config": { + "files": ["app.vue"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": ["app.vue", "pages/index.vue", "nuxt.config.ts"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps hero in pages/index.vue", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "pages/index.vue" + } + ], + "_runtimeOmitted": "Nuxt's app.vue is a Vue template that compiles to a render function — a + + diff --git a/tests/framework-fixtures/vite8-https/files/package.json b/tests/framework-fixtures/vite8-https/files/package.json new file mode 100644 index 000000000..4674340c1 --- /dev/null +++ b/tests/framework-fixtures/vite8-https/files/package.json @@ -0,0 +1,20 @@ +{ + "name": "vite8-https-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@vitejs/plugin-basic-ssl": "^2.3.0", + "@vitejs/plugin-react": "^6.0.0", + "vite": "^8.0.0" + } +} diff --git a/tests/framework-fixtures/vite8-https/files/src/App.jsx b/tests/framework-fixtures/vite8-https/files/src/App.jsx new file mode 100644 index 000000000..337be8c40 --- /dev/null +++ b/tests/framework-fixtures/vite8-https/files/src/App.jsx @@ -0,0 +1,12 @@ +export default function App() { + return ( +
+

Vite 8 + HTTPS Fixture

+

Self-signed HTTPS. http://localhost:PORT/live.js should be blocked as mixed content.

+
+
One
+
Two
+
+
+ ); +} diff --git a/tests/framework-fixtures/vite8-https/files/src/main.jsx b/tests/framework-fixtures/vite8-https/files/src/main.jsx new file mode 100644 index 000000000..f2baba283 --- /dev/null +++ b/tests/framework-fixtures/vite8-https/files/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.jsx'; +import './styles.css'; + +createRoot(document.getElementById('root')).render( + + + , +); diff --git a/tests/framework-fixtures/vite8-https/files/src/styles.css b/tests/framework-fixtures/vite8-https/files/src/styles.css new file mode 100644 index 000000000..62fbb5e89 --- /dev/null +++ b/tests/framework-fixtures/vite8-https/files/src/styles.css @@ -0,0 +1,6 @@ +body { margin: 0; font-family: system-ui, sans-serif; } +.page { padding: 2rem; } +.hero-title { font-size: 2rem; } +.hero-hook { color: #555; } +.feature-grid { display: grid; gap: 1rem; grid-template-columns: repeat(2, 1fr); } +.feature-card { padding: 1rem; border: 1px solid #ddd; border-radius: 0.5rem; } diff --git a/tests/framework-fixtures/vite8-https/files/vite.config.js b/tests/framework-fixtures/vite8-https/files/vite.config.js new file mode 100644 index 000000000..e6505b961 --- /dev/null +++ b/tests/framework-fixtures/vite8-https/files/vite.config.js @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import basicSsl from '@vitejs/plugin-basic-ssl'; + +export default defineConfig({ + plugins: [react(), basicSsl()], + server: { host: '127.0.0.1', strictPort: false }, +}); diff --git a/tests/framework-fixtures/vite8-https/fixture.json b/tests/framework-fixtures/vite8-https/fixture.json new file mode 100644 index 000000000..c5fac0cb9 --- /dev/null +++ b/tests/framework-fixtures/vite8-https/fixture.json @@ -0,0 +1,30 @@ +{ + "name": "Vite 8 + React + HTTPS (self-signed)", + "config": { + "files": ["index.html"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": ["index.html", "src/App.jsx", "src/main.jsx", "src/styles.css", "vite.config.js"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps hero title in source JSX", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "src/App.jsx" + } + ], + "runtime": { + "styling": "plain-css", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "vite", "--host", "127.0.0.1"], + "scheme": "https", + "ignoreHTTPSErrors": true, + "readyPattern": "Local:\\s+https?://[^:]+:(\\d+)", + "readyTimeoutMs": 120000, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/vite8-https/gitignore.txt b/tests/framework-fixtures/vite8-https/gitignore.txt new file mode 100644 index 000000000..8cda9ad20 --- /dev/null +++ b/tests/framework-fixtures/vite8-https/gitignore.txt @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.vite/ +package-lock.json diff --git a/tests/framework-fixtures/vite8-react-base-path/files/index.html b/tests/framework-fixtures/vite8-react-base-path/files/index.html new file mode 100644 index 000000000..6dd31e269 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-base-path/files/index.html @@ -0,0 +1,11 @@ + + + + + Vite 8 + base path Fixture + + +
+ + + diff --git a/tests/framework-fixtures/vite8-react-base-path/files/package.json b/tests/framework-fixtures/vite8-react-base-path/files/package.json new file mode 100644 index 000000000..50e09be6d --- /dev/null +++ b/tests/framework-fixtures/vite8-react-base-path/files/package.json @@ -0,0 +1,19 @@ +{ + "name": "vite8-react-base-path-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^6.0.0", + "vite": "^8.0.0" + } +} diff --git a/tests/framework-fixtures/vite8-react-base-path/files/src/App.jsx b/tests/framework-fixtures/vite8-react-base-path/files/src/App.jsx new file mode 100644 index 000000000..8afbc5d8b --- /dev/null +++ b/tests/framework-fixtures/vite8-react-base-path/files/src/App.jsx @@ -0,0 +1,8 @@ +export default function App() { + return ( +
+

Vite 8 + base path Fixture

+

Served under base: '/app/'.

+
+ ); +} diff --git a/tests/framework-fixtures/vite8-react-base-path/files/src/main.jsx b/tests/framework-fixtures/vite8-react-base-path/files/src/main.jsx new file mode 100644 index 000000000..f2baba283 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-base-path/files/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.jsx'; +import './styles.css'; + +createRoot(document.getElementById('root')).render( + + + , +); diff --git a/tests/framework-fixtures/vite8-react-base-path/files/src/styles.css b/tests/framework-fixtures/vite8-react-base-path/files/src/styles.css new file mode 100644 index 000000000..c16eeacc1 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-base-path/files/src/styles.css @@ -0,0 +1,4 @@ +body { margin: 0; font-family: system-ui, sans-serif; } +.page { padding: 2rem; } +.hero-title { font-size: 2rem; } +.hero-hook { color: #555; } diff --git a/tests/framework-fixtures/vite8-react-base-path/files/vite.config.js b/tests/framework-fixtures/vite8-react-base-path/files/vite.config.js new file mode 100644 index 000000000..057287d06 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-base-path/files/vite.config.js @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + base: '/app/', + plugins: [react()], + server: { host: '127.0.0.1', strictPort: false }, +}); diff --git a/tests/framework-fixtures/vite8-react-base-path/fixture.json b/tests/framework-fixtures/vite8-react-base-path/fixture.json new file mode 100644 index 000000000..ef8ec9144 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-base-path/fixture.json @@ -0,0 +1,29 @@ +{ + "name": "Vite 8 + React + base: '/app/'", + "config": { + "files": ["index.html"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": ["index.html", "src/App.jsx", "src/main.jsx", "src/styles.css", "vite.config.js"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps hero title in source JSX", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "src/App.jsx" + } + ], + "runtime": { + "styling": "plain-css", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "vite", "--host", "127.0.0.1"], + "readyPattern": "Local:\\s+https?://[^:]+:(\\d+)", + "readyTimeoutMs": 120000, + "preActions": [{ "type": "goto", "path": "/app/" }], + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/vite8-react-base-path/gitignore.txt b/tests/framework-fixtures/vite8-react-base-path/gitignore.txt new file mode 100644 index 000000000..8cda9ad20 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-base-path/gitignore.txt @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.vite/ +package-lock.json diff --git a/tests/framework-fixtures/vite8-react-csp-meta/files/index.html b/tests/framework-fixtures/vite8-react-csp-meta/files/index.html new file mode 100644 index 000000000..900caaef9 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-csp-meta/files/index.html @@ -0,0 +1,15 @@ + + + + + + Vite 8 + CSP meta Fixture + + +
+ + + diff --git a/tests/framework-fixtures/vite8-react-csp-meta/files/package.json b/tests/framework-fixtures/vite8-react-csp-meta/files/package.json new file mode 100644 index 000000000..44f920b9e --- /dev/null +++ b/tests/framework-fixtures/vite8-react-csp-meta/files/package.json @@ -0,0 +1,19 @@ +{ + "name": "vite8-react-csp-meta-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^6.0.0", + "vite": "^8.0.0" + } +} diff --git a/tests/framework-fixtures/vite8-react-csp-meta/files/src/App.jsx b/tests/framework-fixtures/vite8-react-csp-meta/files/src/App.jsx new file mode 100644 index 000000000..6261ef9fb --- /dev/null +++ b/tests/framework-fixtures/vite8-react-csp-meta/files/src/App.jsx @@ -0,0 +1,12 @@ +export default function App() { + return ( +
+

Vite 8 + CSP meta Fixture

+

CSP meta tag blocks cross-origin script-src.

+
+
One
+
Two
+
+
+ ); +} diff --git a/tests/framework-fixtures/vite8-react-csp-meta/files/src/main.jsx b/tests/framework-fixtures/vite8-react-csp-meta/files/src/main.jsx new file mode 100644 index 000000000..f2baba283 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-csp-meta/files/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.jsx'; +import './styles.css'; + +createRoot(document.getElementById('root')).render( + + + , +); diff --git a/tests/framework-fixtures/vite8-react-csp-meta/files/src/styles.css b/tests/framework-fixtures/vite8-react-csp-meta/files/src/styles.css new file mode 100644 index 000000000..62fbb5e89 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-csp-meta/files/src/styles.css @@ -0,0 +1,6 @@ +body { margin: 0; font-family: system-ui, sans-serif; } +.page { padding: 2rem; } +.hero-title { font-size: 2rem; } +.hero-hook { color: #555; } +.feature-grid { display: grid; gap: 1rem; grid-template-columns: repeat(2, 1fr); } +.feature-card { padding: 1rem; border: 1px solid #ddd; border-radius: 0.5rem; } diff --git a/tests/framework-fixtures/vite8-react-csp-meta/files/vite.config.js b/tests/framework-fixtures/vite8-react-csp-meta/files/vite.config.js new file mode 100644 index 000000000..f7ad4b565 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-csp-meta/files/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { host: '127.0.0.1', strictPort: false }, +}); diff --git a/tests/framework-fixtures/vite8-react-csp-meta/fixture.json b/tests/framework-fixtures/vite8-react-csp-meta/fixture.json new file mode 100644 index 000000000..f8213f630 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-csp-meta/fixture.json @@ -0,0 +1,33 @@ +{ + "name": "Vite 8 + React + CSP meta tag", + "config": { + "files": ["index.html"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": ["index.html", "src/App.jsx", "src/main.jsx", "src/styles.css", "vite.config.js"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps hero title in source JSX", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "src/App.jsx" + } + ], + "csp": { + "shape": "meta-tag", + "signals": ["index.html"], + "patchTarget": "index.html" + }, + "runtime": { + "styling": "plain-css", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "vite", "--host", "127.0.0.1"], + "readyPattern": "Local:\\s+https?://[^:]+:(\\d+)", + "readyTimeoutMs": 120000, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/vite8-react-csp-meta/gitignore.txt b/tests/framework-fixtures/vite8-react-csp-meta/gitignore.txt new file mode 100644 index 000000000..8cda9ad20 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-csp-meta/gitignore.txt @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.vite/ +package-lock.json diff --git a/tests/framework-fixtures/vite8-react-css-modules/files/index.html b/tests/framework-fixtures/vite8-react-css-modules/files/index.html new file mode 100644 index 000000000..295a44479 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-css-modules/files/index.html @@ -0,0 +1,11 @@ + + + + + Vite 8 + CSS Modules Fixture + + +
+ + + diff --git a/tests/framework-fixtures/vite8-react-css-modules/files/package.json b/tests/framework-fixtures/vite8-react-css-modules/files/package.json new file mode 100644 index 000000000..741825618 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-css-modules/files/package.json @@ -0,0 +1,19 @@ +{ + "name": "vite8-react-css-modules-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^6.0.0", + "vite": "^8.0.0" + } +} diff --git a/tests/framework-fixtures/vite8-react-css-modules/files/src/App.jsx b/tests/framework-fixtures/vite8-react-css-modules/files/src/App.jsx new file mode 100644 index 000000000..1043047e3 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-css-modules/files/src/App.jsx @@ -0,0 +1,14 @@ +import styles from './App.module.css'; + +export default function App() { + return ( +
+

Vite 8 + CSS Modules Fixture

+

CSS Modules via foo.module.css — hashed classNames at runtime.

+
+
One
+
Two
+
+
+ ); +} diff --git a/tests/framework-fixtures/vite8-react-css-modules/files/src/App.module.css b/tests/framework-fixtures/vite8-react-css-modules/files/src/App.module.css new file mode 100644 index 000000000..dfe1cef45 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-css-modules/files/src/App.module.css @@ -0,0 +1,5 @@ +.page { padding: 2rem; font-family: system-ui, sans-serif; } +.heroTitle { font-size: 2rem; } +.heroHook { color: #555; } +.featureGrid { display: grid; gap: 1rem; grid-template-columns: repeat(2, 1fr); margin-top: 1rem; } +.featureCard { padding: 1rem; border: 1px solid #ddd; border-radius: 0.5rem; } diff --git a/tests/framework-fixtures/vite8-react-css-modules/files/src/main.jsx b/tests/framework-fixtures/vite8-react-css-modules/files/src/main.jsx new file mode 100644 index 000000000..6dab183de --- /dev/null +++ b/tests/framework-fixtures/vite8-react-css-modules/files/src/main.jsx @@ -0,0 +1,9 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.jsx'; + +createRoot(document.getElementById('root')).render( + + + , +); diff --git a/tests/framework-fixtures/vite8-react-css-modules/files/vite.config.js b/tests/framework-fixtures/vite8-react-css-modules/files/vite.config.js new file mode 100644 index 000000000..f7ad4b565 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-css-modules/files/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { host: '127.0.0.1', strictPort: false }, +}); diff --git a/tests/framework-fixtures/vite8-react-css-modules/fixture.json b/tests/framework-fixtures/vite8-react-css-modules/fixture.json new file mode 100644 index 000000000..2b743ced1 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-css-modules/fixture.json @@ -0,0 +1,28 @@ +{ + "name": "Vite 8 + React + CSS Modules", + "config": { + "files": ["index.html"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": ["index.html", "src/App.jsx", "src/App.module.css", "src/main.jsx", "vite.config.js"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps hero title in source JSX", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "src/App.jsx" + } + ], + "runtime": { + "styling": "css-modules", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "vite", "--host", "127.0.0.1"], + "readyPattern": "Local:\\s+https?://[^:]+:(\\d+)", + "readyTimeoutMs": 120000, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/vite8-react-css-modules/gitignore.txt b/tests/framework-fixtures/vite8-react-css-modules/gitignore.txt new file mode 100644 index 000000000..8cda9ad20 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-css-modules/gitignore.txt @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.vite/ +package-lock.json diff --git a/tests/framework-fixtures/vite8-react-emotion/files/index.html b/tests/framework-fixtures/vite8-react-emotion/files/index.html new file mode 100644 index 000000000..8e5008063 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-emotion/files/index.html @@ -0,0 +1,11 @@ + + + + + Vite 8 + Emotion Fixture + + +
+ + + diff --git a/tests/framework-fixtures/vite8-react-emotion/files/package.json b/tests/framework-fixtures/vite8-react-emotion/files/package.json new file mode 100644 index 000000000..e690a3899 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-emotion/files/package.json @@ -0,0 +1,21 @@ +{ + "name": "vite8-react-emotion-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^6.0.0", + "vite": "^8.0.0" + } +} diff --git a/tests/framework-fixtures/vite8-react-emotion/files/src/App.jsx b/tests/framework-fixtures/vite8-react-emotion/files/src/App.jsx new file mode 100644 index 000000000..f970d035e --- /dev/null +++ b/tests/framework-fixtures/vite8-react-emotion/files/src/App.jsx @@ -0,0 +1,21 @@ +/** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; + +const pageStyle = css`padding: 2rem; font-family: system-ui, sans-serif;`; +const titleStyle = css`font-size: 2rem;`; +const hookStyle = css`color: #555;`; +const gridStyle = css`display: grid; gap: 1rem; grid-template-columns: repeat(2, 1fr); margin-top: 1rem;`; +const cardStyle = css`padding: 1rem; border: 1px solid #ddd; border-radius: 0.5rem;`; + +export default function App() { + return ( +
+

Vite 8 + Emotion Fixture

+

Runtime CSS-in-JS via the Emotion css prop.

+
+
One
+
Two
+
+
+ ); +} diff --git a/tests/framework-fixtures/vite8-react-emotion/files/src/main.jsx b/tests/framework-fixtures/vite8-react-emotion/files/src/main.jsx new file mode 100644 index 000000000..6dab183de --- /dev/null +++ b/tests/framework-fixtures/vite8-react-emotion/files/src/main.jsx @@ -0,0 +1,9 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.jsx'; + +createRoot(document.getElementById('root')).render( + + + , +); diff --git a/tests/framework-fixtures/vite8-react-emotion/files/vite.config.js b/tests/framework-fixtures/vite8-react-emotion/files/vite.config.js new file mode 100644 index 000000000..67f3c1fd9 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-emotion/files/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react({ jsxImportSource: '@emotion/react' })], + server: { host: '127.0.0.1', strictPort: false }, +}); diff --git a/tests/framework-fixtures/vite8-react-emotion/fixture.json b/tests/framework-fixtures/vite8-react-emotion/fixture.json new file mode 100644 index 000000000..cd3b4f261 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-emotion/fixture.json @@ -0,0 +1,28 @@ +{ + "name": "Vite 8 + React + Emotion", + "config": { + "files": ["index.html"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": ["index.html", "src/App.jsx", "src/main.jsx", "vite.config.js"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps hero title in source JSX", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "src/App.jsx" + } + ], + "runtime": { + "styling": "emotion", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "vite", "--host", "127.0.0.1"], + "readyPattern": "Local:\\s+https?://[^:]+:(\\d+)", + "readyTimeoutMs": 120000, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/vite8-react-emotion/gitignore.txt b/tests/framework-fixtures/vite8-react-emotion/gitignore.txt new file mode 100644 index 000000000..8cda9ad20 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-emotion/gitignore.txt @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.vite/ +package-lock.json diff --git a/tests/framework-fixtures/vite8-react-mapped-list/files/index.html b/tests/framework-fixtures/vite8-react-mapped-list/files/index.html new file mode 100644 index 000000000..e977b6b2b --- /dev/null +++ b/tests/framework-fixtures/vite8-react-mapped-list/files/index.html @@ -0,0 +1,11 @@ + + + + + Vite 8 + Mapped List Fixture + + +
+ + + diff --git a/tests/framework-fixtures/vite8-react-mapped-list/files/package.json b/tests/framework-fixtures/vite8-react-mapped-list/files/package.json new file mode 100644 index 000000000..718a925fa --- /dev/null +++ b/tests/framework-fixtures/vite8-react-mapped-list/files/package.json @@ -0,0 +1,19 @@ +{ + "name": "vite8-react-mapped-list-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^6.0.0", + "vite": "^8.0.0" + } +} diff --git a/tests/framework-fixtures/vite8-react-mapped-list/files/src/App.jsx b/tests/framework-fixtures/vite8-react-mapped-list/files/src/App.jsx new file mode 100644 index 000000000..11c34befc --- /dev/null +++ b/tests/framework-fixtures/vite8-react-mapped-list/files/src/App.jsx @@ -0,0 +1,21 @@ +const ITEMS = [ + { id: 'a', title: 'Alpha card' }, + { id: 'b', title: 'Beta card' }, + { id: 'c', title: 'Gamma card' }, +]; + +export default function App() { + return ( +
+

Three cards rendered from a single mapped template.

+
+ {ITEMS.map((item) => ( +
+

{item.title}

+

Body for {item.id}.

+
+ ))} +
+
+ ); +} diff --git a/tests/framework-fixtures/vite8-react-mapped-list/files/src/main.jsx b/tests/framework-fixtures/vite8-react-mapped-list/files/src/main.jsx new file mode 100644 index 000000000..f2baba283 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-mapped-list/files/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.jsx'; +import './styles.css'; + +createRoot(document.getElementById('root')).render( + + + , +); diff --git a/tests/framework-fixtures/vite8-react-mapped-list/files/src/styles.css b/tests/framework-fixtures/vite8-react-mapped-list/files/src/styles.css new file mode 100644 index 000000000..d0b6e81e5 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-mapped-list/files/src/styles.css @@ -0,0 +1,6 @@ +body { margin: 0; font-family: system-ui, sans-serif; } +.page { padding: 2rem; } +.hero-hook { color: #555; } +.grid { display: grid; gap: 1rem; grid-template-columns: repeat(3, 1fr); margin-top: 1rem; } +.card { padding: 1rem; border: 1px solid #ddd; border-radius: 0.5rem; } +.hero-title { font-size: 1.4rem; margin: 0 0 0.5rem; } diff --git a/tests/framework-fixtures/vite8-react-mapped-list/files/vite.config.js b/tests/framework-fixtures/vite8-react-mapped-list/files/vite.config.js new file mode 100644 index 000000000..f7ad4b565 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-mapped-list/files/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { host: '127.0.0.1', strictPort: false }, +}); diff --git a/tests/framework-fixtures/vite8-react-mapped-list/fixture.json b/tests/framework-fixtures/vite8-react-mapped-list/fixture.json new file mode 100644 index 000000000..d9e1a1911 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-mapped-list/fixture.json @@ -0,0 +1,28 @@ +{ + "name": "Vite 8 + React + .map() instances (3 cards from one template)", + "config": { + "files": ["index.html"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": ["index.html", "src/App.jsx", "src/main.jsx", "src/styles.css", "vite.config.js"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps the mapped h1 template in source JSX", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "src/App.jsx" + } + ], + "runtime": { + "styling": "plain-css", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "vite", "--host", "127.0.0.1"], + "readyPattern": "Local:\\s+https?://[^:]+:(\\d+)", + "readyTimeoutMs": 120000, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/vite8-react-mapped-list/gitignore.txt b/tests/framework-fixtures/vite8-react-mapped-list/gitignore.txt new file mode 100644 index 000000000..8cda9ad20 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-mapped-list/gitignore.txt @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.vite/ +package-lock.json diff --git a/tests/framework-fixtures/vite8-react-modal/files/index.html b/tests/framework-fixtures/vite8-react-modal/files/index.html new file mode 100644 index 000000000..6d2fa5ebe --- /dev/null +++ b/tests/framework-fixtures/vite8-react-modal/files/index.html @@ -0,0 +1,11 @@ + + + + + Vite 8 + Modal Fixture + + +
+ + + diff --git a/tests/framework-fixtures/vite8-react-modal/files/package.json b/tests/framework-fixtures/vite8-react-modal/files/package.json new file mode 100644 index 000000000..f9324fff5 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-modal/files/package.json @@ -0,0 +1,19 @@ +{ + "name": "vite8-react-modal-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^6.0.0", + "vite": "^8.0.0" + } +} diff --git a/tests/framework-fixtures/vite8-react-modal/files/src/App.jsx b/tests/framework-fixtures/vite8-react-modal/files/src/App.jsx new file mode 100644 index 000000000..d7b89f1c8 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-modal/files/src/App.jsx @@ -0,0 +1,24 @@ +import { useState } from 'react'; + +export default function App() { + const [open, setOpen] = useState(false); + + return ( +
+ + {open && ( +
+
+

Modal Heading

+

Conditionally rendered — only mounts on click.

+ +
+
+ )} +
+ ); +} diff --git a/tests/framework-fixtures/vite8-react-modal/files/src/main.jsx b/tests/framework-fixtures/vite8-react-modal/files/src/main.jsx new file mode 100644 index 000000000..f2baba283 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-modal/files/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.jsx'; +import './styles.css'; + +createRoot(document.getElementById('root')).render( + + + , +); diff --git a/tests/framework-fixtures/vite8-react-modal/files/src/styles.css b/tests/framework-fixtures/vite8-react-modal/files/src/styles.css new file mode 100644 index 000000000..338b7f75f --- /dev/null +++ b/tests/framework-fixtures/vite8-react-modal/files/src/styles.css @@ -0,0 +1,7 @@ +body { margin: 0; font-family: system-ui, sans-serif; } +.page { padding: 2rem; } +.hero-title { font-size: 2rem; margin: 0; } +.hero-hook { color: #555; } +.modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.4); display: grid; place-items: center; } +.modal { background: white; padding: 2rem; border-radius: 1rem; min-width: 320px; } +button { padding: 0.5rem 1rem; border-radius: 0.5rem; cursor: pointer; } diff --git a/tests/framework-fixtures/vite8-react-modal/files/vite.config.js b/tests/framework-fixtures/vite8-react-modal/files/vite.config.js new file mode 100644 index 000000000..f7ad4b565 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-modal/files/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { host: '127.0.0.1', strictPort: false }, +}); diff --git a/tests/framework-fixtures/vite8-react-modal/fixture.json b/tests/framework-fixtures/vite8-react-modal/fixture.json new file mode 100644 index 000000000..199b299ad --- /dev/null +++ b/tests/framework-fixtures/vite8-react-modal/fixture.json @@ -0,0 +1,39 @@ +{ + "name": "Vite 8 + React + conditional Modal", + "config": { + "files": ["index.html"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": ["index.html", "src/App.jsx", "src/main.jsx", "src/styles.css", "vite.config.js"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps modal heading in source JSX", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "src/App.jsx" + } + ], + "runtime": { + "styling": "plain-css", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "vite", "--host", "127.0.0.1"], + "readyPattern": "Local:\\s+https?://[^:]+:(\\d+)", + "readyTimeoutMs": 120000, + "preActions": [ + { "type": "click", "selector": "[data-testid='open-modal']" }, + { "type": "wait", "selector": "h1.hero-title" } + ], + "reloadProbe": { + "preActions": [ + { "type": "click", "selector": "[data-testid='open-modal']" }, + { "type": "wait", "selector": "h1.hero-title" } + ], + "expectSelector": "h1.hero-title" + }, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/vite8-react-modal/gitignore.txt b/tests/framework-fixtures/vite8-react-modal/gitignore.txt new file mode 100644 index 000000000..8cda9ad20 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-modal/gitignore.txt @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.vite/ +package-lock.json diff --git a/tests/framework-fixtures/vite8-react-plain/files/index.html b/tests/framework-fixtures/vite8-react-plain/files/index.html new file mode 100644 index 000000000..d81098c94 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-plain/files/index.html @@ -0,0 +1,11 @@ + + + + + Vite 8 + React Fixture + + +
+ + + diff --git a/tests/framework-fixtures/vite8-react-plain/files/package.json b/tests/framework-fixtures/vite8-react-plain/files/package.json new file mode 100644 index 000000000..d09089d66 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-plain/files/package.json @@ -0,0 +1,19 @@ +{ + "name": "vite8-react-plain-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^6.0.0", + "vite": "^8.0.0" + } +} diff --git a/tests/framework-fixtures/vite8-react-plain/files/src/App.jsx b/tests/framework-fixtures/vite8-react-plain/files/src/App.jsx new file mode 100644 index 000000000..b2a87cf5c --- /dev/null +++ b/tests/framework-fixtures/vite8-react-plain/files/src/App.jsx @@ -0,0 +1,12 @@ +export default function App() { + return ( +
+

Vite 8 Fixture

+

Minimal React tree for live-mode E2E tests.

+
+
One
+
Two
+
+
+ ); +} diff --git a/tests/framework-fixtures/vite8-react-plain/files/src/main.jsx b/tests/framework-fixtures/vite8-react-plain/files/src/main.jsx new file mode 100644 index 000000000..f2baba283 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-plain/files/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.jsx'; +import './styles.css'; + +createRoot(document.getElementById('root')).render( + + + , +); diff --git a/tests/framework-fixtures/vite8-react-plain/files/src/styles.css b/tests/framework-fixtures/vite8-react-plain/files/src/styles.css new file mode 100644 index 000000000..62fbb5e89 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-plain/files/src/styles.css @@ -0,0 +1,6 @@ +body { margin: 0; font-family: system-ui, sans-serif; } +.page { padding: 2rem; } +.hero-title { font-size: 2rem; } +.hero-hook { color: #555; } +.feature-grid { display: grid; gap: 1rem; grid-template-columns: repeat(2, 1fr); } +.feature-card { padding: 1rem; border: 1px solid #ddd; border-radius: 0.5rem; } diff --git a/tests/framework-fixtures/vite8-react-plain/files/vite.config.js b/tests/framework-fixtures/vite8-react-plain/files/vite.config.js new file mode 100644 index 000000000..dd5cfa6f0 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-plain/files/vite.config.js @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { + host: '127.0.0.1', + strictPort: false, + }, +}); diff --git a/tests/framework-fixtures/vite8-react-plain/fixture.json b/tests/framework-fixtures/vite8-react-plain/fixture.json new file mode 100644 index 000000000..9a7147d12 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-plain/fixture.json @@ -0,0 +1,28 @@ +{ + "name": "Vite 8 + React + plain CSS", + "config": { + "files": ["index.html"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": ["index.html", "src/App.jsx", "src/main.jsx", "src/styles.css", "vite.config.js"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps hero title in source JSX", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "src/App.jsx" + } + ], + "runtime": { + "styling": "plain-css", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "vite", "--host", "127.0.0.1"], + "readyPattern": "Local:\\s+https?://[^:]+:(\\d+)", + "readyTimeoutMs": 120000, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/vite8-react-plain/gitignore.txt b/tests/framework-fixtures/vite8-react-plain/gitignore.txt new file mode 100644 index 000000000..8cda9ad20 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-plain/gitignore.txt @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.vite/ +package-lock.json diff --git a/tests/framework-fixtures/vite8-react-router-spa/files/index.html b/tests/framework-fixtures/vite8-react-router-spa/files/index.html new file mode 100644 index 000000000..9773bc9f2 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-router-spa/files/index.html @@ -0,0 +1,11 @@ + + + + + Vite 8 + React Router Fixture + + +
+ + + diff --git a/tests/framework-fixtures/vite8-react-router-spa/files/package.json b/tests/framework-fixtures/vite8-react-router-spa/files/package.json new file mode 100644 index 000000000..ee1d4c8eb --- /dev/null +++ b/tests/framework-fixtures/vite8-react-router-spa/files/package.json @@ -0,0 +1,20 @@ +{ + "name": "vite8-react-router-spa-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.0.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^6.0.0", + "vite": "^8.0.0" + } +} diff --git a/tests/framework-fixtures/vite8-react-router-spa/files/src/main.jsx b/tests/framework-fixtures/vite8-react-router-spa/files/src/main.jsx new file mode 100644 index 000000000..de215b63d --- /dev/null +++ b/tests/framework-fixtures/vite8-react-router-spa/files/src/main.jsx @@ -0,0 +1,21 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'; +import Home from './routes/Home.jsx'; +import About from './routes/About.jsx'; +import './styles.css'; + +createRoot(document.getElementById('root')).render( + + + + + } /> + } /> + + + , +); diff --git a/tests/framework-fixtures/vite8-react-router-spa/files/src/routes/About.jsx b/tests/framework-fixtures/vite8-react-router-spa/files/src/routes/About.jsx new file mode 100644 index 000000000..ae4c31e15 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-router-spa/files/src/routes/About.jsx @@ -0,0 +1,8 @@ +export default function About() { + return ( +
+

About Page Hero

+

Lives on the /about route — only mounts after navigation.

+
+ ); +} diff --git a/tests/framework-fixtures/vite8-react-router-spa/files/src/routes/Home.jsx b/tests/framework-fixtures/vite8-react-router-spa/files/src/routes/Home.jsx new file mode 100644 index 000000000..3c022d03d --- /dev/null +++ b/tests/framework-fixtures/vite8-react-router-spa/files/src/routes/Home.jsx @@ -0,0 +1,8 @@ +export default function Home() { + return ( +
+

Home

+

Welcome. The hero we'll edit lives on the About page.

+
+ ); +} diff --git a/tests/framework-fixtures/vite8-react-router-spa/files/src/styles.css b/tests/framework-fixtures/vite8-react-router-spa/files/src/styles.css new file mode 100644 index 000000000..7d2c6e91f --- /dev/null +++ b/tests/framework-fixtures/vite8-react-router-spa/files/src/styles.css @@ -0,0 +1,6 @@ +body { margin: 0; font-family: system-ui, sans-serif; } +.nav { display: flex; gap: 1rem; padding: 1rem; border-bottom: 1px solid #eee; } +.nav a { color: #111; text-decoration: none; } +.page { padding: 2rem; } +.hero-title { font-size: 2rem; margin: 0 0 0.5rem; } +.hero-hook { color: #555; } diff --git a/tests/framework-fixtures/vite8-react-router-spa/files/vite.config.js b/tests/framework-fixtures/vite8-react-router-spa/files/vite.config.js new file mode 100644 index 000000000..f7ad4b565 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-router-spa/files/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { host: '127.0.0.1', strictPort: false }, +}); diff --git a/tests/framework-fixtures/vite8-react-router-spa/fixture.json b/tests/framework-fixtures/vite8-react-router-spa/fixture.json new file mode 100644 index 000000000..67a73192e --- /dev/null +++ b/tests/framework-fixtures/vite8-react-router-spa/fixture.json @@ -0,0 +1,46 @@ +{ + "name": "Vite 8 + React Router 7 (multi-route SPA)", + "config": { + "files": ["index.html"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": [ + "index.html", + "src/main.jsx", + "src/routes/Home.jsx", + "src/routes/About.jsx", + "src/styles.css", + "vite.config.js" + ], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps About hero in routes/About.jsx", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "src/routes/About.jsx" + } + ], + "runtime": { + "styling": "plain-css", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "vite", "--host", "127.0.0.1"], + "readyPattern": "Local:\\s+https?://[^:]+:(\\d+)", + "readyTimeoutMs": 120000, + "preActions": [ + { "type": "click", "selector": "[data-testid='nav-about']" }, + { "type": "wait", "selector": "h1.hero-title" } + ], + "reloadProbe": { + "preActions": [ + { "type": "click", "selector": "[data-testid='nav-about']" }, + { "type": "wait", "selector": "h1.hero-title" } + ], + "expectSelector": "h1.hero-title" + }, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/vite8-react-router-spa/gitignore.txt b/tests/framework-fixtures/vite8-react-router-spa/gitignore.txt new file mode 100644 index 000000000..8cda9ad20 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-router-spa/gitignore.txt @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.vite/ +package-lock.json diff --git a/tests/framework-fixtures/vite8-react-styled-components/files/index.html b/tests/framework-fixtures/vite8-react-styled-components/files/index.html new file mode 100644 index 000000000..50b2155b4 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-styled-components/files/index.html @@ -0,0 +1,11 @@ + + + + + Vite 8 + styled-components Fixture + + +
+ + + diff --git a/tests/framework-fixtures/vite8-react-styled-components/files/package.json b/tests/framework-fixtures/vite8-react-styled-components/files/package.json new file mode 100644 index 000000000..cd6297789 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-styled-components/files/package.json @@ -0,0 +1,20 @@ +{ + "name": "vite8-react-styled-components-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "styled-components": "^6.1.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^6.0.0", + "vite": "^8.0.0" + } +} diff --git a/tests/framework-fixtures/vite8-react-styled-components/files/src/App.jsx b/tests/framework-fixtures/vite8-react-styled-components/files/src/App.jsx new file mode 100644 index 000000000..bf28a9fd8 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-styled-components/files/src/App.jsx @@ -0,0 +1,19 @@ +import styled from 'styled-components'; + +const Page = styled.main`padding: 2rem; font-family: system-ui, sans-serif;`; +const Hook = styled.p`color: #555;`; +const Grid = styled.section`display: grid; gap: 1rem; grid-template-columns: repeat(2, 1fr); margin-top: 1rem;`; +const Card = styled.article`padding: 1rem; border: 1px solid #ddd; border-radius: 0.5rem;`; + +export default function App() { + return ( + +

Vite 8 + styled-components Fixture

+ Runtime CSS-in-JS via styled-components v6. + + One + Two + +
+ ); +} diff --git a/tests/framework-fixtures/vite8-react-styled-components/files/src/main.jsx b/tests/framework-fixtures/vite8-react-styled-components/files/src/main.jsx new file mode 100644 index 000000000..6dab183de --- /dev/null +++ b/tests/framework-fixtures/vite8-react-styled-components/files/src/main.jsx @@ -0,0 +1,9 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.jsx'; + +createRoot(document.getElementById('root')).render( + + + , +); diff --git a/tests/framework-fixtures/vite8-react-styled-components/files/vite.config.js b/tests/framework-fixtures/vite8-react-styled-components/files/vite.config.js new file mode 100644 index 000000000..f7ad4b565 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-styled-components/files/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { host: '127.0.0.1', strictPort: false }, +}); diff --git a/tests/framework-fixtures/vite8-react-styled-components/fixture.json b/tests/framework-fixtures/vite8-react-styled-components/fixture.json new file mode 100644 index 000000000..52657f61c --- /dev/null +++ b/tests/framework-fixtures/vite8-react-styled-components/fixture.json @@ -0,0 +1,28 @@ +{ + "name": "Vite 8 + React + styled-components", + "config": { + "files": ["index.html"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": ["index.html", "src/App.jsx", "src/main.jsx", "vite.config.js"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps hero title in source JSX", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "src/App.jsx" + } + ], + "runtime": { + "styling": "styled-components", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "vite", "--host", "127.0.0.1"], + "readyPattern": "Local:\\s+https?://[^:]+:(\\d+)", + "readyTimeoutMs": 120000, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/vite8-react-styled-components/gitignore.txt b/tests/framework-fixtures/vite8-react-styled-components/gitignore.txt new file mode 100644 index 000000000..8cda9ad20 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-styled-components/gitignore.txt @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.vite/ +package-lock.json diff --git a/tests/framework-fixtures/vite8-react-tabs/files/index.html b/tests/framework-fixtures/vite8-react-tabs/files/index.html new file mode 100644 index 000000000..cb6757446 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tabs/files/index.html @@ -0,0 +1,11 @@ + + + + + Vite 8 + Tabs Fixture + + +
+ + + diff --git a/tests/framework-fixtures/vite8-react-tabs/files/package.json b/tests/framework-fixtures/vite8-react-tabs/files/package.json new file mode 100644 index 000000000..6719b2a08 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tabs/files/package.json @@ -0,0 +1,19 @@ +{ + "name": "vite8-react-tabs-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^6.0.0", + "vite": "^8.0.0" + } +} diff --git a/tests/framework-fixtures/vite8-react-tabs/files/src/App.jsx b/tests/framework-fixtures/vite8-react-tabs/files/src/App.jsx new file mode 100644 index 000000000..9ca039911 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tabs/files/src/App.jsx @@ -0,0 +1,41 @@ +import { useState } from 'react'; + +export default function App() { + const [tab, setTab] = useState('overview'); + + return ( +
+ + + {tab === 'overview' && ( +
+

Default overview content. The hero lives in the Features tab.

+
+ )} + + {tab === 'features' && ( +
+

Features Hero

+

Lives in the non-default tab — only mounts when Features is active.

+
+ )} +
+ ); +} diff --git a/tests/framework-fixtures/vite8-react-tabs/files/src/main.jsx b/tests/framework-fixtures/vite8-react-tabs/files/src/main.jsx new file mode 100644 index 000000000..f2baba283 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tabs/files/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.jsx'; +import './styles.css'; + +createRoot(document.getElementById('root')).render( + + + , +); diff --git a/tests/framework-fixtures/vite8-react-tabs/files/src/styles.css b/tests/framework-fixtures/vite8-react-tabs/files/src/styles.css new file mode 100644 index 000000000..842bd06a7 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tabs/files/src/styles.css @@ -0,0 +1,8 @@ +body { margin: 0; font-family: system-ui, sans-serif; } +.page { padding: 2rem; } +.tabs { display: flex; gap: 0.5rem; margin-bottom: 1rem; } +.tabs button { padding: 0.5rem 1rem; cursor: pointer; } +.tabs button[aria-selected='true'] { background: #111; color: #fff; } +.tab-panel { padding: 1rem; border: 1px solid #ddd; border-radius: 0.5rem; } +.hero-title { font-size: 2rem; margin: 0 0 0.5rem; } +.hero-hook { color: #555; } diff --git a/tests/framework-fixtures/vite8-react-tabs/files/vite.config.js b/tests/framework-fixtures/vite8-react-tabs/files/vite.config.js new file mode 100644 index 000000000..f7ad4b565 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tabs/files/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { host: '127.0.0.1', strictPort: false }, +}); diff --git a/tests/framework-fixtures/vite8-react-tabs/fixture.json b/tests/framework-fixtures/vite8-react-tabs/fixture.json new file mode 100644 index 000000000..b02859a6b --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tabs/fixture.json @@ -0,0 +1,39 @@ +{ + "name": "Vite 8 + React + tabbed UI (hidden default)", + "config": { + "files": ["index.html"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": ["index.html", "src/App.jsx", "src/main.jsx", "src/styles.css", "vite.config.js"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps features-tab heading in source JSX", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "src/App.jsx" + } + ], + "runtime": { + "styling": "plain-css", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "vite", "--host", "127.0.0.1"], + "readyPattern": "Local:\\s+https?://[^:]+:(\\d+)", + "readyTimeoutMs": 120000, + "preActions": [ + { "type": "click", "selector": "[data-testid='tab-features']" }, + { "type": "wait", "selector": "h1.hero-title" } + ], + "reloadProbe": { + "preActions": [ + { "type": "click", "selector": "[data-testid='tab-features']" }, + { "type": "wait", "selector": "h1.hero-title" } + ], + "expectSelector": "h1.hero-title" + }, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/vite8-react-tabs/gitignore.txt b/tests/framework-fixtures/vite8-react-tabs/gitignore.txt new file mode 100644 index 000000000..8cda9ad20 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tabs/gitignore.txt @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.vite/ +package-lock.json diff --git a/tests/framework-fixtures/vite8-react-tailwindv3/files/index.html b/tests/framework-fixtures/vite8-react-tailwindv3/files/index.html new file mode 100644 index 000000000..f031559c3 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tailwindv3/files/index.html @@ -0,0 +1,11 @@ + + + + + Vite 8 + Tailwind v3 Fixture + + +
+ + + diff --git a/tests/framework-fixtures/vite8-react-tailwindv3/files/package.json b/tests/framework-fixtures/vite8-react-tailwindv3/files/package.json new file mode 100644 index 000000000..ee86006f0 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tailwindv3/files/package.json @@ -0,0 +1,22 @@ +{ + "name": "vite8-react-tailwindv3-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^6.0.0", + "autoprefixer": "^10.4.0", + "postcss": "^8.4.0", + "tailwindcss": "^3.4.0", + "vite": "^8.0.0" + } +} diff --git a/tests/framework-fixtures/vite8-react-tailwindv3/files/postcss.config.js b/tests/framework-fixtures/vite8-react-tailwindv3/files/postcss.config.js new file mode 100644 index 000000000..2aa7205d4 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tailwindv3/files/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/tests/framework-fixtures/vite8-react-tailwindv3/files/src/App.jsx b/tests/framework-fixtures/vite8-react-tailwindv3/files/src/App.jsx new file mode 100644 index 000000000..25247c6f4 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tailwindv3/files/src/App.jsx @@ -0,0 +1,12 @@ +export default function App() { + return ( +
+

Vite 8 + Tailwind v3 Fixture

+

Classic Tailwind via PostCSS.

+
+
One
+
Two
+
+
+ ); +} diff --git a/tests/framework-fixtures/vite8-react-tailwindv3/files/src/main.jsx b/tests/framework-fixtures/vite8-react-tailwindv3/files/src/main.jsx new file mode 100644 index 000000000..f2baba283 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tailwindv3/files/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.jsx'; +import './styles.css'; + +createRoot(document.getElementById('root')).render( + + + , +); diff --git a/tests/framework-fixtures/vite8-react-tailwindv3/files/src/styles.css b/tests/framework-fixtures/vite8-react-tailwindv3/files/src/styles.css new file mode 100644 index 000000000..b5c61c956 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tailwindv3/files/src/styles.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/tests/framework-fixtures/vite8-react-tailwindv3/files/tailwind.config.js b/tests/framework-fixtures/vite8-react-tailwindv3/files/tailwind.config.js new file mode 100644 index 000000000..694683097 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tailwindv3/files/tailwind.config.js @@ -0,0 +1,5 @@ +export default { + content: ['./index.html', './src/**/*.{js,jsx,ts,tsx}'], + theme: { extend: {} }, + plugins: [], +}; diff --git a/tests/framework-fixtures/vite8-react-tailwindv3/files/vite.config.js b/tests/framework-fixtures/vite8-react-tailwindv3/files/vite.config.js new file mode 100644 index 000000000..f7ad4b565 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tailwindv3/files/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { host: '127.0.0.1', strictPort: false }, +}); diff --git a/tests/framework-fixtures/vite8-react-tailwindv3/fixture.json b/tests/framework-fixtures/vite8-react-tailwindv3/fixture.json new file mode 100644 index 000000000..06b2ee871 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tailwindv3/fixture.json @@ -0,0 +1,28 @@ +{ + "name": "Vite 8 + React + Tailwind v3", + "config": { + "files": ["index.html"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": ["index.html", "src/App.jsx", "src/main.jsx", "src/styles.css", "vite.config.js", "tailwind.config.js", "postcss.config.js"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps hero title in source JSX", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "src/App.jsx" + } + ], + "runtime": { + "styling": "tailwind-v3", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "vite", "--host", "127.0.0.1"], + "readyPattern": "Local:\\s+https?://[^:]+:(\\d+)", + "readyTimeoutMs": 120000, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/vite8-react-tailwindv3/gitignore.txt b/tests/framework-fixtures/vite8-react-tailwindv3/gitignore.txt new file mode 100644 index 000000000..8cda9ad20 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tailwindv3/gitignore.txt @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.vite/ +package-lock.json diff --git a/tests/framework-fixtures/vite8-react-tailwindv4/files/index.html b/tests/framework-fixtures/vite8-react-tailwindv4/files/index.html new file mode 100644 index 000000000..b9d8c2f2c --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tailwindv4/files/index.html @@ -0,0 +1,11 @@ + + + + + Vite 8 + Tailwind v4 Fixture + + +
+ + + diff --git a/tests/framework-fixtures/vite8-react-tailwindv4/files/package.json b/tests/framework-fixtures/vite8-react-tailwindv4/files/package.json new file mode 100644 index 000000000..c74861714 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tailwindv4/files/package.json @@ -0,0 +1,21 @@ +{ + "name": "vite8-react-tailwindv4-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.2.0", + "@vitejs/plugin-react": "^6.0.0", + "tailwindcss": "^4.2.0", + "vite": "^8.0.0" + } +} diff --git a/tests/framework-fixtures/vite8-react-tailwindv4/files/src/App.jsx b/tests/framework-fixtures/vite8-react-tailwindv4/files/src/App.jsx new file mode 100644 index 000000000..2f2202e35 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tailwindv4/files/src/App.jsx @@ -0,0 +1,12 @@ +export default function App() { + return ( +
+

Vite 8 + Tailwind v4 Fixture

+

Minimal React tree for live-mode E2E tests.

+
+
One
+
Two
+
+
+ ); +} diff --git a/tests/framework-fixtures/vite8-react-tailwindv4/files/src/main.jsx b/tests/framework-fixtures/vite8-react-tailwindv4/files/src/main.jsx new file mode 100644 index 000000000..f2baba283 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tailwindv4/files/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.jsx'; +import './styles.css'; + +createRoot(document.getElementById('root')).render( + + + , +); diff --git a/tests/framework-fixtures/vite8-react-tailwindv4/files/src/styles.css b/tests/framework-fixtures/vite8-react-tailwindv4/files/src/styles.css new file mode 100644 index 000000000..f1d8c73cd --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tailwindv4/files/src/styles.css @@ -0,0 +1 @@ +@import "tailwindcss"; diff --git a/tests/framework-fixtures/vite8-react-tailwindv4/files/vite.config.js b/tests/framework-fixtures/vite8-react-tailwindv4/files/vite.config.js new file mode 100644 index 000000000..75e84866d --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tailwindv4/files/vite.config.js @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import tailwindcss from '@tailwindcss/vite'; + +export default defineConfig({ + plugins: [react(), tailwindcss()], + server: { host: '127.0.0.1', strictPort: false }, +}); diff --git a/tests/framework-fixtures/vite8-react-tailwindv4/fixture.json b/tests/framework-fixtures/vite8-react-tailwindv4/fixture.json new file mode 100644 index 000000000..85577e8f0 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tailwindv4/fixture.json @@ -0,0 +1,28 @@ +{ + "name": "Vite 8 + React + Tailwind v4", + "config": { + "files": ["index.html"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": ["index.html", "src/App.jsx", "src/main.jsx", "src/styles.css", "vite.config.js"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps hero title in source JSX", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "src/App.jsx" + } + ], + "runtime": { + "styling": "tailwind-v4", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "vite", "--host", "127.0.0.1"], + "readyPattern": "Local:\\s+https?://[^:]+:(\\d+)", + "readyTimeoutMs": 120000, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/vite8-react-tailwindv4/gitignore.txt b/tests/framework-fixtures/vite8-react-tailwindv4/gitignore.txt new file mode 100644 index 000000000..8cda9ad20 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-tailwindv4/gitignore.txt @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.vite/ +package-lock.json diff --git a/tests/framework-fixtures/vite8-react-ts/files/index.html b/tests/framework-fixtures/vite8-react-ts/files/index.html new file mode 100644 index 000000000..e3d50d0e1 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-ts/files/index.html @@ -0,0 +1,11 @@ + + + + + Vite 8 + React + TS Fixture + + +
+ + + diff --git a/tests/framework-fixtures/vite8-react-ts/files/package.json b/tests/framework-fixtures/vite8-react-ts/files/package.json new file mode 100644 index 000000000..3e6d7fdaf --- /dev/null +++ b/tests/framework-fixtures/vite8-react-ts/files/package.json @@ -0,0 +1,22 @@ +{ + "name": "vite8-react-ts-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^6.0.0", + "typescript": "^5.6.0", + "vite": "^8.0.0" + } +} diff --git a/tests/framework-fixtures/vite8-react-ts/files/src/App.tsx b/tests/framework-fixtures/vite8-react-ts/files/src/App.tsx new file mode 100644 index 000000000..ce7e508e7 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-ts/files/src/App.tsx @@ -0,0 +1,12 @@ +export default function App() { + return ( +
+

Vite 8 + TS Fixture

+

Minimal typed React tree for live-mode E2E tests.

+
+
One
+
Two
+
+
+ ); +} diff --git a/tests/framework-fixtures/vite8-react-ts/files/src/main.tsx b/tests/framework-fixtures/vite8-react-ts/files/src/main.tsx new file mode 100644 index 000000000..cf91907e6 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-ts/files/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App'; +import './styles.css'; + +createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/tests/framework-fixtures/vite8-react-ts/files/src/styles.css b/tests/framework-fixtures/vite8-react-ts/files/src/styles.css new file mode 100644 index 000000000..62fbb5e89 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-ts/files/src/styles.css @@ -0,0 +1,6 @@ +body { margin: 0; font-family: system-ui, sans-serif; } +.page { padding: 2rem; } +.hero-title { font-size: 2rem; } +.hero-hook { color: #555; } +.feature-grid { display: grid; gap: 1rem; grid-template-columns: repeat(2, 1fr); } +.feature-card { padding: 1rem; border: 1px solid #ddd; border-radius: 0.5rem; } diff --git a/tests/framework-fixtures/vite8-react-ts/files/tsconfig.json b/tests/framework-fixtures/vite8-react-ts/files/tsconfig.json new file mode 100644 index 000000000..937d8a1b7 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-ts/files/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "react-jsx", + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "isolatedModules": true, + "noEmit": true, + "types": ["vite/client"] + }, + "include": ["src", "vite.config.ts"] +} diff --git a/tests/framework-fixtures/vite8-react-ts/files/vite.config.ts b/tests/framework-fixtures/vite8-react-ts/files/vite.config.ts new file mode 100644 index 000000000..f7ad4b565 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-ts/files/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { host: '127.0.0.1', strictPort: false }, +}); diff --git a/tests/framework-fixtures/vite8-react-ts/fixture.json b/tests/framework-fixtures/vite8-react-ts/fixture.json new file mode 100644 index 000000000..fdb0896a0 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-ts/fixture.json @@ -0,0 +1,28 @@ +{ + "name": "Vite 8 + React + TypeScript", + "config": { + "files": ["index.html"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": ["index.html", "src/App.tsx", "src/main.tsx", "src/styles.css", "vite.config.ts", "tsconfig.json"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps hero title in source TSX", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "src/App.tsx" + } + ], + "runtime": { + "styling": "plain-css-ts", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "vite", "--host", "127.0.0.1"], + "readyPattern": "Local:\\s+https?://[^:]+:(\\d+)", + "readyTimeoutMs": 120000, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/vite8-react-ts/gitignore.txt b/tests/framework-fixtures/vite8-react-ts/gitignore.txt new file mode 100644 index 000000000..8cda9ad20 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-ts/gitignore.txt @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.vite/ +package-lock.json diff --git a/tests/framework-fixtures/vite8-react-unocss/files/index.html b/tests/framework-fixtures/vite8-react-unocss/files/index.html new file mode 100644 index 000000000..6b52889fa --- /dev/null +++ b/tests/framework-fixtures/vite8-react-unocss/files/index.html @@ -0,0 +1,11 @@ + + + + + Vite 8 + UnoCSS Fixture + + +
+ + + diff --git a/tests/framework-fixtures/vite8-react-unocss/files/package.json b/tests/framework-fixtures/vite8-react-unocss/files/package.json new file mode 100644 index 000000000..6c765379e --- /dev/null +++ b/tests/framework-fixtures/vite8-react-unocss/files/package.json @@ -0,0 +1,21 @@ +{ + "name": "vite8-react-unocss-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@unocss/vite": "^66.0.0", + "@vitejs/plugin-react": "^6.0.0", + "unocss": "^66.0.0", + "vite": "^8.0.0" + } +} diff --git a/tests/framework-fixtures/vite8-react-unocss/files/src/App.jsx b/tests/framework-fixtures/vite8-react-unocss/files/src/App.jsx new file mode 100644 index 000000000..fd5a2763c --- /dev/null +++ b/tests/framework-fixtures/vite8-react-unocss/files/src/App.jsx @@ -0,0 +1,12 @@ +export default function App() { + return ( +
+

Vite 8 + UnoCSS Fixture

+

Atomic CSS on demand via the UnoCSS Vite plugin.

+
+
One
+
Two
+
+
+ ); +} diff --git a/tests/framework-fixtures/vite8-react-unocss/files/src/main.jsx b/tests/framework-fixtures/vite8-react-unocss/files/src/main.jsx new file mode 100644 index 000000000..77c5dc865 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-unocss/files/src/main.jsx @@ -0,0 +1,10 @@ +import 'virtual:uno.css'; +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.jsx'; + +createRoot(document.getElementById('root')).render( + + + , +); diff --git a/tests/framework-fixtures/vite8-react-unocss/files/uno.config.js b/tests/framework-fixtures/vite8-react-unocss/files/uno.config.js new file mode 100644 index 000000000..4aa5d6e84 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-unocss/files/uno.config.js @@ -0,0 +1,5 @@ +import { defineConfig, presetUno } from 'unocss'; + +export default defineConfig({ + presets: [presetUno()], +}); diff --git a/tests/framework-fixtures/vite8-react-unocss/files/vite.config.js b/tests/framework-fixtures/vite8-react-unocss/files/vite.config.js new file mode 100644 index 000000000..aafaf3aad --- /dev/null +++ b/tests/framework-fixtures/vite8-react-unocss/files/vite.config.js @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import UnoCSS from 'unocss/vite'; + +export default defineConfig({ + plugins: [UnoCSS(), react()], + server: { host: '127.0.0.1', strictPort: false }, +}); diff --git a/tests/framework-fixtures/vite8-react-unocss/fixture.json b/tests/framework-fixtures/vite8-react-unocss/fixture.json new file mode 100644 index 000000000..71cd4eb9d --- /dev/null +++ b/tests/framework-fixtures/vite8-react-unocss/fixture.json @@ -0,0 +1,28 @@ +{ + "name": "Vite 8 + React + UnoCSS", + "config": { + "files": ["index.html"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": ["index.html", "src/App.jsx", "src/main.jsx", "vite.config.js", "uno.config.js"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps hero title in source JSX", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "src/App.jsx" + } + ], + "runtime": { + "styling": "unocss", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "vite", "--host", "127.0.0.1"], + "readyPattern": "Local:\\s+https?://[^:]+:(\\d+)", + "readyTimeoutMs": 120000, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/vite8-react-unocss/gitignore.txt b/tests/framework-fixtures/vite8-react-unocss/gitignore.txt new file mode 100644 index 000000000..8cda9ad20 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-unocss/gitignore.txt @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.vite/ +package-lock.json diff --git a/tests/framework-fixtures/vite8-react-vanilla-extract/files/index.html b/tests/framework-fixtures/vite8-react-vanilla-extract/files/index.html new file mode 100644 index 000000000..61b1b20bd --- /dev/null +++ b/tests/framework-fixtures/vite8-react-vanilla-extract/files/index.html @@ -0,0 +1,11 @@ + + + + + Vite 8 + vanilla-extract Fixture + + +
+ + + diff --git a/tests/framework-fixtures/vite8-react-vanilla-extract/files/package.json b/tests/framework-fixtures/vite8-react-vanilla-extract/files/package.json new file mode 100644 index 000000000..0e4a6c36c --- /dev/null +++ b/tests/framework-fixtures/vite8-react-vanilla-extract/files/package.json @@ -0,0 +1,21 @@ +{ + "name": "vite8-react-vanilla-extract-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@vanilla-extract/css": "^1.20.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@vanilla-extract/vite-plugin": "^5.2.0", + "@vitejs/plugin-react": "^6.0.0", + "vite": "^8.0.0" + } +} diff --git a/tests/framework-fixtures/vite8-react-vanilla-extract/files/src/App.jsx b/tests/framework-fixtures/vite8-react-vanilla-extract/files/src/App.jsx new file mode 100644 index 000000000..37aa66c81 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-vanilla-extract/files/src/App.jsx @@ -0,0 +1,14 @@ +import * as s from './styles.css.js'; + +export default function App() { + return ( +
+

Vite 8 + vanilla-extract Fixture

+

Zero-runtime CSS-in-TS via a Vite plugin.

+
+
One
+
Two
+
+
+ ); +} diff --git a/tests/framework-fixtures/vite8-react-vanilla-extract/files/src/main.jsx b/tests/framework-fixtures/vite8-react-vanilla-extract/files/src/main.jsx new file mode 100644 index 000000000..6dab183de --- /dev/null +++ b/tests/framework-fixtures/vite8-react-vanilla-extract/files/src/main.jsx @@ -0,0 +1,9 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.jsx'; + +createRoot(document.getElementById('root')).render( + + + , +); diff --git a/tests/framework-fixtures/vite8-react-vanilla-extract/files/src/styles.css.js b/tests/framework-fixtures/vite8-react-vanilla-extract/files/src/styles.css.js new file mode 100644 index 000000000..64386687c --- /dev/null +++ b/tests/framework-fixtures/vite8-react-vanilla-extract/files/src/styles.css.js @@ -0,0 +1,20 @@ +import { style } from '@vanilla-extract/css'; + +export const page = style({ + padding: '2rem', + fontFamily: 'system-ui, sans-serif', +}); + +export const heroTitle = style({ fontSize: '2rem' }); +export const heroHook = style({ color: '#555' }); +export const featureGrid = style({ + display: 'grid', + gap: '1rem', + gridTemplateColumns: 'repeat(2, 1fr)', + marginTop: '1rem', +}); +export const featureCard = style({ + padding: '1rem', + border: '1px solid #ddd', + borderRadius: '0.5rem', +}); diff --git a/tests/framework-fixtures/vite8-react-vanilla-extract/files/vite.config.js b/tests/framework-fixtures/vite8-react-vanilla-extract/files/vite.config.js new file mode 100644 index 000000000..5469db8db --- /dev/null +++ b/tests/framework-fixtures/vite8-react-vanilla-extract/files/vite.config.js @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin'; + +export default defineConfig({ + plugins: [react(), vanillaExtractPlugin()], + server: { host: '127.0.0.1', strictPort: false }, +}); diff --git a/tests/framework-fixtures/vite8-react-vanilla-extract/fixture.json b/tests/framework-fixtures/vite8-react-vanilla-extract/fixture.json new file mode 100644 index 000000000..413314f8e --- /dev/null +++ b/tests/framework-fixtures/vite8-react-vanilla-extract/fixture.json @@ -0,0 +1,28 @@ +{ + "name": "Vite 8 + React + vanilla-extract", + "config": { + "files": ["index.html"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": ["index.html", "src/App.jsx", "src/main.jsx", "src/styles.css.js", "vite.config.js"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps hero title in source JSX", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "src/App.jsx" + } + ], + "runtime": { + "styling": "vanilla-extract", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "vite", "--host", "127.0.0.1"], + "readyPattern": "Local:\\s+https?://[^:]+:(\\d+)", + "readyTimeoutMs": 120000, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/vite8-react-vanilla-extract/gitignore.txt b/tests/framework-fixtures/vite8-react-vanilla-extract/gitignore.txt new file mode 100644 index 000000000..8cda9ad20 --- /dev/null +++ b/tests/framework-fixtures/vite8-react-vanilla-extract/gitignore.txt @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.vite/ +package-lock.json diff --git a/tests/framework-fixtures/vite8-sveltekit/files/package.json b/tests/framework-fixtures/vite8-sveltekit/files/package.json new file mode 100644 index 000000000..0efe6f940 --- /dev/null +++ b/tests/framework-fixtures/vite8-sveltekit/files/package.json @@ -0,0 +1,18 @@ +{ + "name": "vite8-sveltekit-fixture", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite dev --host 127.0.0.1", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^7.0.0", + "@sveltejs/kit": "^2.58.0", + "@sveltejs/vite-plugin-svelte": "^7.0.0", + "svelte": "^5.0.0", + "vite": "^8.0.0" + } +} diff --git a/tests/framework-fixtures/vite8-sveltekit/files/src/app.html b/tests/framework-fixtures/vite8-sveltekit/files/src/app.html new file mode 100644 index 000000000..d4c1b17c6 --- /dev/null +++ b/tests/framework-fixtures/vite8-sveltekit/files/src/app.html @@ -0,0 +1,11 @@ + + + + + Vite 8 + SvelteKit Fixture + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/tests/framework-fixtures/vite8-sveltekit/files/src/routes/+layout.svelte b/tests/framework-fixtures/vite8-sveltekit/files/src/routes/+layout.svelte new file mode 100644 index 000000000..6466f5d07 --- /dev/null +++ b/tests/framework-fixtures/vite8-sveltekit/files/src/routes/+layout.svelte @@ -0,0 +1,5 @@ + + +{@render children?.()} diff --git a/tests/framework-fixtures/vite8-sveltekit/files/src/routes/+page.svelte b/tests/framework-fixtures/vite8-sveltekit/files/src/routes/+page.svelte new file mode 100644 index 000000000..83e4c5545 --- /dev/null +++ b/tests/framework-fixtures/vite8-sveltekit/files/src/routes/+page.svelte @@ -0,0 +1,8 @@ +
+

Vite 8 + SvelteKit Fixture

+

Minimal SvelteKit route for live-mode E2E tests.

+
+
One
+
Two
+
+
diff --git a/tests/framework-fixtures/vite8-sveltekit/files/svelte.config.js b/tests/framework-fixtures/vite8-sveltekit/files/svelte.config.js new file mode 100644 index 000000000..bbc8b7164 --- /dev/null +++ b/tests/framework-fixtures/vite8-sveltekit/files/svelte.config.js @@ -0,0 +1,7 @@ +import adapter from '@sveltejs/adapter-auto'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +export default { + preprocess: vitePreprocess(), + kit: { adapter: adapter() }, +}; diff --git a/tests/framework-fixtures/vite8-sveltekit/files/vite.config.js b/tests/framework-fixtures/vite8-sveltekit/files/vite.config.js new file mode 100644 index 000000000..05feadbc1 --- /dev/null +++ b/tests/framework-fixtures/vite8-sveltekit/files/vite.config.js @@ -0,0 +1,7 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [sveltekit()], + server: { host: '127.0.0.1', strictPort: false }, +}); diff --git a/tests/framework-fixtures/vite8-sveltekit/fixture.json b/tests/framework-fixtures/vite8-sveltekit/fixture.json new file mode 100644 index 000000000..656fca870 --- /dev/null +++ b/tests/framework-fixtures/vite8-sveltekit/fixture.json @@ -0,0 +1,28 @@ +{ + "name": "Vite 8 + SvelteKit 2 + Svelte 5", + "config": { + "files": ["src/app.html"], + "insertBefore": "", + "commentSyntax": "html" + }, + "sourceFiles": ["src/app.html", "src/routes/+page.svelte", "src/routes/+layout.svelte", "svelte.config.js", "vite.config.js"], + "generatedFiles": [], + "wrapCases": [ + { + "name": "wraps hero title in route source", + "args": { "classes": "hero-title", "tag": "h1" }, + "expectedFile": "src/routes/+page.svelte" + } + ], + "runtime": { + "styling": "plain-css", + "install": ["npm", "install", "--no-audit", "--no-fund", "--loglevel=error"], + "devCommand": ["npx", "vite", "dev", "--host", "127.0.0.1"], + "readyPattern": "Local:\\s+https?://[^:]+:(\\d+)", + "readyTimeoutMs": 120000, + "probe": { + "expectLiveInit": true, + "expectConsoleClean": true + } + } +} diff --git a/tests/framework-fixtures/vite8-sveltekit/gitignore.txt b/tests/framework-fixtures/vite8-sveltekit/gitignore.txt new file mode 100644 index 000000000..7645cd701 --- /dev/null +++ b/tests/framework-fixtures/vite8-sveltekit/gitignore.txt @@ -0,0 +1,5 @@ +node_modules/ +.svelte-kit/ +build/ +dist/ +package-lock.json diff --git a/tests/live-e2e.test.mjs b/tests/live-e2e.test.mjs new file mode 100644 index 000000000..46edecfe9 --- /dev/null +++ b/tests/live-e2e.test.mjs @@ -0,0 +1,405 @@ +/** + * End-to-end live-mode tests — full click-to-accept cycle. + * + * For every framework fixture with a `runtime` block in fixture.json, this + * runner exercises the entire user-visible chain: + * + * 1. Stage → install → start live-server + dev server → inject script tag + * 2. Open Playwright Chromium, assert the live handshake fires + * 3. Spawn a deterministic fake-agent polling loop in this same process + * 4. Drive the bar UI: pick element → Go → wait CYCLING → cycle → Accept + * 5. Assert source rewrite (variants block, then accepted-only after accept) + * 6. Assert DOM reflects the accepted variant via getComputedStyle + * 7. Tear down (browser, dev server, agent loop, live-server, tmp) + * + * The fake agent is pluggable — see tests/live-e2e/agent.mjs. A future + * LLM-backed agent slots in by implementing the same VariantAgent interface. + * + * Run with: bun run test:live-e2e + */ + +import { describe, it, before, after } from 'node:test'; +import assert from 'node:assert/strict'; +import { existsSync, readdirSync, readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { createFakeAgent } from './live-e2e/agent.mjs'; +import { bootFixtureSession, FIXTURES_DIR } from './live-e2e/session.mjs'; +import { + clickAccept, + clickGo, + clickNext, + getVisibleVariant, + pickElement, + waitForCycling, + waitForHandshake, +} from './live-e2e/ui.mjs'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +// Discover fixtures that opt into the runtime E2E pass. +function listRuntimeFixtures() { + const names = readdirSync(FIXTURES_DIR, { withFileTypes: true }) + .filter((e) => e.isDirectory()) + .map((e) => e.name); + + const out = []; + for (const name of names) { + const fixturePath = join(FIXTURES_DIR, name, 'fixture.json'); + if (!existsSync(fixturePath)) continue; + const fixture = JSON.parse(readFileSync(fixturePath, 'utf-8')); + if (fixture.runtime) out.push({ name, fixture }); + } + return out; +} + +const allFixtures = listRuntimeFixtures(); + +// During development of the full-cycle test, a single fixture is much faster +// to iterate on. Set IMPECCABLE_E2E_ONLY= to scope the run. +const onlyName = process.env.IMPECCABLE_E2E_ONLY; +const fixtures = onlyName + ? allFixtures.filter((f) => f.name === onlyName) + : allFixtures; + +if (fixtures.length === 0) { + describe('live-e2e (no runtime fixtures registered)', () => { + it('is a no-op', () => assert.ok(true)); + }); +} + +let playwright; +let browser; + +before(async () => { + if (fixtures.length === 0) return; + try { + playwright = await import('playwright'); + } catch (err) { + throw new Error( + `Playwright is required for live-e2e tests (${err.message}). Run: npx playwright install chromium`, + ); + } + try { + browser = await playwright.chromium.launch({ headless: true }); + } catch (err) { + throw new Error(`Failed to launch Chromium (${err.message}). Run: npx playwright install chromium`); + } +}); + +after(async () => { + if (browser) await browser.close(); +}); + +for (const { name, fixture } of fixtures) { + describe(`live-e2e · ${name} (${fixture.runtime.styling || 'unknown-styling'})`, () => { + it('drives the full click → Go → cycle → accept cycle', async (t) => { + // Fixtures may declare `runtime.knownLimitation` to flag a scenario + // that exposes a genuine live-mode gap rather than a test bug. The + // test still attempts the full chain but does not fail the suite when + // the documented failure mode appears — it surfaces the diagnostic so + // the limitation is visible in the run output. + const knownLimitation = fixture.runtime.knownLimitation; + + t.diagnostic(`Booting fixture ${name}`); + const session = await bootFixtureSession({ + name, + fixture, + browser, + agent: createFakeAgent(), + log: (m) => t.diagnostic(m), + }); + + const { page, tmp, consoleErrors, teardown } = session; + const expectedCount = 3; + const pickSelector = fixture.runtime.pickSelector || 'h1.hero-title'; + + try { + // 1. Handshake + t.diagnostic('Waiting for live handshake'); + await waitForHandshake(page); + + // 2. preActions — fixtures with hidden/conditional content (modals, + // tabs, routes) drive the page into the right state before pick. + if (fixture.runtime.preActions) { + t.diagnostic(`Running ${fixture.runtime.preActions.length} preAction(s)`); + await runPreActions(page, fixture.runtime.preActions); + } + + // 3. Pick the target element + t.diagnostic(`Picking ${pickSelector}`); + await pickElement(page, pickSelector); + + if (process.env.IMPECCABLE_E2E_DEBUG) { + const barText = await page.evaluate(() => { + const bar = document.querySelector('#impeccable-live-bar'); + return bar ? { display: bar.style.display, text: bar.textContent || '', html: bar.innerHTML.slice(0, 500) } : null; + }); + t.diagnostic(`Bar after pick: ${JSON.stringify(barText)}`); + } + + // 3. Click Go (default action 'impeccable', default count 3 — fixture-stable) + t.diagnostic('Clicking Go'); + await clickGo(page); + + // 4. Wait for the agent's variants to land (HMR + MutationObserver). + // For fixtures whose picked element lives inside a conditional + // render (modal, tab, route), HMR can remount the parent and lose + // the open/active state — the wrapper exists in source but isn't + // in the DOM, so MutationObserver never fires. Live mode now + // surfaces a toast asking the user to retrace the path; we mirror + // that here by re-running preActions on the first short timeout. + t.diagnostic(`Waiting for CYCLING state with ${expectedCount} variants`); + let cyclingReached = false; + if (fixture.runtime.preActions) { + try { + await waitForCycling(page, expectedCount, { timeout: 5_000 }); + cyclingReached = true; + } catch { + t.diagnostic('Cycling not reached in 5s — retracing preActions'); + await runPreActions(page, fixture.runtime.preActions); + } + } + try { + if (!cyclingReached) await waitForCycling(page, expectedCount); + } catch (err) { + if (process.env.IMPECCABLE_E2E_DEBUG) { + const variantCount = await page.evaluate(() => + document.querySelectorAll('[data-impeccable-variant]').length, + ); + const barInfo = await page.evaluate(() => { + const bars = document.querySelectorAll('#impeccable-live-bar'); + return { + count: bars.length, + bars: [...bars].map((bar) => ({ + display: bar.style.display, + opacity: bar.style.opacity, + text: bar.textContent || '', + innerHtml: bar.innerHTML.slice(0, 600), + })), + __init: window.__IMPECCABLE_LIVE_INIT__, + }; + }); + t.diagnostic(`waitForCycling failed; variants in DOM: ${variantCount}`); + t.diagnostic(`Bar state: ${JSON.stringify(barInfo)}`); + t.diagnostic(`--- dev server tail ---\n${session.dev.log()}`); + } + throw err; + } + + // 5. Source-side check: wrapper + style + variants are present + const sourceFile = await locateSessionFile(tmp); + const after = readFileSync(sourceFile, 'utf-8'); + assert.match(after, /data-impeccable-variants="/, 'wrapper inserted'); + assert.match(after, / wraps CSS in a template literal so JSX + * doesn't choke on the {} in CSS + * - non-default visible variants use style={{display: 'none'}} + * - inner element class= becomes className= + * - data-impeccable-params stays a single-quoted JSON string (JSX-legal) + */ +function renderVariantsBlock({ sessionId, indent, output, commentSyntax, file }) { + const isJsx = commentSyntax.open === '{/*'; + const isSvelte = !!file && file.endsWith('.svelte'); + + const styleLines = isJsx + ? [ + indent + ' ', + ] + : [ + indent + ' ', + ]; + + const variantBlocks = output.variants.map((v, i) => { + const idx = i + 1; + const paramsAttr = v.params && v.params.length + ? " data-impeccable-params='" + attrEscape(JSON.stringify(v.params), { svelte: isSvelte }) + "'" + : ''; + let styleAttr = ''; + if (i !== 0) styleAttr = isJsx ? " style={{display: 'none'}}" : ' style="display: none"'; + const inner = isJsx ? htmlToJsx(v.innerHtml) : v.innerHtml; + return [ + indent + ' ' + commentSyntax.open + ' Variant ' + idx + ' ' + commentSyntax.close, + indent + '
', + indent + ' ' + inner, + indent + '
', + ].join('\n'); + }); + + return [...styleLines, ...variantBlocks].join('\n'); +} + +/** + * Read the wrapped file, find the "insert below this line" marker, splice in + * the rendered variants block, write back. + */ +async function spliceVariantsIntoWrapper({ tmp, wrapInfo, sessionId, output }) { + const filePath = path.join(tmp, wrapInfo.file); + const src = await fs.readFile(filePath, 'utf-8'); + const lines = src.split('\n'); + + // Find the "Variants: insert below this line" comment line — definitive + // marker, robust to any indentation off-by-one. Matches in any comment + // style (HTML / JSX / Astro). + const markerIdx = lines.findIndex((l) => + l.includes('Variants: insert below this line'), + ); + if (markerIdx === -1) { + throw new Error('insert marker not found in ' + wrapInfo.file); + } + + const indent = (lines[markerIdx].match(/^\s*/) || [''])[0]; + // Indent INSIDE the wrapper is one level shallower (the marker is indented + // 2 spaces relative to the wrapper opening). Remove the 2-space comment + // indent to get the wrapper indent. + const wrapperIndent = indent.replace(/ $/, ''); + + const block = renderVariantsBlock({ + sessionId, + indent: wrapperIndent, + output, + commentSyntax: wrapInfo.commentSyntax, + file: wrapInfo.file, + }); + + const next = [ + ...lines.slice(0, markerIdx + 1), + block, + ...lines.slice(markerIdx + 1), + ]; + await fs.writeFile(filePath, next.join('\n'), 'utf-8'); +} + +// --------------------------------------------------------------------------- +// Poll loop — the "agent" runs this until aborted +// --------------------------------------------------------------------------- + +/** + * @param {object} opts + * @param {string} opts.tmp Project tmp dir (cwd for live-* scripts). + * @param {string} opts.scriptsDir Path to the impeccable scripts dir. + * @param {number} opts.port live-server port. + * @param {string} opts.token live-server token. + * @param {VariantAgent} opts.agent + * @param {AbortSignal} opts.signal + * @param {(msg: string) => void} [opts.log] + * @param {object} [opts.wrapTarget] Default target for live-wrap when an + * element comes from the picker without an + * id we can resolve. e.g. {classes:'hero-title', tag:'h1'}. + */ +export async function runAgentLoop({ + tmp, + scriptsDir, + port, + token, + agent, + signal, + log = () => {}, + wrapTarget = { classes: 'hero-title', tag: 'h1' }, +}) { + const base = `http://127.0.0.1:${port}`; + + while (!signal.aborted) { + let event; + try { + const res = await fetch(`${base}/poll?token=${token}&timeout=5000`, { signal }); + event = await res.json(); + } catch (err) { + if (signal.aborted) return; + log('poll error: ' + err.message); + await new Promise((r) => setTimeout(r, 200)); + continue; + } + + if (event.type === 'timeout') continue; + if (event.type === 'exit') return; + if (event.type === 'prefetch') continue; + if (event.type === 'connected') continue; + + if (event.type === 'generate') { + log(`generate id=${event.id} action=${event.action} count=${event.count}`); + try { + // 1. Wrap the original element in the variant scaffold (deterministic CLI) + const wrapInfo = await runWrap({ + tmp, + scriptsDir, + id: event.id, + count: event.count, + ...wrapTarget, + }); + log(`wrapped: ${wrapInfo.file} insertLine=${wrapInfo.insertLine}`); + + // 2. Agent generates variant content (LLM-pluggable seam) + const output = await agent.generateVariants(event, { wrapTarget }); + if (output.variants.length !== event.count) { + log(`warning: agent returned ${output.variants.length} variants, expected ${event.count}`); + } + + // 3. Splice variants block into the wrapper (deterministic fs) + await spliceVariantsIntoWrapper({ tmp, wrapInfo, sessionId: event.id, output }); + if (process.env.IMPECCABLE_E2E_DEBUG) { + const post = await fs.readFile(path.join(tmp, wrapInfo.file), 'utf-8'); + log(`--- post-splice (variants written) ---\n${post}`); + } + + // 4. Tell the server we're done (broadcasts SSE done → browser settles to CYCLING) + await fetch(`${base}/poll`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ token, type: 'done', id: event.id }), + signal, + }); + } catch (err) { + if (signal.aborted) return; + log('generate failed: ' + err.message); + await fetch(`${base}/poll`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ token, type: 'error', id: event.id, message: err.message }), + signal, + }).catch(() => {}); + } + continue; + } + + if (event.type === 'accept') { + log(`accept id=${event.id} variantId=${event.variantId}`); + try { + const acceptResult = await runAccept({ + tmp, + scriptsDir, + id: event.id, + variant: event.variantId, + paramValues: event.paramValues, + }); + + // Carbonize cleanup — required after accept per the live skill spec. + // For the fake agent, we perform a faithful but minimal cleanup: + // delete the carbonize block (markers + dead variants + inline