diff --git a/README.md b/README.md
index 4576372..9419fb1 100644
--- a/README.md
+++ b/README.md
@@ -118,6 +118,26 @@ it.each([
See [cypress/integration/title-function.js](./cypress/integration/ title-function.js) for more examples
+## Chunking
+
+There is a built-in chunking helper in `describe.each` and `it.each` to only take a subset of the items. For example, to split all items into 3 chunks, and take the middle one, use
+
+```js
+it.each(items, 3, 1)(...)
+```
+
+The other spec files can take the other chunks. The index starts at 0, and should be less than the number of chunks.
+
+```js
+// split all items among 3 specs
+// spec-a.js
+it.each(items, 3, 0)(...)
+// spec-b.js
+it.each(items, 3, 1)(...)
+// spec-c.js
+it.each(items, 3, 2)(...)
+```
+
## Examples
- Watch [Using cypress-each To Create Separate Tests](https://youtu.be/utPKRV_fL1E)
diff --git a/cypress/integration/chunk-spec.js b/cypress/integration/chunk-spec.js
new file mode 100644
index 0000000..1f074ae
--- /dev/null
+++ b/cypress/integration/chunk-spec.js
@@ -0,0 +1,58 @@
+// @ts-check
+///
+
+import '../..'
+
+describe('chunking items', () => {
+ const items = [1, 2, 3, 4]
+
+ // split all items across four machines
+ // and this is the first machine, so it should only run the first item
+ it.each(
+ items,
+ 4,
+ 0,
+ )('checks %d', (x) => {
+ expect(x, 'the first item only').to.equal(1)
+ })
+
+ it.each(
+ items,
+ 4,
+ 1,
+ )('checks %d', (x) => {
+ expect(x, 'the second item only').to.equal(2)
+ })
+
+ it.each(
+ items,
+ 4,
+ 2,
+ )('checks %d', (x) => {
+ expect(x, 'the third item only').to.equal(3)
+ })
+
+ it.each(
+ items,
+ 4,
+ 3,
+ )('checks %d', (x) => {
+ expect(x, 'the last item only').to.equal(4)
+ })
+
+ it.each(
+ items,
+ 2, // split all items into 2 chunks
+ 0, // and this is chunk index 0
+ )('checks %d', (x) => {
+ expect(x, '1 or 2').to.be.oneOf([1, 2])
+ })
+
+ it.each(
+ items,
+ 2, // split all items into 2 chunks
+ 1, // and this is chunk index 1
+ )('checks %d', (x) => {
+ expect(x, '3 or 4').to.be.oneOf([3, 4])
+ })
+})
diff --git a/src/index.d.ts b/src/index.d.ts
index 05fca64..9976474 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -8,16 +8,36 @@ declare namespace Mocha {
type TestCallback = (this: Context, arg0: T, arg1: any, arg2: any) => void
interface TestFunction {
- // definition for it.each
+ /**
+ * Iterates over each given item (optionally chunked), and creates
+ * a separate test for each one.
+ * @param values Input items to create the tests form
+ * @param totalChunks (Optional) number of chunks to split the items into
+ * @param chunkIndex (Optional) index of the chunk to get items from
+ * @example it.each([1, 2, 3])('test %K', (x) => ...)
+ * @see https://github.com/bahmutov/cypress-each
+ */
each(
values: T[],
+ totalChunks?: number,
+ chunkIndex?: number,
): (titlePattern: string | TestTitleFn, fn: TestCallback) => void
}
interface SuiteFunction {
- // definition for describe.each
+ /**
+ * Iterates over each given item (optionally chunked), and creates
+ * a separate suite for each one.
+ * @param values Input items to create the tests form
+ * @param totalChunks (Optional) number of chunks to split the items into
+ * @param chunkIndex (Optional) index of the chunk to get items from
+ * @example describe.each([1, 2, 3])('suite %K', (item) => ...)
+ * @see https://github.com/bahmutov/cypress-each
+ */
each(
values: T[],
+ totalChunks?: number,
+ chunkIndex?: number,
): (titlePattern: string | TestTitleFn, fn: TestCallback) => void
}
}
diff --git a/src/index.js b/src/index.js
index 779c892..73bf0e3 100644
--- a/src/index.js
+++ b/src/index.js
@@ -11,6 +11,25 @@ function formatTitle(pattern, ...values) {
return format.apply(null, [pattern].concat(values.slice(0, count)))
}
+function getChunk(values, totalChunks, chunkIndex) {
+ // split all items into N chunks and take just a single chunk
+ if (totalChunks < 0) {
+ throw new Error('totalChunks must be >= 0')
+ }
+
+ if (chunkIndex < 0 || chunkIndex >= totalChunks) {
+ throw new Error(
+ `Invalid chunk index ${chunkIndex} vs all chunks ${totalChunks}`,
+ )
+ }
+
+ const chunkSize = Math.ceil(values.length / totalChunks)
+ const chunkStart = chunkIndex * chunkSize
+ const chunkEnd = chunkStart + chunkSize
+ const chunk = values.slice(chunkStart, chunkEnd)
+ return chunk
+}
+
function makeTitle(titlePattern, value, k, values) {
if (typeof titlePattern === 'string') {
const testTitle = titlePattern.replace('%k', k).replace('%K', k + 1)
@@ -27,12 +46,17 @@ function makeTitle(titlePattern, value, k, values) {
}
if (!it.each) {
- it.each = function (values) {
+ it.each = function (values, totalChunks, chunkIndex) {
if (!Array.isArray(values)) {
throw new Error('cypress-each: values must be an array')
}
return function (titlePattern, testCallback) {
+ if (typeof totalChunks === 'number' && typeof chunkIndex === 'number') {
+ // split all items into N chunks and take just a single chunk
+ values = getChunk(values, totalChunks, chunkIndex)
+ }
+
values.forEach(function (value, k) {
// const testTitle = titlePattern.replace('%k', k).replace('%K', k + 1)
const title = makeTitle(titlePattern, value, k, values)
@@ -65,6 +89,11 @@ if (!describe.each) {
throw new Error('cypress-each: values must be an array')
}
+ if (typeof totalChunks === 'number' && typeof chunkIndex === 'number') {
+ // split all items into N chunks and take just a single chunk
+ values = getChunk(values, totalChunks, chunkIndex)
+ }
+
return function describeEach(titlePattern, testCallback) {
// define a test for each value
values.forEach((value, k) => {
@@ -89,4 +118,4 @@ if (!describe.each) {
}
}
-module.exports = { formatTitle }
+module.exports = { formatTitle, getChunk }