Skip to content

Commit

Permalink
feat(test): jest serializers
Browse files Browse the repository at this point in the history
  • Loading branch information
huafu committed Aug 8, 2018
1 parent cc04021 commit dfa9c0f
Show file tree
Hide file tree
Showing 15 changed files with 6,195 additions and 26 deletions.
5 changes: 3 additions & 2 deletions e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ The returned value is an object with those properties and methods:
- `stdout`, _string_: the data written to stdout during the run
- `stderr`, _string_: the data written to stderr during the run
- `output`, _string_: the data written to stdout and stderr during the run
- `outputForSnapshot`, _string_: same as `output`, expect it's sanitized for jest snapshot (time values are replaced with static values, ...)

**Note**: _You can optionally pass the expected status code as the first argument of `run()`. In the case it's not the correct one, it'll write in the console the actual `output` so that you can debug the test case._
**Note 1**: _The value returned by `run()` is snapshot friendly (ie.: you can `expect(x.run()).toMatchSnapshot()`, it'll remove any changin values such as time values)_

**Note 2**: _You can optionally pass the expected status code as the first argument of `run()`. In the case it's not the correct one, it'll write in the console the actual `output` so that you can debug the test case._

Bare simple example of using it in your tests:
```ts
Expand Down
9 changes: 9 additions & 0 deletions e2e/__cases__/source-maps/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "ts-jest-debug",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"test": "jest --config ./test/jest.config.json"
}
}
7 changes: 7 additions & 0 deletions e2e/__cases__/source-maps/src/echo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function echo(shout: string) {
console.log('WITHIN SOURCE');
if (process.env.__FORCE_FAIL) {
throw new Error('WITHIN SOURCE');
}
return shout;
}
8 changes: 8 additions & 0 deletions e2e/__cases__/source-maps/test/echo.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { echo } from '../src/echo';

describe('echo', () => {
it('echoes', () => {
console.log('WITHIN TEST');
expect(echo('repeat')).toEqual('repeat');
});
});
10 changes: 10 additions & 0 deletions e2e/__cases__/source-maps/test/jest.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"roots": ["../src", "."],
"testRegex": ".spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"coverageDirectory": "./coverage",
"testEnvironment": "node"
}
13 changes: 13 additions & 0 deletions e2e/__cases__/source-maps/test/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"lib": [
"es2015",
"dom"
]
},
"include": [
"../src/**/*",
"**/*"
]
}
20 changes: 20 additions & 0 deletions e2e/__cases__/source-maps/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"strict": true,
"sourceMap": true,
"outDir": "./dist",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"lib": [
"es2015",
"es2017",
"dom"
]
},
"include": [
"src/**/*"
]
}
98 changes: 75 additions & 23 deletions e2e/__helpers__/test-case.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// tslint:disable-file:no-shadowed-variable
import { sync as spawnSync } from 'cross-spawn';
import { join } from 'path';
import * as Paths from '../../scripts/paths';
Expand Down Expand Up @@ -44,15 +45,12 @@ class TestCaseRunDescriptor {
return this._options.template;
}

run(logOutputUnlessStatusIs?: number): TestRunResult {
run(logUnlessStatus?: number): TestRunResult {
const result = run(this.name, {
...this._options,
template: this.templateName,
});
if (
logOutputUnlessStatusIs != null &&
logOutputUnlessStatusIs !== result.status
) {
if (logUnlessStatus != null && logUnlessStatus !== result.status) {
console.log(
`Output of test run in "${this.name}" using template "${
this.templateName
Expand All @@ -62,29 +60,93 @@ class TestCaseRunDescriptor {
}
return result;
}

runWithTemplates<T extends string>(
logUnlessStatus: number,
...templates: T[]
): TestRunResultsMap<T>;
runWithTemplates<T extends string>(...templates: T[]): TestRunResultsMap<T>;
runWithTemplates<T extends string>(
logUnlessStatus: number | T,
...templates: T[]
): TestRunResultsMap<T> {
if (typeof logUnlessStatus !== 'number') {
templates.unshift(logUnlessStatus);
logUnlessStatus = undefined;
}
if (templates.length < 1) {
throw new RangeError(
`There must be at least one template to run the test case with.`,
);
}
if (!templates.every((t, i) => templates.indexOf(t, i + 1) === -1)) {
throw new Error(
`Each template must be unique. Given ${templates.join(', ')}`,
);
}
return templates.reduce(
(map, template) => {
const desc = new TestCaseRunDescriptor(this.name, {
...this._options,
template,
});
map[template as string] = desc.run(logUnlessStatus as number);
return map;
},
{} as TestRunResultsMap<T>,
);
}
}

export const TestRunResultFlag = Symbol.for('[ts-jest-test-run-result]');

export interface RunTestOptions {
template?: string;
env?: {};
args?: string[];
}

export interface TestRunResult {
[TestRunResultFlag]: true;
status: number;
stdout: string;
stderr: string;
output: string;
outputForSnapshot: string;
}

// tslint:disable-next-line:interface-over-type-literal
export type TestRunResultsMap<T extends string = string> = {
[key in T]: TestRunResult
};

export default function configureTestCase(
name: string,
options: RunTestOptions = {},
): TestCaseRunDescriptor {
return new TestCaseRunDescriptor(name, options);
}

export function sanitizeOutput(output: string): string {
return (
output
.trim()
// removes total and estimated times
.replace(
/^(\s*Time\s*:\s*)[\d.]+m?s(?:(,\s*estimated\s+)[\d.]+m?s)?(\s*)$/gm,
(_, start, estimatedPrefix, end) => {
return `${start}XXs${
estimatedPrefix ? `${estimatedPrefix}YYs` : ''
}${end}`;
},
)
// removes each test time values
.replace(
/^(\s*(?:✕|✓)\s+.+\s+\()[\d.]+m?s(\)\s*)$/gm,
(_, start, end) => `${start}XXms${end}`,
)
);
}

export function run(
name: string,
{ args = [], env = {}, template }: RunTestOptions = {},
Expand Down Expand Up @@ -115,24 +177,14 @@ export function run(
const output = result.output
? stripAnsiColors(result.output.join('\n\n'))
: '';
const outputForSnapshot = output
.trim()
// removes total and estimated time(s)
.replace(
/^(\s*Time\s*:\s*)[\d.]+m?s(?:(,\s*estimated\s+)[\d.]+m?s)?(\s*)$/gm,
(_, start, estimatedPrefix, end) => {
return `${start}XXs${
estimatedPrefix ? `${estimatedPrefix}YYs` : ''
}${end}`;
},
)
// removes each test time(s)
.replace(
/^(\s*(?:✕|✓)\s+.+\s+\()[\d.]+m?s(\)\s*)$/gm,
(_, start, end) => `${start}XXms${end}`,
);

return { status: result.status, stderr, stdout, output, outputForSnapshot };
return {
[TestRunResultFlag]: true,
status: result.status,
stderr,
stdout,
output,
};
}

// from https://stackoverflow.com/questions/25245716/remove-all-ansi-colors-styles-from-strings
Expand Down
19 changes: 19 additions & 0 deletions e2e/__serializers__/test-run-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {
TestRunResultFlag,
TestRunResult,
sanitizeOutput,
} from '../__helpers__/test-case';

export const test = (val: any) => val && val[TestRunResultFlag];
export const print = (val: TestRunResult, serialize: any, indent: any) => {
const out = [
`===[ STDOUT ]${'='.repeat(67)}`,
sanitizeOutput(val.stdout),
`===[ STDERR ]${'='.repeat(67)}`,
sanitizeOutput(val.stderr),
'='.repeat(80),
]
.map(l => indent(l))
.join('\n');
return `jest exit code: ${val.status}\n${out}`;
};
Loading

0 comments on commit dfa9c0f

Please sign in to comment.