Skip to content

Commit fcadc46

Browse files
mattphillipscpojer
authored andcommitted
[jest-each]: Add support for keyPaths in test titles (jestjs#6457)
* Add support for keyPaths in test titles * Move getPath from expect/utils to jest-util * Add changelog entry * Add keypath interpolation docs * Add missing license and flow comments * Revert "Move getPath from expect/utils to jest-util" This reverts commit 6b98ef1. * Refactor getPath to be simplier * Remove unused code * Fix changelog link
1 parent d9b324e commit fcadc46

File tree

5 files changed

+82
-6
lines changed

5 files changed

+82
-6
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
### Features
44

5+
- `[jest-each]` Add support for keyPaths in test titles ([#6457](https://github.com/facebook/jest/pull/6457))
56
- `[jest-cli]` Add `jest --init` option that generates a basic configuration file with a short description for each option ([#6442](https://github.com/facebook/jest/pull/6442))
67

78
### Fixes

docs/GlobalAPI.md

+2
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ describe.each([[1, 1, 2], [1, 2, 3], [2, 1, 3]])(
271271
- First row of variable name column headings separated with `|`
272272
- One or more subsequent rows of data supplied as template literal expressions using `${value}` syntax.
273273
- `name`: `String` the title of the test suite, use `$variable` to inject test data into the suite title from the tagged template expressions.
274+
- To inject nested object values use you can supply a keyPath i.e. `$variable.path.to.value`
274275
- `fn`: `Function` the suite of tests to be ran, this is the function that will receive the test data object.
275276

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

512514
Example:

packages/jest-each/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ each`
271271
##### `.test`:
272272

273273
- 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
274+
- To inject nested object values use you can supply a keyPath i.e. `$variable.path.to.value`
274275
- testFn: `Function` the test logic, this is the function that will receive the parameters of each row as function arguments
275276

276277
#### `each[tagged template].describe(name, suiteFn)`
@@ -306,6 +307,7 @@ each`
306307
##### `.describe`:
307308

308309
- 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
310+
- To inject nested object values use you can supply a keyPath i.e. `$variable.path.to.value`
309311
- 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
310312

311313
### Usage

packages/jest-each/src/__tests__/template.test.js

+60-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,66 @@ describe('jest-each', () => {
138138
);
139139
});
140140

141-
test('calls global with cb function with object built from tabel headings and values', () => {
141+
test('calls global with title containing $key in multiple positions', () => {
142+
const globalTestMocks = getGlobalTestMocks();
143+
const eachObject = each.withGlobal(globalTestMocks)`
144+
a | b | expected
145+
${0} | ${1} | ${1}
146+
${1} | ${1} | ${2}
147+
`;
148+
const testFunction = get(eachObject, keyPath);
149+
testFunction(
150+
'add($a, $b) expected string: a=$a, b=$b, expected=$expected',
151+
noop,
152+
);
153+
154+
const globalMock = get(globalTestMocks, keyPath);
155+
expect(globalMock).toHaveBeenCalledTimes(2);
156+
expect(globalMock).toHaveBeenCalledWith(
157+
'add(0, 1) expected string: a=0, b=1, expected=1',
158+
expectFunction,
159+
);
160+
expect(globalMock).toHaveBeenCalledWith(
161+
'add(1, 1) expected string: a=1, b=1, expected=2',
162+
expectFunction,
163+
);
164+
});
165+
166+
test('calls global with title containing $key.path', () => {
167+
const globalTestMocks = getGlobalTestMocks();
168+
const eachObject = each.withGlobal(globalTestMocks)`
169+
a
170+
${{foo: {bar: 'baz'}}}
171+
`;
172+
const testFunction = get(eachObject, keyPath);
173+
testFunction('interpolates object keyPath to value: $a.foo.bar', noop);
174+
175+
const globalMock = get(globalTestMocks, keyPath);
176+
expect(globalMock).toHaveBeenCalledTimes(1);
177+
expect(globalMock).toHaveBeenCalledWith(
178+
'interpolates object keyPath to value: "baz"',
179+
expectFunction,
180+
);
181+
});
182+
183+
test('calls global with title containing last seen object when $key.path is invalid', () => {
184+
const globalTestMocks = getGlobalTestMocks();
185+
const eachObject = each.withGlobal(globalTestMocks)`
186+
a
187+
${{foo: {bar: 'baz'}}}
188+
`;
189+
const testFunction = get(eachObject, keyPath);
190+
testFunction('interpolates object keyPath to value: $a.foo.qux', noop);
191+
192+
const globalMock = get(globalTestMocks, keyPath);
193+
expect(globalMock).toHaveBeenCalledTimes(1);
194+
expect(globalMock).toHaveBeenCalledWith(
195+
'interpolates object keyPath to value: {"bar": "baz"}',
196+
expectFunction,
197+
);
198+
});
199+
200+
test('calls global with cb function with object built from table headings and values', () => {
142201
const globalTestMocks = getGlobalTestMocks();
143202
const testCallBack = jest.fn();
144203
const eachObject = each.withGlobal(globalTestMocks)`

packages/jest-each/src/bind.js

+17-5
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,19 @@ const buildTable = (
129129
),
130130
);
131131

132+
const getMatchingKeyPaths = title => (matches, key) =>
133+
matches.concat(title.match(new RegExp(`\\$${key}[\\.\\w]*`, 'g')) || []);
134+
135+
const replaceKeyPathWithValue = data => (title, match) => {
136+
const keyPath = match.replace('$', '').split('.');
137+
const value = getPath(data, keyPath);
138+
return title.replace(match, pretty(value, {maxDepth: 1, min: true}));
139+
};
140+
132141
const interpolate = (title: string, data: any) =>
133-
Object.keys(data).reduce(
134-
(acc, key) =>
135-
acc.replace('$' + key, pretty(data[key], {maxDepth: 1, min: true})),
136-
title,
137-
);
142+
Object.keys(data)
143+
.reduce(getMatchingKeyPaths(title), []) // aka flatMap
144+
.reduce(replaceKeyPathWithValue(data), title);
138145

139146
const applyObjectParams = (obj: any, test: Function) => {
140147
if (test.length > 1) return done => test(obj, done);
@@ -144,3 +151,8 @@ const applyObjectParams = (obj: any, test: Function) => {
144151

145152
const pluralize = (word: string, count: number) =>
146153
word + (count === 1 ? '' : 's');
154+
155+
const getPath = (o: Object, [head, ...tail]: Array<string>) => {
156+
if (!head || !o.hasOwnProperty || !o.hasOwnProperty(head)) return o;
157+
return getPath(o[head], tail);
158+
};

0 commit comments

Comments
 (0)