Skip to content

Commit

Permalink
feat(jest-editor-snapshot): Add Snapshots metadata (#4570)
Browse files Browse the repository at this point in the history
* first iteration

* Use babel-traverse

* comments on the pr

* feat(jest-editor-support): Add Snapshot metadata

Following #2629, I rebased the code on current master. I still have to
actually test the feature with jest-community/vscode-jest#73.

What i would like to add ultimately is the ability to:
- preview the snapshot
- have the snapshot diff
- have a button to accept diff per snapshot (would update it)

WDYT?
  • Loading branch information
vvo authored and cpojer committed Sep 30, 2017
1 parent 9af7bef commit e81f1fb
Show file tree
Hide file tree
Showing 14 changed files with 669 additions and 10 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ module.exports = {
'packages/jest-editor-support/src/Process.js',
'packages/jest-editor-support/src/Runner.js',
'packages/jest-editor-support/src/Settings.js',
'packages/jest-editor-support/src/Snapshot.js',
'packages/jest-editor-support/src/__tests__/Snapshot-test.js',
'packages/jest-jasmine2/src/jasmine/Env.js',
'packages/jest-jasmine2/src/jasmine/Spec.js',
'packages/jest-jasmine2/src/jasmine/Suite.js',
Expand Down
4 changes: 3 additions & 1 deletion packages/jest-editor-support/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
"license": "MIT",
"main": "build/index.js",
"dependencies": {
"babylon": "^6.14.1"
"babylon": "^6.14.1",
"babel-traverse": "^6.14.1",
"jest-snapshot": "^21.1.0"
},
"typings": "index.d.ts"
}
167 changes: 167 additions & 0 deletions packages/jest-editor-support/src/Snapshot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/

'use strict';

import traverse from 'babel-traverse';
import {getASTfor} from './parsers/babylon_parser';
import {utils} from 'jest-snapshot';

type Node = any;

type SnapshotMetadata = {
exists: true | false,
name: string,
node: Node,
content?: string,
};

const describeVariants = Object.assign(
(Object.create(null): {[string]: boolean}),
{
describe: true,
fdescribe: true,
xdescribe: true,
},
);
const base = Object.assign((Object.create(null): {[string]: boolean}), {
describe: true,
it: true,
test: true,
});
const decorators = Object.assign((Object.create(null): {[string]: boolean}), {
only: true,
skip: true,
});

const validParents = Object.assign(
(Object.create(null): any),
base,
describeVariants,
Object.assign((Object.create(null): {[string]: boolean}), {
fit: true,
xit: true,
xtest: true,
}),
);

const isValidMemberExpression = node =>
node.object &&
base[node.object.name] &&
node.property &&
decorators[node.property.name];

const isDescribe = node =>
describeVariants[node.name] ||
(isValidMemberExpression(node) && node.object.name === 'describe');

const isValidParent = parent =>
parent.callee &&
(validParents[parent.callee.name] || isValidMemberExpression(parent.callee));

const getArrayOfParents = path => {
const result = [];
let parent = path.parentPath;
while (parent) {
result.unshift(parent.node);
parent = parent.parentPath;
}
return result;
};

const buildName: (
snapshotNode: Node,
parents: Array<Node>,
position: number,
) => string = (snapshotNode, parents, position) => {
const fullName = parents.map(parent => parent.arguments[0].value).join(' ');

let describeLess = '';
if (!isDescribe(parents[0].callee)) {
// If `it` or `test` exists without a surrounding `describe`
// then `test ` is prepended to the snapshot fullName.
describeLess = 'test ';
}

return utils.testNameToKey(describeLess + fullName, position);
};

export default class Snapshot {
_parser: Function;
_matchers: Array<string>;
constructor(parser: any, customMatchers?: Array<string>) {
this._parser = parser || getASTfor;
this._matchers = ['toMatchSnapshot', 'toThrowErrorMatchingSnapshot'].concat(
customMatchers || [],
);
}

getMetadata(filePath: string): Array<SnapshotMetadata> {
const fileNode = this._parser(filePath);
const state = {
found: [],
};
const Visitors = {
Identifier(path, state, matchers) {
if (matchers.includes(path.node.name)) {
state.found.push({
node: path.node,
parents: getArrayOfParents(path),
});
}
},
};

traverse(fileNode, {
enter: path => {
const visitor = Visitors[path.node.type];
if (visitor != null) {
visitor(path, state, this._matchers);
}
},
});

const snapshotPath = utils.getSnapshotPath(filePath);
const snapshots = utils.getSnapshotData(snapshotPath, 'none').data;
let lastParent = null;
let count = 1;

return state.found.map((snapshotNode, index) => {
const parents = snapshotNode.parents.filter(isValidParent);
const innerAssertion = parents[parents.length - 1];

if (lastParent !== innerAssertion) {
lastParent = innerAssertion;
count = 1;
}

const result = {
content: undefined,
count: count++,
exists: false,
name: '',
node: snapshotNode.node,
};

if (!innerAssertion || isDescribe(innerAssertion.callee)) {
// An expectation inside describe never gets executed.
return result;
}

result.name = buildName(snapshotNode, parents, result.count);

if (snapshots[result.name]) {
result.exists = true;
result.content = snapshots[result.name];
}
return result;
});
}
}
128 changes: 128 additions & 0 deletions packages/jest-editor-support/src/__tests__/Snapshot-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

'use strict';

const path = require('path');
const Snapshot = require('../Snapshot');
const snapshotHelper = new Snapshot();
const snapshotFixturePath = path.resolve(__dirname, 'fixtures/snapshots');

test('nodescribe.example', () => {
const filePath = path.join(snapshotFixturePath, 'nodescribe.example');
const results = snapshotHelper.getMetadata(filePath);
const allAssertion = [
'fit',
'it',
'it.only',
'it.skip',
'test',
'test.only',
'test.skip',
'xit',
'xtest',
];

const expectations = Object.create(null);
allAssertion.forEach(assertion => {
expectations['test ' + assertion + ' 1'] = {
assertion,
checked: false,
number: 1,
};
expectations['test ' + assertion + ' 2'] = {
assertion,
checked: false,
number: 2,
};
});

results.forEach(result => {
const check = expectations[result.name];
check.checked = result.content === `${check.assertion} ${check.number}`;
});

expect(
Object.keys(expectations)
.map(key => expectations[key])
.filter(expectation => !expectation.checked).length,
).toBe(0);
});

test('describe.example', () => {
const filePath = path.join(snapshotFixturePath, 'describe.example');
const results = snapshotHelper.getMetadata(filePath);
const allDescribe = [
'describe',
'describe.only',
'describe.skip',
'fdescribe',
'xdescribe',
];
const allAssertion = [
'fit',
'it',
'it.only',
'it.skip',
'test',
'test.only',
'test.skip',
'xit',
'xtest',
];

const expectations = Object.create(null);

allDescribe.forEach(describe => {
allAssertion.forEach(assertion => {
expectations[describe.toUpperCase() + ' ' + assertion + ' 1'] = {
assertion,
checked: false,
describe,
number: 1,
};

expectations[describe.toUpperCase() + ' ' + assertion + ' 2'] = {
assertion,
checked: false,
describe,
number: 2,
};
});
});

results.forEach(result => {
const check = expectations[result.name];
check.checked =
result.content === `${check.number} ${check.assertion} ${check.describe}`;
});
expect(
Object.keys(expectations)
.map(key => expectations[key])
.filter(expectation => !expectation.checked).length,
).toBe(0);
});

test('nested.example', () => {
const filePath = path.join(snapshotFixturePath, 'nested.example');
const results = snapshotHelper.getMetadata(filePath);
expect(results[0].content).toBe('first nested');
expect(results[1].content).toBe('second nested');

expect(results[0].name).toBe(
'outer describe outer it inner describe inner it 1',
);
expect(results[1].name).toBe(
'outer describe outer it inner describe inner it 2',
);

expect(results[0].node.loc.start).toEqual({column: 21, line: 5});
expect(results[0].node.loc.end).toEqual({column: 36, line: 5});
expect(results[1].node.loc.start).toEqual({column: 21, line: 6});
expect(results[1].node.loc.end).toEqual({column: 36, line: 6});
});
Loading

0 comments on commit e81f1fb

Please sign in to comment.