Skip to content

Commit 82a6c9f

Browse files
committed
tests
1 parent da5013a commit 82a6c9f

3 files changed

Lines changed: 295 additions & 1 deletion

File tree

packages/nextjs/test/config/getBuildPluginOptions.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,27 @@ describe('getBuildPluginOptions', () => {
902902
});
903903
});
904904

905+
describe('applicationKey is not forwarded to webpack plugin', () => {
906+
it('does not include turbopackApplicationKey in webpack plugin options', () => {
907+
const sentryBuildOptions: SentryBuildOptions = {
908+
org: 'test-org',
909+
project: 'test-project',
910+
_experimental: { turbopackApplicationKey: 'my-app' },
911+
};
912+
913+
const result = getBuildPluginOptions({
914+
sentryBuildOptions,
915+
releaseName: mockReleaseName,
916+
distDirAbsPath: mockDistDirAbsPath,
917+
buildTool: 'webpack-client',
918+
});
919+
920+
// turbopackApplicationKey should only be used by the Turbopack loader,
921+
// not forwarded to the webpack plugin
922+
expect(result.applicationKey).toBeUndefined();
923+
});
924+
});
925+
905926
describe('edge cases', () => {
906927
it('handles undefined release name gracefully', () => {
907928
const sentryBuildOptions: SentryBuildOptions = {
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { describe, expect, it } from 'vitest';
2+
import type { ModuleMetadataInjectionLoaderOptions } from '../../src/config/loaders/moduleMetadataInjectionLoader';
3+
import moduleMetadataInjectionLoader from '../../src/config/loaders/moduleMetadataInjectionLoader';
4+
import type { LoaderThis } from '../../src/config/loaders/types';
5+
6+
function createLoaderThis(
7+
applicationKey: string,
8+
useGetOptions = true,
9+
): LoaderThis<ModuleMetadataInjectionLoaderOptions> {
10+
const base = {
11+
addDependency: () => undefined,
12+
async: () => undefined,
13+
cacheable: () => undefined,
14+
callback: () => undefined,
15+
resourcePath: './app/page.tsx',
16+
};
17+
18+
if (useGetOptions) {
19+
return { ...base, getOptions: () => ({ applicationKey }) } as LoaderThis<ModuleMetadataInjectionLoaderOptions>;
20+
}
21+
22+
return { ...base, query: { applicationKey } } as LoaderThis<ModuleMetadataInjectionLoaderOptions>;
23+
}
24+
25+
describe('moduleMetadataInjectionLoader', () => {
26+
it('should inject metadata snippet into simple code', () => {
27+
const loaderThis = createLoaderThis('my-app');
28+
const userCode = 'import * as Sentry from \'@sentry/nextjs\';\nSentry.init();';
29+
30+
const result = moduleMetadataInjectionLoader.call(loaderThis, userCode);
31+
32+
expect(result).toContain('_sentryModuleMetadata');
33+
expect(result).toContain('_sentryBundlerPluginAppKey:my-app');
34+
expect(result).toContain('Object.assign');
35+
});
36+
37+
it('should inject after "use strict" directive', () => {
38+
const loaderThis = createLoaderThis('my-app');
39+
const userCode = '"use strict";\nconsole.log("hello");';
40+
41+
const result = moduleMetadataInjectionLoader.call(loaderThis, userCode);
42+
43+
const metadataIndex = result.indexOf('_sentryModuleMetadata');
44+
const directiveIndex = result.indexOf('"use strict"');
45+
expect(metadataIndex).toBeGreaterThan(directiveIndex);
46+
});
47+
48+
it('should inject after "use client" directive', () => {
49+
const loaderThis = createLoaderThis('my-app');
50+
const userCode = '"use client";\nimport React from \'react\';';
51+
52+
const result = moduleMetadataInjectionLoader.call(loaderThis, userCode);
53+
54+
const metadataIndex = result.indexOf('_sentryModuleMetadata');
55+
const directiveIndex = result.indexOf('"use client"');
56+
expect(metadataIndex).toBeGreaterThan(directiveIndex);
57+
});
58+
59+
it('should handle code with leading comments before directives', () => {
60+
const loaderThis = createLoaderThis('my-app');
61+
const userCode = '// some comment\n"use client";\nimport React from \'react\';';
62+
63+
const result = moduleMetadataInjectionLoader.call(loaderThis, userCode);
64+
65+
expect(result).toContain('_sentryBundlerPluginAppKey:my-app');
66+
const metadataIndex = result.indexOf('_sentryModuleMetadata');
67+
const directiveIndex = result.indexOf('"use client"');
68+
expect(metadataIndex).toBeGreaterThan(directiveIndex);
69+
});
70+
71+
it('should handle code with block comments before directives', () => {
72+
const loaderThis = createLoaderThis('my-app');
73+
const userCode = '/* block comment */\n"use client";\nimport React from \'react\';';
74+
75+
const result = moduleMetadataInjectionLoader.call(loaderThis, userCode);
76+
77+
expect(result).toContain('_sentryBundlerPluginAppKey:my-app');
78+
});
79+
80+
it('should set cacheable to false', () => {
81+
let cacheableValue: boolean | undefined;
82+
const loaderThis = {
83+
addDependency: () => undefined,
84+
async: () => undefined,
85+
cacheable: (flag: boolean) => {
86+
cacheableValue = flag;
87+
},
88+
callback: () => undefined,
89+
resourcePath: './app/page.tsx',
90+
getOptions: () => ({ applicationKey: 'my-app' }),
91+
} as LoaderThis<ModuleMetadataInjectionLoaderOptions>;
92+
93+
moduleMetadataInjectionLoader.call(loaderThis, 'const x = 1;');
94+
95+
expect(cacheableValue).toBe(false);
96+
});
97+
98+
it('should work with webpack 4 query API', () => {
99+
const loaderThis = createLoaderThis('my-app', false);
100+
const userCode = 'const x = 1;';
101+
102+
const result = moduleMetadataInjectionLoader.call(loaderThis, userCode);
103+
104+
expect(result).toContain('_sentryBundlerPluginAppKey:my-app');
105+
});
106+
107+
it('should use globalThis and Object.assign merge pattern keyed by stack trace', () => {
108+
const loaderThis = createLoaderThis('my-app');
109+
const userCode = 'const x = 1;';
110+
111+
const result = moduleMetadataInjectionLoader.call(loaderThis, userCode);
112+
113+
// Should use globalThis to avoid ReferenceError in strict mode
114+
expect(result).toContain('globalThis._sentryModuleMetadata = globalThis._sentryModuleMetadata || {}');
115+
// Should key by stack trace like the webpack plugin does
116+
expect(result).toContain('globalThis._sentryModuleMetadata[(new Error).stack]');
117+
// Should use Object.assign to merge metadata
118+
expect(result).toContain('Object.assign({}');
119+
});
120+
121+
it('should contain the correct app key format in output', () => {
122+
const loaderThis = createLoaderThis('test-key-123');
123+
const userCode = 'export default function Page() {}';
124+
125+
const result = moduleMetadataInjectionLoader.call(loaderThis, userCode);
126+
127+
expect(result).toContain('"_sentryBundlerPluginAppKey:test-key-123":true');
128+
});
129+
});

packages/nextjs/test/config/turbopack/constructTurbopackConfig.test.ts

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ vi.mock('path', async () => {
1212
const actual = await vi.importActual('path');
1313
return {
1414
...actual,
15-
resolve: vi.fn().mockReturnValue('/mocked/path/to/valueInjectionLoader.js'),
15+
resolve: vi.fn().mockImplementation((...args: string[]) => {
16+
const lastArg = args[args.length - 1];
17+
if (lastArg === 'moduleMetadataInjectionLoader.js') {
18+
return '/mocked/path/to/moduleMetadataInjectionLoader.js';
19+
}
20+
return '/mocked/path/to/valueInjectionLoader.js';
21+
}),
1622
};
1723
});
1824

@@ -936,6 +942,144 @@ describe('condition field version gating', () => {
936942
});
937943
});
938944

945+
describe('moduleMetadataInjection with applicationKey', () => {
946+
it('should add metadata loader rule when applicationKey is set and Next.js >= 16', () => {
947+
const pathResolveSpy = vi.spyOn(path, 'resolve');
948+
pathResolveSpy.mockImplementation((...args: string[]) => {
949+
const lastArg = args[args.length - 1];
950+
if (lastArg === 'moduleMetadataInjectionLoader.js') {
951+
return '/mocked/path/to/moduleMetadataInjectionLoader.js';
952+
}
953+
return '/mocked/path/to/valueInjectionLoader.js';
954+
});
955+
956+
const userNextConfig: NextConfigObject = {};
957+
958+
const result = constructTurbopackConfig({
959+
userNextConfig,
960+
userSentryOptions: { _experimental: { turbopackApplicationKey: 'my-app' } },
961+
nextJsVersion: '16.0.0',
962+
});
963+
964+
expect(result.rules!['*.{ts,tsx,js,jsx,mjs,cjs}']).toEqual({
965+
condition: { not: 'foreign' },
966+
loaders: [
967+
{
968+
loader: '/mocked/path/to/moduleMetadataInjectionLoader.js',
969+
options: {
970+
applicationKey: 'my-app',
971+
},
972+
},
973+
],
974+
});
975+
});
976+
977+
it('should NOT add metadata loader rule when Next.js < 16', () => {
978+
const userNextConfig: NextConfigObject = {};
979+
980+
const result = constructTurbopackConfig({
981+
userNextConfig,
982+
userSentryOptions: { _experimental: { turbopackApplicationKey: 'my-app' } },
983+
nextJsVersion: '15.4.1',
984+
});
985+
986+
expect(result.rules!['*.{ts,tsx,js,jsx,mjs,cjs}']).toBeUndefined();
987+
});
988+
989+
it('should NOT add metadata loader rule when applicationKey is not set', () => {
990+
const userNextConfig: NextConfigObject = {};
991+
992+
const result = constructTurbopackConfig({
993+
userNextConfig,
994+
userSentryOptions: {},
995+
nextJsVersion: '16.0.0',
996+
});
997+
998+
expect(result.rules!['*.{ts,tsx,js,jsx,mjs,cjs}']).toBeUndefined();
999+
});
1000+
1001+
it('should NOT add metadata loader rule when nextJsVersion is undefined', () => {
1002+
const userNextConfig: NextConfigObject = {};
1003+
1004+
const result = constructTurbopackConfig({
1005+
userNextConfig,
1006+
userSentryOptions: { _experimental: { turbopackApplicationKey: 'my-app' } },
1007+
nextJsVersion: undefined,
1008+
});
1009+
1010+
expect(result.rules!['*.{ts,tsx,js,jsx,mjs,cjs}']).toBeUndefined();
1011+
});
1012+
1013+
it('should pass applicationKey through to loader options correctly', () => {
1014+
const userNextConfig: NextConfigObject = {};
1015+
1016+
const result = constructTurbopackConfig({
1017+
userNextConfig,
1018+
userSentryOptions: { _experimental: { turbopackApplicationKey: 'custom-key-123' } },
1019+
nextJsVersion: '16.0.0',
1020+
});
1021+
1022+
const rule = result.rules!['*.{ts,tsx,js,jsx,mjs,cjs}'] as {
1023+
condition: unknown;
1024+
loaders: Array<{ loader: string; options: { applicationKey: string } }>;
1025+
};
1026+
expect(rule.loaders[0]!.options.applicationKey).toBe('custom-key-123');
1027+
});
1028+
1029+
it('should coexist with existing value injection rules', () => {
1030+
const userNextConfig: NextConfigObject = {};
1031+
const mockRouteManifest: RouteManifest = {
1032+
dynamicRoutes: [],
1033+
staticRoutes: [{ path: '/', regex: '/' }],
1034+
isrRoutes: [],
1035+
};
1036+
1037+
const result = constructTurbopackConfig({
1038+
userNextConfig,
1039+
userSentryOptions: { _experimental: { turbopackApplicationKey: 'my-app' } },
1040+
routeManifest: mockRouteManifest,
1041+
nextJsVersion: '16.0.0',
1042+
});
1043+
1044+
// Value injection rules should still be present
1045+
expect(result.rules!['**/instrumentation-client.*']).toBeDefined();
1046+
expect(result.rules!['**/instrumentation.*']).toBeDefined();
1047+
// Metadata loader rule should also be present
1048+
expect(result.rules!['*.{ts,tsx,js,jsx,mjs,cjs}']).toBeDefined();
1049+
});
1050+
1051+
it('should add metadata loader rule for Next.js 17+', () => {
1052+
const pathResolveSpy = vi.spyOn(path, 'resolve');
1053+
pathResolveSpy.mockImplementation((...args: string[]) => {
1054+
const lastArg = args[args.length - 1];
1055+
if (lastArg === 'moduleMetadataInjectionLoader.js') {
1056+
return '/mocked/path/to/moduleMetadataInjectionLoader.js';
1057+
}
1058+
return '/mocked/path/to/valueInjectionLoader.js';
1059+
});
1060+
1061+
const userNextConfig: NextConfigObject = {};
1062+
1063+
const result = constructTurbopackConfig({
1064+
userNextConfig,
1065+
userSentryOptions: { _experimental: { turbopackApplicationKey: 'my-app' } },
1066+
nextJsVersion: '17.0.0',
1067+
});
1068+
1069+
expect(result.rules!['*.{ts,tsx,js,jsx,mjs,cjs}']).toEqual({
1070+
condition: { not: 'foreign' },
1071+
loaders: [
1072+
{
1073+
loader: '/mocked/path/to/moduleMetadataInjectionLoader.js',
1074+
options: {
1075+
applicationKey: 'my-app',
1076+
},
1077+
},
1078+
],
1079+
});
1080+
});
1081+
});
1082+
9391083
describe('safelyAddTurbopackRule', () => {
9401084
const mockRule = {
9411085
loaders: [

0 commit comments

Comments
 (0)