Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit c4e0c1a

Browse files
authored
Merge pull request #19 from pyreon/add-integration-tests
Add integration tests: boot Vite, resolve routes, serve pages
2 parents 8a02fb2 + b26e034 commit c4e0c1a

7 files changed

Lines changed: 172 additions & 0 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
dist
3+
.pyreon
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Zero Integration Test</title>
7+
</head>
8+
<body>
9+
<div id="app"><!--pyreon-app--></div>
10+
<!--pyreon-scripts-->
11+
</body>
12+
</html>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export default function About() {
2+
return (
3+
<div>
4+
<h1>About</h1>
5+
<p>Integration test page</p>
6+
</div>
7+
)
8+
}
9+
10+
export const meta = {
11+
title: 'About',
12+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function GET() {
2+
return Response.json({ status: 'ok' })
3+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function Home() {
2+
return <h1>Hello from Zero</h1>
3+
}
4+
5+
export const meta = {
6+
title: 'Home',
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { LoaderContext } from '@pyreon/zero'
2+
3+
export async function loader(ctx: LoaderContext) {
4+
return { userId: ctx.params.id, name: `User ${ctx.params.id}` }
5+
}
6+
7+
export default function UserPage() {
8+
return <h1>User Page</h1>
9+
}
10+
11+
export const meta = {
12+
title: 'User',
13+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { resolve } from 'node:path'
2+
import { createServer, type ViteDevServer } from 'vite'
3+
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
4+
import { zeroPlugin } from '../../vite-plugin'
5+
6+
const FIXTURE_DIR = resolve(import.meta.dirname, 'fixture')
7+
8+
let server: ViteDevServer
9+
let baseUrl: string
10+
11+
beforeAll(async () => {
12+
server = await createServer({
13+
root: FIXTURE_DIR,
14+
configFile: false, // Don't load vite.config.ts — configure inline
15+
plugins: [zeroPlugin({ mode: 'ssr' })],
16+
server: { port: 0 },
17+
logLevel: 'silent',
18+
})
19+
await server.listen()
20+
const address = server.httpServer?.address()
21+
if (address && typeof address === 'object') {
22+
baseUrl = `http://localhost:${address.port}`
23+
}
24+
}, 30_000)
25+
26+
afterAll(async () => {
27+
await server?.close()
28+
})
29+
30+
describe('SSR integration', () => {
31+
it('boots the Vite dev server', () => {
32+
expect(baseUrl).toBeDefined()
33+
expect(baseUrl).toMatch(/^http:\/\/localhost:\d+$/)
34+
})
35+
36+
it('resolves virtual:zero/routes module', async () => {
37+
const mod = await server.ssrLoadModule('virtual:zero/routes')
38+
expect(mod.routes).toBeDefined()
39+
expect(Array.isArray(mod.routes)).toBe(true)
40+
expect(mod.routes.length).toBeGreaterThan(0)
41+
})
42+
43+
it('generates routes for fixture pages', async () => {
44+
const mod = await server.ssrLoadModule('virtual:zero/routes')
45+
const paths = flattenPaths(mod.routes)
46+
expect(paths).toContain('/')
47+
expect(paths).toContain('/about')
48+
})
49+
50+
it('generates route for dynamic [id] param', async () => {
51+
const mod = await server.ssrLoadModule('virtual:zero/routes')
52+
const paths = flattenPaths(mod.routes)
53+
expect(paths.some((p: string) => p.includes(':id'))).toBe(true)
54+
})
55+
56+
it('wires renderMode into route meta', async () => {
57+
const mod = await server.ssrLoadModule('virtual:zero/routes')
58+
const route = mod.routes.find((r: { path: string }) => r.path === '/')
59+
expect(route).toBeDefined()
60+
expect(route.meta).toBeDefined()
61+
})
62+
63+
it('serves index.html on GET /', async () => {
64+
const res = await fetch(`${baseUrl}/`)
65+
expect(res.status).toBe(200)
66+
const html = await res.text()
67+
expect(html).toContain('<div id="app">')
68+
})
69+
70+
it('serves the about page', async () => {
71+
const res = await fetch(`${baseUrl}/about`)
72+
expect(res.status).toBe(200)
73+
const html = await res.text()
74+
expect(html).toContain('<!DOCTYPE html>')
75+
})
76+
77+
it('returns HTML for dynamic routes', async () => {
78+
const res = await fetch(`${baseUrl}/users/42`)
79+
expect(res.status).toBe(200)
80+
const html = await res.text()
81+
expect(html).toContain('<div id="app">')
82+
})
83+
84+
it('loads virtual modules via plugin resolveId', async () => {
85+
const resolved = await server.pluginContainer.resolveId(
86+
'virtual:zero/routes',
87+
)
88+
expect(resolved).toBeTruthy()
89+
expect(resolved?.id).toContain('virtual:zero/routes')
90+
})
91+
92+
it('generates API route module for fixture', async () => {
93+
const { scanRouteFiles } = await import('../../fs-router')
94+
const { generateApiRouteModule } = await import('../../api-routes')
95+
const routesDir = resolve(FIXTURE_DIR, 'src/routes')
96+
const files = await scanRouteFiles(routesDir)
97+
const code = generateApiRouteModule(files, routesDir)
98+
expect(code).toContain('/api/health')
99+
expect(code).toContain('apiRoutes')
100+
})
101+
102+
it('generates middleware module for fixture', async () => {
103+
const { generateMiddlewareModule, scanRouteFiles } = await import(
104+
'../../fs-router'
105+
)
106+
const routesDir = resolve(FIXTURE_DIR, 'src/routes')
107+
const files = await scanRouteFiles(routesDir)
108+
const code = generateMiddlewareModule(files, routesDir)
109+
expect(code).toContain('routeMiddleware')
110+
})
111+
})
112+
113+
function flattenPaths(
114+
routes: Array<{ path?: string; children?: unknown[] }>,
115+
): string[] {
116+
const paths: string[] = []
117+
for (const r of routes) {
118+
if (r.path) paths.push(r.path)
119+
if (r.children) paths.push(...flattenPaths(r.children as typeof routes))
120+
}
121+
return paths
122+
}

0 commit comments

Comments
 (0)