UNPKG

@sentry/wizard

Version:

Sentry wizard helping you to configure your project

260 lines (246 loc) 11.6 kB
"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