Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hoist mock calls #239

Merged
merged 21 commits into from
Jun 3, 2017
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
[*.js]
[*.ts]
indent_style = space
indent_size = 4
indent_size = 4

[*.tsx]
indent_style = space
indent_size = 4
88 changes: 45 additions & 43 deletions src/postprocess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,57 @@
* https://github.com/facebook/jest/blob/9b157c3a7c325c3971b2aabbe4c8ab4ce0b0c56d/packages/babel-jest/src/index.js
*/
import * as jestPreset from 'babel-preset-jest';
import { JestConfig, PostProcessHook, PostProcessorOptions, TransformOptions } from './jest-types';
import {JestConfig, PostProcessHook, PostProcessorOptions, TransformOptions} from './jest-types';
import * as babel from 'babel-core';
import { CompilerOptions } from 'typescript/lib/typescript';
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]),
retainLines: true,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't really need retainLines, do we?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsure. It breaks a bunch of tests if it is not included. Perhaps it only needs to be set if inline sourcemaps are turned off?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to set inlineSourcemaps to true for sourcemaps to work. This is because we don't generate sourcemap files on disk during testing

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean "sourceMaps": "inline" in babel or in tsconfig? I think we already set it for tsconfig.
I have tried setting it for babel but it gives me off by one error for every tests.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inlineSourcemaps: true for typescript and devtool: inline-source-map for babel

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't devtool: inline-source-map a webpack thing? I'm not seeing in in the babel docs anywhere?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right.

I'm gonna lay off of this thing for today. I don't think I'm helping

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that babel does have an inputSourceMap though. What problem are we trying to solve exactly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's cool. I think this is worth exploring, but if we think the sourcemaps work as-is, I don't think there's a need to change the code we copied from babel-jest

};
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([
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to use istanbul? With mapCoverage set to true, won't Jest take care of the instrumentation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't actually know - I just assumed it was there for a reason in babel-jest. I'll try removing it and see what happens.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing istanbul leads to some markedly different coverage numbers - I think it has to stay.

[
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
}

//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
const plugins = [];
//If we're not skipping babel
if (tsCompilerOptions.allowSyntheticDefaultImports) {
plugins.push('transform-es2015-modules-commonjs');
}


return createBabelTransformer({
presets: [],
plugins: plugins,
});
};
4 changes: 2 additions & 2 deletions tests/__tests__/__snapshots__/html-transform.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -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=\`<div class=\\"html-test\\">
<span class=\\"html-test__element\\">This is element</span>
</div>\`;"
`;

exports[`transforms html if config.globals.__TRANSFORM_HTML__ is set 2`] = `
exports[`Html transforms transforms html if config.globals.__TRANSFORM_HTML__ is set 2`] = `
"<div class=\\"html-test\\">
<span class=\\"html-test__element\\">This is element</span>
</div>"
Expand Down
12 changes: 7 additions & 5 deletions tests/__tests__/html-transform.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
13 changes: 13 additions & 0 deletions tests/__tests__/jest-hoist.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
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);
});

});
6 changes: 3 additions & 3 deletions tests/__tests__/ts-compilation.spec.ts
Original file line number Diff line number Diff line change
@@ -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']);

Expand All @@ -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');

});

Expand Down
4 changes: 2 additions & 2 deletions tests/__tests__/ts-coverage-async.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
Expand Down
4 changes: 2 additions & 2 deletions tests/__tests__/ts-coverage.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
Expand Down
15 changes: 13 additions & 2 deletions tests/__tests__/ts-errors.spec.ts
Original file line number Diff line number Diff line change
@@ -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', () => {

Expand All @@ -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');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look right. If the source file hasn't changed, why should the line number change in the test?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch - the file has changed though. I wanted to be completely sure I didn't break anything, so I added a typescript interface to simple/Hello.ts - I wanted to put something in there that'd fill up a few lines, but I knew would be stripped by tsc

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses simple/Hello.ts as the source, right? That should show an error on line 13

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Not since I edited the file ;)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the wrong version of the file while making that comment. It looks alright!

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');
});


});
4 changes: 2 additions & 2 deletions tests/__tests__/tsx-compilation.spec.ts
Original file line number Diff line number Diff line change
@@ -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']);

Expand Down
4 changes: 2 additions & 2 deletions tests/__tests__/tsx-errors.spec.ts
Original file line number Diff line number Diff line change
@@ -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', () => {

Expand All @@ -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');

});
Expand Down
2 changes: 1 addition & 1 deletion tests/__tests__/watch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe('Hello Class', () => {
});
`;

describe('hello_world', () => {
describe('Typescript watch tests', () => {
let result: { childProcess: ChildProcess, getStderrAsync: () => Promise<string> };
let DEFAULT_TIMEOUT_INTERVAL: number;

Expand Down
27 changes: 27 additions & 0 deletions tests/hoist-test/__tests__/jest-hoist.test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
14 changes: 14 additions & 0 deletions tests/hoist-test/package.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
}
10 changes: 10 additions & 0 deletions tests/hoist-test/src/things-to-mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@


export class SomeClass {
}

export const SomeFunctionDeclaredAsConst = () => 'originalValue';

export function SomeFunction(){
return 'original';
}
12 changes: 12 additions & 0 deletions tests/hoist-test/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ES5",
"module": "commonjs",
"moduleResolution": "node",
"noEmitOnError": false,
"jsx": "react",
"allowJs": true,
"baseUrl": "src",
"allowSyntheticDefaultImports": false
}
}
2 changes: 1 addition & 1 deletion tests/simple-async/__tests__/Hello.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
5 changes: 5 additions & 0 deletions tests/simple/Hello.ts
Original file line number Diff line number Diff line change
@@ -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 = `
Expand Down
2 changes: 1 addition & 1 deletion tests/simple/__tests__/Hello.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
Loading