diff --git a/.editorconfig b/.editorconfig index 9541a249ba..ee8367f852 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,3 +1,7 @@ -[*.js] +[*.ts] indent_style = space -indent_size = 4 \ No newline at end of file +indent_size = 4 + +[*.tsx] +indent_style = space +indent_size = 4 diff --git a/package.json b/package.json index fed3fd8a2a..3a01d1bb1b 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ ] }, "dependencies": { + "@types/babel-core": "^6.7.14", "babel-core": "^6.24.1", "babel-plugin-istanbul": "^4.1.4", "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", diff --git a/src/postprocess.ts b/src/postprocess.ts index 7cc9f001d9..d699bba355 100644 --- a/src/postprocess.ts +++ b/src/postprocess.ts @@ -8,50 +8,58 @@ import * as babel from 'babel-core'; import { CompilerOptions } from 'typescript/lib/typescript'; function createBabelTransformer(options: PostProcessorOptions) { - options = { - ...options, - plugins: (options && options.plugins) || [], - presets: ((options && options.presets) || []).concat([jestPreset]), - retainLines: true, - }; - delete options.cacheDirectory; - delete options.filename; + options = { + ...options, + plugins: (options && options.plugins) || [], + presets: ((options && options.presets) || []).concat([jestPreset]), + // If retainLines isn't set to true, the line numbers + // are off by 1 + retainLines: true, + // force the sourceMaps property to be 'inline' during testing + // to help generate accurate sourcemaps. + sourceMaps: 'inline' + }; + delete options.cacheDirectory; + delete options.filename; - return (src: string, - filename: string, - config: JestConfig, - transformOptions: TransformOptions): string => { - const theseOptions = Object.assign({ filename }, options); - if (transformOptions && transformOptions.instrument) { - theseOptions.auxiliaryCommentBefore = ' istanbul ignore next '; - // Copied from jest-runtime transform.js - theseOptions.plugins = theseOptions.plugins.concat([ - [ - require('babel-plugin-istanbul').default, - { - // files outside `cwd` will not be instrumented - cwd: config.rootDir, - exclude: [], - }, - ], - ]); - } - return babel.transform(src, theseOptions).code; - }; + return (src: string, + filename: string, + config: JestConfig, + transformOptions: TransformOptions): string => { + const theseOptions = Object.assign({ filename }, options); + if (transformOptions && transformOptions.instrument) { + theseOptions.auxiliaryCommentBefore = ' istanbul ignore next '; + // Copied from jest-runtime transform.js + theseOptions.plugins = theseOptions.plugins.concat([ + [ + require('babel-plugin-istanbul').default, + { + // files outside `cwd` will not be instrumented + cwd: config.rootDir, + exclude: [], + }, + ], + ]); + } + + return babel.transform(src, theseOptions).code; + }; } export const getPostProcessHook = (tsCompilerOptions: CompilerOptions, jestConfig: JestConfig, tsJestConfig: any): PostProcessHook => { - if (tsJestConfig.skipBabel) { - return (src) => src; //Identity function - } + if (tsJestConfig.skipBabel) { + return (src) => src; //Identity function + } + + const plugins = []; + //If we're not skipping babel + if (tsCompilerOptions.allowSyntheticDefaultImports) { + plugins.push('transform-es2015-modules-commonjs'); + } + - //If we're not skipping babel - if (tsCompilerOptions.allowSyntheticDefaultImports) { - const plugins = ['transform-es2015-modules-commonjs']; - return createBabelTransformer({ - presets: [], - plugins: plugins, - }); - } - return (src) => src; //Identity function + return createBabelTransformer({ + presets: [], + plugins: plugins, + }); }; diff --git a/tests/__tests__/__snapshots__/html-transform.spec.ts.snap b/tests/__tests__/__snapshots__/html-transform.spec.ts.snap index d2ed4c69ff..bdea8378b9 100644 --- a/tests/__tests__/__snapshots__/html-transform.spec.ts.snap +++ b/tests/__tests__/__snapshots__/html-transform.spec.ts.snap @@ -1,12 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`transforms html if config.globals.__TRANSFORM_HTML__ is set 1`] = ` +exports[`Html transforms transforms html if config.globals.__TRANSFORM_HTML__ is set 1`] = ` "module.exports=\`
This is element
\`;" `; -exports[`transforms html if config.globals.__TRANSFORM_HTML__ is set 2`] = ` +exports[`Html transforms transforms html if config.globals.__TRANSFORM_HTML__ is set 2`] = ` "
This is element
" diff --git a/tests/__tests__/html-transform.spec.ts b/tests/__tests__/html-transform.spec.ts index 9305bbf0b8..d6d0145e9a 100644 --- a/tests/__tests__/html-transform.spec.ts +++ b/tests/__tests__/html-transform.spec.ts @@ -11,8 +11,10 @@ const config = { } }; -test('transforms html if config.globals.__TRANSFORM_HTML__ is set', () => { - expect(process(source, path, config)).toMatchSnapshot(); - delete config.globals.__TRANSFORM_HTML__; - expect(process(source, path, config)).toMatchSnapshot(); -}); +describe('Html transforms', () => { + it('transforms html if config.globals.__TRANSFORM_HTML__ is set', () => { + expect(process(source, path, config)).toMatchSnapshot(); + delete config.globals.__TRANSFORM_HTML__; + expect(process(source, path, config)).toMatchSnapshot(); + }); +}); \ No newline at end of file diff --git a/tests/__tests__/jest-hoist.spec.ts b/tests/__tests__/jest-hoist.spec.ts new file mode 100644 index 0000000000..e310185fdf --- /dev/null +++ b/tests/__tests__/jest-hoist.spec.ts @@ -0,0 +1,27 @@ +import runJest from '../__helpers__/runJest'; + +describe('Jest.mock() calls', () => { + + it('Should run all tests using jest.mock() underneath the imports succesfully.', () => { + const result = runJest('../hoist-test', ['--no-cache']); + const output = result.output.toString(); + + expect(output).toContain('4 passed, 4 total'); + expect(result.status).toBe(0); + }); + + + it('Should retain proper line endings while hoisting', () => { + const result = runJest('../hoist-errors', ['--no-cache']); + + const stderr = result.stderr.toString(); + + expect(result.status).toBe(1); + expect(stderr).toContain('Hello.ts:22:11'); + + // The actual error occurs at line 16. However, because the mock calls + // are hoisted, this changes - in this case, to 26 + // The column numbers are accurate. + expect(stderr).toContain('Hello.test.ts:26:19'); + }); +}); diff --git a/tests/__tests__/ts-compilation.spec.ts b/tests/__tests__/ts-compilation.spec.ts index 3e98918783..468c302df9 100644 --- a/tests/__tests__/ts-compilation.spec.ts +++ b/tests/__tests__/ts-compilation.spec.ts @@ -1,8 +1,8 @@ import runJest from '../__helpers__/runJest'; -describe('hello_world', () => { +describe('TS Compilation', () => { - it('should run successfully', () => { + it('should compile typescript succesfully', () => { const result = runJest('../simple', ['--no-cache']); @@ -12,7 +12,7 @@ describe('hello_world', () => { expect(result.status).toBe(1); expect(output).toContain('1 failed, 1 total'); expect(stderr).toContain('Hello Class'); - expect(stderr).toContain('should throw an error on line 11'); + expect(stderr).toContain('should throw an error on line 18'); }); diff --git a/tests/__tests__/ts-coverage-async.spec.ts b/tests/__tests__/ts-coverage-async.spec.ts index 3a13305c7b..0f5c05ca85 100644 --- a/tests/__tests__/ts-coverage-async.spec.ts +++ b/tests/__tests__/ts-coverage-async.spec.ts @@ -1,8 +1,8 @@ import runJest from '../__helpers__/runJest'; -describe('hello_world', () => { +describe('Typescript async coverage', () => { - it('should run successfully', () => { + it('Should generate the correct async coverage numbers', () => { const result = runJest('../simple-async', ['--no-cache', '--coverage']); const output = result.stdout.toString(); diff --git a/tests/__tests__/ts-coverage.spec.ts b/tests/__tests__/ts-coverage.spec.ts index ce501ed548..e56410f1a0 100644 --- a/tests/__tests__/ts-coverage.spec.ts +++ b/tests/__tests__/ts-coverage.spec.ts @@ -1,8 +1,8 @@ import runJest from '../__helpers__/runJest'; -describe('hello_world', () => { +describe('Typescript coverage', () => { - it('should run successfully', () => { + it('Should generate the correct coverage numbers.', () => { const result = runJest('../simple', ['--no-cache', '--coverage']); const output = result.stdout.toString(); diff --git a/tests/__tests__/ts-errors.spec.ts b/tests/__tests__/ts-errors.spec.ts index 81899f8001..5a745dc2f9 100644 --- a/tests/__tests__/ts-errors.spec.ts +++ b/tests/__tests__/ts-errors.spec.ts @@ -1,6 +1,6 @@ import runJest from '../__helpers__/runJest'; -describe('hello_world', () => { +describe('Typescript errors', () => { it('should show the correct error locations in the typescript files', () => { @@ -9,9 +9,20 @@ describe('hello_world', () => { const stderr = result.stderr.toString(); expect(result.status).toBe(1); - expect(stderr).toContain('Hello.ts:13:11'); + expect(stderr).toContain('Hello.ts:18:11'); expect(stderr).toContain('Hello.test.ts:9:19'); }); + it('Should show the correct error locations in async typescript files', async () => { + const result = runJest('../simple-async', ['--no-cache']); + + const stderr = result.stderr.toString(); + + expect(result.status).toBe(1); + expect(stderr).toContain('Hello.ts:13:11'); + expect(stderr).toContain('Hello.test.ts:9:21'); + }); + + }); \ No newline at end of file diff --git a/tests/__tests__/tsx-compilation.spec.ts b/tests/__tests__/tsx-compilation.spec.ts index 62e38e7628..8360cd3e3a 100644 --- a/tests/__tests__/tsx-compilation.spec.ts +++ b/tests/__tests__/tsx-compilation.spec.ts @@ -1,8 +1,8 @@ import runJest from '../__helpers__/runJest'; -describe('button', () => { +describe('TSX Compilation', () => { - it('should run successfully', () => { + it('Should compile a button succesfully', () => { const result = runJest('../button', ['--no-cache', '-u']); diff --git a/tests/__tests__/tsx-errors.spec.ts b/tests/__tests__/tsx-errors.spec.ts index cb410f7b1f..f2dfd9c716 100644 --- a/tests/__tests__/tsx-errors.spec.ts +++ b/tests/__tests__/tsx-errors.spec.ts @@ -1,6 +1,6 @@ import runJest from '../__helpers__/runJest'; -describe('button', () => { +describe('TSX Errors', () => { it('should show the correct error locations in the typescript files', () => { @@ -9,7 +9,7 @@ describe('button', () => { const stderr = result.stderr.toString(); expect(result.status).toBe(1); - expect(stderr).toContain('Button.tsx:18:23'); + expect(stderr).toContain('Button.tsx:18:17'); expect(stderr).toContain('Button.test.tsx:15:12'); }); diff --git a/tests/__tests__/watch.spec.ts b/tests/__tests__/watch.spec.ts index 411b2ec71b..e72b0b0b7e 100644 --- a/tests/__tests__/watch.spec.ts +++ b/tests/__tests__/watch.spec.ts @@ -44,7 +44,7 @@ describe('Hello Class', () => { }); `; -describe('hello_world', () => { +describe('Typescript watch tests', () => { let result: { childProcess: ChildProcess, getStderrAsync: () => Promise }; let DEFAULT_TIMEOUT_INTERVAL: number; diff --git a/tests/hoist-errors/.gitignore b/tests/hoist-errors/.gitignore new file mode 100644 index 0000000000..9e233d795a --- /dev/null +++ b/tests/hoist-errors/.gitignore @@ -0,0 +1 @@ +coverage-custom diff --git a/tests/hoist-errors/Hello.ts b/tests/hoist-errors/Hello.ts new file mode 100644 index 0000000000..0a0534abf2 --- /dev/null +++ b/tests/hoist-errors/Hello.ts @@ -0,0 +1,32 @@ +interface FooInterface { + foo: string, + bar: number, //This interface should be stripped and the line numbers should still fit. +} + +export const foo = () => { + console.log('foo'); +}; + +export class Hello { + constructor() { + const greeting = ` + this + is + a + multiline + greeting + `; + + this.unexcuted(() => { }); + + throw new Error('Hello error!'); + } + + public unexcuted(action: () => void = () => { }): void { + if (action) { + action(); + } else { + console.log('unexcuted'); + } + } +} diff --git a/tests/hoist-errors/NullCoverage.js b/tests/hoist-errors/NullCoverage.js new file mode 100644 index 0000000000..eefe7a0cfb --- /dev/null +++ b/tests/hoist-errors/NullCoverage.js @@ -0,0 +1,6 @@ +function nullCoverageFunction(value) { + if (value) { + return value; + } + return null; +} \ No newline at end of file diff --git a/tests/hoist-errors/__tests__/Hello.test.ts b/tests/hoist-errors/__tests__/Hello.test.ts new file mode 100644 index 0000000000..f08cd4a6e2 --- /dev/null +++ b/tests/hoist-errors/__tests__/Hello.test.ts @@ -0,0 +1,26 @@ +declare var jest, describe, it, expect; + +import { Hello } from '../Hello'; + +jest.mock('path'); +jest.mock('path'); +jest.mock('path'); +jest.mock('path'); +jest.mock('path'); +jest.mock('path'); + +describe('Hello Class', () => { + + it('should throw an error on line 22', () => { + + const hello = new Hello(); + + }); + +}); +jest.mock('path'); +jest.mock('path'); +jest.mock('path'); +jest.mock('path'); +jest.mock('path'); +jest.mock('path'); diff --git a/tests/hoist-errors/package.json b/tests/hoist-errors/package.json new file mode 100644 index 0000000000..c2d9c1f52b --- /dev/null +++ b/tests/hoist-errors/package.json @@ -0,0 +1,21 @@ +{ + "jest": { + "transform": { + ".(ts|tsx)": "../../preprocessor.js" + }, + "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", + "coverageReporters": [ + "json" + ], + "collectCoverageFrom": [ + "Hello.ts", + "NullCoverage.js" + ], + "mapCoverage": true, + "moduleFileExtensions": [ + "ts", + "tsx", + "js" + ] + } +} diff --git a/tests/hoist-errors/tsconfig.json b/tests/hoist-errors/tsconfig.json new file mode 100644 index 0000000000..55f8667f97 --- /dev/null +++ b/tests/hoist-errors/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ES5", + "module": "commonjs", + "moduleResolution": "node", + "noEmitOnError": false, + "jsx": "react", + "allowJs": true + } +} diff --git a/tests/hoist-test/__tests__/jest-hoist.test.ts b/tests/hoist-test/__tests__/jest-hoist.test.ts new file mode 100644 index 0000000000..c773fa7991 --- /dev/null +++ b/tests/hoist-test/__tests__/jest-hoist.test.ts @@ -0,0 +1,27 @@ + +import * as path from 'path'; +import {SomeClass, SomeFunctionDeclaredAsConst, SomeFunction} from '../src/things-to-mock'; + +jest.mock('../src/things-to-mock'); +jest.mock('path'); + + +describe('Global mocks', () => { + it('A global var should be mocked when the jest.mock call is underneath', () => { + expect((path.basename as any).mock).toBeDefined(); + }); +}); + +describe('Local mocks', () => { + it('Jest should be able to mock a local class', () => { + expect((SomeClass as any).mockReturnValue).toBeDefined(); + }); + + it('Jest should be able to mock a local class', () => { + expect((SomeFunction as any).mockReturnValueOnce).toBeDefined(); + }); + + it('Jest should be able to mock a local class', () => { + expect((SomeFunctionDeclaredAsConst as any).mockImplementation).toBeDefined(); + }); +}); diff --git a/tests/hoist-test/package.json b/tests/hoist-test/package.json new file mode 100644 index 0000000000..c2f99eac77 --- /dev/null +++ b/tests/hoist-test/package.json @@ -0,0 +1,14 @@ +{ + "jest": { + "transform": { + ".(ts|tsx)": "../../preprocessor.js" + }, + "moduleDirectories": ["node_modules", "src"], + "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", + "moduleFileExtensions": [ + "ts", + "tsx", + "js" + ] + } +} diff --git a/tests/hoist-test/src/things-to-mock.ts b/tests/hoist-test/src/things-to-mock.ts new file mode 100644 index 0000000000..bb656fdd3e --- /dev/null +++ b/tests/hoist-test/src/things-to-mock.ts @@ -0,0 +1,10 @@ + + +export class SomeClass { +} + +export const SomeFunctionDeclaredAsConst = () => 'originalValue'; + +export function SomeFunction(){ + return 'original'; +} diff --git a/tests/hoist-test/tsconfig.json b/tests/hoist-test/tsconfig.json new file mode 100644 index 0000000000..9af8d1760f --- /dev/null +++ b/tests/hoist-test/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES5", + "module": "commonjs", + "moduleResolution": "node", + "noEmitOnError": false, + "jsx": "react", + "allowJs": true, + "baseUrl": "src", + "allowSyntheticDefaultImports": false + } +} diff --git a/tests/simple-async/__tests__/Hello.test.ts b/tests/simple-async/__tests__/Hello.test.ts index c5e07503ce..9c6544edb8 100644 --- a/tests/simple-async/__tests__/Hello.test.ts +++ b/tests/simple-async/__tests__/Hello.test.ts @@ -4,7 +4,7 @@ import { Hello } from '../Hello'; describe('Hello Class', () => { - it('should throw an error on line 11', () => { + it('should throw an error on line 13', () => { return new Promise((resolve: () => void) => { const hello = new Hello(); resolve(); diff --git a/tests/simple/Hello.ts b/tests/simple/Hello.ts index c2dfee9115..2761d2e6cb 100644 --- a/tests/simple/Hello.ts +++ b/tests/simple/Hello.ts @@ -1,3 +1,8 @@ +interface FooInterface { + foo: string, + bar: number, //This interface should be stripped and the line numbers should still fit. +} + export class Hello { constructor() { const greeting = ` diff --git a/tests/simple/__tests__/Hello.test.ts b/tests/simple/__tests__/Hello.test.ts index e3038e335e..6c8637fe71 100644 --- a/tests/simple/__tests__/Hello.test.ts +++ b/tests/simple/__tests__/Hello.test.ts @@ -4,7 +4,7 @@ import { Hello } from '../Hello'; describe('Hello Class', () => { - it('should throw an error on line 11', () => { + it('should throw an error on line 18', () => { const hello = new Hello(); diff --git a/yarn.lock b/yarn.lock index 8d1356797d..d20d2cf4de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,37 @@ # yarn lockfile v1 +"@types/babel-core@^6.7.14": + version "6.7.14" + resolved "https://registry.yarnpkg.com/@types/babel-core/-/babel-core-6.7.14.tgz#a08c900a98e8987c1a98d2ea4fa0a1805a7d131f" + dependencies: + "@types/babel-template" "*" + "@types/babel-traverse" "*" + "@types/babel-types" "*" + +"@types/babel-template@*": + version "6.7.14" + resolved "https://registry.yarnpkg.com/@types/babel-template/-/babel-template-6.7.14.tgz#8088a56f9d697d620d3d079c3ef66025b7a08d02" + dependencies: + "@types/babel-types" "*" + "@types/babylon" "*" + +"@types/babel-traverse@*": + version "6.7.16" + resolved "https://registry.yarnpkg.com/@types/babel-traverse/-/babel-traverse-6.7.16.tgz#52f475893b8633f653499d745302fa5572a7bd05" + dependencies: + "@types/babel-types" "*" + +"@types/babel-types@*": + version "6.7.16" + resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-6.7.16.tgz#e2602896636858a0067971f7ca4bb8678038293f" + +"@types/babylon@*": + version "6.16.1" + resolved "https://registry.yarnpkg.com/@types/babylon/-/babylon-6.16.1.tgz#e4d10ab9e43a73703a17c6f41438bede28769340" + dependencies: + "@types/babel-types" "*" + "@types/es6-shim@latest": version "0.31.33" resolved "https://registry.yarnpkg.com/@types/es6-shim/-/es6-shim-0.31.33.tgz#4792bdecc8c7f09a7e086968cfbb8058e459379d" @@ -218,7 +249,7 @@ babel-helpers@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-jest@^20.0.0, babel-jest@^20.0.3: +babel-jest@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-20.0.3.tgz#e4a03b13dc10389e140fc645d09ffc4ced301671" dependencies: @@ -887,13 +918,6 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob-all@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-all/-/glob-all-3.1.0.tgz#8913ddfb5ee1ac7812656241b03d5217c64b02ab" - dependencies: - glob "^7.0.5" - yargs "~1.2.6" - glob-base@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" @@ -1694,10 +1718,6 @@ minimist@0.0.8, minimist@~0.0.1: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -minimist@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.1.0.tgz#99df657a52574c21c9057497df742790b2b4c0de" - minimist@^1.1.1, minimist@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" @@ -2454,13 +2474,14 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.0.tgz#6bdedfe7f2aa49a6f3c432257687555957f342fd" ts-jest@latest: - version "20.0.4" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-20.0.4.tgz#ffe3987d48c8762cd7b6ae83518d203eca167618" + version "20.0.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-20.0.5.tgz#6cb151e718e8739529d0a79459e09f7280fe044f" dependencies: - babel-jest "^20.0.0" + babel-core "^6.24.1" + babel-plugin-istanbul "^4.1.4" babel-plugin-transform-es2015-modules-commonjs "^6.24.1" + babel-preset-jest "^20.0.3" fs-extra "^3.0.0" - glob-all "^3.1.0" jest-config "^20.0.0" jest-util "^20.0.0" pkg-dir "^2.0.0" @@ -2745,12 +2766,6 @@ yargs@^8.0.1: y18n "^3.2.1" yargs-parser "^7.0.0" -yargs@~1.2.6: - version "1.2.6" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-1.2.6.tgz#9c7b4a82fd5d595b2bf17ab6dcc43135432fe34b" - dependencies: - minimist "^0.1.0" - yargs@~3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"