Skip to content

Commit 014b0ad

Browse files
authored
fix(create-pages): e2e test createApi and fix method+path combos (#1141)
e2e tests for createApi usage in createPages chore: remove default main.tsx from spec (happy to revert this if we want it there for some reason) --------- Co-authored-by: Tyler <[email protected]>
1 parent df93a56 commit 014b0ad

File tree

5 files changed

+119
-38
lines changed

5 files changed

+119
-38
lines changed

e2e/create-pages.spec.ts

+27
Original file line numberDiff line numberDiff line change
@@ -116,5 +116,32 @@ for (const mode of ['DEV', 'PRD'] as const) {
116116
).toBeVisible();
117117
({ port, stopApp } = await startApp(mode));
118118
});
119+
120+
test('api hi.txt', async () => {
121+
const res = await fetch(`http://localhost:${port}/api/hi.txt`);
122+
expect(res.status).toBe(200);
123+
expect(await res.text()).toBe('hello from a text file!');
124+
});
125+
126+
test('api hi', async () => {
127+
const res = await fetch(`http://localhost:${port}/api/hi`);
128+
expect(res.status).toBe(200);
129+
expect(await res.text()).toBe('hello world!');
130+
});
131+
132+
test('api empty', async () => {
133+
const res = await fetch(`http://localhost:${port}/api/empty`);
134+
expect(res.status).toBe(200);
135+
expect(await res.text()).toBe('');
136+
});
137+
138+
test('api hi with POST', async () => {
139+
const res = await fetch(`http://localhost:${port}/api/hi`, {
140+
method: 'POST',
141+
body: 'from the test!',
142+
});
143+
expect(res.status).toBe(200);
144+
expect(await res.text()).toBe('POST to hello world! from the test!');
145+
});
119146
});
120147
}
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hello from a text file!

e2e/fixtures/create-pages/src/entries.tsx

+40-1
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import NestedBazPage from './components/NestedBazPage.js';
88
import NestedLayout from './components/NestedLayout.js';
99
import { DeeplyNestedLayout } from './components/DeeplyNestedLayout.js';
1010
import ErrorPage from './components/ErrorPage.js';
11+
import { readFile } from 'node:fs/promises';
1112

1213
const pages: ReturnType<typeof createPages> = createPages(
13-
async ({ createPage, createLayout }) => [
14+
async ({ createPage, createLayout, createApi }) => [
1415
createLayout({
1516
render: 'static',
1617
path: '/',
@@ -99,6 +100,44 @@ const pages: ReturnType<typeof createPages> = createPages(
99100
path: '/404',
100101
component: () => <h2>Not Found</h2>,
101102
}),
103+
104+
createApi({
105+
path: '/api/hi.txt',
106+
mode: 'static',
107+
method: 'GET',
108+
handler: async () => {
109+
const hiTxt = await readFile('./private/hi.txt');
110+
return new Response(hiTxt);
111+
},
112+
}),
113+
114+
createApi({
115+
path: '/api/hi',
116+
mode: 'dynamic',
117+
method: 'GET',
118+
handler: async () => {
119+
return new Response('hello world!');
120+
},
121+
}),
122+
123+
createApi({
124+
path: '/api/hi',
125+
mode: 'dynamic',
126+
method: 'POST',
127+
handler: async (req) => {
128+
const body = await req.text();
129+
return new Response(`POST to hello world! ${body}`);
130+
},
131+
}),
132+
133+
createApi({
134+
path: '/api/empty',
135+
mode: 'static',
136+
method: 'GET',
137+
handler: async () => {
138+
return new Response(null);
139+
},
140+
}),
102141
],
103142
);
104143

e2e/fixtures/create-pages/src/main.tsx

-15
This file was deleted.

packages/waku/src/router/create-pages.ts

+51-22
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,21 @@ export type CreateLayout = <Path extends string>(
150150

151151
type Method = 'GET' | 'POST' | 'PUT' | 'DELETE';
152152

153-
export type CreateApi = <Path extends string>(params: {
154-
path: Path;
155-
mode: 'static' | 'dynamic';
156-
method: Method;
157-
handler: (req: Request) => Promise<Response>;
158-
}) => void;
153+
export type CreateApi = <Path extends string>(
154+
params:
155+
| {
156+
path: Path;
157+
mode: 'static';
158+
method: 'GET';
159+
handler: (req: Request) => Promise<Response>;
160+
}
161+
| {
162+
path: Path;
163+
mode: 'dynamic';
164+
method: Method;
165+
handler: (req: Request) => Promise<Response>;
166+
},
167+
) => void;
159168

160169
type RootItem = {
161170
render: 'static' | 'dynamic';
@@ -227,27 +236,26 @@ export const createPages = <
227236
[PathSpec, FunctionComponent<any>]
228237
>();
229238
const apiPathMap = new Map<
230-
string,
239+
string, // `${method} ${path}`
231240
{
232241
mode: 'static' | 'dynamic';
233242
pathSpec: PathSpec;
234-
method: Method;
235243
handler: Parameters<CreateApi>[0]['handler'];
236244
}
237245
>();
246+
const staticApiPaths = new Set<string>();
238247
const staticComponentMap = new Map<string, FunctionComponent<any>>();
239248
let rootItem: RootItem | undefined = undefined;
240249
const noSsrSet = new WeakSet<PathSpec>();
241250

242251
/** helper to find dynamic path when slugs are used */
243-
const getRoutePath: (path: string) => string | undefined = (path) => {
252+
const getPageRoutePath: (path: string) => string | undefined = (path) => {
244253
if (staticComponentMap.has(joinPath(path, 'page').slice(1))) {
245254
return path;
246255
}
247256
const allPaths = [
248257
...dynamicPagePathMap.keys(),
249258
...wildcardPagePathMap.keys(),
250-
...apiPathMap.keys(),
251259
];
252260
for (const p of allPaths) {
253261
if (getPathMapping(parsePathWithSlug(p), path)) {
@@ -256,12 +264,29 @@ export const createPages = <
256264
}
257265
};
258266

259-
const pathExists = (path: string) => {
267+
const getApiRoutePath: (
268+
path: string,
269+
method: string,
270+
) => string | undefined = (path, method) => {
271+
for (const pathKey of apiPathMap.keys()) {
272+
const [m, p] = pathKey.split(' ');
273+
if (m === method && getPathMapping(parsePathWithSlug(p!), path)) {
274+
return p;
275+
}
276+
}
277+
};
278+
279+
const pagePathExists = (path: string) => {
280+
for (const pathKey of apiPathMap.keys()) {
281+
const [_m, p] = pathKey.split(' ');
282+
if (p === path) {
283+
return true;
284+
}
285+
}
260286
return (
261287
staticPathMap.has(path) ||
262288
dynamicPagePathMap.has(path) ||
263-
wildcardPagePathMap.has(path) ||
264-
apiPathMap.has(path)
289+
wildcardPagePathMap.has(path)
265290
);
266291
};
267292

@@ -290,7 +315,7 @@ export const createPages = <
290315
if (configured) {
291316
throw new Error('createPage no longer available');
292317
}
293-
if (pathExists(page.path)) {
318+
if (pagePathExists(page.path)) {
294319
throw new Error(`Duplicated path: ${page.path}`);
295320
}
296321

@@ -388,12 +413,16 @@ export const createPages = <
388413
if (configured) {
389414
throw new Error('createApi no longer available');
390415
}
391-
if (apiPathMap.has(path)) {
392-
throw new Error(`Duplicated api path: ${path}`);
416+
if (apiPathMap.has(`${method} ${path}`)) {
417+
throw new Error(`Duplicated api path+method: ${path} ${method}`);
418+
} else if (mode === 'static' && staticApiPaths.has(path)) {
419+
throw new Error('Static API Routes cannot share paths: ' + path);
420+
}
421+
if (mode === 'static') {
422+
staticApiPaths.add(path);
393423
}
394-
395424
const pathSpec = parsePathWithSlug(path);
396-
apiPathMap.set(path, { mode, pathSpec, method, handler });
425+
apiPathMap.set(`${method} ${path}`, { mode, pathSpec, handler });
397426
};
398427

399428
const createRoot: CreateRoot = (root) => {
@@ -530,7 +559,7 @@ export const createPages = <
530559
await configure();
531560

532561
// path without slugs
533-
const routePath = getRoutePath(path);
562+
const routePath = getPageRoutePath(path);
534563
if (!routePath) {
535564
throw new Error('Route not found: ' + path);
536565
}
@@ -607,11 +636,11 @@ export const createPages = <
607636
},
608637
handleApi: async (path, options) => {
609638
await configure();
610-
const routePath = getRoutePath(path);
639+
const routePath = getApiRoutePath(path, options.method);
611640
if (!routePath) {
612-
throw new Error('Route not found: ' + path);
641+
throw new Error('API Route not found: ' + path);
613642
}
614-
const { handler } = apiPathMap.get(routePath)!;
643+
const { handler } = apiPathMap.get(`${options.method} ${routePath}`)!;
615644

616645
const req = new Request(
617646
new URL(

0 commit comments

Comments
 (0)