From 085bdf590734ecdd48b963e2db8bae2a09c1c77f Mon Sep 17 00:00:00 2001 From: Ahn Date: Mon, 25 May 2020 13:41:29 +0200 Subject: [PATCH] feat(config): show a warning message when TypeScript `target` version doesn't match with recommended NodeJs version (#1678) --- .../__snapshots__/logger.test.ts.snap | 15 +++- e2e/__tests__/logger.test.ts | 81 +++++++++++++------ e2e/jest.config.js | 9 +-- jest-base.js | 12 +++ jest.config.js | 9 +-- src/config/config-set.spec.ts | 55 ++++++++++++- src/config/config-set.ts | 21 ++++- src/util/messages.ts | 1 + src/util/version-checkers.ts | 4 +- tsconfig.json | 12 +-- tsconfig.spec.json | 4 + 11 files changed, 170 insertions(+), 53 deletions(-) create mode 100644 jest-base.js create mode 100644 tsconfig.spec.json diff --git a/e2e/__tests__/__snapshots__/logger.test.ts.snap b/e2e/__tests__/__snapshots__/logger.test.ts.snap index 894c67fb12..7f91977c17 100644 --- a/e2e/__tests__/__snapshots__/logger.test.ts.snap +++ b/e2e/__tests__/__snapshots__/logger.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`TS_JEST_LOG should pass and create log file when using template "default" 1`] = ` +exports[`ts-jest logging TS_JEST_LOG should pass and create log file when using template "default" 1`] = ` Array [ "[level:20] creating jest presets not handling JavaScript files", "[level:20] creating Importer singleton", @@ -37,7 +37,7 @@ Array [ ] `; -exports[`TS_JEST_LOG should pass and create log file when using template "with-babel-7" 1`] = ` +exports[`ts-jest logging TS_JEST_LOG should pass and create log file when using template "with-babel-7" 1`] = ` Array [ "[level:20] creating jest presets not handling JavaScript files", "[level:20] creating Importer singleton", @@ -80,7 +80,7 @@ Array [ ] `; -exports[`TS_JEST_LOG should pass and create log file when using template "with-babel-7-string-config" 1`] = ` +exports[`ts-jest logging TS_JEST_LOG should pass and create log file when using template "with-babel-7-string-config" 1`] = ` Array [ "[level:20] creating jest presets not handling JavaScript files", "[level:20] creating Importer singleton", @@ -124,7 +124,7 @@ Array [ ] `; -exports[`With unsupported version test should pass using template "with-unsupported-version" 1`] = ` +exports[`ts-jest logging with unsupported version test should pass using template "with-unsupported-version" 1`] = ` √ jest ↳ exit code: 0 ===[ STDOUT ]=================================================================== @@ -142,3 +142,10 @@ exports[`With unsupported version test should pass using template "with-unsuppor Ran all test suites. ================================================================================ `; + +exports[`ts-jest logging typescript target is higher than es2019 for NodeJs 12 should pass using template "default" 1`] = ` +Array [ + "[level:40] There is a mismatch between your NodeJs version v12.16.3 and your TypeScript target es2020. This might lead to some unexpected errors when running tests with \`ts-jest\`. To fix this, you can check https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping", + "[level:40] message TS151001: If you have issues related to imports, you should consider setting \`esModuleInterop\` to \`true\` in your TypeScript configuration file (usually \`tsconfig.json\`). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.", +] +`; diff --git a/e2e/__tests__/logger.test.ts b/e2e/__tests__/logger.test.ts index 6563b51e88..4bf46c1578 100644 --- a/e2e/__tests__/logger.test.ts +++ b/e2e/__tests__/logger.test.ts @@ -4,35 +4,68 @@ import { existsSync } from 'fs' import { PackageSets, allValidPackageSets } from '../__helpers__/templates' import { configureTestCase } from '../__helpers__/test-case' -describe('With unsupported version test', () => { - const testCase = configureTestCase('simple') +describe('ts-jest logging', () => { + describe('with unsupported version test', () => { + const testCase = configureTestCase('simple') - testCase.runWithTemplates([PackageSets.unsupportedVersion], 0, (runTest, { testLabel }) => { - it(testLabel, () => { - const result = runTest() - expect(result.status).toBe(0) - expect(result).toMatchSnapshot() + testCase.runWithTemplates([PackageSets.unsupportedVersion], 0, (runTest, { testLabel }) => { + it(testLabel, () => { + const result = runTest() + expect(result.status).toBe(0) + expect(result).toMatchSnapshot() + }) }) }) -}) -describe('TS_JEST_LOG', () => { - const testCase = configureTestCase('simple', { - env: { TS_JEST_LOG: 'ts-jest.log' }, - noCache: true, - }) + describe('TS_JEST_LOG', () => { + const testCase = configureTestCase('simple', { + env: { TS_JEST_LOG: 'ts-jest.log' }, + noCache: true, + }) - testCase.runWithTemplates(allValidPackageSets, 0, (runTest, { templateName }) => { - it(`should pass and create log file when using template "${templateName}"`, () => { - const result = runTest() - expect(result.status).toBe(0) - expect(existsSync(result.logFilePath)).toBe(true) - const filteredEntries = result.logFileEntries - // keep only debug and above - .filter(m => (m.context[LogContexts.logLevel] || 0) >= LogLevels.debug) - // simplify entires - .map(e => result.normalize(`[level:${e.context[LogContexts.logLevel]}] ${e.message}`)) - expect(filteredEntries).toMatchSnapshot() + testCase.runWithTemplates(allValidPackageSets, 0, (runTest, { templateName }) => { + it(`should pass and create log file when using template "${templateName}"`, () => { + const result = runTest() + expect(result.status).toBe(0) + expect(existsSync(result.logFilePath)).toBe(true) + const filteredEntries = result.logFileEntries + // keep only debug and above + .filter(m => (m.context[LogContexts.logLevel] || 0) >= LogLevels.debug) + // simplify entires + .map(e => result.normalize(`[level:${e.context[LogContexts.logLevel]}] ${e.message}`)) + expect(filteredEntries).toMatchSnapshot() + }) }) }) + + /** + * Since we only run e2e for node 12 so we need this if here. We follow latest LTS Node version so once latest LTS version + * changes, we also need to change this test. + */ + if (process.version.startsWith('v12')) { + describe('typescript target is higher than es2019 for NodeJs 12', () => { + const testCase = configureTestCase('simple', { + env: { TS_JEST_LOG: 'ts-jest.log' }, + noCache: true, + tsJestConfig: { + tsConfig: { + target: 'es2020' + } + } + }) + + testCase.runWithTemplates([PackageSets.default], 0, (runTest, { testLabel }) => { + it(testLabel, () => { + const result = runTest() + expect(result.status).toBe(0) + const filteredEntries = result.logFileEntries + // keep only debug and above + .filter(m => (m.context[LogContexts.logLevel] || 0) === LogLevels.warn) + // simplify entires + .map(e => result.normalize(`[level:${e.context[LogContexts.logLevel]}] ${e.message}`)) + expect(filteredEntries).toMatchSnapshot() + }) + }) + }) + } }) diff --git a/e2e/jest.config.js b/e2e/jest.config.js index fef46c6df5..cb4799f7dd 100644 --- a/e2e/jest.config.js +++ b/e2e/jest.config.js @@ -1,11 +1,10 @@ +const jestBaseConfig = require('../jest-base') + +/** @type {import('@jest/types').Config.InitialOptions} */ module.exports = { + ...jestBaseConfig, rootDir: '..', - transform: { - '\\.ts$': '/dist/index.js', - }, testMatch: ['/e2e/__tests__/**/*.test.ts'], - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'], - testEnvironment: 'node', snapshotSerializers: [ '/e2e/__serializers__/run-result.ts', '/e2e/__serializers__/processed-source.ts', diff --git a/jest-base.js b/jest-base.js new file mode 100644 index 0000000000..3ceff19009 --- /dev/null +++ b/jest-base.js @@ -0,0 +1,12 @@ +/** @type {import('@jest/types').Config.InitialOptions} */ +module.exports = { + globals: { + 'ts-jest': { + tsConfig: 'tsconfig.spec.json', + }, + }, + transform: { + '\\.ts$': '/dist/index.js', + }, + testEnvironment: 'node', +} diff --git a/jest.config.js b/jest.config.js index fea7bdb9e6..78764901f0 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,9 +1,10 @@ +const baseConfig = require('./jest-base') + +/** @type {import('@jest/types').Config.InitialOptions} */ module.exports = { + ...baseConfig, rootDir: '.', setupFilesAfterEnv: ['/src/__helpers__/setup.ts'], - transform: { - '\\.ts$': '/dist/index.js', - }, testMatch: ['/src/**/*.spec.ts'], testPathIgnorePatterns: ['/src/__mocks__/*'], collectCoverageFrom: [ @@ -14,8 +15,6 @@ module.exports = { '!/src/**/__*__/*', '!/src/util/testing.ts', ], - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'], - testEnvironment: 'node', snapshotSerializers: ['/src/__serializers__/processed-source.ts'], cacheDirectory: '/.cache/unit', } diff --git a/src/config/config-set.spec.ts b/src/config/config-set.spec.ts index 54958061d1..7329dd7443 100644 --- a/src/config/config-set.spec.ts +++ b/src/config/config-set.spec.ts @@ -1,7 +1,7 @@ /* eslint-disable jest/no-mocks-import */ import { Transformer } from '@jest/transform' import { Config } from '@jest/types' -import { testing } from 'bs-logger' +import { LogLevels, testing } from 'bs-logger' import { readFileSync } from 'fs' import json5 = require('json5') import { resolve } from 'path' @@ -792,6 +792,59 @@ describe('readTsConfig', () => { }) }) }) + + describe('mismatch nodejs version and typescript target', () => { + const logTarget = logTargetMock() + + beforeEach(() => { + logTarget.clear() + cs = createConfigSet({ jestConfig: { rootDir: '/root', cwd: '/cwd' } as any }) + findConfig.mockImplementation((p) => `${p}/tsconfig.json`) + }) + + afterEach(() => { + findConfig.mockClear() + }) + + function mismatchTestCaseContent(tsTarget: string, scriptTarget: ts.ScriptTarget) { + parseConfig.mockImplementation((conf: any) => ({ + options: { + ...conf, + target: scriptTarget, + }, + fileNames: [], + errors: [], + })) + readConfig.mockImplementation((p) => ({ config: { path: p, compilerOptions: { target: tsTarget } } })) + + cs.readTsConfig() + + // expect.toEqual gives weird result here so toContain is workaround for it. + expect(logTarget.filteredLines(LogLevels.warn, Infinity)[0]).toContain( + '[level:40] There is a mismatch between your ' + + `NodeJs version ${process.version} and your TypeScript target ${tsTarget}. This might lead to some unexpected errors ` + + 'when running tests with `ts-jest`. To fix this, you can check https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping', + ) + + parseConfig.mockClear() + readConfig.mockClear() + } + + /** + * It seems like not possible to mock process.version so the condition here is needed + */ + if (process.version.startsWith('v10')) { + // eslint-disable-next-line jest/expect-expect + it('should show warning message when nodejs version is 10 and typescript target is higher than es2018', () => { + mismatchTestCaseContent('es2019', ts.ScriptTarget.ES2019) + }) + } else { + // eslint-disable-next-line jest/expect-expect + it('should show warning message when nodejs version is 12 and typescript target is higher than es2019', () => { + mismatchTestCaseContent('es2020', ts.ScriptTarget.ES2020) + }) + } + }) }) // readTsConfig describe('versions', () => { diff --git a/src/config/config-set.ts b/src/config/config-set.ts index 227b4e4596..99e5f52d61 100644 --- a/src/config/config-set.ts +++ b/src/config/config-set.ts @@ -20,6 +20,7 @@ import { DiagnosticCategory, FormatDiagnosticsHost, ParsedCommandLine, + ScriptTarget, SourceFile, } from 'typescript' @@ -31,11 +32,11 @@ import { AstTransformerDesc, BabelConfig, BabelJestTransformer, - TTypeScript, TsCompiler, TsJestConfig, TsJestGlobalOptions, TsJestHooksMap, + TTypeScript, } from '../types' import { backportJestConfig } from '../util/backports' import { getPackageVersion } from '../util/get-package-version' @@ -723,7 +724,7 @@ export class ConfigSet { resolvedConfigFile?: string | null, noProject?: boolean | null, ): ParsedCommandLine { - let config = { compilerOptions: {} } + let config = { compilerOptions: Object.create(null) } let basePath = normalizeSlashes(this.rootDir) let configFileName: string | undefined const ts = this.compilerModule @@ -800,6 +801,22 @@ export class ConfigSet { finalOptions[key] = val } } + /** + * See https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping + * Every time this page is updated, we also need to update here. Here we only show warning message for Node LTS versions + */ + const nodeJsVer = process.version + const compilationTarget = result.options.target! + if ( + (nodeJsVer.startsWith('v10') && compilationTarget > ScriptTarget.ES2018) || + (nodeJsVer.startsWith('v12') && compilationTarget > ScriptTarget.ES2019) + ) { + const message = interpolate(Errors.MismatchNodeTargetMapping, { + nodeJsVer: process.version, + compilationTarget: config.compilerOptions.target, + }) + logger.warn(message) + } return result } diff --git a/src/util/messages.ts b/src/util/messages.ts index 648ccc9720..fc3873179a 100644 --- a/src/util/messages.ts +++ b/src/util/messages.ts @@ -18,6 +18,7 @@ export const enum Errors { GotUnknownFileTypeWithBabel = 'Got a unknown file type to compile (file: {{path}}). To fix this, in your Jest config change the `transform` key which value is `ts-jest` so that it does not match this kind of files anymore. If you still want Babel to process it, add another entry to the `transform` option with value `babel-jest` which key matches this type of files.', ConfigNoModuleInterop = 'If you have issues related to imports, you should consider setting `esModuleInterop` to `true` in your TypeScript configuration file (usually `tsconfig.json`). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.', UnableToFindProjectRoot = 'Unable to find the root of the project where ts-jest has been installed.', + MismatchNodeTargetMapping = 'There is a mismatch between your NodeJs version {{nodeJsVer}} and your TypeScript target {{compilationTarget}}. This might lead to some unexpected errors when running tests with `ts-jest`. To fix this, you can check https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping', } /** diff --git a/src/util/version-checkers.ts b/src/util/version-checkers.ts index 19d0f3e586..9c84dce57b 100644 --- a/src/util/version-checkers.ts +++ b/src/util/version-checkers.ts @@ -9,7 +9,7 @@ const logger = rootLogger.child({ namespace: 'versions' }) /** * @internal */ -export const enum ExpectedVersions { +const enum ExpectedVersions { Jest = '>=26 <27', TypeScript = '>=3.8 <4', BabelJest = '>=26 <27', @@ -49,6 +49,7 @@ function checkVersion( ): boolean | never { const version = getPackageVersion(name) const success = !!version && satisfies(version, expectedRange) + logger.debug( { actualVersion: version, @@ -58,6 +59,7 @@ function checkVersion( name, success ? 'OK' : 'NOT OK', ) + if (!action || success) return success const message = interpolate(version ? Errors.UntestedDependencyVersion : Errors.MissingDependency, { diff --git a/tsconfig.json b/tsconfig.json index 801f8740f2..9826858d73 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,15 +30,5 @@ "node", "react" ] - }, - "include": [ - "e2e/__helpers__", - "e2e/__serializers__", - "e2e/__tests__", - "scripts/", - "src/", - "utils/", - "presets", - "./*.js" - ] + } } diff --git a/tsconfig.spec.json b/tsconfig.spec.json new file mode 100644 index 0000000000..6f2bdb7379 --- /dev/null +++ b/tsconfig.spec.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": [] +}