diff --git a/packages/nextjs/jest.config.js b/packages/nextjs/jest.config.js deleted file mode 100644 index fd23311e1656..000000000000 --- a/packages/nextjs/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const baseConfig = require('../../jest/jest.config.js'); - -module.exports = { - ...baseConfig, -}; diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index ea1953969d20..f3e309d31572 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -117,8 +117,8 @@ "lint": "eslint . --format stylish", "test": "yarn test:unit", "test:all": "run-s test:unit", - "test:unit": "jest", - "test:watch": "jest --watch", + "test:unit": "vitest run", + "test:watch": "vitest --watch", "vercel:branch": "source vercel/set-up-branch-for-test-app-use.sh", "vercel:project": "source vercel/make-project-use-current-branch.sh", "yalc:publish": "yalc publish --push --sig" diff --git a/packages/nextjs/test/clientSdk.test.ts b/packages/nextjs/test/clientSdk.test.ts index 3464c5de3c93..08f3fc5ca2a9 100644 --- a/packages/nextjs/test/clientSdk.test.ts +++ b/packages/nextjs/test/clientSdk.test.ts @@ -4,11 +4,12 @@ import type { Integration } from '@sentry/core'; import * as SentryReact from '@sentry/react'; import { WINDOW, getClient, getCurrentScope } from '@sentry/react'; import { JSDOM } from 'jsdom'; +import { describe, vi, afterAll, afterEach, it, expect } from 'vitest'; import { breadcrumbsIntegration, browserTracingIntegration, init } from '../src/client'; -const reactInit = jest.spyOn(SentryReact, 'init'); -const loggerLogSpy = jest.spyOn(logger, 'log'); +const reactInit = vi.spyOn(SentryReact, 'init'); +const loggerLogSpy = vi.spyOn(logger, 'log'); // We're setting up JSDom here because the Next.js routing instrumentations requires a few things to be present on pageload: // 1. Access to window.document API for `window.document.getElementById` @@ -38,7 +39,7 @@ const TEST_DSN = 'https://public@dsn.ingest.sentry.io/1337'; describe('Client init()', () => { afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); getGlobalScope().clear(); getIsolationScope().clear(); @@ -83,7 +84,7 @@ describe('Client init()', () => { dsn: 'https://dogsarebadatkeepingsecrets@squirrelchasers.ingest.sentry.io/12312012', tracesSampleRate: 1.0, }); - const transportSend = jest.spyOn(getClient()!.getTransport()!, 'send'); + const transportSend = vi.spyOn(getClient()!.getTransport()!, 'send'); // Ensure we have no current span, so our next span is a transaction SentryReact.withActiveSpan(null, () => { diff --git a/packages/nextjs/test/config/__snapshots__/valueInjectionLoader.test.ts.snap b/packages/nextjs/test/config/__snapshots__/valueInjectionLoader.test.ts.snap index ca901580da63..9e65a8b72384 100644 --- a/packages/nextjs/test/config/__snapshots__/valueInjectionLoader.test.ts.snap +++ b/packages/nextjs/test/config/__snapshots__/valueInjectionLoader.test.ts.snap @@ -1,16 +1,16 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`valueInjectionLoader should correctly insert values for basic config 1`] = ` +exports[`valueInjectionLoader > should correctly insert values for basic config 1`] = ` " - ;globalThis[\\"foo\\"] = \\"bar\\";import * as Sentry from '@sentry/nextjs'; + ;globalThis["foo"] = "bar";import * as Sentry from '@sentry/nextjs'; Sentry.init(); " `; -exports[`valueInjectionLoader should correctly insert values with a misplaced directive 1`] = ` +exports[`valueInjectionLoader > should correctly insert values with a misplaced directive 1`] = ` " - ;globalThis[\\"foo\\"] = \\"bar\\";console.log('This will render the directive useless'); - \\"use client\\"; + ;globalThis["foo"] = "bar";console.log('This will render the directive useless'); + "use client"; @@ -19,44 +19,44 @@ exports[`valueInjectionLoader should correctly insert values with a misplaced di " `; -exports[`valueInjectionLoader should correctly insert values with directive 1`] = ` +exports[`valueInjectionLoader > should correctly insert values with directive 1`] = ` " - \\"use client\\";globalThis[\\"foo\\"] = \\"bar\\"; + "use client";globalThis["foo"] = "bar"; import * as Sentry from '@sentry/nextjs'; Sentry.init(); " `; -exports[`valueInjectionLoader should correctly insert values with directive and block comments 1`] = ` +exports[`valueInjectionLoader > should correctly insert values with directive and block comments 1`] = ` " /* test */ - \\"use client\\";;globalThis[\\"foo\\"] = \\"bar\\"; + "use client";;globalThis["foo"] = "bar"; import * as Sentry from '@sentry/nextjs'; Sentry.init(); " `; -exports[`valueInjectionLoader should correctly insert values with directive and inline comments 1`] = ` +exports[`valueInjectionLoader > should correctly insert values with directive and inline comments 1`] = ` " // test - \\"use client\\";;globalThis[\\"foo\\"] = \\"bar\\"; + "use client";;globalThis["foo"] = "bar"; import * as Sentry from '@sentry/nextjs'; Sentry.init(); " `; -exports[`valueInjectionLoader should correctly insert values with directive and multiline block comments 1`] = ` +exports[`valueInjectionLoader > should correctly insert values with directive and multiline block comments 1`] = ` " /* test */ - \\"use client\\";;globalThis[\\"foo\\"] = \\"bar\\"; + "use client";;globalThis["foo"] = "bar"; import * as Sentry from '@sentry/nextjs'; Sentry.init(); " `; -exports[`valueInjectionLoader should correctly insert values with directive and multiline block comments and a bunch of whitespace 1`] = ` +exports[`valueInjectionLoader > should correctly insert values with directive and multiline block comments and a bunch of whitespace 1`] = ` " /* test @@ -65,7 +65,7 @@ exports[`valueInjectionLoader should correctly insert values with directive and - \\"use client\\";;globalThis[\\"foo\\"] = \\"bar\\"; + "use client";;globalThis["foo"] = "bar"; @@ -74,9 +74,9 @@ exports[`valueInjectionLoader should correctly insert values with directive and " `; -exports[`valueInjectionLoader should correctly insert values with directive and semicolon 1`] = ` +exports[`valueInjectionLoader > should correctly insert values with directive and semicolon 1`] = ` " - \\"use client\\";;globalThis[\\"foo\\"] = \\"bar\\"; + "use client";;globalThis["foo"] = "bar"; import * as Sentry from '@sentry/nextjs'; Sentry.init(); " diff --git a/packages/nextjs/test/config/loaders.test.ts b/packages/nextjs/test/config/loaders.test.ts index c559ee643baf..62a128469f28 100644 --- a/packages/nextjs/test/config/loaders.test.ts +++ b/packages/nextjs/test/config/loaders.test.ts @@ -2,6 +2,7 @@ import './mocks'; import * as fs from 'fs'; +import { describe, vi, it, expect } from 'vitest'; import type { ModuleRuleUseProperty, WebpackModuleRule } from '../../src/config/types'; import { @@ -13,36 +14,8 @@ import { } from './fixtures'; import { materializeFinalWebpackConfig } from './testUtils'; -const existsSyncSpy = jest.spyOn(fs, 'existsSync'); -const lstatSyncSpy = jest.spyOn(fs, 'lstatSync'); - -type MatcherResult = { pass: boolean; message: () => string }; - -expect.extend({ - stringEndingWith(received: string, expectedEnding: string): MatcherResult { - const failsTest = !received.endsWith(expectedEnding); - const generateErrorMessage = () => - failsTest - ? // Regular error message for match failing - `expected string ending with '${expectedEnding}', but got '${received}'` - : // Error message for the match passing if someone has called it with `expect.not` - `expected string not ending with '${expectedEnding}', but got '${received}'`; - - return { - pass: !failsTest, - message: generateErrorMessage, - }; - }, -}); - -declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace jest { - interface Expect { - stringEndingWith: (expectedEnding: string) => MatcherResult; - } - } -} +const existsSyncSpy = vi.spyOn(fs, 'existsSync'); +const lstatSyncSpy = vi.spyOn(fs, 'lstatSync'); function applyRuleToResource(rule: WebpackModuleRule, resourcePath: string): ModuleRuleUseProperty[] { const applications = []; @@ -80,7 +53,7 @@ describe('webpack loaders', () => { test: expect.any(RegExp), use: [ { - loader: expect.stringEndingWith('valueInjectionLoader.js'), + loader: expect.stringMatching(/valueInjectionLoader\.js$/), // We use `expect.objectContaining({})` rather than `expect.any(Object)` to match any plain object because // the latter will also match arrays, regexes, dates, sets, etc. - anything whose `typeof` value is // `'object'`. @@ -272,7 +245,7 @@ describe('webpack loaders', () => { test: /sentry\.client\.config\.(jsx?|tsx?)/, use: [ { - loader: expect.stringEndingWith('valueInjectionLoader.js'), + loader: expect.stringMatching(/valueInjectionLoader\.js$/), // We use `expect.objectContaining({})` rather than `expect.any(Object)` to match any plain object because // the latter will also match arrays, regexes, dates, sets, etc. - anything whose `typeof` value is // `'object'`. @@ -285,9 +258,10 @@ describe('webpack loaders', () => { }); describe('`distDir` value in default server-side `RewriteFrames` integration', () => { - describe('`RewriteFrames` ends up with correct `distDir` value', () => { + it('`RewriteFrames` ends up with correct `distDir` value', () => { // TODO: this, along with any number of other parts of the build process, should be tested with an integration // test which actually runs webpack and inspects the resulting bundles (and that integration test should test - // custom `distDir` values with and without a `.`, to make sure the regex escaping is working) + // custom `distDir` values with and without a `.`, to make sure the regex + // escaping is working) }); }); diff --git a/packages/nextjs/test/config/mocks.ts b/packages/nextjs/test/config/mocks.ts index 7d2cc1a0f4ac..82c46841a6f4 100644 --- a/packages/nextjs/test/config/mocks.ts +++ b/packages/nextjs/test/config/mocks.ts @@ -4,14 +4,21 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; +import { vi, afterAll, afterEach } from 'vitest'; + +vi.mock('fs'); +vi.mock('os'); import { CLIENT_SDK_CONFIG_FILE, EDGE_SDK_CONFIG_FILE, SERVER_SDK_CONFIG_FILE } from './fixtures'; // We use `fs.existsSync()` in `getUserConfigFile()`. When we're not testing `getUserConfigFile()` specifically, all we // need is for it to give us any valid answer, so make it always find what it's looking for. Since this is a core node // built-in, though, which jest itself uses, otherwise let it do the normal thing. Storing the real version of the -// function also lets us restore the original when we do want to test `getUserConfigFile()`. -export const realExistsSync = jest.requireActual('fs').existsSync; +// function also lets us restore the original when we do want to test +// `getUserConfigFile()`. +// eslint-disable-next-line @typescript-eslint/consistent-type-imports +const fsReal = (await vi.importActual('fs')) as typeof import('fs'); +export const realExistsSync = fsReal.existsSync; export const mockExistsSync = (path: fs.PathLike): ReturnType => { if ( (path as string).endsWith(SERVER_SDK_CONFIG_FILE) || @@ -23,20 +30,22 @@ export const mockExistsSync = (path: fs.PathLike): ReturnType { // In order to know what to expect in the webpack config `entry` property, we need to know the path of the temporary // directory created when doing the file injection, so wrap the real `mkdtempSync` and store the resulting path where we // can access it -export const mkdtempSyncSpy = jest.spyOn(fs, 'mkdtempSync'); +export const mkdtempSyncSpy = vi.spyOn(fs, 'mkdtempSync'); afterEach(() => { mkdtempSyncSpy.mockClear(); diff --git a/packages/nextjs/test/config/valueInjectionLoader.test.ts b/packages/nextjs/test/config/valueInjectionLoader.test.ts index 2d810ad87c5a..e0091bd52c6c 100644 --- a/packages/nextjs/test/config/valueInjectionLoader.test.ts +++ b/packages/nextjs/test/config/valueInjectionLoader.test.ts @@ -1,3 +1,5 @@ +import { describe, it, expect } from 'vitest'; + import type { LoaderThis } from '../../src/config/loaders/types'; import type { ValueInjectionLoaderOptions } from '../../src/config/loaders/valueInjectionLoader'; import valueInjectionLoader from '../../src/config/loaders/valueInjectionLoader'; diff --git a/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts b/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts index 4e99dd61950b..aaedb3d729df 100644 --- a/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts +++ b/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts @@ -1,3 +1,5 @@ +import { describe, expect, it, vi } from 'vitest'; + // mock helper functions not tested directly in this file import '../mocks'; @@ -12,9 +14,16 @@ import { userNextConfig, } from '../fixtures'; import { materializeFinalNextConfig, materializeFinalWebpackConfig } from '../testUtils'; +import * as core from '@sentry/core'; describe('constructWebpackConfigFunction()', () => { it('includes expected properties', async () => { + vi.spyOn(core, 'loadModule').mockImplementation(() => ({ + sentryWebpackPlugin: () => ({ + _name: 'sentry-webpack-plugin', + }), + })); + const finalWebpackConfig = await materializeFinalWebpackConfig({ exportedNextConfig, incomingWebpackConfig: serverWebpackConfig, @@ -46,7 +55,12 @@ describe('constructWebpackConfigFunction()', () => { }); it('automatically enables deleteSourcemapsAfterUpload for client builds when not explicitly set', async () => { - const getWebpackPluginOptionsSpy = jest.spyOn(getWebpackPluginOptionsModule, 'getWebpackPluginOptions'); + const getWebpackPluginOptionsSpy = vi.spyOn(getWebpackPluginOptionsModule, 'getWebpackPluginOptions'); + vi.spyOn(core, 'loadModule').mockImplementation(() => ({ + sentryWebpackPlugin: () => ({ + _name: 'sentry-webpack-plugin', + }), + })); await materializeFinalWebpackConfig({ exportedNextConfig, @@ -112,6 +126,12 @@ describe('constructWebpackConfigFunction()', () => { }); it('uses `hidden-source-map` as `devtool` value for client-side builds', async () => { + vi.spyOn(core, 'loadModule').mockImplementation(() => ({ + sentryWebpackPlugin: () => ({ + _name: 'sentry-webpack-plugin', + }), + })); + const finalClientWebpackConfig = await materializeFinalWebpackConfig({ exportedNextConfig: exportedNextConfig, incomingWebpackConfig: clientWebpackConfig, diff --git a/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts b/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts index 55f394fd268f..d2aad8fb19a0 100644 --- a/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts +++ b/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts @@ -1,3 +1,5 @@ +import { describe, it, expect } from 'vitest'; + import type { BuildContext, NextConfigObject } from '../../../src/config/types'; import { getWebpackPluginOptions } from '../../../src/config/webpackPluginOptions'; diff --git a/packages/nextjs/test/config/withSentry.test.ts b/packages/nextjs/test/config/withSentry.test.ts index 30f34634d9fd..5ac222559e38 100644 --- a/packages/nextjs/test/config/withSentry.test.ts +++ b/packages/nextjs/test/config/withSentry.test.ts @@ -1,11 +1,12 @@ import * as SentryCore from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; import type { NextApiRequest, NextApiResponse } from 'next'; +import { describe, vi, beforeEach, afterEach, it, expect } from 'vitest'; import type { AugmentedNextApiResponse, NextApiHandler } from '../../src/common/types'; import { wrapApiHandlerWithSentry } from '../../src/server'; -const startSpanManualSpy = jest.spyOn(SentryCore, 'startSpanManual'); +const startSpanManualSpy = vi.spyOn(SentryCore, 'startSpanManual'); describe('withSentry', () => { let req: NextApiRequest, res: NextApiResponse; @@ -32,7 +33,7 @@ describe('withSentry', () => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('tracing', () => { diff --git a/packages/nextjs/test/config/withSentryConfig.test.ts b/packages/nextjs/test/config/withSentryConfig.test.ts index e457174f5fa9..ab80526b3fcc 100644 --- a/packages/nextjs/test/config/withSentryConfig.test.ts +++ b/packages/nextjs/test/config/withSentryConfig.test.ts @@ -1,3 +1,5 @@ +import { describe, vi, it, expect } from 'vitest'; + import { defaultRuntimePhase, defaultsObject, exportedNextConfig, userNextConfig } from './fixtures'; import { materializeFinalNextConfig } from './testUtils'; @@ -43,7 +45,7 @@ describe('withSentryConfig', () => { }); it('correctly passes `phase` and `defaultConfig` through to functional `userNextConfig`', () => { - const exportedNextConfigFunction = jest.fn().mockReturnValue(userNextConfig); + const exportedNextConfigFunction = vi.fn().mockReturnValue(userNextConfig); materializeFinalNextConfig(exportedNextConfigFunction); diff --git a/packages/nextjs/test/config/wrappers.test.ts b/packages/nextjs/test/config/wrappers.test.ts index f18160a9fbf0..332a1b89c4bd 100644 --- a/packages/nextjs/test/config/wrappers.test.ts +++ b/packages/nextjs/test/config/wrappers.test.ts @@ -1,10 +1,11 @@ import type { IncomingMessage, ServerResponse } from 'http'; import * as SentryCore from '@sentry/core'; - import type { Client } from '@sentry/core'; +import { describe, vi, beforeEach, afterEach, test, expect } from 'vitest'; + import { wrapGetInitialPropsWithSentry, wrapGetServerSidePropsWithSentry } from '../../src/common'; -const startSpanManualSpy = jest.spyOn(SentryCore, 'startSpanManual'); +const startSpanManualSpy = vi.spyOn(SentryCore, 'startSpanManual'); describe('data-fetching function wrappers should not create manual spans', () => { const route = '/tricks/[trickName]'; @@ -13,10 +14,10 @@ describe('data-fetching function wrappers should not create manual spans', () => beforeEach(() => { req = { headers: {}, url: 'http://dogs.are.great/tricks/kangaroo' } as IncomingMessage; - res = { end: jest.fn() } as unknown as ServerResponse; + res = { end: vi.fn() } as unknown as ServerResponse; - jest.spyOn(SentryCore, 'hasSpansEnabled').mockReturnValue(true); - jest.spyOn(SentryCore, 'getClient').mockImplementation(() => { + vi.spyOn(SentryCore, 'hasSpansEnabled').mockReturnValue(true); + vi.spyOn(SentryCore, 'getClient').mockImplementation(() => { return { getOptions: () => ({}), getDsn: () => {}, @@ -25,11 +26,11 @@ describe('data-fetching function wrappers should not create manual spans', () => }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); test('wrapGetServerSidePropsWithSentry', async () => { - const origFunction = jest.fn(async () => ({ props: {} })); + const origFunction = vi.fn(async () => ({ props: {} })); const wrappedOriginal = wrapGetServerSidePropsWithSentry(origFunction, route); await wrappedOriginal({ req, res } as any); @@ -38,7 +39,7 @@ describe('data-fetching function wrappers should not create manual spans', () => }); test('wrapGetInitialPropsWithSentry', async () => { - const origFunction = jest.fn(async () => ({})); + const origFunction = vi.fn(async () => ({})); const wrappedOriginal = wrapGetInitialPropsWithSentry(origFunction); await wrappedOriginal({ req, res, pathname: route } as any); @@ -47,15 +48,15 @@ describe('data-fetching function wrappers should not create manual spans', () => }); test('wrapped function sets route backfill attribute when called within an active span', async () => { - const mockSetAttribute = jest.fn(); - const mockGetActiveSpan = jest.spyOn(SentryCore, 'getActiveSpan').mockReturnValue({ + const mockSetAttribute = vi.fn(); + const mockGetActiveSpan = vi.spyOn(SentryCore, 'getActiveSpan').mockReturnValue({ setAttribute: mockSetAttribute, } as any); - const mockGetRootSpan = jest.spyOn(SentryCore, 'getRootSpan').mockReturnValue({ + const mockGetRootSpan = vi.spyOn(SentryCore, 'getRootSpan').mockReturnValue({ setAttribute: mockSetAttribute, } as any); - const origFunction = jest.fn(async () => ({ props: {} })); + const origFunction = vi.fn(async () => ({ props: {} })); const wrappedOriginal = wrapGetServerSidePropsWithSentry(origFunction, route); await wrappedOriginal({ req, res } as any); @@ -66,15 +67,15 @@ describe('data-fetching function wrappers should not create manual spans', () => }); test('wrapped function does not set route backfill attribute for /_error route', async () => { - const mockSetAttribute = jest.fn(); - const mockGetActiveSpan = jest.spyOn(SentryCore, 'getActiveSpan').mockReturnValue({ + const mockSetAttribute = vi.fn(); + const mockGetActiveSpan = vi.spyOn(SentryCore, 'getActiveSpan').mockReturnValue({ setAttribute: mockSetAttribute, } as any); - const mockGetRootSpan = jest.spyOn(SentryCore, 'getRootSpan').mockReturnValue({ + const mockGetRootSpan = vi.spyOn(SentryCore, 'getRootSpan').mockReturnValue({ setAttribute: mockSetAttribute, } as any); - const origFunction = jest.fn(async () => ({ props: {} })); + const origFunction = vi.fn(async () => ({ props: {} })); const wrappedOriginal = wrapGetServerSidePropsWithSentry(origFunction, '/_error'); await wrappedOriginal({ req, res } as any); diff --git a/packages/nextjs/test/config/wrappingLoader.test.ts b/packages/nextjs/test/config/wrappingLoader.test.ts index 4458a9ce16b6..fc4d3018faa4 100644 --- a/packages/nextjs/test/config/wrappingLoader.test.ts +++ b/packages/nextjs/test/config/wrappingLoader.test.ts @@ -1,9 +1,12 @@ import * as fs from 'fs'; import * as path from 'path'; +import { describe, vi, it, expect } from 'vitest'; + +vi.mock('fs', { spy: true }); const originalReadfileSync = fs.readFileSync; -jest.spyOn(fs, 'readFileSync').mockImplementation((filePath, options) => { +vi.spyOn(fs, 'readFileSync').mockImplementation((filePath, options) => { if (filePath.toString().endsWith('/config/templates/apiWrapperTemplate.js')) { return originalReadfileSync( path.join(__dirname, '../../build/cjs/config/templates/apiWrapperTemplate.js'), @@ -51,7 +54,7 @@ jest.spyOn(fs, 'readFileSync').mockImplementation((filePath, options) => { import type { LoaderThis } from '../../src/config/loaders/types'; import type { WrappingLoaderOptions } from '../../src/config/loaders/wrappingLoader'; -import wrappingLoader from '../../src/config/loaders/wrappingLoader'; +const { default: wrappingLoader } = await import('../../src/config/loaders/wrappingLoader'); const DEFAULT_PAGE_EXTENSION_REGEX = ['tsx', 'ts', 'jsx', 'js'].join('|'); @@ -63,7 +66,7 @@ const defaultLoaderThis = { describe('wrappingLoader', () => { it('should correctly wrap API routes on unix', async () => { - const callback = jest.fn(); + const callback = vi.fn(); const userCode = ` export default function handler(req, res) { diff --git a/packages/nextjs/test/edge/withSentryAPI.test.ts b/packages/nextjs/test/edge/withSentryAPI.test.ts index 11449da0e1ef..d268e8419ec6 100644 --- a/packages/nextjs/test/edge/withSentryAPI.test.ts +++ b/packages/nextjs/test/edge/withSentryAPI.test.ts @@ -1,3 +1,5 @@ +import { describe, vi, afterAll, afterEach, it } from 'vitest'; + import { wrapApiHandlerWithSentry } from '../../src/edge'; const origRequest = global.Request; @@ -29,12 +31,12 @@ afterAll(() => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('wrapApiHandlerWithSentry', () => { it('should return a function that does not throw when no request is passed', async () => { - const origFunction = jest.fn(() => new Response()); + const origFunction = vi.fn(() => new Response()); const wrappedFunction = wrapApiHandlerWithSentry(origFunction, '/user/[userId]/post/[postId]'); diff --git a/packages/nextjs/test/performance/pagesRouterInstrumentation.test.ts b/packages/nextjs/test/performance/pagesRouterInstrumentation.test.ts index ac20f0ab21ab..846da0b3e83a 100644 --- a/packages/nextjs/test/performance/pagesRouterInstrumentation.test.ts +++ b/packages/nextjs/test/performance/pagesRouterInstrumentation.test.ts @@ -3,6 +3,7 @@ import { WINDOW } from '@sentry/react'; import { JSDOM } from 'jsdom'; import type { NEXT_DATA } from 'next/dist/shared/lib/utils'; import Router from 'next/router'; +import { describe, vi, afterEach, it, expect } from 'vitest'; import { pagesRouterInstrumentNavigation, @@ -20,7 +21,7 @@ const originalBuildManifestRoutes = globalObject.__BUILD_MANIFEST?.sortedPages; let eventHandlers: { [eventName: string]: Set<(...args: any[]) => void> } = {}; -jest.mock('next/router', () => { +vi.mock('next/router', () => { return { default: { events: { @@ -31,7 +32,7 @@ jest.mock('next/router', () => { eventHandlers[type]!.add(handler); }, - off: jest.fn((type: string, handler: (...args: any[]) => void) => { + off: vi.fn((type: string, handler: (...args: any[]) => void) => { if (eventHandlers[type]) { eventHandlers[type]!.delete(handler); } @@ -107,7 +108,7 @@ describe('pagesRouterInstrumentPageLoad', () => { eventHandlers = {}; // Necessary to clear all Router.events.off() mock call numbers - jest.clearAllMocks(); + vi.clearAllMocks(); }); it.each([ @@ -186,7 +187,7 @@ describe('pagesRouterInstrumentPageLoad', () => { (url, route, query, props, hasNextData, expectedStartTransactionArgument) => { setUpNextPage({ url, route, query, props, hasNextData }); - const emit = jest.fn(); + const emit = vi.fn(); const client = { emit, getOptions: () => ({}), @@ -269,7 +270,7 @@ describe('pagesRouterInstrumentNavigation', () => { eventHandlers = {}; // Necessary to clear all Router.events.off() mock call numbers - jest.clearAllMocks(); + vi.clearAllMocks(); }); it.each([ @@ -311,7 +312,7 @@ describe('pagesRouterInstrumentNavigation', () => { ], }); - const emit = jest.fn(); + const emit = vi.fn(); const client = { emit, getOptions: () => ({}), diff --git a/packages/nextjs/test/serverSdk.test.ts b/packages/nextjs/test/serverSdk.test.ts index 17e46e0f90e5..f01cc55a1d17 100644 --- a/packages/nextjs/test/serverSdk.test.ts +++ b/packages/nextjs/test/serverSdk.test.ts @@ -2,13 +2,14 @@ import { GLOBAL_OBJ } from '@sentry/core'; import type { Integration } from '@sentry/core'; import { getCurrentScope } from '@sentry/node'; import * as SentryNode from '@sentry/node'; +import { describe, vi, afterEach, it, expect } from 'vitest'; import { init } from '../src/server'; // normally this is set as part of the build process, so mock it here (GLOBAL_OBJ as typeof GLOBAL_OBJ & { _sentryRewriteFramesDistDir: string })._sentryRewriteFramesDistDir = '.next'; -const nodeInit = jest.spyOn(SentryNode, 'init'); +const nodeInit = vi.spyOn(SentryNode, 'init'); function findIntegrationByName(integrations: Integration[] = [], name: string): Integration | undefined { return integrations.find(integration => integration.name === name); @@ -16,7 +17,7 @@ function findIntegrationByName(integrations: Integration[] = [], name: string): describe('Server init()', () => { afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); SentryNode.getGlobalScope().clear(); SentryNode.getIsolationScope().clear(); diff --git a/packages/nextjs/test/utils/tunnelRoute.test.ts b/packages/nextjs/test/utils/tunnelRoute.test.ts index 3a33130a3220..24f8ae7ee92a 100644 --- a/packages/nextjs/test/utils/tunnelRoute.test.ts +++ b/packages/nextjs/test/utils/tunnelRoute.test.ts @@ -1,4 +1,5 @@ import type { BrowserOptions } from '@sentry/react'; +import { describe, vi, beforeEach, it, expect } from 'vitest'; import { applyTunnelRouteOption } from '../../src/client/tunnelRoute'; @@ -35,7 +36,7 @@ describe('applyTunnelRouteOption()', () => { it("Doesn't apply `tunnelRoute` when DSN is invalid", () => { // Avoid polluting the test output with error messages - const mockConsoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); + const mockConsoleError = vi.spyOn(console, 'error').mockImplementation(() => {}); globalWithInjectedValues._sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { diff --git a/packages/nextjs/tsconfig.test.json b/packages/nextjs/tsconfig.test.json index f72f7d93a39e..ecd411a65dc3 100644 --- a/packages/nextjs/tsconfig.test.json +++ b/packages/nextjs/tsconfig.test.json @@ -1,11 +1,15 @@ { "extends": "./tsconfig.json", - "include": ["test/**/*"], + "include": ["test/**/*", "vite.config.ts"], "compilerOptions": { // should include all types from `./tsconfig.json` plus types for all test frameworks used - "types": ["node", "jest"], + "types": ["node"], + + // require for top-level await + "module": "Node16", + "target": "es2017", // other package-specific, test-specific options "lib": ["DOM", "ESNext"] diff --git a/packages/nextjs/vite.config.ts b/packages/nextjs/vite.config.ts new file mode 100644 index 000000000000..ff64487a9265 --- /dev/null +++ b/packages/nextjs/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config'; +import baseConfig from '../../vite/vite.config'; + +export default defineConfig({ + ...baseConfig, + test: { + ...baseConfig.test, + environment: 'node', + }, +});