Skip to content

Commit

Permalink
feat: add make test title function support (#9)
Browse files Browse the repository at this point in the history
* add test title function spec

* feat: implement test title function

* add readme section
  • Loading branch information
bahmutov committed Oct 15, 2021
1 parent a43824d commit bd7e45c
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 20 deletions.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,37 @@ it.each([10, 20, 30])('checking item %K', (x) => { ... })
// "checking item 3"
```

### Title function

You can form the test title yourself using a function. The function will get the item, the index, and all items and should return a string with the test title.

```js
function makeTestTitle(s, k, strings) {
return `test ${k + 1} for "${s}"`
}
it.each(['first', 'second'])(makeTestTitle, () => ...)
// creates the tests
// 'test 1 for "first"'
// 'test 2 for "second"'
```

It is very useful for forming a test title based on a property of an object, like

```js
it.each([
{ name: 'Joe', age: 30 },
{ name: 'Mary', age: 20 },
])(
(person) => `tests person ${person.name}`,
(person) => { ... }
})
// creates the tests
// "tests person Joe"
// "tests person Mary"
```

See [cypress/integration/title-function.js](./cypress/integration/ title-function.js) for more examples

## Examples

- Watch [Using cypress-each To Create Separate Tests](https://youtu.be/utPKRV_fL1E)
Expand Down
26 changes: 14 additions & 12 deletions cypress/integration/each-k.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@

import '../..'

it('has test title', function () {
expect(this, 'has test').to.have.property('test')
// @ts-ignore
expect(this.test.title).to.equal('has test title')
})
describe('Using %k and %K placeholders', () => {
it('has test title', function () {
expect(this, 'has test').to.have.property('test')
// @ts-ignore
expect(this.test.title).to.equal('has test title')
})

it.each([1, 2, 3])('has 0-based index %k', function (x) {
expect(x).to.be.oneOf([1, 2, 3])
// needs test context to be passed correctly by it.each
// expect(this.test.title).to.equal('has 0-based index 0')
})
it.each([1, 2, 3])('has 0-based index %k', function (x) {
expect(x).to.be.oneOf([1, 2, 3])
// needs test context to be passed correctly by it.each
// expect(this.test.title).to.equal('has 0-based index 0')
})

it.each([1, 2, 3])('has 1-based index %K', (x) => {
expect(x).to.be.oneOf([1, 2, 3])
it.each([1, 2, 3])('has 1-based index %K', (x) => {
expect(x).to.be.oneOf([1, 2, 3])
})
})
42 changes: 42 additions & 0 deletions cypress/integration/title-function.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// @ts-check
/// <reference types="cypress" />

import '../../src'

/**
* Forms the test title from the item and index
* @param {string} s Individual string
* @param {number} k Item index (0-based)
* @param {string[]} strings All strings
* @returns
*/
function makeTestTitle(s, k, strings) {
expect(s, 'first argument is the item').to.equal('foo')
expect(k, 'second argument is the index').to.equal(0)
expect(strings, 'all items').to.deep.equal(['foo'])

return `test ${k + 1} for "${s}"`
}

describe('Form test title using a function', () => {
it.each(['foo'])(makeTestTitle, function (s) {
expect(s, 'item value').to.equal('foo')
// @ts-ignore
expect(this.test.title, 'computed test title').to.equal('test 1 for "foo"')
})

it.each([
{ name: 'Joe', age: 30 },
{ name: 'Mary', age: 20 },
])(
(person) => `tests person ${person.name}`,
function (user) {
expect(user).to.have.keys('name', 'age')
// @ts-ignore
expect(this.test.title, 'computed test title').to.be.oneOf([
'tests person Joe',
'tests person Mary',
])
},
)
})
7 changes: 5 additions & 2 deletions src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
// types for it.each and describe.each
// any help improving them is welcome
// https://github.com/bahmutov/cypress-each

type TestTitleFn<T> = (item: T, index: number, items: T[]) => string

declare namespace Mocha {
type TestCallback<T> = (this: Context, arg0: T, arg1: any, arg2: any) => void

interface TestFunction {
// definition for it.each
each<T = unknown>(
values: T[],
): (titlePattern: string, fn: TestCallback<T>) => void
): (titlePattern: string | TestTitleFn<T>, fn: TestCallback<T>) => void
}

interface SuiteFunction {
// definition for describe.each
each<T = unknown>(
values: T[],
): (titlePattern: string, fn: TestCallback<T>) => void
): (titlePattern: string | TestTitleFn<T>, fn: TestCallback<T>) => void
}
}
40 changes: 34 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,41 @@ function formatTitle(pattern, ...values) {
return format.apply(null, [pattern].concat(values.slice(0, count)))
}

function makeTitle(titlePattern, value, k, values) {
if (typeof titlePattern === 'string') {
const testTitle = titlePattern.replace('%k', k).replace('%K', k + 1)
if (Array.isArray(value)) {
return formatTitle(testTitle, ...value)
} else {
return formatTitle(testTitle, value)
}
} else if (typeof titlePattern === 'function') {
return titlePattern(value, k, values)
}

throw new Error('titlePattern must be a string or function')
}

if (!it.each) {
it.each = function (values) {
return function (titlePattern, testCallback) {
values.forEach(function (value, k) {
const testTitle = titlePattern.replace('%k', k).replace('%K', k + 1)
// const testTitle = titlePattern.replace('%k', k).replace('%K', k + 1)
const title = makeTitle(titlePattern, value, k, values)
if (!title) {
throw new Error(
`Could not compute the test title ${k} for value ${value}`,
)
}

// define a test for each value
if (Array.isArray(value)) {
const title = formatTitle(testTitle, ...value)
// const title = formatTitle(testTitle, ...value)
it(title, function itArrayCallback() {
return testCallback.apply(this, value)
})
} else {
const title = formatTitle(testTitle, value)
// const title = formatTitle(testTitle, value)
it(title, function itCallback() {
return testCallback.call(this, value)
})
Expand All @@ -39,13 +60,20 @@ if (!describe.each) {
return function describeEach(titlePattern, testCallback) {
// define a test for each value
values.forEach((value, k) => {
const testTitle = titlePattern.replace('%k', k).replace('%K', k + 1)
// const testTitle = titlePattern.replace('%k', k).replace('%K', k + 1)
const title = makeTitle(titlePattern, value, k, values)

if (!title) {
throw new Error(
`Could not compute the suite title ${k} for value ${value}`,
)
}

if (Array.isArray(value)) {
const title = formatTitle(testTitle, ...value)
// const title = formatTitle(testTitle, ...value)
describe(title, testCallback.bind(null, ...value))
} else {
const title = formatTitle(testTitle, value)
// const title = formatTitle(testTitle, value)
describe(title, testCallback.bind(null, value))
}
})
Expand Down

0 comments on commit bd7e45c

Please sign in to comment.