Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 68 additions & 70 deletions e2e-tests/tests/react-router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as path from 'node:path';
import * as fs from 'node:fs';
import { Integration } from '../../lib/Constants';
import {
KEYS,
TEST_ARGS,
checkEnvBuildPlugin,
checkFileContents,
Expand All @@ -12,101 +11,92 @@ import {
checkIfRunsOnProdMode,
checkPackageJson,
createIsolatedTestEnv,
startWizardInstance,
getWizardCommand,
} from '../utils';
import { afterAll, beforeAll, describe, test, expect } from 'vitest';

//@ts-expect-error - clifty is ESM only
import { KEYS, withEnv } from 'clifty';

async function runWizardOnReactRouterProject(
projectDir: string,
integration: Integration,
opts?: {
modifiedFiles?: boolean;
},
) {
): Promise<number> {
const { modifiedFiles = false } = opts || {};

const wizardInstance = startWizardInstance(integration, projectDir);
const wizardInteraction = withEnv({
cwd: projectDir,
}).defineInteraction();

if (modifiedFiles) {
await wizardInstance.waitForOutput('Do you want to continue anyway?');

await wizardInstance.sendStdinAndWaitForOutput(
[KEYS.ENTER],
'Please select your package manager.',
);
} else {
await wizardInstance.waitForOutput('Please select your package manager.');
wizardInteraction
.whenAsked('Do you want to continue anyway?')
.respondWith(KEYS.ENTER);
}

const tracingOptionPrompted = await wizardInstance.sendStdinAndWaitForOutput(
[KEYS.DOWN, KEYS.ENTER],
'to track the performance of your application?',
{ timeout: 240_000 },
);

const replayOptionPrompted =
tracingOptionPrompted &&
(await wizardInstance.sendStdinAndWaitForOutput(
[KEYS.ENTER],
'to get a video-like reproduction of errors during a user session?',
));

const logOptionPrompted =
replayOptionPrompted &&
(await wizardInstance.sendStdinAndWaitForOutput(
[KEYS.ENTER],
'to send your application logs to Sentry?',
));

const profilingOptionPrompted =
logOptionPrompted &&
(await wizardInstance.sendStdinAndWaitForOutput(
[KEYS.ENTER],
'to track application performance in detail?',
));

const examplePagePrompted =
profilingOptionPrompted &&
(await wizardInstance.sendStdinAndWaitForOutput(
[KEYS.ENTER],
'Do you want to create an example page',
));

const mcpPrompted =
examplePagePrompted &&
(await wizardInstance.sendStdinAndWaitForOutput(
[KEYS.ENTER],
'Optionally add a project-scoped MCP server configuration for the Sentry MCP?',
{ optional: true },
));
wizardInteraction
.whenAsked('Please select your package manager.')
.respondWith(KEYS.DOWN, KEYS.ENTER)
.expectOutput('Installing @sentry/react-router')
.expectOutput('Installed @sentry/react-router', {
timeout: 240_000,
})

.whenAsked('Do you want to enable Tracing')
.respondWith(KEYS.ENTER)
.whenAsked('Do you want to enable Session Replay')
.respondWith(KEYS.ENTER)
.whenAsked('Do you want to enable Logs')
.respondWith(KEYS.ENTER)
.whenAsked('Do you want to enable Profiling')
.respondWith(KEYS.ENTER)
.expectOutput('Installing @sentry/profiling-node')
.expectOutput('Installed @sentry/profiling-node', {
timeout: 240_000,
})
.whenAsked('Do you want to create an example page')
.respondWith(KEYS.ENTER);

mcpPrompted &&
(await wizardInstance.sendStdinAndWaitForOutput(
[KEYS.DOWN, KEYS.ENTER],
'Successfully installed the Sentry React Router SDK!',
));
if (modifiedFiles) {
wizardInteraction
.whenAsked('Would you like to try running npx react-router reveal')
.respondWith(KEYS.ENTER)
.whenAsked('Did you apply the snippet above?')
.respondWith(KEYS.ENTER);
}

wizardInstance.kill();
return wizardInteraction
.whenAsked(
'Optionally add a project-scoped MCP server configuration for the Sentry MCP?',
)
.respondWith(KEYS.DOWN, KEYS.ENTER)
.expectOutput('Successfully installed the Sentry React Router SDK!')
.run(getWizardCommand(Integration.reactRouter));
}

describe('React Router', () => {
describe('with empty project', () => {
const integration = Integration.reactRouter;

let wizardExitCode: number;
const { projectDir, cleanup } = createIsolatedTestEnv(
'react-router-test-app',
);

beforeAll(async () => {
await runWizardOnReactRouterProject(projectDir, integration);
wizardExitCode = await runWizardOnReactRouterProject(projectDir);
});

afterAll(() => {
cleanup();
});

test('exits with exit code 0', () => {
expect(wizardExitCode).toBe(0);
});

test('package.json is updated correctly', () => {
checkPackageJson(projectDir, integration);
checkPackageJson(projectDir, Integration.reactRouter);
});

test('.env.sentry-build-plugin is created and contains the auth token', () => {
Expand Down Expand Up @@ -212,7 +202,7 @@ describe('React Router', () => {

describe('edge cases', () => {
describe('existing Sentry setup', () => {
const integration = Integration.reactRouter;
let wizardExitCode: number;

const { projectDir, cleanup } = createIsolatedTestEnv(
'react-router-test-app',
Expand Down Expand Up @@ -245,7 +235,7 @@ startTransition(() => {
});`;
fs.writeFileSync(clientEntryPath, existingContent);

await runWizardOnReactRouterProject(projectDir, integration, {
wizardExitCode = await runWizardOnReactRouterProject(projectDir, {
modifiedFiles: true,
});
});
Expand All @@ -254,6 +244,10 @@ startTransition(() => {
cleanup();
});

test('exits with exit code 0', () => {
expect(wizardExitCode).toBe(0);
});

test('wizard handles existing Sentry without duplication', () => {
const clientContent = fs.readFileSync(
`${projectDir}/app/entry.client.tsx`,
Expand All @@ -273,7 +267,7 @@ startTransition(() => {

// Only test the essential checks for this edge case
test('package.json is updated correctly', () => {
checkPackageJson(projectDir, integration);
checkPackageJson(projectDir, Integration.reactRouter);
});

test('essential files exist or wizard completes gracefully', () => {
Expand Down Expand Up @@ -302,7 +296,7 @@ startTransition(() => {
});

describe('missing entry files', () => {
const integration = Integration.reactRouter;
let wizardExitCode: number;

const { projectDir, cleanup } = createIsolatedTestEnv(
'react-router-test-app',
Expand All @@ -325,20 +319,24 @@ startTransition(() => {
if (fs.existsSync(entryClientPath)) fs.unlinkSync(entryClientPath);
if (fs.existsSync(entryServerPath)) fs.unlinkSync(entryServerPath);

await runWizardOnReactRouterProject(projectDir, integration);
wizardExitCode = await runWizardOnReactRouterProject(projectDir);
});

afterAll(() => {
cleanup();
});

test('exits with exit code 0', () => {
expect(wizardExitCode).toBe(0);
});

test('wizard creates missing entry files', () => {
checkFileExists(`${projectDir}/app/entry.client.tsx`);
checkFileExists(`${projectDir}/app/entry.server.tsx`);
});

test('basic configuration still works', () => {
checkPackageJson(projectDir, integration);
checkPackageJson(projectDir, Integration.reactRouter);
checkFileExists(`${projectDir}/instrument.server.mjs`);
});
});
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"ts-node": "^10.9.1",
"typescript": "^5.0.4",
"vite": "^6.2.3",
"vitest": "^3.0.9"
"vitest": "3.0.9"
},
"resolutions": {
"simple-plist": "1.4.0-0",
Expand Down
2 changes: 1 addition & 1 deletion src/react-router/react-router-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ async function runReactRouterWizardWithTelemetry(

traceStep('Reveal missing entry files', () => {
try {
runReactRouterReveal(typeScriptDetected);
runReactRouterReveal();
clack.log.success('Entry files are ready for instrumentation');
} catch (e) {
clack.log.warn(`Could not run 'npx react-router reveal'.
Expand Down
7 changes: 3 additions & 4 deletions src/react-router/sdk-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,10 @@ async function ensureEntryFileExists(
}
}

export function runReactRouterReveal(force = false): void {
export function runReactRouterReveal(): void {
if (
force ||
(!fs.existsSync(path.join(process.cwd(), 'app/entry.client.tsx')) &&
!fs.existsSync(path.join(process.cwd(), 'app/entry.client.jsx')))
!fs.existsSync(path.join(process.cwd(), 'app', 'entry.client.tsx')) &&
!fs.existsSync(path.join(process.cwd(), 'app', 'entry.client.jsx'))
) {
try {
childProcess.execSync(REACT_ROUTER_REVEAL_COMMAND, {
Expand Down
4 changes: 2 additions & 2 deletions test/react-router/sdk-setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ describe('runReactRouterReveal', () => {

(childProcess.execSync as unknown as Mock).mockImplementation(() => 'ok');

runReactRouterReveal(false);
runReactRouterReveal();

expect(childProcess.execSync).toHaveBeenCalledWith(
'npx react-router reveal',
Expand All @@ -226,7 +226,7 @@ describe('runReactRouterReveal', () => {

(childProcess.execSync as unknown as Mock).mockReset();

runReactRouterReveal(false);
runReactRouterReveal();

expect(childProcess.execSync).not.toHaveBeenCalled();
});
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3531,9 +3531,9 @@ [email protected]:
optionalDependencies:
fsevents "~2.3.3"

vitest@^3.0.9:
[email protected]:
version "3.0.9"
resolved "https://registry.npmjs.org/vitest/-/vitest-3.0.9.tgz"
resolved "https://registry.yarnpkg.com/vitest/-/vitest-3.0.9.tgz#8cf607d27dcaa12b9f21111f001a4e3e92511ba5"
integrity sha512-BbcFDqNyBlfSpATmTtXOAOj71RNKDDvjBM/uPfnxxVGrG+FSH2RQIwgeEngTaTkuU/h0ScFvf+tRcKfYXzBybQ==
dependencies:
"@vitest/expect" "3.0.9"
Expand Down
Loading