Skip to content

Commit ffe0478

Browse files
committed
chore: create monorepo e2e spec
1 parent 3140926 commit ffe0478

15 files changed

+304
-0
lines changed

e2e/fixtures/monorepo/package.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "monorepo",
3+
"private": true,
4+
"workspaces": [
5+
"packages/*"
6+
]
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
node_modules
2+
dist
3+
.env*
4+
*.tsbuildinfo
5+
.cache
6+
.DS_Store
7+
*.pem
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "waku-project",
3+
"version": "0.0.0",
4+
"type": "module",
5+
"private": true,
6+
"scripts": {
7+
"dev": "waku dev",
8+
"build": "waku build",
9+
"start": "waku start"
10+
},
11+
"dependencies": {
12+
"react": "19.0.0",
13+
"react-dom": "19.0.0",
14+
"react-server-dom-webpack": "19.0.0"
15+
},
16+
"devDependencies": {
17+
"@types/react": "19.0.1",
18+
"@types/react-dom": "19.0.2",
19+
"autoprefixer": "10.4.20",
20+
"tailwindcss": "3.4.16",
21+
"typescript": "5.7.2"
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/** @type {import('postcss-load-config').Config} */
2+
export default {
3+
plugins: {
4+
tailwindcss: {},
5+
autoprefixer: {},
6+
},
7+
};
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
5+
export const Counter = () => {
6+
const [count, setCount] = useState(0);
7+
8+
const handleIncrement = () => setCount((c) => c + 1);
9+
10+
return (
11+
<section className="border-blue-400 -mx-4 mt-4 rounded border border-dashed p-4">
12+
<div>Count: {count}</div>
13+
<button
14+
onClick={handleIncrement}
15+
className="rounded-sm bg-black px-2 py-0.5 text-sm text-white"
16+
>
17+
Increment
18+
</button>
19+
</section>
20+
);
21+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export const Footer = () => {
2+
return (
3+
<footer className="p-6 lg:fixed lg:bottom-0 lg:left-0">
4+
<div>
5+
visit{' '}
6+
<a
7+
href="https://waku.gg/"
8+
target="_blank"
9+
rel="noreferrer"
10+
className="mt-4 inline-block underline"
11+
>
12+
waku.gg
13+
</a>{' '}
14+
to learn more
15+
</div>
16+
</footer>
17+
);
18+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Link } from 'waku';
2+
3+
export const Header = () => {
4+
return (
5+
<header className="flex items-center gap-4 p-6 lg:fixed lg:left-0 lg:top-0">
6+
<h2 className="text-lg font-bold tracking-tight">
7+
<Link to="/">Waku starter</Link>
8+
</h2>
9+
</header>
10+
);
11+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import '../styles.css';
2+
3+
import type { ReactNode } from 'react';
4+
5+
import { Header } from '../components/header';
6+
import { Footer } from '../components/footer';
7+
8+
type RootLayoutProps = { children: ReactNode };
9+
10+
export default async function RootLayout({ children }: RootLayoutProps) {
11+
const data = await getData();
12+
13+
return (
14+
<div className="font-['Nunito']">
15+
<meta name="description" content={data.description} />
16+
<link rel="icon" type="image/png" href={data.icon} />
17+
<Header />
18+
<main className="m-6 flex items-center *:min-h-64 *:min-w-64 lg:m-0 lg:min-h-svh lg:justify-center">
19+
{children}
20+
</main>
21+
<Footer />
22+
</div>
23+
);
24+
}
25+
26+
const getData = async () => {
27+
const data = {
28+
description: 'An internet website!',
29+
icon: '/images/favicon.png',
30+
};
31+
32+
return data;
33+
};
34+
35+
export const getConfig = async () => {
36+
return {
37+
render: 'static',
38+
} as const;
39+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Link } from 'waku';
2+
3+
export default async function AboutPage() {
4+
const data = await getData();
5+
6+
return (
7+
<div>
8+
<title>{data.title}</title>
9+
<h1 className="text-4xl font-bold tracking-tight">{data.headline}</h1>
10+
<p>{data.body}</p>
11+
<Link to="/" className="mt-4 inline-block underline">
12+
Return home
13+
</Link>
14+
</div>
15+
);
16+
}
17+
18+
const getData = async () => {
19+
const data = {
20+
title: 'About',
21+
headline: 'About Waku',
22+
body: 'The minimal React framework',
23+
};
24+
25+
return data;
26+
};
27+
28+
export const getConfig = async () => {
29+
return {
30+
render: 'static',
31+
} as const;
32+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Link } from 'waku';
2+
3+
import { Counter } from '../components/counter';
4+
5+
export default async function HomePage() {
6+
const data = await getData();
7+
8+
return (
9+
<div>
10+
<title>{data.title}</title>
11+
<h1 className="text-4xl font-bold tracking-tight" data-testid="header">{data.headline}</h1>
12+
<p>{data.body}</p>
13+
<Counter />
14+
<Link to="/about" className="mt-4 inline-block underline">
15+
About page
16+
</Link>
17+
</div>
18+
);
19+
}
20+
21+
const getData = async () => {
22+
const data = {
23+
title: 'Waku',
24+
headline: 'Waku',
25+
body: 'Hello world!',
26+
};
27+
28+
return data;
29+
};
30+
31+
export const getConfig = async () => {
32+
return {
33+
render: 'static',
34+
} as const;
35+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,400;0,700;1,400;1,700&display=swap');
2+
@tailwind base;
3+
@tailwind components;
4+
@tailwind utilities;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/** @type {import('tailwindcss').Config} */
2+
export default {
3+
content: ['./src/**/*.{js,jsx,ts,tsx}'],
4+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"compilerOptions": {
3+
"strict": true,
4+
"target": "esnext",
5+
"downlevelIteration": true,
6+
"esModuleInterop": true,
7+
"module": "esnext",
8+
"moduleResolution": "bundler",
9+
"skipLibCheck": true,
10+
"noUncheckedIndexedAccess": true,
11+
"exactOptionalPropertyTypes": true,
12+
"types": ["react/experimental"],
13+
"jsx": "react-jsx"
14+
}
15+
}

e2e/monorepo.spec.ts

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { debugChildProcess, getFreePort, terminate, test } from './utils.js';
2+
import { fileURLToPath } from 'node:url';
3+
import { cp, mkdtemp } from 'node:fs/promises';
4+
import { exec, execSync } from 'node:child_process';
5+
import { expect } from '@playwright/test';
6+
import waitPort from 'wait-port';
7+
import { join } from 'node:path';
8+
import { tmpdir } from 'node:os';
9+
import { createRequire } from 'node:module';
10+
11+
let standaloneDir: string;
12+
let standaloneAppDir: string;
13+
const appRelativePath = '/packages/waku-project';
14+
const exampleDir = fileURLToPath(
15+
new URL('./fixtures/monorepo', import.meta.url),
16+
);
17+
const wakuDir = fileURLToPath(new URL('../packages/waku', import.meta.url));
18+
const { version } = createRequire(import.meta.url)(
19+
join(wakuDir, 'package.json'),
20+
);
21+
22+
async function start() {
23+
const port = await getFreePort();
24+
const cp = exec(
25+
`node ${join(standaloneDir, './node_modules/waku/dist/cli.js')} start --port ${port}`,
26+
{ cwd: standaloneDir },
27+
);
28+
debugChildProcess(cp, fileURLToPath(import.meta.url), [
29+
/ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time/,
30+
]);
31+
32+
await waitPort({ port });
33+
return [port, cp.pid] as const;
34+
}
35+
36+
test.describe('monorepo', async () => {
37+
test.beforeEach(async () => {
38+
// GitHub Action on Windows doesn't support mkdtemp on global temp dir,
39+
// Which will cause files in `src` folder to be empty.
40+
// I don't know why
41+
const tmpDir = process.env.TEMP_DIR ? process.env.TEMP_DIR : tmpdir();
42+
standaloneDir = await mkdtemp(join(tmpDir, 'waku-monorepo-'));
43+
standaloneAppDir = join(standaloneDir, appRelativePath);
44+
await cp(exampleDir, standaloneDir, {
45+
filter: (src) => {
46+
return !src.includes('node_modules') && !src.includes('dist');
47+
},
48+
recursive: true,
49+
});
50+
51+
// FIXME we need to `npm install` in the monorepo but
52+
// how do we copy in the local version of waku or link
53+
// it in a way that will accurately match what happens
54+
// with the npm published version.
55+
execSync(`pnpm pack --pack-destination ${standaloneDir}`, {
56+
cwd: wakuDir,
57+
stdio: 'inherit',
58+
});
59+
const name = `waku-${version}.tgz`;
60+
execSync(`npm install ${join(standaloneDir, name)}`, {
61+
cwd: standaloneDir,
62+
stdio: 'inherit',
63+
});
64+
execSync(
65+
`node ${join(standaloneDir, './node_modules/waku/dist/cli.js')} build`,
66+
{
67+
cwd: standaloneAppDir,
68+
stdio: 'inherit',
69+
},
70+
);
71+
});
72+
73+
test.describe('renders', () => {
74+
test(`the home page`, async ({ page }) => {
75+
const [port, pid] = await start();
76+
await page.goto(`http://localhost:${port}`);
77+
await expect(page.getByTestId('header')).toHaveText('Waku');
78+
await terminate(pid!);
79+
});
80+
});
81+
});

0 commit comments

Comments
 (0)