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

[jest-each]: Add support for keyPaths in test titles #6457

Merged
merged 10 commits into from
Jun 20, 2018
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## master

### Features

- `[jest-each]` Add support for keyPaths in test titles ([#6457](https://github.com/facebook/jest/pull/6457/files))
Copy link
Member

Choose a reason for hiding this comment

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

remove /files


### Fixes

- `[jest-config]` Add missing options to the `defaults` object ([#6428](https://github.com/facebook/jest/pull/6428))
Expand Down
2 changes: 2 additions & 0 deletions docs/GlobalAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ describe.each([[1, 1, 2], [1, 2, 3], [2, 1, 3]])(
- First row of variable name column headings separated with `|`
- One or more subsequent rows of data supplied as template literal expressions using `${value}` syntax.
- `name`: `String` the title of the test suite, use `$variable` to inject test data into the suite title from the tagged template expressions.
- To inject nested object values use you can supply a keyPath i.e. `$variable.path.to.value`
- `fn`: `Function` the suite of tests to be ran, this is the function that will receive the test data object.

Example:
Expand Down Expand Up @@ -507,6 +508,7 @@ test.each([[1, 1, 2], [1, 2, 3], [2, 1, 3]])(
- First row of variable name column headings separated with `|`
- One or more subsequent rows of data supplied as template literal expressions using `${value}` syntax.
- `name`: `String` the title of the test, use `$variable` to inject test data into the test title from the tagged template expressions.
- To inject nested object values use you can supply a keyPath i.e. `$variable.path.to.value`
- `fn`: `Function` the test to be ran, this is the function that will receive the test data object.

Example:
Expand Down
2 changes: 2 additions & 0 deletions packages/jest-each/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ each`
##### `.test`:

- name: `String` the title of the `test`, use `$variable` in the name string to inject test values into the test title from the tagged template expressions
- To inject nested object values use you can supply a keyPath i.e. `$variable.path.to.value`
- testFn: `Function` the test logic, this is the function that will receive the parameters of each row as function arguments

#### `each[tagged template].describe(name, suiteFn)`
Expand Down Expand Up @@ -306,6 +307,7 @@ each`
##### `.describe`:

- name: `String` the title of the `test`, use `$variable` in the name string to inject test values into the test title from the tagged template expressions
- To inject nested object values use you can supply a keyPath i.e. `$variable.path.to.value`
- suiteFn: `Function` the suite of `test`/`it`s to be ran, this is the function that will receive the parameters in each row as function arguments

### Usage
Expand Down
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
22 changes: 17 additions & 5 deletions packages/jest-each/src/bind.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,19 @@ 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 value = getPath(data, keyPath);
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 +151,8 @@ const applyObjectParams = (obj: any, test: Function) => {

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

const getPath = (o: Object, [head, ...tail]: Array<string>) => {
if (!head || !o.hasOwnProperty || !o.hasOwnProperty(head)) return o;
return getPath(o[head], tail);
};