Skip to content

Commit

Permalink
Add support for keyPaths in test titles
Browse files Browse the repository at this point in the history
  • Loading branch information
mattphillips committed Jun 13, 2018
1 parent 9c1c3b1 commit 611dbf8
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 6 deletions.
61 changes: 60 additions & 1 deletion packages/jest-each/src/__tests__/template.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,66 @@ describe('jest-each', () => {
);
});

test('calls global with cb function with object built from tabel headings and values', () => {
test('calls global with title containing $key in multiple positions', () => {
const globalTestMocks = getGlobalTestMocks();
const eachObject = each.withGlobal(globalTestMocks)`
a | b | expected
${0} | ${1} | ${1}
${1} | ${1} | ${2}
`;
const testFunction = get(eachObject, keyPath);
testFunction(
'add($a, $b) expected string: a=$a, b=$b, expected=$expected',
noop,
);

const globalMock = get(globalTestMocks, keyPath);
expect(globalMock).toHaveBeenCalledTimes(2);
expect(globalMock).toHaveBeenCalledWith(
'add(0, 1) expected string: a=0, b=1, expected=1',
expectFunction,
);
expect(globalMock).toHaveBeenCalledWith(
'add(1, 1) expected string: a=1, b=1, expected=2',
expectFunction,
);
});

test('calls global with title containing $key.path', () => {
const globalTestMocks = getGlobalTestMocks();
const eachObject = each.withGlobal(globalTestMocks)`
a
${{foo: {bar: 'baz'}}}
`;
const testFunction = get(eachObject, keyPath);
testFunction('interpolates object keyPath to value: $a.foo.bar', noop);

const globalMock = get(globalTestMocks, keyPath);
expect(globalMock).toHaveBeenCalledTimes(1);
expect(globalMock).toHaveBeenCalledWith(
'interpolates object keyPath to value: "baz"',
expectFunction,
);
});

test('calls global with title containing last seen object when $key.path is invalid', () => {
const globalTestMocks = getGlobalTestMocks();
const eachObject = each.withGlobal(globalTestMocks)`
a
${{foo: {bar: 'baz'}}}
`;
const testFunction = get(eachObject, keyPath);
testFunction('interpolates object keyPath to value: $a.foo.qux', noop);

const globalMock = get(globalTestMocks, keyPath);
expect(globalMock).toHaveBeenCalledTimes(1);
expect(globalMock).toHaveBeenCalledWith(
'interpolates object keyPath to value: {"bar": "baz"}',
expectFunction,
);
});

test('calls global with cb function with object built from table headings and values', () => {
const globalTestMocks = getGlobalTestMocks();
const testCallBack = jest.fn();
const eachObject = each.withGlobal(globalTestMocks)`
Expand Down
68 changes: 63 additions & 5 deletions packages/jest-each/src/bind.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,20 @@ const buildTable = (
),
);

const getMatchingKeyPaths = title => (matches, key) =>
matches.concat(title.match(new RegExp(`\\$${key}[\\.\\w]*`, 'g')) || []);

const replaceKeyPathWithValue = data => (title, match) => {
const keyPath = match.replace('$', '').split('.');
const result = getPath(data, keyPath);
const value = result.hasEndProp ? result.value : result.lastTraversedObject;
return title.replace(match, pretty(value, {maxDepth: 1, min: true}));
};

const interpolate = (title: string, data: any) =>
Object.keys(data).reduce(
(acc, key) =>
acc.replace('$' + key, pretty(data[key], {maxDepth: 1, min: true})),
title,
);
Object.keys(data)
.reduce(getMatchingKeyPaths(title), []) // aka flatMap
.reduce(replaceKeyPathWithValue(data), title);

const applyObjectParams = (obj: any, test: Function) => {
if (test.length > 1) return done => test(obj, done);
Expand All @@ -144,3 +152,53 @@ const applyObjectParams = (obj: any, test: Function) => {

const pluralize = (word: string, count: number) =>
word + (count === 1 ? '' : 's');

const hasOwnProperty = (object: Object, value: string) =>
Object.prototype.hasOwnProperty.call(object, value) ||
Object.prototype.hasOwnProperty.call(object.constructor.prototype, value);

const getPath = (object: Object, propertyPath: string | Array<string>) => {
if (!Array.isArray(propertyPath)) {
propertyPath = propertyPath.split('.');
}

if (propertyPath.length) {
const lastProp = propertyPath.length === 1;
const prop = propertyPath[0];
const newObject = object[prop];

if (!lastProp && (newObject === null || newObject === undefined)) {
// This is not the last prop in the chain. If we keep recursing it will
// hit a `can't access property X of undefined | null`. At this point we
// know that the chain has broken and we can return right away.
return {
hasEndProp: false,
lastTraversedObject: object,
traversedPath: [],
};
}

const result = getPath(newObject, propertyPath.slice(1));

if (result.lastTraversedObject === null) {
result.lastTraversedObject = object;
}

result.traversedPath.unshift(prop);

if (lastProp) {
result.hasEndProp = hasOwnProperty(object, prop);
if (!result.hasEndProp) {
result.traversedPath.shift();
}
}

return result;
}

return {
lastTraversedObject: null,
traversedPath: [],
value: object,
};
};

0 comments on commit 611dbf8

Please sign in to comment.