From 27d3cdf605a82cac8d2b34badaccb0027abb8a8c Mon Sep 17 00:00:00 2001 From: John Daly Date: Fri, 10 Feb 2023 19:57:24 -0800 Subject: [PATCH 01/11] Adding paths-to-module-name-mapper utility --- packages/next/src/build/jest/jest.ts | 19 +++++ .../build/jest/paths-to-module-name-mapper.ts | 63 +++++++++++++++ .../jest-paths-to-module-name-mapper.test.ts | 79 +++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 packages/next/src/build/jest/paths-to-module-name-mapper.ts create mode 100644 test/unit/jest-paths-to-module-name-mapper.test.ts diff --git a/packages/next/src/build/jest/jest.ts b/packages/next/src/build/jest/jest.ts index 9ba1fb0b2a9d5..0b32988467787 100644 --- a/packages/next/src/build/jest/jest.ts +++ b/packages/next/src/build/jest/jest.ts @@ -7,6 +7,7 @@ import loadJsConfig from '../load-jsconfig' import * as Log from '../output/log' import { findPagesDir } from '../../lib/find-pages-dir' import { loadBindings, lockfilePatchPromise } from '../swc' +import { pathsToModuleNameMapper } from './paths-to-module-name-mapper' async function getConfig(dir: string) { const conf = await loadConfig(PHASE_TEST, dir) @@ -95,8 +96,23 @@ export default function nextJest(options: { dir?: string } = {}) { await lockfilePatchPromise.cur } + // Derive moduleNameMapper settings from jsconfig + let moduleDirectoriesJsConfig + let moduleNameMapperJsConfig = {} + if (jsConfig?.compilerOptions?.baseUrl === '.') { + // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work + moduleDirectoriesJsConfig = ['node_modules', '/'] + moduleNameMapperJsConfig = pathsToModuleNameMapper( + jsConfig?.compilerOptions?.paths, + { prefix: '/', useESM: isEsmProject } + ) + } + const transpiled = (nextConfig?.transpilePackages ?? []).join('|') return { + ...(moduleDirectoriesJsConfig + ? { moduleDirectories: moduleDirectoriesJsConfig } + : {}), ...resolvedJestConfig, moduleNameMapper: { @@ -119,6 +135,9 @@ export default function nextJest(options: { dir?: string } = {}) { // Handle @next/font '@next/font/(.*)': require.resolve('./__mocks__/nextFontMock.js'), + // If we were able to derive moduleNameMapper settings from the paths in jsconfig + ...moduleNameMapperJsConfig, + // custom config comes last to ensure the above rules are matched, // fixes the case where @pages/(.*) -> src/pages/$! doesn't break // CSS/image mocks diff --git a/packages/next/src/build/jest/paths-to-module-name-mapper.ts b/packages/next/src/build/jest/paths-to-module-name-mapper.ts new file mode 100644 index 0000000000000..75bb99fc7beeb --- /dev/null +++ b/packages/next/src/build/jest/paths-to-module-name-mapper.ts @@ -0,0 +1,63 @@ +// Copied from: https://github.com/kulshekhar/ts-jest/blob/5a0880add0da8d71900eee20c7642e6be65f6a66/src/config/paths-to-module-name-mapper.ts + +// we don't need to escape all chars, so commented out is the real one +// const escapeRegex = (str: string) => str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') +const escapeRegex = (str: string) => str.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&') + +export const pathsToModuleNameMapper = ( + mapping: Record, + { prefix = '', useESM = false }: { prefix?: string; useESM?: boolean } = {} +) => { + const jestMap: Record = {} + for (const fromPath of Object.keys(mapping)) { + const toPaths = mapping[fromPath] + // check that we have only one target path + if (toPaths.length === 0) { + // logger.warn(interpolate(Errors.NotMappingPathWithEmptyMap, { path: fromPath })) + + continue + } + + // split with '*' + const segments = fromPath.split(/\*/g) + if (segments.length === 1) { + const paths = toPaths.map((target) => { + const enrichedPrefix = + prefix !== '' && !prefix.endsWith('/') ? `${prefix}/` : prefix + + return `${enrichedPrefix}${target}` + }) + const cjsPattern = `^${escapeRegex(fromPath)}$` + jestMap[cjsPattern] = paths.length === 1 ? paths[0] : paths + } else if (segments.length === 2) { + const paths = toPaths.map((target) => { + const enrichedTarget = + target.startsWith('./') && prefix !== '' + ? target.substring(target.indexOf('/') + 1) + : target + const enrichedPrefix = + prefix !== '' && !prefix.endsWith('/') ? `${prefix}/` : prefix + + return `${enrichedPrefix}${enrichedTarget.replace(/\*/g, '$1')}` + }) + if (useESM) { + const esmPattern = `^${escapeRegex(segments[0])}(.*)${escapeRegex( + segments[1] + )}\\.js$` + jestMap[esmPattern] = paths.length === 1 ? paths[0] : paths + } + const cjsPattern = `^${escapeRegex(segments[0])}(.*)${escapeRegex( + segments[1] + )}$` + jestMap[cjsPattern] = paths.length === 1 ? paths[0] : paths + } else { + // logger.warn(interpolate(Errors.NotMappingMultiStarPath, { path: fromPath })) + } + } + + if (useESM) { + jestMap['^(\\.{1,2}/.*)\\.js$'] = '$1' + } + + return jestMap +} diff --git a/test/unit/jest-paths-to-module-name-mapper.test.ts b/test/unit/jest-paths-to-module-name-mapper.test.ts new file mode 100644 index 0000000000000..0a3002f2560c4 --- /dev/null +++ b/test/unit/jest-paths-to-module-name-mapper.test.ts @@ -0,0 +1,79 @@ +/* eslint-env jest */ +import { pathsToModuleNameMapper } from 'next/dist/build/jest/paths-to-module-name-mapper' + +const tsconfigMap = { + log: ['src/utils/log'], + server: ['src/server'], + client: ['src/client', 'src/client/index'], + 'util/*': ['src/utils/*'], + 'api/*': ['src/api/*'], + 'test/*': ['test/*'], + 'mocks/*': ['test/mocks/*'], + 'test/*/mock': ['test/mocks/*', 'test/__mocks__/*'], + '@foo-bar/common': ['../common/dist/library'], + '@pkg/*': ['./packages/*'], +} + +describe('pathsToModuleNameMapper', () => { + test('should convert tsconfig mapping', () => { + expect(pathsToModuleNameMapper(tsconfigMap, { prefix: '/' })) + .toMatchInlineSnapshot(` + { + "^@foo\\-bar/common$": "/../common/dist/library", + "^@pkg/(.*)$": "/./packages/$1", + "^api/(.*)$": "/src/api/$1", + "^client$": [ + "/src/client", + "/src/client/index", + ], + "^log$": "/src/utils/log", + "^mocks/(.*)$": "/test/mocks/$1", + "^server$": "/src/server", + "^test/(.*)$": "/test/$1", + "^test/(.*)/mock$": [ + "/test/mocks/$1", + "/test/__mocks__/$1", + ], + "^util/(.*)$": "/src/utils/$1", + } + `) + }) + + test('should add `js` extension to resolved config with useESM: true', () => { + expect( + pathsToModuleNameMapper(tsconfigMap, { + prefix: '/', + useESM: true, + }) + ).toEqual({ + /** + * Why not using snapshot here? + * Because the snapshot does not keep the property order, which is important for jest. + * A pattern ending with `\\.js` should appear before another pattern without the extension does. + */ + '^log$': '/src/utils/log', + '^server$': '/src/server', + '^client$': ['/src/client', '/src/client/index'], + '^util/(.*)\\.js$': '/src/utils/$1', + '^util/(.*)$': '/src/utils/$1', + '^api/(.*)\\.js$': '/src/api/$1', + '^api/(.*)$': '/src/api/$1', + '^test/(.*)\\.js$': '/test/$1', + '^test/(.*)$': '/test/$1', + '^mocks/(.*)\\.js$': '/test/mocks/$1', + '^mocks/(.*)$': '/test/mocks/$1', + '^test/(.*)/mock\\.js$': [ + '/test/mocks/$1', + '/test/__mocks__/$1', + ], + '^test/(.*)/mock$': [ + '/test/mocks/$1', + '/test/__mocks__/$1', + ], + '^@foo\\-bar/common$': '/../common/dist/library', + '^@pkg/(.*)\\.js$': '/./packages/$1', + '^@pkg/(.*)$': '/./packages/$1', + '^(\\.{1,2}/.*)\\.js$': '$1', + }) + }) +}) From f2ef5f0db853d06f49beb91558902c2f7ff7dddf Mon Sep 17 00:00:00 2001 From: John Daly Date: Fri, 10 Feb 2023 20:08:02 -0800 Subject: [PATCH 02/11] Getting tests working --- test/unit/jest-paths-to-module-name-mapper.test.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/unit/jest-paths-to-module-name-mapper.test.ts b/test/unit/jest-paths-to-module-name-mapper.test.ts index 0a3002f2560c4..db3ce820e8f98 100644 --- a/test/unit/jest-paths-to-module-name-mapper.test.ts +++ b/test/unit/jest-paths-to-module-name-mapper.test.ts @@ -18,11 +18,11 @@ describe('pathsToModuleNameMapper', () => { test('should convert tsconfig mapping', () => { expect(pathsToModuleNameMapper(tsconfigMap, { prefix: '/' })) .toMatchInlineSnapshot(` - { - "^@foo\\-bar/common$": "/../common/dist/library", - "^@pkg/(.*)$": "/./packages/$1", + Object { + "^@foo\\\\-bar/common$": "/../common/dist/library", + "^@pkg/(.*)$": "/packages/$1", "^api/(.*)$": "/src/api/$1", - "^client$": [ + "^client$": Array [ "/src/client", "/src/client/index", ], @@ -30,7 +30,7 @@ describe('pathsToModuleNameMapper', () => { "^mocks/(.*)$": "/test/mocks/$1", "^server$": "/src/server", "^test/(.*)$": "/test/$1", - "^test/(.*)/mock$": [ + "^test/(.*)/mock$": Array [ "/test/mocks/$1", "/test/__mocks__/$1", ], @@ -71,8 +71,8 @@ describe('pathsToModuleNameMapper', () => { '/test/__mocks__/$1', ], '^@foo\\-bar/common$': '/../common/dist/library', - '^@pkg/(.*)\\.js$': '/./packages/$1', - '^@pkg/(.*)$': '/./packages/$1', + '^@pkg/(.*)\\.js$': '/packages/$1', + '^@pkg/(.*)$': '/packages/$1', '^(\\.{1,2}/.*)\\.js$': '$1', }) }) From 2c463d9559a4bd505a33d79b7d107132892b7bad Mon Sep 17 00:00:00 2001 From: John Daly Date: Fri, 10 Feb 2023 21:07:40 -0800 Subject: [PATCH 03/11] Handle case where baseUrl from jsconfig is not the rootDir --- packages/next/src/build/jest/jest.ts | 29 ++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/next/src/build/jest/jest.ts b/packages/next/src/build/jest/jest.ts index 0b32988467787..b9502119c716a 100644 --- a/packages/next/src/build/jest/jest.ts +++ b/packages/next/src/build/jest/jest.ts @@ -1,5 +1,5 @@ import { loadEnvConfig } from '@next/env' -import { resolve, join } from 'path' +import { relative, resolve, join } from 'path' import loadConfig from '../../server/config' import type { NextConfigComplete } from '../../server/config-shared' import { PHASE_TEST } from '../../shared/lib/constants' @@ -60,6 +60,7 @@ export default function nextJest(options: { dir?: string } = {}) { // Function that is provided as the module.exports of jest.config.js // Will be called and awaited by Jest return async () => { + let resolvedDir let nextConfig let jsConfig let resolvedBaseUrl @@ -68,7 +69,7 @@ export default function nextJest(options: { dir?: string } = {}) { let hasServerComponents: boolean | undefined if (options.dir) { - const resolvedDir = resolve(options.dir) + resolvedDir = resolve(options.dir) const packageConfig = loadClosestPackageJson(resolvedDir) isEsmProject = packageConfig.type === 'module' @@ -96,15 +97,23 @@ export default function nextJest(options: { dir?: string } = {}) { await lockfilePatchPromise.cur } - // Derive moduleNameMapper settings from jsconfig + // Automatically set up 'moduleDirectories' and 'moduleNameMapper' + // configuration based on the paths that are defined in the jsconfig let moduleDirectoriesJsConfig - let moduleNameMapperJsConfig = {} - if (jsConfig?.compilerOptions?.baseUrl === '.') { - // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work - moduleDirectoriesJsConfig = ['node_modules', '/'] + let moduleNameMapperJsConfig + if ( + typeof resolvedDir !== 'undefined' && + typeof resolvedBaseUrl !== 'undefined' + ) { + const relativePathFromRootToBaseUrl = relative( + resolvedDir, + resolvedBaseUrl + ) + const jestRootDir = join('/', relativePathFromRootToBaseUrl) + moduleDirectoriesJsConfig = ['node_modules', jestRootDir] moduleNameMapperJsConfig = pathsToModuleNameMapper( - jsConfig?.compilerOptions?.paths, - { prefix: '/', useESM: isEsmProject } + jsConfig?.compilerOptions?.paths ?? {}, + { prefix: jestRootDir, useESM: isEsmProject } ) } @@ -136,7 +145,7 @@ export default function nextJest(options: { dir?: string } = {}) { '@next/font/(.*)': require.resolve('./__mocks__/nextFontMock.js'), // If we were able to derive moduleNameMapper settings from the paths in jsconfig - ...moduleNameMapperJsConfig, + ...(moduleNameMapperJsConfig || {}), // custom config comes last to ensure the above rules are matched, // fixes the case where @pages/(.*) -> src/pages/$! doesn't break From e678357e08b0e2db6491d79dcc868a7501c0e15c Mon Sep 17 00:00:00 2001 From: John Daly Date: Sat, 11 Feb 2023 11:45:33 -0800 Subject: [PATCH 04/11] Adding logs --- packages/next/src/build/jest/jest.ts | 2 +- .../build/jest/paths-to-module-name-mapper.ts | 23 +++++++++++++++---- .../jest-paths-to-module-name-mapper.test.ts | 2 ++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/next/src/build/jest/jest.ts b/packages/next/src/build/jest/jest.ts index b9502119c716a..a1a6e30a4be5a 100644 --- a/packages/next/src/build/jest/jest.ts +++ b/packages/next/src/build/jest/jest.ts @@ -113,7 +113,7 @@ export default function nextJest(options: { dir?: string } = {}) { moduleDirectoriesJsConfig = ['node_modules', jestRootDir] moduleNameMapperJsConfig = pathsToModuleNameMapper( jsConfig?.compilerOptions?.paths ?? {}, - { prefix: jestRootDir, useESM: isEsmProject } + { prefix: jestRootDir, useESM: isEsmProject, logger: Log } ) } diff --git a/packages/next/src/build/jest/paths-to-module-name-mapper.ts b/packages/next/src/build/jest/paths-to-module-name-mapper.ts index 75bb99fc7beeb..c9f20039b22f8 100644 --- a/packages/next/src/build/jest/paths-to-module-name-mapper.ts +++ b/packages/next/src/build/jest/paths-to-module-name-mapper.ts @@ -1,4 +1,4 @@ -// Copied from: https://github.com/kulshekhar/ts-jest/blob/5a0880add0da8d71900eee20c7642e6be65f6a66/src/config/paths-to-module-name-mapper.ts +// Copied from ts-jest: https://github.com/kulshekhar/ts-jest/blob/5a0880add0da8d71900eee20c7642e6be65f6a66/src/config/paths-to-module-name-mapper.ts // we don't need to escape all chars, so commented out is the real one // const escapeRegex = (str: string) => str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') @@ -6,15 +6,26 @@ const escapeRegex = (str: string) => str.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&') export const pathsToModuleNameMapper = ( mapping: Record, - { prefix = '', useESM = false }: { prefix?: string; useESM?: boolean } = {} + { + prefix = '', + useESM = false, + logger, + }: { + prefix?: string + useESM?: boolean + logger?: { + warn: (...args: any[]) => void + } + } = {} ) => { const jestMap: Record = {} for (const fromPath of Object.keys(mapping)) { const toPaths = mapping[fromPath] // check that we have only one target path if (toPaths.length === 0) { - // logger.warn(interpolate(Errors.NotMappingPathWithEmptyMap, { path: fromPath })) - + logger?.warn( + `[next/jest]: Not mapping "${fromPath}" because it has no target.` + ) continue } @@ -51,7 +62,9 @@ export const pathsToModuleNameMapper = ( )}$` jestMap[cjsPattern] = paths.length === 1 ? paths[0] : paths } else { - // logger.warn(interpolate(Errors.NotMappingMultiStarPath, { path: fromPath })) + logger?.warn( + `[next/jest]: Not mapping "${fromPath}" because it has more than one star ('*').` + ) } } diff --git a/test/unit/jest-paths-to-module-name-mapper.test.ts b/test/unit/jest-paths-to-module-name-mapper.test.ts index db3ce820e8f98..0938d39ca23e5 100644 --- a/test/unit/jest-paths-to-module-name-mapper.test.ts +++ b/test/unit/jest-paths-to-module-name-mapper.test.ts @@ -1,6 +1,8 @@ /* eslint-env jest */ import { pathsToModuleNameMapper } from 'next/dist/build/jest/paths-to-module-name-mapper' +// Tests copied from ts-jest: https://github.com/kulshekhar/ts-jest/blob/5a0880add0da8d71900eee20c7642e6be65f6a66/src/config/paths-to-module-name-mapper.spec.ts + const tsconfigMap = { log: ['src/utils/log'], server: ['src/server'], From 85e6a9530bf77dfc778ad4fd94683124f6ebdfe7 Mon Sep 17 00:00:00 2001 From: John Daly Date: Sat, 11 Feb 2023 12:20:34 -0800 Subject: [PATCH 05/11] Updating docs --- docs/testing.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/docs/testing.md b/docs/testing.md index ac53daf2bc8c9..cbd6fda10d2e5 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -320,17 +320,7 @@ const createJestConfig = nextJest({ const customJestConfig = { // Add more setup options before each test is run // setupFilesAfterEnv: ['/jest.setup.js'], - // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work - moduleDirectories: ['node_modules', '/'], - // If you're using [Module Path Aliases](https://nextjs.org/docs/advanced-features/module-path-aliases), - // you will have to add the moduleNameMapper in order for jest to resolve your absolute paths. - // The paths have to be matching with the paths option within the compilerOptions in the tsconfig.json - // For example: - - moduleNameMapper: { - '@/(.*)$': '/src/$1', - }, testEnvironment: 'jest-environment-jsdom', } From aa7a26bcfc4a5b6ae5b0b12c9b8b953e63d2c733 Mon Sep 17 00:00:00 2001 From: John Daly Date: Mon, 27 Feb 2023 18:44:37 -0800 Subject: [PATCH 06/11] - Removing paths-to-module-name-mapper - Plumbing resolvedBaseUrl through getJestSWCOptions to let SWC handle path aliases --- packages/next/src/build/jest/jest.ts | 29 +------ .../build/jest/paths-to-module-name-mapper.ts | 76 ----------------- packages/next/src/build/swc/options.ts | 3 +- .../jest-paths-to-module-name-mapper.test.ts | 81 ------------------- 4 files changed, 3 insertions(+), 186 deletions(-) delete mode 100644 packages/next/src/build/jest/paths-to-module-name-mapper.ts delete mode 100644 test/unit/jest-paths-to-module-name-mapper.test.ts diff --git a/packages/next/src/build/jest/jest.ts b/packages/next/src/build/jest/jest.ts index 330190a7c2cf2..d6b3da185d66d 100644 --- a/packages/next/src/build/jest/jest.ts +++ b/packages/next/src/build/jest/jest.ts @@ -1,5 +1,5 @@ import { loadEnvConfig } from '@next/env' -import { relative, resolve, join } from 'path' +import { resolve, join } from 'path' import loadConfig from '../../server/config' import type { NextConfigComplete } from '../../server/config-shared' import { PHASE_TEST } from '../../shared/lib/constants' @@ -7,7 +7,6 @@ import loadJsConfig from '../load-jsconfig' import * as Log from '../output/log' import { findPagesDir } from '../../lib/find-pages-dir' import { loadBindings, lockfilePatchPromise } from '../swc' -import { pathsToModuleNameMapper } from './paths-to-module-name-mapper' async function getConfig(dir: string) { const conf = await loadConfig(PHASE_TEST, dir) @@ -97,31 +96,8 @@ export default function nextJest(options: { dir?: string } = {}) { await lockfilePatchPromise.cur } - // Automatically set up 'moduleDirectories' and 'moduleNameMapper' - // configuration based on the paths that are defined in the jsconfig - let moduleDirectoriesJsConfig - let moduleNameMapperJsConfig - if ( - typeof resolvedDir !== 'undefined' && - typeof resolvedBaseUrl !== 'undefined' - ) { - const relativePathFromRootToBaseUrl = relative( - resolvedDir, - resolvedBaseUrl - ) - const jestRootDir = join('/', relativePathFromRootToBaseUrl) - moduleDirectoriesJsConfig = ['node_modules', jestRootDir] - moduleNameMapperJsConfig = pathsToModuleNameMapper( - jsConfig?.compilerOptions?.paths ?? {}, - { prefix: jestRootDir, useESM: isEsmProject, logger: Log } - ) - } - const transpiled = (nextConfig?.transpilePackages ?? []).join('|') return { - ...(moduleDirectoriesJsConfig - ? { moduleDirectories: moduleDirectoriesJsConfig } - : {}), ...resolvedJestConfig, moduleNameMapper: { @@ -146,9 +122,6 @@ export default function nextJest(options: { dir?: string } = {}) { // Handle next/font 'next/font/(.*)': require.resolve('./__mocks__/nextFontMock.js'), - // If we were able to derive moduleNameMapper settings from the paths in jsconfig - ...(moduleNameMapperJsConfig || {}), - // custom config comes last to ensure the above rules are matched, // fixes the case where @pages/(.*) -> src/pages/$! doesn't break // CSS/image mocks diff --git a/packages/next/src/build/jest/paths-to-module-name-mapper.ts b/packages/next/src/build/jest/paths-to-module-name-mapper.ts deleted file mode 100644 index c9f20039b22f8..0000000000000 --- a/packages/next/src/build/jest/paths-to-module-name-mapper.ts +++ /dev/null @@ -1,76 +0,0 @@ -// Copied from ts-jest: https://github.com/kulshekhar/ts-jest/blob/5a0880add0da8d71900eee20c7642e6be65f6a66/src/config/paths-to-module-name-mapper.ts - -// we don't need to escape all chars, so commented out is the real one -// const escapeRegex = (str: string) => str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') -const escapeRegex = (str: string) => str.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&') - -export const pathsToModuleNameMapper = ( - mapping: Record, - { - prefix = '', - useESM = false, - logger, - }: { - prefix?: string - useESM?: boolean - logger?: { - warn: (...args: any[]) => void - } - } = {} -) => { - const jestMap: Record = {} - for (const fromPath of Object.keys(mapping)) { - const toPaths = mapping[fromPath] - // check that we have only one target path - if (toPaths.length === 0) { - logger?.warn( - `[next/jest]: Not mapping "${fromPath}" because it has no target.` - ) - continue - } - - // split with '*' - const segments = fromPath.split(/\*/g) - if (segments.length === 1) { - const paths = toPaths.map((target) => { - const enrichedPrefix = - prefix !== '' && !prefix.endsWith('/') ? `${prefix}/` : prefix - - return `${enrichedPrefix}${target}` - }) - const cjsPattern = `^${escapeRegex(fromPath)}$` - jestMap[cjsPattern] = paths.length === 1 ? paths[0] : paths - } else if (segments.length === 2) { - const paths = toPaths.map((target) => { - const enrichedTarget = - target.startsWith('./') && prefix !== '' - ? target.substring(target.indexOf('/') + 1) - : target - const enrichedPrefix = - prefix !== '' && !prefix.endsWith('/') ? `${prefix}/` : prefix - - return `${enrichedPrefix}${enrichedTarget.replace(/\*/g, '$1')}` - }) - if (useESM) { - const esmPattern = `^${escapeRegex(segments[0])}(.*)${escapeRegex( - segments[1] - )}\\.js$` - jestMap[esmPattern] = paths.length === 1 ? paths[0] : paths - } - const cjsPattern = `^${escapeRegex(segments[0])}(.*)${escapeRegex( - segments[1] - )}$` - jestMap[cjsPattern] = paths.length === 1 ? paths[0] : paths - } else { - logger?.warn( - `[next/jest]: Not mapping "${fromPath}" because it has more than one star ('*').` - ) - } - } - - if (useESM) { - jestMap['^(\\.{1,2}/.*)\\.js$'] = '$1' - } - - return jestMap -} diff --git a/packages/next/src/build/swc/options.ts b/packages/next/src/build/swc/options.ts index 7499ec9c47c7c..45cf9d05cc9b9 100644 --- a/packages/next/src/build/swc/options.ts +++ b/packages/next/src/build/swc/options.ts @@ -183,6 +183,7 @@ export function getJestSWCOptions({ esm, nextConfig, jsConfig, + resolvedBaseUrl, pagesDir, hasServerComponents, }: // This is not passed yet as "paths" resolving needs a test first @@ -197,7 +198,7 @@ any) { nextConfig, jsConfig, hasServerComponents, - // resolvedBaseUrl, + resolvedBaseUrl, }) const isNextDist = nextDistPath.test(filename) diff --git a/test/unit/jest-paths-to-module-name-mapper.test.ts b/test/unit/jest-paths-to-module-name-mapper.test.ts deleted file mode 100644 index 0938d39ca23e5..0000000000000 --- a/test/unit/jest-paths-to-module-name-mapper.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* eslint-env jest */ -import { pathsToModuleNameMapper } from 'next/dist/build/jest/paths-to-module-name-mapper' - -// Tests copied from ts-jest: https://github.com/kulshekhar/ts-jest/blob/5a0880add0da8d71900eee20c7642e6be65f6a66/src/config/paths-to-module-name-mapper.spec.ts - -const tsconfigMap = { - log: ['src/utils/log'], - server: ['src/server'], - client: ['src/client', 'src/client/index'], - 'util/*': ['src/utils/*'], - 'api/*': ['src/api/*'], - 'test/*': ['test/*'], - 'mocks/*': ['test/mocks/*'], - 'test/*/mock': ['test/mocks/*', 'test/__mocks__/*'], - '@foo-bar/common': ['../common/dist/library'], - '@pkg/*': ['./packages/*'], -} - -describe('pathsToModuleNameMapper', () => { - test('should convert tsconfig mapping', () => { - expect(pathsToModuleNameMapper(tsconfigMap, { prefix: '/' })) - .toMatchInlineSnapshot(` - Object { - "^@foo\\\\-bar/common$": "/../common/dist/library", - "^@pkg/(.*)$": "/packages/$1", - "^api/(.*)$": "/src/api/$1", - "^client$": Array [ - "/src/client", - "/src/client/index", - ], - "^log$": "/src/utils/log", - "^mocks/(.*)$": "/test/mocks/$1", - "^server$": "/src/server", - "^test/(.*)$": "/test/$1", - "^test/(.*)/mock$": Array [ - "/test/mocks/$1", - "/test/__mocks__/$1", - ], - "^util/(.*)$": "/src/utils/$1", - } - `) - }) - - test('should add `js` extension to resolved config with useESM: true', () => { - expect( - pathsToModuleNameMapper(tsconfigMap, { - prefix: '/', - useESM: true, - }) - ).toEqual({ - /** - * Why not using snapshot here? - * Because the snapshot does not keep the property order, which is important for jest. - * A pattern ending with `\\.js` should appear before another pattern without the extension does. - */ - '^log$': '/src/utils/log', - '^server$': '/src/server', - '^client$': ['/src/client', '/src/client/index'], - '^util/(.*)\\.js$': '/src/utils/$1', - '^util/(.*)$': '/src/utils/$1', - '^api/(.*)\\.js$': '/src/api/$1', - '^api/(.*)$': '/src/api/$1', - '^test/(.*)\\.js$': '/test/$1', - '^test/(.*)$': '/test/$1', - '^mocks/(.*)\\.js$': '/test/mocks/$1', - '^mocks/(.*)$': '/test/mocks/$1', - '^test/(.*)/mock\\.js$': [ - '/test/mocks/$1', - '/test/__mocks__/$1', - ], - '^test/(.*)/mock$': [ - '/test/mocks/$1', - '/test/__mocks__/$1', - ], - '^@foo\\-bar/common$': '/../common/dist/library', - '^@pkg/(.*)\\.js$': '/packages/$1', - '^@pkg/(.*)$': '/packages/$1', - '^(\\.{1,2}/.*)\\.js$': '$1', - }) - }) -}) From e67664a7ee828ebd2f5a89a597fb0b3225701cb1 Mon Sep 17 00:00:00 2001 From: John Daly Date: Mon, 27 Feb 2023 18:45:17 -0800 Subject: [PATCH 07/11] Removing moduleNameMapper overrides in existing tests --- test/production/jest/relay/app/jest.config.js | 4 ---- .../jest/remove-react-properties/app/jest.config.js | 4 ---- 2 files changed, 8 deletions(-) diff --git a/test/production/jest/relay/app/jest.config.js b/test/production/jest/relay/app/jest.config.js index 23f86dd58d7cf..a52ceba313029 100644 --- a/test/production/jest/relay/app/jest.config.js +++ b/test/production/jest/relay/app/jest.config.js @@ -10,10 +10,6 @@ const customJestConfig = { // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work moduleDirectories: ['node_modules', '/'], testEnvironment: 'jest-environment-jsdom', - moduleNameMapper: { - // When changing these, also look at the tsconfig! - '^types/(.+)$': '/types/$1', - }, } // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async diff --git a/test/production/jest/remove-react-properties/app/jest.config.js b/test/production/jest/remove-react-properties/app/jest.config.js index 23f86dd58d7cf..a52ceba313029 100644 --- a/test/production/jest/remove-react-properties/app/jest.config.js +++ b/test/production/jest/remove-react-properties/app/jest.config.js @@ -10,10 +10,6 @@ const customJestConfig = { // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work moduleDirectories: ['node_modules', '/'], testEnvironment: 'jest-environment-jsdom', - moduleNameMapper: { - // When changing these, also look at the tsconfig! - '^types/(.+)$': '/types/$1', - }, } // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async From 976e59aff081cdbff4d419ea5236cf708e0780f4 Mon Sep 17 00:00:00 2001 From: John Daly Date: Mon, 27 Feb 2023 19:03:53 -0800 Subject: [PATCH 08/11] Cleaning up earlier change --- packages/next/src/build/jest/jest.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/next/src/build/jest/jest.ts b/packages/next/src/build/jest/jest.ts index d6b3da185d66d..195fb065dc3c6 100644 --- a/packages/next/src/build/jest/jest.ts +++ b/packages/next/src/build/jest/jest.ts @@ -59,7 +59,6 @@ export default function nextJest(options: { dir?: string } = {}) { // Function that is provided as the module.exports of jest.config.js // Will be called and awaited by Jest return async () => { - let resolvedDir let nextConfig let jsConfig let resolvedBaseUrl @@ -68,7 +67,7 @@ export default function nextJest(options: { dir?: string } = {}) { let hasServerComponents: boolean | undefined if (options.dir) { - resolvedDir = resolve(options.dir) + const resolvedDir = resolve(options.dir) const packageConfig = loadClosestPackageJson(resolvedDir) isEsmProject = packageConfig.type === 'module' From 18da9f0ad980c298ebc34e6f38ec05f35786ac2a Mon Sep 17 00:00:00 2001 From: John Daly Date: Mon, 27 Feb 2023 19:15:36 -0800 Subject: [PATCH 09/11] Updating existing tests to use aliases --- test/production/jest/relay/app/pages/_app.tsx | 2 +- .../production/jest/relay/app/pages/index.tsx | 2 +- test/production/jest/relay/app/tsconfig.json | 2 +- test/production/jest/relay/relay-jest.test.ts | 2 +- .../app/pages/{index.js => index.tsx} | 0 .../remove-react-properties-jest.test.ts | 2 +- .../remove-react-properties/tsconfig.json | 24 +++++++++++++++++++ 7 files changed, 29 insertions(+), 5 deletions(-) rename test/production/jest/remove-react-properties/app/pages/{index.js => index.tsx} (100%) create mode 100644 test/production/jest/remove-react-properties/tsconfig.json diff --git a/test/production/jest/relay/app/pages/_app.tsx b/test/production/jest/relay/app/pages/_app.tsx index ec6e4caf2db11..63271041b64ef 100644 --- a/test/production/jest/relay/app/pages/_app.tsx +++ b/test/production/jest/relay/app/pages/_app.tsx @@ -1,5 +1,5 @@ import { RelayEnvironmentProvider } from 'react-relay/hooks' -import RelayEnvironment from '../components/environment' +import RelayEnvironment from '@/components/environment' export default function MyApp({ Component, pageProps }) { return ( diff --git a/test/production/jest/relay/app/pages/index.tsx b/test/production/jest/relay/app/pages/index.tsx index f03d8d22378b6..96e8277a46ace 100644 --- a/test/production/jest/relay/app/pages/index.tsx +++ b/test/production/jest/relay/app/pages/index.tsx @@ -1,6 +1,6 @@ import { graphql, useRelayEnvironment, QueryRenderer } from 'react-relay' -import type { pagesQueryResponse } from '../types/pagesQuery.graphql' +import type { pagesQueryResponse } from '@/types/pagesQuery.graphql' function Component() { const env = useRelayEnvironment() diff --git a/test/production/jest/relay/app/tsconfig.json b/test/production/jest/relay/app/tsconfig.json index d5a9c1e3401c7..776590cecf2ff 100644 --- a/test/production/jest/relay/app/tsconfig.json +++ b/test/production/jest/relay/app/tsconfig.json @@ -15,7 +15,7 @@ "isolatedModules": true, "jsx": "preserve", "paths": { - "types/*": ["./types/*"] + "@/*": ["./*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "tests/entry.test.tsx"], diff --git a/test/production/jest/relay/relay-jest.test.ts b/test/production/jest/relay/relay-jest.test.ts index d829a323fe421..e88973360d507 100644 --- a/test/production/jest/relay/relay-jest.test.ts +++ b/test/production/jest/relay/relay-jest.test.ts @@ -17,7 +17,7 @@ describe('next/jest', () => { import { RelayEnvironmentProvider } from 'react-relay' import { createMockEnvironment, MockPayloadGenerator } from 'relay-test-utils' - import Page from '../pages' + import Page from '@/pages' describe('test graphql tag transformation', () => { it('should work', async () => { diff --git a/test/production/jest/remove-react-properties/app/pages/index.js b/test/production/jest/remove-react-properties/app/pages/index.tsx similarity index 100% rename from test/production/jest/remove-react-properties/app/pages/index.js rename to test/production/jest/remove-react-properties/app/pages/index.tsx diff --git a/test/production/jest/remove-react-properties/remove-react-properties-jest.test.ts b/test/production/jest/remove-react-properties/remove-react-properties-jest.test.ts index 3c13cab629b5e..452e3e1051fb0 100644 --- a/test/production/jest/remove-react-properties/remove-react-properties-jest.test.ts +++ b/test/production/jest/remove-react-properties/remove-react-properties-jest.test.ts @@ -16,7 +16,7 @@ describe('next/jest', () => { import { render as renderFn, waitFor } from '@testing-library/react' import '@testing-library/jest-dom/extend-expect'; - import Page from '../pages' + import Page from '@app/pages' describe('testid', () => { it('data-testid should be available in the test', async () => { diff --git a/test/production/jest/remove-react-properties/tsconfig.json b/test/production/jest/remove-react-properties/tsconfig.json new file mode 100644 index 0000000000000..85574262701c3 --- /dev/null +++ b/test/production/jest/remove-react-properties/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "incremental": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "paths": { + "@/*": ["./*"], + "@app/*": ["./app/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "tests/entry.test.tsx"], + "exclude": ["node_modules"] +} From 2809214050785f6834cc3384aad4f8d42f9b6f08 Mon Sep 17 00:00:00 2001 From: John Daly Date: Mon, 27 Feb 2023 19:50:53 -0800 Subject: [PATCH 10/11] Fixing test --- .../jest/remove-react-properties/{ => app}/tsconfig.json | 3 +-- .../remove-react-properties-jest.test.ts | 8 +++++--- 2 files changed, 6 insertions(+), 5 deletions(-) rename test/production/jest/remove-react-properties/{ => app}/tsconfig.json (91%) diff --git a/test/production/jest/remove-react-properties/tsconfig.json b/test/production/jest/remove-react-properties/app/tsconfig.json similarity index 91% rename from test/production/jest/remove-react-properties/tsconfig.json rename to test/production/jest/remove-react-properties/app/tsconfig.json index 85574262701c3..776590cecf2ff 100644 --- a/test/production/jest/remove-react-properties/tsconfig.json +++ b/test/production/jest/remove-react-properties/app/tsconfig.json @@ -15,8 +15,7 @@ "isolatedModules": true, "jsx": "preserve", "paths": { - "@/*": ["./*"], - "@app/*": ["./app/*"] + "@/*": ["./*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "tests/entry.test.tsx"], diff --git a/test/production/jest/remove-react-properties/remove-react-properties-jest.test.ts b/test/production/jest/remove-react-properties/remove-react-properties-jest.test.ts index 452e3e1051fb0..fed569c147e22 100644 --- a/test/production/jest/remove-react-properties/remove-react-properties-jest.test.ts +++ b/test/production/jest/remove-react-properties/remove-react-properties-jest.test.ts @@ -12,11 +12,11 @@ describe('next/jest', () => { next = await createNext({ files: { pages: new FileRef(path.join(appDir, 'pages')), - 'tests/index.test.js': ` + 'tests/index.test.tsx': ` import { render as renderFn, waitFor } from '@testing-library/react' import '@testing-library/jest-dom/extend-expect'; - import Page from '@app/pages' + import Page from '@/pages' describe('testid', () => { it('data-testid should be available in the test', async () => { @@ -30,6 +30,7 @@ describe('next/jest', () => { `, 'jest.config.js': new FileRef(path.join(appDir, 'jest.config.js')), 'next.config.js': new FileRef(path.join(appDir, 'next.config.js')), + 'tsconfig.json': new FileRef(path.join(appDir, 'tsconfig.json')), }, dependencies: { jest: '27.4.7', @@ -40,7 +41,8 @@ describe('next/jest', () => { packageJson: { scripts: { // Runs jest and bails if jest fails - build: 'yarn jest --forceExit tests/index.test.js && yarn next build', + build: + 'yarn jest --forceExit tests/index.test.tsx && yarn next build', }, }, buildCommand: `yarn build`, From cdeba5a5990d4b97629c1634ddfe56efff4394c0 Mon Sep 17 00:00:00 2001 From: John Daly Date: Tue, 28 Feb 2023 09:32:32 -0800 Subject: [PATCH 11/11] Removing comment --- packages/next/src/build/swc/options.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/next/src/build/swc/options.ts b/packages/next/src/build/swc/options.ts index 45cf9d05cc9b9..9d4d91ed190db 100644 --- a/packages/next/src/build/swc/options.ts +++ b/packages/next/src/build/swc/options.ts @@ -186,9 +186,7 @@ export function getJestSWCOptions({ resolvedBaseUrl, pagesDir, hasServerComponents, -}: // This is not passed yet as "paths" resolving needs a test first -// resolvedBaseUrl, -any) { +}: any) { let baseOptions = getBaseSWCOptions({ filename, jest: true,