Skip to content

Commit 8acdb4e

Browse files
committed
add integration test for nextjs
1 parent 4c469af commit 8acdb4e

File tree

12 files changed

+406
-29
lines changed

12 files changed

+406
-29
lines changed

packages/render/integrations/integrations.spec.ts

Lines changed: 160 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import path from 'node:path';
22
import child_process from 'node:child_process';
3+
import http from 'node:http';
34
import * as playwright from 'playwright';
45
import shell from 'shelljs';
56

@@ -19,6 +20,59 @@ const $ = (command: string, cwd: string = path.resolve(__dirname, '..')) => {
1920
).toBe(0);
2021
};
2122

23+
const waitForServer = async (url: string, timeout: number) => {
24+
const start = Date.now();
25+
const isServerUp = (url: string) => {
26+
return new Promise<boolean>((resolve) => {
27+
http
28+
.get(url, { timeout: 100 }, (response) => {
29+
if (
30+
response.statusCode &&
31+
response.statusCode >= 200 &&
32+
response.statusCode < 300
33+
) {
34+
resolve(true);
35+
} else {
36+
resolve(false);
37+
}
38+
})
39+
.on('timeout', () => {
40+
resolve(false);
41+
})
42+
.on('error', () => {
43+
resolve(false);
44+
});
45+
});
46+
};
47+
while (Date.now() - start < timeout) {
48+
if (await isServerUp(url)) {
49+
return;
50+
}
51+
}
52+
throw new Error(`Server at ${url} did not respond within ${timeout}ms`);
53+
};
54+
55+
const startWebServer = async (command: string, url: string, cwd: string) => {
56+
const argsv = command.split(' ');
57+
const child = child_process.spawn(argsv[0], argsv.slice(1), {
58+
shell: true,
59+
cwd,
60+
stdio: 'pipe',
61+
});
62+
63+
process.on('exit', () => {
64+
child.kill();
65+
});
66+
67+
process.on('SIGINT', () => {
68+
child.kill('SIGINT');
69+
});
70+
71+
await waitForServer(url, 15_000);
72+
73+
return child;
74+
};
75+
2276
describe('integrations', () => {
2377
let page!: playwright.Page;
2478
let browser!: playwright.Browser;
@@ -36,49 +90,125 @@ describe('integrations', () => {
3690
browser.close();
3791
});
3892

39-
describe('vite', () => {
40-
const viteLocation = path.resolve(import.meta.dirname, './vite/');
93+
describe.only('nextjs', () => {
94+
const nextLocation = path.resolve(import.meta.dirname, './nextjs/');
4195

4296
beforeAll(() => {
43-
$('npm install', viteLocation);
97+
$('npm install', nextLocation);
4498
});
4599

46-
const startWebServer = async (
47-
command: string,
48-
url: string,
49-
cwd: string,
50-
) => {
51-
const argsv = command.split(' ');
52-
const devServer = child_process.spawn(argsv[0], argsv.slice(1), {
53-
shell: true,
54-
cwd,
55-
stdio: 'pipe',
100+
describe('dev', () => {
101+
let devServer: child_process.ChildProcess | undefined;
102+
103+
beforeAll(async () => {
104+
devServer = await startWebServer(
105+
'npm run dev',
106+
'http://localhost:3000',
107+
nextLocation,
108+
);
109+
}, 20_000);
110+
111+
afterAll(async () => {
112+
devServer?.kill();
56113
});
57-
const waitForServer = async (url: string, timeout: number) => {
58-
const start = Date.now();
59-
while (Date.now() - start < timeout) {
60-
try {
61-
await page.goto(url, { timeout: 1000 });
62-
return;
63-
} catch (e) {
64-
await new Promise((resolve) => setTimeout(resolve, 100));
65-
}
114+
115+
it('should not error when rendering in node api route', async () => {
116+
const response = await fetch('http://localhost:3000/api');
117+
118+
if (response.status !== 200) {
119+
console.log(await response.text());
66120
}
67-
throw new Error(`Server at ${url} did not respond within ${timeout}ms`);
68-
};
121+
expect(response.status).toBe(200);
122+
});
123+
124+
it('should not error when rendering in edge api route', async () => {
125+
const response = await fetch('http://localhost:3000/edge');
69126

70-
await waitForServer(url, 1000);
71-
return devServer;
72-
};
127+
if (response.status !== 200) {
128+
console.log(await response.text());
129+
}
130+
expect(response.status).toBe(200);
131+
});
132+
133+
it('should not error when rendering in the browser', async () => {
134+
await page.goto('http://localhost:3000');
135+
136+
await expect(() =>
137+
page.waitForSelector('[data-testid="rendered-error"]', {
138+
timeout: 500,
139+
}),
140+
).rejects.toThrow();
141+
await page.waitForSelector('[data-testid="rendered-html"]', {
142+
timeout: 500,
143+
});
144+
});
145+
});
146+
147+
describe('build', () => {
148+
let server: child_process.ChildProcess | undefined;
149+
150+
beforeAll(async () => {
151+
$('npm run build', nextLocation);
152+
server = await startWebServer(
153+
'npm run start',
154+
'http://localhost:3001',
155+
nextLocation,
156+
);
157+
}, 50_000);
158+
159+
afterAll(async () => {
160+
server?.kill();
161+
});
162+
163+
it('should not error when rendering in node api route', async () => {
164+
const response = await fetch('http://localhost:3001/api');
165+
166+
if (response.status !== 200) {
167+
console.log(await response.text());
168+
}
169+
expect(response.status).toBe(200);
170+
});
171+
172+
it('should not error when rendering in edge api route', async () => {
173+
const response = await fetch('http://localhost:3001/edge');
174+
175+
if (response.status !== 200) {
176+
console.log(await response.text());
177+
}
178+
expect(response.status).toBe(200);
179+
});
180+
181+
it('should not error when rendering in the browser', async () => {
182+
await page.goto('http://localhost:3001');
183+
184+
await expect(() =>
185+
page.waitForSelector('[data-testid="rendered-error"]', {
186+
timeout: 500,
187+
}),
188+
).rejects.toThrow();
189+
await page.waitForSelector('[data-testid="rendered-html"]', {
190+
timeout: 500,
191+
});
192+
});
193+
});
194+
});
195+
196+
describe('vite', () => {
197+
const viteLocation = path.resolve(import.meta.dirname, './vite/');
198+
199+
beforeAll(() => {
200+
$('npm install', viteLocation);
201+
});
73202

74203
// The code being run after build has been modified by Vite and might run differently
75204
it('should not error when rendering in vite preview', async () => {
76205
$('npm run build', viteLocation);
77-
const devServer = await startWebServer(
206+
const previewServer = await startWebServer(
78207
'npm run preview',
79208
'http://localhost:4173',
80209
viteLocation,
81210
);
211+
await page.goto('http://localhost:4173');
82212

83213
await expect(() =>
84214
page.waitForSelector('[data-testid="rendered-error"]', {
@@ -89,7 +219,7 @@ describe('integrations', () => {
89219
timeout: 500,
90220
});
91221

92-
devServer.kill();
222+
previewServer.kill();
93223
});
94224

95225
it('should not error when rendering in vite dev', async () => {
@@ -98,6 +228,7 @@ describe('integrations', () => {
98228
'http://localhost:5173',
99229
viteLocation,
100230
);
231+
await page.goto('http://localhost:5173');
101232

102233
await expect(() =>
103234
page.waitForSelector('[data-testid="rendered-error"]', {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.*
7+
.yarn/*
8+
!.yarn/patches
9+
!.yarn/plugins
10+
!.yarn/releases
11+
!.yarn/versions
12+
13+
# testing
14+
/coverage
15+
16+
# next.js
17+
/.next/
18+
/out/
19+
20+
# production
21+
/build
22+
23+
# misc
24+
.DS_Store
25+
*.pem
26+
27+
# debug
28+
npm-debug.log*
29+
yarn-debug.log*
30+
yarn-error.log*
31+
.pnpm-debug.log*
32+
33+
# env files (can opt-in for committing if needed)
34+
.env*
35+
36+
# vercel
37+
.vercel
38+
39+
# typescript
40+
*.tsbuildinfo
41+
next-env.d.ts
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2+
3+
## Getting Started
4+
5+
First, run the development server:
6+
7+
```bash
8+
npm run dev
9+
# or
10+
yarn dev
11+
# or
12+
pnpm dev
13+
# or
14+
bun dev
15+
```
16+
17+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18+
19+
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20+
21+
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22+
23+
## Learn More
24+
25+
To learn more about Next.js, take a look at the following resources:
26+
27+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29+
30+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31+
32+
## Deploy on Vercel
33+
34+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35+
36+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { render } from '@react-email/render';
2+
3+
export async function GET() {
4+
try {
5+
const html = await render(<div>testing element</div>, { pretty: true });
6+
return new Response(html);
7+
} catch (exception) {
8+
return new Response(JSON.stringify(exception), {
9+
status: 500,
10+
});
11+
}
12+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { render } from '@react-email/render';
2+
3+
export const runtime = 'edge';
4+
5+
export async function GET() {
6+
try {
7+
const html = await render(<div>testing element</div>, { pretty: true });
8+
return new Response(html);
9+
} catch (exception) {
10+
let message = 'Some error ocurred';
11+
if (exception instanceof Error) {
12+
message = exception.toString();
13+
}
14+
return new Response(message, {
15+
status: 500,
16+
});
17+
}
18+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@import "tailwindcss";
2+
3+
@theme inline {
4+
--font-sans: var(--font-geist-sans);
5+
--font-mono: var(--font-geist-mono);
6+
}
7+
8+
body {
9+
font-family: Arial, Helvetica, sans-serif;
10+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { Metadata } from "next";
2+
import { Geist, Geist_Mono } from "next/font/google";
3+
import "./globals.css";
4+
5+
const geistSans = Geist({
6+
variable: "--font-geist-sans",
7+
subsets: ["latin"],
8+
});
9+
10+
const geistMono = Geist_Mono({
11+
variable: "--font-geist-mono",
12+
subsets: ["latin"],
13+
});
14+
15+
export const metadata: Metadata = {
16+
title: "Create Next App",
17+
description: "Generated by create next app",
18+
};
19+
20+
export default function RootLayout({
21+
children,
22+
}: Readonly<{
23+
children: React.ReactNode;
24+
}>) {
25+
return (
26+
<html lang="en">
27+
<body
28+
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
29+
>
30+
{children}
31+
</body>
32+
</html>
33+
);
34+
}

0 commit comments

Comments
 (0)