Skip to content

Commit 2d3bcaa

Browse files
committed
Add failing test for workspace route library
Adds workspace routes fixture and a failing test
1 parent f9f4a27 commit 2d3bcaa

File tree

17 files changed

+344
-264
lines changed

17 files changed

+344
-264
lines changed

Diff for: integration/helpers/filesystem.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import fse from "fs-extra";
2+
import stripIndent from "strip-indent";
3+
import path from "node:path";
4+
import url from "node:url";
5+
6+
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
7+
const root = path.resolve(__dirname, "../..");
8+
const TMP_DIR = path.join(root, ".tmp/integration");
9+
10+
export async function writeTestFiles(
11+
files: Record<string, string> | undefined,
12+
dir: string
13+
) {
14+
await Promise.all(
15+
Object.keys(files ?? {}).map(async (filename) => {
16+
let filePath = path.join(dir, filename);
17+
await fse.ensureDir(path.dirname(filePath));
18+
let file = files![filename];
19+
20+
await fse.writeFile(filePath, stripIndent(file));
21+
})
22+
);
23+
}
24+
25+
export async function getAllFilesInDir(dirPath: string): Promise<string[]> {
26+
const entries = await fse.promises.readdir(dirPath, { withFileTypes: true });
27+
const files = await Promise.all(
28+
entries.map((entry) => {
29+
const resolvedPath = path.resolve(dirPath, entry.name);
30+
if (entry.isDirectory()) {
31+
return getAllFilesInDir(resolvedPath);
32+
} else {
33+
return [resolvedPath];
34+
}
35+
})
36+
);
37+
return files.flat();
38+
}
39+
40+
export async function createTestDirectory() {
41+
let folderName = `rr-${Math.random().toString(32).slice(2)}`;
42+
let testDir = path.join(TMP_DIR, folderName);
43+
await fse.ensureDir(testDir);
44+
return testDir;
45+
}

Diff for: integration/helpers/vite.ts

+35-13
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import url from "node:url";
66
import { createRequire } from "node:module";
77
import { platform } from "node:os";
88
import fse from "fs-extra";
9-
import stripIndent from "strip-indent";
109
import waitOn from "wait-on";
1110
import getPort from "get-port";
1211
import shell from "shelljs";
@@ -15,13 +14,12 @@ import dedent from "dedent";
1514
import type { Page } from "@playwright/test";
1615
import { test as base, expect } from "@playwright/test";
1716
import type { Config } from "@react-router/dev/config";
17+
import { createTestDirectory, writeTestFiles } from "./filesystem";
1818

1919
const require = createRequire(import.meta.url);
2020

2121
const reactRouterBin = "node_modules/@react-router/dev/bin.js";
2222
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
23-
const root = path.resolve(__dirname, "../..");
24-
const TMP_DIR = path.join(root, ".tmp/integration");
2523

2624
export const reactRouterConfig = ({
2725
ssr,
@@ -157,26 +155,50 @@ export async function createProject(
157155
files: Record<string, string> = {},
158156
templateName: TemplateName = "vite-5-template"
159157
) {
160-
let projectName = `rr-${Math.random().toString(32).slice(2)}`;
161-
let projectDir = path.join(TMP_DIR, projectName);
162-
await fse.ensureDir(projectDir);
158+
let projectDir = await createTestDirectory();
163159

164160
// base template
165161
let templateDir = path.resolve(__dirname, templateName);
166162
await fse.copy(templateDir, projectDir, { errorOnExist: true });
167163

168164
// user-defined files
169-
await Promise.all(
170-
Object.entries(files).map(async ([filename, contents]) => {
171-
let filepath = path.join(projectDir, filename);
172-
await fse.ensureDir(path.dirname(filepath));
173-
await fse.writeFile(filepath, stripIndent(contents));
174-
})
175-
);
165+
await writeTestFiles(files, projectDir);
176166

177167
return projectDir;
178168
}
179169

170+
type WorkspaceFileCreatorFn = (
171+
libDir: string,
172+
appDir: string
173+
) => { appFiles?: Record<string, string>; libFiles?: Record<string, string> };
174+
175+
export async function createWorkspaceProject(files: WorkspaceFileCreatorFn) {
176+
const workspaceDir = await createTestDirectory();
177+
178+
const workspaceTemplateDir = path.resolve(
179+
__dirname,
180+
"workspace-routes-template"
181+
);
182+
await fse.copy(workspaceTemplateDir, workspaceDir, { errorOnExist: true });
183+
184+
const appProjectDir = path.resolve(workspaceDir, "apps", "test-app");
185+
const libProjectDir = path.resolve(workspaceDir, "libs", "test-lib");
186+
187+
const userFiles = files(libProjectDir, appProjectDir);
188+
if (userFiles.libFiles) {
189+
await writeTestFiles(userFiles.libFiles, libProjectDir);
190+
}
191+
if (userFiles.appFiles) {
192+
await writeTestFiles(userFiles.appFiles, appProjectDir);
193+
}
194+
195+
return {
196+
appProjectDir,
197+
libProjectDir,
198+
workspaceDir,
199+
};
200+
}
201+
180202
// Avoid "Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env
181203
// being set" in vite-ecosystem-ci which breaks empty stderr assertions. To fix
182204
// this we always ensure that only NO_COLOR is set after spreading process.env.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules
2+
3+
/.cache
4+
/build
5+
.env
6+
.react-router
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
2+
3+
export default function App() {
4+
return (
5+
<html lang="en">
6+
<head>
7+
<meta charSet="utf-8" />
8+
<meta name="viewport" content="width=device-width, initial-scale=1" />
9+
<Meta />
10+
<Links />
11+
</head>
12+
<body>
13+
<Outlet />
14+
<ScrollRestoration />
15+
<Scripts />
16+
</body>
17+
</html>
18+
);
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { type RouteConfig } from "@react-router/dev/routes";
2+
import { flatRoutes } from "@react-router/fs-routes";
3+
4+
export default flatRoutes() satisfies RouteConfig;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { MetaFunction } from "react-router";
2+
3+
export const meta: MetaFunction = () => {
4+
return [
5+
{ title: "New React Router App" },
6+
{ name: "description", content: "Welcome to React Router!" },
7+
];
8+
};
9+
10+
export default function Index() {
11+
return (
12+
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
13+
<h1>Welcome to React Router</h1>
14+
</div>
15+
);
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/// <reference types="@react-router/node" />
2+
/// <reference types="vite/client" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "integration-workspace-test-app-template",
3+
"version": "0.0.0",
4+
"private": true,
5+
"sideEffects": false,
6+
"type": "module",
7+
"scripts": {
8+
"dev": "react-router dev",
9+
"build": "react-router build",
10+
"start": "react-router-serve ./build/server/index.js",
11+
"typecheck": "react-router typegen && tsc"
12+
},
13+
"dependencies": {
14+
"@react-router/express": "workspace:*",
15+
"@react-router/node": "workspace:*",
16+
"@react-router/serve": "workspace:*",
17+
"@vanilla-extract/css": "^1.10.0",
18+
"@vanilla-extract/vite-plugin": "^3.9.2",
19+
"express": "^4.19.2",
20+
"isbot": "^5.1.11",
21+
"react": "^18.2.0",
22+
"react-dom": "^18.2.0",
23+
"react-router": "workspace:*",
24+
"test-route-library": "workspace:*",
25+
"serialize-javascript": "^6.0.1"
26+
},
27+
"devDependencies": {
28+
"@react-router/dev": "workspace:*",
29+
"@react-router/fs-routes": "workspace:*",
30+
"@react-router/remix-routes-option-adapter": "workspace:*",
31+
"@types/react": "^18.2.20",
32+
"@types/react-dom": "^18.2.7",
33+
"eslint": "^8.38.0",
34+
"typescript": "^5.1.6",
35+
"vite": "^6.0.0",
36+
"vite-env-only": "^3.0.1",
37+
"vite-tsconfig-paths": "^4.2.1"
38+
},
39+
"engines": {
40+
"node": ">=20.0.0"
41+
}
42+
}
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"include": [
3+
"env.d.ts",
4+
"**/*.ts",
5+
"**/*.tsx",
6+
".react-router/types/**/*.d.ts"
7+
],
8+
"compilerOptions": {
9+
"lib": ["DOM", "DOM.Iterable", "ES2022"],
10+
"verbatimModuleSyntax": true,
11+
"esModuleInterop": true,
12+
"jsx": "react-jsx",
13+
"module": "ESNext",
14+
"moduleResolution": "Bundler",
15+
"resolveJsonModule": true,
16+
"target": "ES2022",
17+
"strict": true,
18+
"allowJs": true,
19+
"skipLibCheck": true,
20+
"baseUrl": ".",
21+
"paths": {
22+
"~/*": ["./app/*"]
23+
},
24+
"noEmit": true,
25+
"rootDirs": [".", ".react-router/types/"]
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { reactRouter } from "@react-router/dev/vite";
2+
import { defineConfig } from "vite";
3+
import tsconfigPaths from "vite-tsconfig-paths";
4+
5+
export default defineConfig({
6+
plugins: [reactRouter(), tsconfigPaths()],
7+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export function sum(a: number, b: number): number;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function sum(a, b) {
2+
return a + b;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "test-route-library",
3+
"type": "module",
4+
"private": true,
5+
"exports": {
6+
".": "./index.js"
7+
},
8+
"peerDependencies": {
9+
"react": "^18.2.0",
10+
"@react-router/dev": "workspace:*"
11+
}
12+
}

Diff for: integration/typegen-test.ts

+40-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import { expect, test } from "@playwright/test";
55
import dedent from "dedent";
66
import fse from "fs-extra";
77

8-
import { createProject } from "./helpers/vite";
8+
import {
9+
createProject,
10+
createWorkspaceProject,
11+
} from "./helpers/vite";
12+
import { getAllFilesInDir } from "./helpers/filesystem";
913

1014
const tsx = dedent;
1115

@@ -362,4 +366,39 @@ test.describe("typegen", () => {
362366
expect(proc.stderr.toString()).toBe("");
363367
expect(proc.status).toBe(0);
364368
});
369+
370+
test("route files from workspace packages", async () => {
371+
const { appProjectDir } = await createWorkspaceProject((libDir) => ({
372+
appFiles: {
373+
"app/routes.ts": tsx`
374+
import type { RouteConfig } from "@react-router/dev/routes";
375+
export default [
376+
{ path: 'lib-route', file: "${libDir}/my-route.jsx" }
377+
] satisfies RouteConfig;
378+
`,
379+
},
380+
libFiles: {
381+
"my-route.jsx": tsx`
382+
export default function MyRoute() {
383+
return <h1>My Route</h1>;
384+
}
385+
`,
386+
},
387+
}));
388+
389+
const proc = typecheck(appProjectDir);
390+
391+
expect(proc.stdout.toString()).toBe("");
392+
expect(proc.stderr.toString()).toBe("");
393+
expect(proc.status).toBe(0);
394+
395+
// Expect that the route library's typing is not generated into the app project
396+
const files = await getAllFilesInDir(appProjectDir);
397+
const appProjectFiles = files.filter(
398+
(name) => !name.includes("node_modules")
399+
);
400+
expect(
401+
appProjectFiles.some((name) => name.includes("my-route"))
402+
).toBeFalsy();
403+
});
365404
});

0 commit comments

Comments
 (0)