Skip to content

Commit a91ad6d

Browse files
committed
test(e2e): cover static-build surface and drop counter example
Each example now runs through Playwright twice — once against the live CLI dev server (WS RPC, actions, streaming) and once against the static-build output served as plain files (pre-computed `static` / `query{snapshot:true}` dumps, `backend: 'static'` connection meta). The static surface is exactly what `node bin.mjs build --out-dir ...` ships to a CDN, so the new specs guard the deploy contract end-to-end. Static dumps are served by a tiny zero-dep `tests/e2e/_support/ serve-static.mjs` script with SPA fallback. streaming-chat's static specs intentionally only cover the demo-prompts list and the static backend marker — `send`/`clear` are actions and never reach a static dump. The counter example was removed; it was too thin to carry useful coverage and its dev mode runs in bridge mode (no SPA mount), so the e2e suite couldn't drive it without bespoke wiring.
1 parent aecb193 commit a91ad6d

15 files changed

Lines changed: 170 additions & 152 deletions

docs/guide/built-with.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,5 @@ Real-world devtools shipping on Devframe:
1212

1313
End-to-end examples in this repo, exercising the full adapter surface:
1414

15-
- [**counter**](https://github.com/devframes/devframe/tree/main/examples/counter) — smallest possible demo, exercises all adapters.
1615
- [**files-inspector**](https://github.com/devframes/devframe/tree/main/examples/files-inspector) — lists files in cwd via RPC; exercises CLI dev/build/spa surfaces.
1716
- [**streaming-chat**](https://github.com/devframes/devframe/tree/main/examples/streaming-chat) — streams synthetic chat tokens from server to client via `ctx.rpc.streaming`.

examples/counter/bin.mjs

Lines changed: 0 additions & 14 deletions
This file was deleted.

examples/counter/package.json

Lines changed: 0 additions & 18 deletions
This file was deleted.

examples/counter/src/client/index.html

Lines changed: 0 additions & 13 deletions
This file was deleted.

examples/counter/src/client/main.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.

examples/counter/src/devframe.ts

Lines changed: 0 additions & 77 deletions
This file was deleted.

examples/streaming-chat/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
},
1111
"scripts": {
1212
"build": "vite build --config src/client/vite.config.ts",
13+
"cli:build": "node bin.mjs build --out-dir dist/static",
1314
"dev": "node bin.mjs",
1415
"test": "vitest run"
1516
},

playwright.config.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import { fileURLToPath } from 'node:url'
33
import { defineConfig, devices } from '@playwright/test'
44

55
const fixtureCwd = fileURLToPath(new URL('./tests/e2e/fixtures', import.meta.url))
6+
const serveStatic = fileURLToPath(new URL('./tests/e2e/_support/serve-static.mjs', import.meta.url))
67

78
export default defineConfig({
89
testDir: './tests/e2e',
10+
testIgnore: ['_support/**'],
911
fullyParallel: true,
1012
forbidOnly: !!process.env.CI,
1113
retries: process.env.CI ? 2 : 0,
@@ -39,5 +41,24 @@ export default defineConfig({
3941
stdout: 'pipe',
4042
stderr: 'pipe',
4143
},
44+
{
45+
command: `node bin.mjs build --out-dir dist/static && node ${JSON.stringify(serveStatic)} dist/static 9886`,
46+
cwd: 'examples/files-inspector',
47+
env: { DEVFRAME_E2E_CWD: fixtureCwd },
48+
url: 'http://127.0.0.1:9886/',
49+
timeout: 60_000,
50+
reuseExistingServer: !process.env.CI,
51+
stdout: 'pipe',
52+
stderr: 'pipe',
53+
},
54+
{
55+
command: `node bin.mjs build --out-dir dist/static && node ${JSON.stringify(serveStatic)} dist/static 9898`,
56+
cwd: 'examples/streaming-chat',
57+
url: 'http://127.0.0.1:9898/',
58+
timeout: 60_000,
59+
reuseExistingServer: !process.env.CI,
60+
stdout: 'pipe',
61+
stderr: 'pipe',
62+
},
4263
],
4364
})

pnpm-lock.yaml

Lines changed: 0 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env node
2+
import { createReadStream, statSync } from 'node:fs'
3+
import { createServer } from 'node:http'
4+
import { extname, join, normalize, resolve } from 'node:path'
5+
import process from 'node:process'
6+
7+
const [, , dirArg, portArg] = process.argv
8+
if (!dirArg || !portArg) {
9+
console.error('Usage: serve-static.mjs <dir> <port>')
10+
process.exit(1)
11+
}
12+
13+
const root = resolve(process.cwd(), dirArg)
14+
const port = Number(portArg)
15+
const host = '127.0.0.1'
16+
17+
const MIME = {
18+
'.html': 'text/html; charset=utf-8',
19+
'.js': 'text/javascript; charset=utf-8',
20+
'.mjs': 'text/javascript; charset=utf-8',
21+
'.css': 'text/css; charset=utf-8',
22+
'.json': 'application/json; charset=utf-8',
23+
'.svg': 'image/svg+xml',
24+
'.png': 'image/png',
25+
'.ico': 'image/x-icon',
26+
'.map': 'application/json; charset=utf-8',
27+
}
28+
29+
function resolveAsset(urlPath) {
30+
// Strip query/hash.
31+
const clean = urlPath.split('?')[0].split('#')[0]
32+
const decoded = decodeURIComponent(clean)
33+
// Treat trailing-slash URLs as index.html.
34+
const relative = decoded.endsWith('/') ? `${decoded}index.html` : decoded
35+
const target = normalize(join(root, relative))
36+
if (!target.startsWith(root))
37+
return null
38+
try {
39+
const st = statSync(target)
40+
if (st.isFile())
41+
return target
42+
}
43+
catch {}
44+
return null
45+
}
46+
47+
function send(res, status, body, headers = {}) {
48+
res.writeHead(status, headers)
49+
res.end(body)
50+
}
51+
52+
const server = createServer((req, res) => {
53+
const found = resolveAsset(req.url ?? '/')
54+
// SPA fallback — unknown routes (no file extension match) serve index.html
55+
// so client-side routing works under the static dump.
56+
const file = found ?? resolveAsset('/index.html')
57+
if (!file) {
58+
send(res, 404, 'Not Found')
59+
return
60+
}
61+
const type = MIME[extname(file)] ?? 'application/octet-stream'
62+
res.writeHead(200, { 'Content-Type': type })
63+
createReadStream(file).pipe(res)
64+
})
65+
66+
server.listen(port, host, () => {
67+
process.stdout.write(`[serve-static] http://${host}:${port}/ -> ${root}\n`)
68+
})
69+
70+
for (const sig of ['SIGINT', 'SIGTERM']) {
71+
process.on(sig, () => {
72+
server.close(() => process.exit(0))
73+
})
74+
}

0 commit comments

Comments
 (0)