UNPKG

one

Version:

One is a new React Framework that makes Vite serve both native and web.

358 lines (315 loc) 14.2 kB
import { describe, expect, it, vi, beforeEach } from 'vitest' import { createHandleRequest } from './createHandleRequest' // Mock the manifest. Specific routes come before dynamic ones, matching how // the real manifest is ordered after matchers.sortRoutes(). vi.mock('./vite/getManifest', () => ({ getManifest: () => ({ pageRoutes: [ { file: 'app/index.tsx', page: '/', namedRegex: '^/(?:/)?$', routeKeys: {}, type: 'ssr', middlewares: [], }, { file: 'app/(public)/otp+ssg.tsx', page: '/otp', namedRegex: '^/otp(?:/)?$', routeKeys: {}, type: 'ssg', middlewares: [], hasLoader: false, }, { file: 'app/(public)/profile+ssg.tsx', page: '/profile', namedRegex: '^/profile(?:/)?$', routeKeys: {}, type: 'ssg', middlewares: [], hasLoader: true, }, { file: 'app/[slug].tsx', page: '/[slug]', namedRegex: '^/(?<nxtPslug>[^/]+?)(?:/)?$', routeKeys: { nxtPslug: 'slug' }, type: 'ssr', middlewares: [], }, { file: 'app/+not-found.tsx', page: '/+not-found', namedRegex: '^/(?<nxtPnotfound>.+?)(?:/)?$', routeKeys: { nxtPnotfound: 'not-found' }, type: 'spa', middlewares: [], }, ], apiRoutes: [ { file: 'app/api/github/agent/run.sh+api.ts', page: '/api/github/agent/run.sh', namedRegex: '^/api/github/agent/run\\.sh(?:/)?$', routeKeys: {}, type: 'api', middlewares: [], }, ], }), })) function createRequest(path: string, method = 'GET') { return new Request(`http://localhost:3000${path}`, { method, headers: { host: 'localhost:3000', }, }) } describe('createHandleRequest', () => { const mockHandlers = { handlePage: vi.fn().mockResolvedValue('<html></html>'), } beforeEach(() => { vi.clearAllMocks() }) describe('static file extension filtering', () => { it('should skip paths with file extensions (favicon.ico)', async () => { const { handler } = createHandleRequest(mockHandlers, { routerRoot: '/app' }) const result = await handler(createRequest('/favicon.ico')) expect(result).toBeNull() expect(mockHandlers.handlePage).not.toHaveBeenCalled() }) it('should skip paths with file extensions (logo.png)', async () => { const { handler } = createHandleRequest(mockHandlers, { routerRoot: '/app' }) const result = await handler(createRequest('/logo.png')) expect(result).toBeNull() expect(mockHandlers.handlePage).not.toHaveBeenCalled() }) it('should skip paths with file extensions (styles.css)', async () => { const { handler } = createHandleRequest(mockHandlers, { routerRoot: '/app' }) const result = await handler(createRequest('/styles.css')) expect(result).toBeNull() expect(mockHandlers.handlePage).not.toHaveBeenCalled() }) it('should skip paths with file extensions (robots.txt)', async () => { const { handler } = createHandleRequest(mockHandlers, { routerRoot: '/app' }) const result = await handler(createRequest('/robots.txt')) expect(result).toBeNull() expect(mockHandlers.handlePage).not.toHaveBeenCalled() }) it('should skip nested paths with file extensions', async () => { const { handler } = createHandleRequest(mockHandlers, { routerRoot: '/app' }) const result = await handler(createRequest('/assets/images/logo.png')) expect(result).toBeNull() expect(mockHandlers.handlePage).not.toHaveBeenCalled() }) it('should skip extensions 2-4 chars like .xyz', async () => { const { handler } = createHandleRequest(mockHandlers, { routerRoot: '/app' }) const result = await handler(createRequest('/somefile.xyz')) expect(result).toBeNull() expect(mockHandlers.handlePage).not.toHaveBeenCalled() }) it('should skip common static extensions longer than 4 chars', async () => { const { handler } = createHandleRequest(mockHandlers, { routerRoot: '/app' }) const result = await handler(createRequest('/font.woff2')) expect(result).toBeNull() expect(mockHandlers.handlePage).not.toHaveBeenCalled() }) it('should not render a user +not-found page for sourcemap requests', async () => { const { handler } = createHandleRequest(mockHandlers, { routerRoot: '/app' }) const result = await handler(createRequest('/deps-web/react-BwkDMLyL.js.map')) expect(result).toBeNull() expect(mockHandlers.handlePage).not.toHaveBeenCalled() }) it('should still route static-looking API paths', async () => { const mockHandlersWithAPI = { handlePage: vi.fn().mockResolvedValue('<html></html>'), handleAPI: vi.fn().mockResolvedValue({ GET: () => new Response('script'), }), } const { handler } = createHandleRequest(mockHandlersWithAPI, { routerRoot: '/app', }) const result = (await handler( createRequest('/api/github/agent/run.sh') )) as Response | null expect(result).not.toBeNull() expect(await result!.text()).toBe('script') expect(mockHandlersWithAPI.handleAPI).toHaveBeenCalled() expect(mockHandlersWithAPI.handlePage).not.toHaveBeenCalled() }) it('should not skip non-static extensions in route names', async () => { const { handler } = createHandleRequest(mockHandlers, { routerRoot: '/app' }) await handler(createRequest('/route.normal')) expect(mockHandlers.handlePage).toHaveBeenCalled() }) it('should NOT skip loader paths ending with _vxrn_loader.js', async () => { const mockHandlersWithLoader = { handlePage: vi.fn().mockResolvedValue('<html></html>'), handleLoader: vi.fn().mockResolvedValue('loader data'), } const { handler } = createHandleRequest(mockHandlersWithLoader, { routerRoot: '/app', }) // Loader paths are /assets/<path>_<nonce>_vxrn_loader.js - they should reach handleLoader // for a matching route (the path /my-page matches the [slug] route) await handler(createRequest('/assets/my-page_123_vxrn_loader.js')) expect(mockHandlersWithLoader.handleLoader).toHaveBeenCalled() }) }) describe('loader dispatch short-circuit for no-loader routes', () => { it('should return empty loader module without calling handleLoader when route has hasLoader: false', async () => { // Regression: for SSG routes with no loader export, the worker must NOT // import the page bundle (which pulls in RN/Tamagui modules that crash // when evaluated inside workerd on Cloudflare Workers). const mockHandlersWithLoader = { handlePage: vi.fn().mockResolvedValue('<html></html>'), handleLoader: vi.fn().mockResolvedValue('loader data'), } const { handler } = createHandleRequest(mockHandlersWithLoader, { routerRoot: '/app', }) // /otp is an SSG route with hasLoader: false in the mock manifest above. // The client fetches /assets/otp_<nonce>_vxrn_loader.js when navigating // client-side to /otp. const response = (await handler( createRequest('/assets/otp_123_vxrn_loader.js') )) as Response | null // handleLoader must not be called — the whole point is to avoid // importing the page module on the server for no-loader routes. expect(mockHandlersWithLoader.handleLoader).not.toHaveBeenCalled() expect(response).not.toBeNull() expect(response!.status).toBe(200) expect(response!.headers.get('Content-Type')).toBe('text/javascript') const body = await response!.text() expect(body).toContain('export function loader()') expect(body).toContain('return undefined') }) it('should still call handleLoader when route has hasLoader: true', async () => { const mockHandlersWithLoader = { handlePage: vi.fn().mockResolvedValue('<html></html>'), handleLoader: vi.fn().mockResolvedValue('export function loader(){return 1}'), } const { handler } = createHandleRequest(mockHandlersWithLoader, { routerRoot: '/app', }) await handler(createRequest('/assets/profile_123_vxrn_loader.js')) expect(mockHandlersWithLoader.handleLoader).toHaveBeenCalled() }) it('should still call handleLoader when hasLoader is undefined (dev/back-compat)', async () => { // Routes without hasLoader set (e.g. dev mode, older builds) must fall // through to the normal handleLoader path — we only short-circuit on an // explicit `false`. const mockHandlersWithLoader = { handlePage: vi.fn().mockResolvedValue('<html></html>'), handleLoader: vi.fn().mockResolvedValue('loader data'), } const { handler } = createHandleRequest(mockHandlersWithLoader, { routerRoot: '/app', }) // /my-page matches the [slug] ssr route which has no hasLoader field await handler(createRequest('/assets/my-page_123_vxrn_loader.js')) expect(mockHandlersWithLoader.handleLoader).toHaveBeenCalled() }) }) describe('dynamic route matching', () => { it('should match dynamic routes for regular paths without extensions', async () => { const { handler } = createHandleRequest(mockHandlers, { routerRoot: '/app' }) await handler(createRequest('/my-page')) expect(mockHandlers.handlePage).toHaveBeenCalled() }) it('should match index route', async () => { const { handler } = createHandleRequest(mockHandlers, { routerRoot: '/app' }) await handler(createRequest('/')) expect(mockHandlers.handlePage).toHaveBeenCalled() }) it('should match paths with hyphens', async () => { const { handler } = createHandleRequest(mockHandlers, { routerRoot: '/app' }) await handler(createRequest('/my-awesome-page')) expect(mockHandlers.handlePage).toHaveBeenCalled() }) }) describe('request method dispatch', () => { it('answers HEAD page requests exactly like GET', async () => { const handlePage = vi.fn( async () => new Response('<html></html>', { headers: { 'content-type': 'text/html' } }) ) const { handler } = createHandleRequest({ handlePage }, { routerRoot: '/app' }) const get = await handler(createRequest('/my-page', 'GET')) const head = await handler(createRequest('/my-page', 'HEAD')) if (!(get instanceof Response) || !(head instanceof Response)) { throw new Error('expected both GET and HEAD to return a Response') } // the transport drops the body for HEAD, so the handler layer must agree // with GET on everything else: status and content type. expect(head.status).toBe(get.status) expect(head.headers.get('content-type')).toBe(get.headers.get('content-type')) expect(handlePage).toHaveBeenCalledTimes(2) }) it('continues routing HEAD requests to API handlers', async () => { const mockHandlersWithAPI = { handlePage: vi.fn().mockResolvedValue('<html></html>'), handleAPI: vi.fn().mockResolvedValue({ HEAD: () => new Response(null, { status: 204 }), }), } const { handler } = createHandleRequest(mockHandlersWithAPI, { routerRoot: '/app', }) const result = await handler(createRequest('/api/github/agent/run.sh', 'HEAD')) expect(result).toBeInstanceOf(Response) if (!(result instanceof Response)) { throw new Error('expected HEAD API request to return a Response') } expect(result.status).toBe(204) expect(mockHandlersWithAPI.handleAPI).toHaveBeenCalledOnce() expect(mockHandlersWithAPI.handlePage).not.toHaveBeenCalled() }) it('does not route POST requests to page handlers', async () => { const { handler } = createHandleRequest(mockHandlers, { routerRoot: '/app' }) const result = await handler(createRequest('/my-page', 'POST')) expect(result).toBeNull() expect(mockHandlers.handlePage).not.toHaveBeenCalled() }) }) describe('special paths', () => { it('should skip __vxrnhmr path', async () => { const { handler } = createHandleRequest(mockHandlers, { routerRoot: '/app' }) const result = await handler(createRequest('/__vxrnhmr')) expect(result).toBeNull() }) it('should skip vite internal paths like /@fs/', async () => { const { handler } = createHandleRequest(mockHandlers, { routerRoot: '/app' }) expect(await handler(createRequest('/@fs/some/path'))).toBeNull() expect(await handler(createRequest('/@vite/client'))).toBeNull() expect(await handler(createRequest('/@id/__x00__virtual:one-entry'))).toBeNull() }) it('should NOT skip user routes that start with @', async () => { const { handler } = createHandleRequest(mockHandlers, { routerRoot: '/app' }) // routes like /@admin should be matched, not skipped await handler(createRequest('/@admin')) expect(mockHandlers.handlePage).toHaveBeenCalled() }) }) describe('requests without a Host header', () => { // every other test here builds its request through createRequest, which // always sets Host, and an incoming request on a real server always carries // one. a request constructed by hand does not, and `host` is a forbidden // header name in a browser so it cannot be added there. resolving the URL // against an empty-string base threw "Invalid base URL" even though the // request URL was already absolute, which took down every embedder that // builds its own Request. it('resolves an absolute request URL with no Host header', async () => { const { handler } = createHandleRequest(mockHandlers, { routerRoot: '/app' }) const request = new Request('http://localhost:3000/some-page') expect(request.headers.get('host')).toBeNull() await expect(handler(request)).resolves.not.toThrow() expect(mockHandlers.handlePage).toHaveBeenCalled() }) }) })