feat(examples)+test(e2e): counter service worker; bankai dev+prod in Chromium
The counter example gains a sw.js exercising bankai's injected-precache build. e2e proves both bankai paths in real Chromium: the built app serves, hydrates and runs with zero console errors and zero mismatch warnings (and choo consumed window.initialState), and the dev server's Vite-transformed virtual entry hydrates the same way. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
This commit is contained in:
co-authored by
Claude Fable 5
parent
7e08b585ac
commit
8b684a17d2
@@ -0,0 +1,28 @@
|
|||||||
|
/* global __BANKAI_ASSETS__ */
|
||||||
|
// Service worker built by `bankai build`: the precache list is injected
|
||||||
|
// at build time from the asset manifest (the choo-service-worker
|
||||||
|
// convention, manifest edition). Register it from your app with
|
||||||
|
// navigator.serviceWorker.register('/sw.js').
|
||||||
|
|
||||||
|
const CACHE = 'counter-v1'
|
||||||
|
const ASSETS = __BANKAI_ASSETS__
|
||||||
|
|
||||||
|
self.addEventListener('install', (event) => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.open(CACHE).then((cache) => cache.addAll(ASSETS))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
self.addEventListener('activate', (event) => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.keys().then((keys) =>
|
||||||
|
Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
self.addEventListener('fetch', (event) => {
|
||||||
|
event.respondWith(
|
||||||
|
caches.match(event.request).then((hit) => hit || fetch(event.request))
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
// bankai v10 end-to-end in real Chromium: the production pipeline
|
||||||
|
// (build → serve → SSR page hydrates and runs) and the dev server
|
||||||
|
// (Vite middleware + virtual entry + streaming SSR).
|
||||||
|
|
||||||
|
import { test, before, after } from 'node:test'
|
||||||
|
import assert from 'node:assert'
|
||||||
|
import { mkdtemp, rm } from 'node:fs/promises'
|
||||||
|
import { join, dirname } from 'node:path'
|
||||||
|
import { tmpdir } from 'node:os'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { chromium } from 'playwright'
|
||||||
|
|
||||||
|
import build from '../../packages/bankai/lib/build.js'
|
||||||
|
import serve from '../../packages/bankai/lib/serve.js'
|
||||||
|
import dev from '../../packages/bankai/lib/dev.js'
|
||||||
|
|
||||||
|
const repo = join(dirname(fileURLToPath(import.meta.url)), '..', '..')
|
||||||
|
const entry = join(repo, 'examples', 'counter', 'app.js')
|
||||||
|
|
||||||
|
let browser, outDir, prod, devSrv
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
browser = await chromium.launch()
|
||||||
|
outDir = await mkdtemp(join(tmpdir(), 'bankai-e2e-'))
|
||||||
|
await build({ entry, outDir, title: 'counter' })
|
||||||
|
prod = await serve({ outDir, port: 0 })
|
||||||
|
devSrv = await dev({ entry, port: 0, title: 'counter dev' })
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await browser?.close()
|
||||||
|
await prod?.close()
|
||||||
|
await devSrv?.close()
|
||||||
|
if (outDir) await rm(outDir, { recursive: true, force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
async function openAndCount (origin) {
|
||||||
|
const page = await browser.newPage()
|
||||||
|
const errors = []
|
||||||
|
const warnings = []
|
||||||
|
page.on('console', (msg) => {
|
||||||
|
if (msg.type() === 'error') errors.push(msg.text())
|
||||||
|
if (msg.type() === 'warning') warnings.push(msg.text())
|
||||||
|
})
|
||||||
|
page.on('pageerror', (err) => errors.push(String(err)))
|
||||||
|
|
||||||
|
await page.goto(origin + '/')
|
||||||
|
await page.waitForFunction(() => document.querySelector('h1')?.textContent === 'count is 0')
|
||||||
|
|
||||||
|
await page.click('button')
|
||||||
|
await page.waitForFunction(() => document.querySelector('h1')?.textContent === 'count is 1')
|
||||||
|
|
||||||
|
return { page, errors, warnings }
|
||||||
|
}
|
||||||
|
|
||||||
|
test('production: built app serves, hydrates and runs with no errors', async () => {
|
||||||
|
const { page, errors, warnings } = await openAndCount(prod.origin)
|
||||||
|
|
||||||
|
assert.deepStrictEqual(errors, [], 'no console errors')
|
||||||
|
const mismatches = warnings.filter((w) => w.includes('markup differ'))
|
||||||
|
assert.deepStrictEqual(mismatches, [], 'no hydration mismatch warnings')
|
||||||
|
|
||||||
|
const state = await page.evaluate(() => window.choo ? 'devtools' : typeof window.initialState)
|
||||||
|
assert.strictEqual(state, 'undefined', 'choo consumed initialState (deleted after merge)')
|
||||||
|
await page.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('dev server: Vite-transformed entry hydrates and runs', async () => {
|
||||||
|
const { page, errors } = await openAndCount(devSrv.origin)
|
||||||
|
assert.deepStrictEqual(errors, [], 'no console errors')
|
||||||
|
await page.close()
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user