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

WIP: Implementing snapshot editor support #2629

Closed
Closed
Show file tree
Hide file tree
Changes from all 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
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": "BSD-3-Clause",
"main": "build/index.js",
"dependencies": {
"babylon": "^6.14.1"
"babylon": "^6.14.1",
"babel-traverse": "^6.14.1",
"jest-snapshot": "^19.0.2"
},
"typings": "index.d.ts"
}
164 changes: 164 additions & 0 deletions packages/jest-editor-support/src/Snapshot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/**
* 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';

const traverse = require('babel-traverse').default;
const {getASTfor} = require('./parsers/BabylonParser');
const {utils} = require('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);
};

module.exports = 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, false).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});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DESCRIBE fit 1`] = `1 fit describe`;
exports[`DESCRIBE fit 2`] = `2 fit describe`;
exports[`DESCRIBE it 1`] = `1 it describe`;
exports[`DESCRIBE it 2`] = `2 it describe`;
exports[`DESCRIBE it.only 1`] = `1 it.only describe`;
exports[`DESCRIBE it.only 2`] = `2 it.only describe`;
exports[`DESCRIBE it.skip 1`] = `1 it.skip describe`;
exports[`DESCRIBE it.skip 2`] = `2 it.skip describe`;
exports[`DESCRIBE test 1`] = `1 test describe`;
exports[`DESCRIBE test 2`] = `2 test describe`;
exports[`DESCRIBE test.only 1`] = `1 test.only describe`;
exports[`DESCRIBE test.only 2`] = `2 test.only describe`;
exports[`DESCRIBE test.skip 1`] = `1 test.skip describe`;
exports[`DESCRIBE test.skip 2`] = `2 test.skip describe`;
exports[`DESCRIBE xit 1`] = `1 xit describe`;
exports[`DESCRIBE xit 2`] = `2 xit describe`;
exports[`DESCRIBE xtest 1`] = `1 xtest describe`;
exports[`DESCRIBE xtest 2`] = `2 xtest describe`;
exports[`DESCRIBE.ONLY fit 1`] = `1 fit describe.only`;
exports[`DESCRIBE.ONLY fit 2`] = `2 fit describe.only`;
exports[`DESCRIBE.ONLY it 1`] = `1 it describe.only`;
exports[`DESCRIBE.ONLY it 2`] = `2 it describe.only`;
exports[`DESCRIBE.ONLY it.only 1`] = `1 it.only describe.only`;
exports[`DESCRIBE.ONLY it.only 2`] = `2 it.only describe.only`;
exports[`DESCRIBE.ONLY it.skip 1`] = `1 it.skip describe.only`;
exports[`DESCRIBE.ONLY it.skip 2`] = `2 it.skip describe.only`;
exports[`DESCRIBE.ONLY test 1`] = `1 test describe.only`;
exports[`DESCRIBE.ONLY test 2`] = `2 test describe.only`;
exports[`DESCRIBE.ONLY test.only 1`] = `1 test.only describe.only`;
exports[`DESCRIBE.ONLY test.only 2`] = `2 test.only describe.only`;
exports[`DESCRIBE.ONLY test.skip 1`] = `1 test.skip describe.only`;
exports[`DESCRIBE.ONLY test.skip 2`] = `2 test.skip describe.only`;
exports[`DESCRIBE.ONLY xit 1`] = `1 xit describe.only`;
exports[`DESCRIBE.ONLY xit 2`] = `2 xit describe.only`;
exports[`DESCRIBE.ONLY xtest 1`] = `1 xtest describe.only`;
exports[`DESCRIBE.ONLY xtest 2`] = `2 xtest describe.only`;
exports[`DESCRIBE.SKIP fit 1`] = `1 fit describe.skip`;
exports[`DESCRIBE.SKIP fit 2`] = `2 fit describe.skip`;
exports[`DESCRIBE.SKIP it 1`] = `1 it describe.skip`;
exports[`DESCRIBE.SKIP it 2`] = `2 it describe.skip`;
exports[`DESCRIBE.SKIP it.only 1`] = `1 it.only describe.skip`;
exports[`DESCRIBE.SKIP it.only 2`] = `2 it.only describe.skip`;
exports[`DESCRIBE.SKIP it.skip 1`] = `1 it.skip describe.skip`;
exports[`DESCRIBE.SKIP it.skip 2`] = `2 it.skip describe.skip`;
exports[`DESCRIBE.SKIP test 1`] = `1 test describe.skip`;
exports[`DESCRIBE.SKIP test 2`] = `2 test describe.skip`;
exports[`DESCRIBE.SKIP test.only 1`] = `1 test.only describe.skip`;
exports[`DESCRIBE.SKIP test.only 2`] = `2 test.only describe.skip`;
exports[`DESCRIBE.SKIP test.skip 1`] = `1 test.skip describe.skip`;
exports[`DESCRIBE.SKIP test.skip 2`] = `2 test.skip describe.skip`;
exports[`DESCRIBE.SKIP xit 1`] = `1 xit describe.skip`;
exports[`DESCRIBE.SKIP xit 2`] = `2 xit describe.skip`;
exports[`DESCRIBE.SKIP xtest 1`] = `1 xtest describe.skip`;
exports[`DESCRIBE.SKIP xtest 2`] = `2 xtest describe.skip`;
exports[`FDESCRIBE fit 1`] = `1 fit fdescribe`;
exports[`FDESCRIBE fit 2`] = `2 fit fdescribe`;
exports[`FDESCRIBE it 1`] = `1 it fdescribe`;
exports[`FDESCRIBE it 2`] = `2 it fdescribe`;
exports[`FDESCRIBE it.only 1`] = `1 it.only fdescribe`;
exports[`FDESCRIBE it.only 2`] = `2 it.only fdescribe`;
exports[`FDESCRIBE it.skip 1`] = `1 it.skip fdescribe`;
exports[`FDESCRIBE it.skip 2`] = `2 it.skip fdescribe`;
exports[`FDESCRIBE test 1`] = `1 test fdescribe`;
exports[`FDESCRIBE test 2`] = `2 test fdescribe`;
exports[`FDESCRIBE test.only 1`] = `1 test.only fdescribe`;
exports[`FDESCRIBE test.only 2`] = `2 test.only fdescribe`;
exports[`FDESCRIBE test.skip 1`] = `1 test.skip fdescribe`;
exports[`FDESCRIBE test.skip 2`] = `2 test.skip fdescribe`;
exports[`FDESCRIBE xit 1`] = `1 xit fdescribe`;
exports[`FDESCRIBE xit 2`] = `2 xit fdescribe`;
exports[`FDESCRIBE xtest 1`] = `1 xtest fdescribe`;
exports[`FDESCRIBE xtest 2`] = `2 xtest fdescribe`;
exports[`XDESCRIBE fit 1`] = `1 fit xdescribe`;
exports[`XDESCRIBE fit 2`] = `2 fit xdescribe`;
exports[`XDESCRIBE it 1`] = `1 it xdescribe`;
exports[`XDESCRIBE it 2`] = `2 it xdescribe`;
exports[`XDESCRIBE it.only 1`] = `1 it.only xdescribe`;
exports[`XDESCRIBE it.only 2`] = `2 it.only xdescribe`;
exports[`XDESCRIBE it.skip 1`] = `1 it.skip xdescribe`;
exports[`XDESCRIBE it.skip 2`] = `2 it.skip xdescribe`;
exports[`XDESCRIBE test 1`] = `1 test xdescribe`;
exports[`XDESCRIBE test 2`] = `2 test xdescribe`;
exports[`XDESCRIBE test.only 1`] = `1 test.only xdescribe`;
exports[`XDESCRIBE test.only 2`] = `2 test.only xdescribe`;
exports[`XDESCRIBE test.skip 1`] = `1 test.skip xdescribe`;
exports[`XDESCRIBE test.skip 2`] = `2 test.skip xdescribe`;
exports[`XDESCRIBE xit 1`] = `1 xit xdescribe`;
exports[`XDESCRIBE xit 2`] = `2 xit xdescribe`;
exports[`XDESCRIBE xtest 1`] = `1 xtest xdescribe`;
exports[`XDESCRIBE xtest 2`] = `2 xtest xdescribe`;
Loading