@sentry/wizard
Version:
Sentry wizard helping you to configure your project
260 lines (246 loc) • 11.6 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
// @ts-expect-error - magicast is ESM and TS complains about that. It works though
const magicast_1 = require("magicast");
const vitest_1 = require("vitest");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const root_1 = require("../../src/remix/codemods/root");
vitest_1.vi.mock('@clack/prompts', () => {
const mock = {
log: {
warn: vitest_1.vi.fn(),
info: vitest_1.vi.fn(),
success: vitest_1.vi.fn(),
},
};
return {
default: mock,
...mock,
};
});
vitest_1.vi.mock('../../src/utils/clack/mcp-config', () => ({
offerProjectScopedMcpConfig: vitest_1.vi.fn().mockResolvedValue(undefined),
}));
(0, vitest_1.describe)('wrapAppWithSentry', () => {
(0, vitest_1.it)('should wrap the app with Sentry', () => {
// Empty root.tsx file for testing
const originalRootAst = (0, magicast_1.parseModule)(`
import { Outlet } from '@remix-run/react';
export default function App() {
return <Outlet />;
}
`);
(0, root_1.wrapAppWithSentry)(originalRootAst, 'root.tsx');
const result = originalRootAst.generate().code;
(0, vitest_1.expect)(result).toMatchInlineSnapshot(`
"import {withSentry} from '@sentry/remix';
import { Outlet } from '@remix-run/react';
function App() {
return <Outlet />;
}
export default withSentry(App);"
`);
});
});
(0, vitest_1.describe)('isWithSentryAlreadyUsed', () => {
(0, vitest_1.it)('should return false when withSentry is not used', () => {
const rootAst = (0, magicast_1.parseModule)(`
import { Outlet } from '@remix-run/react';
export default function App() {
return <Outlet />;
}
`);
(0, vitest_1.expect)((0, root_1.isWithSentryAlreadyUsed)(rootAst)).toBe(false);
});
(0, vitest_1.it)('should return true when withSentry is used in default export', () => {
const rootAst = (0, magicast_1.parseModule)(`
import { withSentry } from '@sentry/remix';
import { Outlet } from '@remix-run/react';
function App() {
return <Outlet />;
}
export default withSentry(App);
`);
(0, vitest_1.expect)((0, root_1.isWithSentryAlreadyUsed)(rootAst)).toBe(true);
});
(0, vitest_1.it)('should return true when withSentry is used in a variable assignment', () => {
const rootAst = (0, magicast_1.parseModule)(`
import { withSentry } from '@sentry/remix';
import { Outlet } from '@remix-run/react';
function App() {
return <Outlet />;
}
const WrappedApp = withSentry(App);
export default WrappedApp;
`);
(0, vitest_1.expect)((0, root_1.isWithSentryAlreadyUsed)(rootAst)).toBe(true);
});
(0, vitest_1.it)('should return true when withSentry is used inside a function', () => {
const rootAst = (0, magicast_1.parseModule)(`
import { withSentry } from '@sentry/remix';
import { Outlet } from '@remix-run/react';
function App() {
return <Outlet />;
}
function createApp() {
return withSentry(App);
}
export default createApp();
`);
(0, vitest_1.expect)((0, root_1.isWithSentryAlreadyUsed)(rootAst)).toBe(true);
});
(0, vitest_1.it)('should return false when withSentry is imported but not used', () => {
const rootAst = (0, magicast_1.parseModule)(`
import { withSentry } from '@sentry/remix';
import { Outlet } from '@remix-run/react';
export default function App() {
return <Outlet />;
}
`);
(0, vitest_1.expect)((0, root_1.isWithSentryAlreadyUsed)(rootAst)).toBe(false);
});
(0, vitest_1.it)('should return false when a different function with similar name is used', () => {
const rootAst = (0, magicast_1.parseModule)(`
import { Outlet } from '@remix-run/react';
function withSentryLike() {
return null;
}
export default function App() {
withSentryLike();
return <Outlet />;
}
`);
(0, vitest_1.expect)((0, root_1.isWithSentryAlreadyUsed)(rootAst)).toBe(false);
});
});
(0, vitest_1.describe)('instrumentRoot', () => {
const fixturesDir = path.join(__dirname, 'fixtures');
const tmpDir = path.join(fixturesDir, 'tmp');
(0, vitest_1.beforeEach)(() => {
vitest_1.vi.clearAllMocks();
// Mock process.cwd() to return the fixtures directory
vitest_1.vi.spyOn(process, 'cwd').mockReturnValue(fixturesDir);
});
(0, vitest_1.afterEach)(() => {
// Clean up any temporary files
if (fs.existsSync(tmpDir)) {
try {
// Remove files first, then directory
const appDir = path.join(tmpDir, 'app');
if (fs.existsSync(appDir)) {
const files = fs.readdirSync(appDir);
files.forEach((file) => {
fs.unlinkSync(path.join(appDir, file));
});
fs.rmdirSync(appDir);
}
const files = fs.readdirSync(tmpDir);
files.forEach((file) => {
const filePath = path.join(tmpDir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
fs.rmdirSync(filePath);
}
else {
fs.unlinkSync(filePath);
}
});
}
catch (error) {
// Ignore cleanup errors in tests
}
}
});
(0, vitest_1.it)('should add ErrorBoundary and wrap app with Sentry when no ErrorBoundary exists and withSentry is not used', async () => {
// Copy fixture to tmp directory for testing
const srcFile = path.join(fixturesDir, 'root-no-error-boundary.tsx');
const appDir = path.join(tmpDir, 'app');
// Create app directory and copy file
fs.mkdirSync(appDir, { recursive: true });
fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));
// Mock process.cwd() to return tmpDir
vitest_1.vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);
await (0, root_1.instrumentRoot)('root.tsx');
// Check that the file was modified correctly
const modifiedContent = fs.readFileSync(path.join(appDir, 'root.tsx'), 'utf8');
(0, vitest_1.expect)(modifiedContent).toContain('import { captureRemixErrorBoundaryError, withSentry } from "@sentry/remix";');
(0, vitest_1.expect)(modifiedContent).toContain("import { Outlet, useRouteError } from '@remix-run/react';");
(0, vitest_1.expect)(modifiedContent).toContain('withSentry(App)');
(0, vitest_1.expect)(modifiedContent).toContain('const ErrorBoundary = () => {');
});
(0, vitest_1.it)('should wrap app with Sentry when ErrorBoundary exists but no Sentry content', async () => {
const srcFile = path.join(fixturesDir, 'root-with-error-boundary.tsx');
const appDir = path.join(tmpDir, 'app');
fs.mkdirSync(appDir, { recursive: true });
fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));
vitest_1.vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);
await (0, root_1.instrumentRoot)('root.tsx');
const modifiedContent = fs.readFileSync(path.join(appDir, 'root.tsx'), 'utf8');
(0, vitest_1.expect)(modifiedContent).toContain('import { captureRemixErrorBoundaryError, withSentry } from "@sentry/remix";');
(0, vitest_1.expect)(modifiedContent).toContain('withSentry(App)');
});
(0, vitest_1.it)('should wrap app with Sentry when ErrorBoundary exists with Sentry content but withSentry is not used', async () => {
const srcFile = path.join(fixturesDir, 'root-with-sentry-error-boundary.tsx');
const appDir = path.join(tmpDir, 'app');
fs.mkdirSync(appDir, { recursive: true });
fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));
vitest_1.vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);
await (0, root_1.instrumentRoot)('root.tsx');
const modifiedContent = fs.readFileSync(path.join(appDir, 'root.tsx'), 'utf8');
(0, vitest_1.expect)(modifiedContent).toContain("import { captureRemixErrorBoundaryError, withSentry } from '@sentry/remix';");
(0, vitest_1.expect)(modifiedContent).toContain('withSentry(App)');
});
(0, vitest_1.it)('should not wrap app when withSentry is already used', async () => {
const srcFile = path.join(fixturesDir, 'root-already-wrapped.tsx');
const appDir = path.join(tmpDir, 'app');
fs.mkdirSync(appDir, { recursive: true });
fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));
vitest_1.vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);
await (0, root_1.instrumentRoot)('root.tsx');
const modifiedContent = fs.readFileSync(path.join(appDir, 'root.tsx'), 'utf8');
// Should not add withSentry import or wrap again
(0, vitest_1.expect)(modifiedContent).toContain('withSentry(App)');
// Count occurrences of withSentry to ensure it's not duplicated
const withSentryOccurrences = (modifiedContent.match(/withSentry/g) || [])
.length;
(0, vitest_1.expect)(withSentryOccurrences).toBe(2); // One import, one usage
// The content should remain largely the same since withSentry is already used
(0, vitest_1.expect)(modifiedContent).toContain('export default withSentry(App)');
});
(0, vitest_1.it)('should handle ErrorBoundary as variable declaration', async () => {
const srcFile = path.join(fixturesDir, 'root-variable-error-boundary.tsx');
const appDir = path.join(tmpDir, 'app');
fs.mkdirSync(appDir, { recursive: true });
fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));
vitest_1.vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);
await (0, root_1.instrumentRoot)('root.tsx');
const modifiedContent = fs.readFileSync(path.join(appDir, 'root.tsx'), 'utf8');
(0, vitest_1.expect)(modifiedContent).toContain('import { captureRemixErrorBoundaryError, withSentry } from "@sentry/remix";');
(0, vitest_1.expect)(modifiedContent).toContain('withSentry(App)');
});
});
//# sourceMappingURL=root.test.js.map