Repository: bahmutov/cypress-map
Pull request #28 (2023-03-11 14:40:32)

feat: add cy.findOne query


Patch:
diff --git a/README.md b/README.md
index 8e19096..f2b32de 100644
--- a/README.md
+++ b/README.md
@@ -63,6 +63,8 @@ const double = (n) => n * 2
 cy.wrap(100).apply(double).should('equal', 200)
 ```
 
+It works like `cy.then` but `cy.apply(fn)` is a query command. Function `fn` should be synchronous, pure function that only uses the subject argument and returns new value The function callback `fn` cannot use any Cypress commands `cy`.
+
 ### partial
 
 Sometimes you have the callback to apply, and you know the first argument(s), and just need to put the subject at the last position. This is where you can partially apply the known arguments to the given callback.
@@ -158,6 +160,20 @@ cy.wrap(arr).print((list) => list[2]) // JSON.stringify(arr[2])
 
 See [print.cy.js](./cypress/e2e/print.cy.js) for more examples
 
+### findOne
+
+Finds a single item in the subject. Assumes subject is an array or a jQuery object. Uses Lodash `_.find` method.
+
+```js
+// using predicate function
+const isThree = n => n === 3
+cy.wrap([...]).findOne(isThree).should('equal', 3)
+// using partial known properties of an object
+cy.wrap([...]).findOne({ name: 'Anna' }).should('have.property', 'name', 'Anna')
+```
+
+See [find-one.cy.js](./cypress/e2e/find-one.cy.js)
+
 ### primo
 
 ```js
diff --git a/commands/find-one.js b/commands/find-one.js
new file mode 100644
index 0000000..553d3d6
--- /dev/null
+++ b/commands/find-one.js
@@ -0,0 +1,13 @@
+/// <reference types="cypress" />
+
+Cypress.Commands.addQuery('findOne', (predicate) => {
+  const logOptions = { name: 'findOne' }
+  if (typeof predicate === 'function') {
+    logOptions.message = predicate.name
+  }
+  const log = Cypress.log(logOptions)
+
+  return (subject) => {
+    return Cypress._.find(subject, predicate)
+  }
+})
diff --git a/commands/index.d.ts b/commands/index.d.ts
index 745d09f..7388c51 100644
--- a/commands/index.d.ts
+++ b/commands/index.d.ts
@@ -170,5 +170,16 @@ declare namespace Cypress {
      * This is a COMMAND, not a query, so it won't retry, unlike the stock `cy.invoke`
      */
     invokeOnce(methodName: string, ...args: unknown[]): Chainable<any>
+
+    /**
+     * A query command that finds an item in the array or jQuery object.
+     * Uses Lodash _.find under the hood.
+     * @see https://github.com/bahmutov/cypress-map
+     * @example
+     *  cy.get('...').findOne({ innerText: '...' })
+     * @example
+     *  cy.wrap([1, 2, 3]).findOne(n => n === 3).should('equal', 3)
+     */
+    findOne(predicate: object | Function): Chainable<any>
   }
 }
diff --git a/commands/index.js b/commands/index.js
index cf30ccf..1be5727 100644
--- a/commands/index.js
+++ b/commands/index.js
@@ -15,3 +15,4 @@ require('./invoke-once')
 require('./update')
 require('./partial')
 require('./map-chain')
+require('./find-one')


Test patch:
diff --git a/cypress/e2e/find-one.cy.js b/cypress/e2e/find-one.cy.js
new file mode 100644
index 0000000..84fb0a2
--- /dev/null
+++ b/cypress/e2e/find-one.cy.js
@@ -0,0 +1,48 @@
+/// <reference types="cypress" />
+// @ts-check
+
+// import cypress-map plugin
+import '../../commands'
+
+describe('findOne', () => {
+  it('yields the element using callback fn', () => {
+    const values = []
+    setTimeout(() => {
+      values.push(1)
+      values.push(2)
+      values.push(3)
+    }, 1000)
+    cy.wrap(values)
+      .findOne((n) => n === 3)
+      .should('equal', 3)
+  })
+
+  it('shows the callback function name if any', () => {
+    const values = [1, 2]
+    setTimeout(() => {
+      values.push(3)
+    }, 1000)
+    cy.wrap(values)
+      .findOne(function is3(n) {
+        return n === 3
+      })
+      .should('equal', 3)
+  })
+
+  it('shows the callback arrow function name if any', () => {
+    const values = [1, 2]
+    const equals3 = (n) => n === 3
+    setTimeout(() => {
+      values.push(3)
+    }, 1000)
+    cy.wrap(values).findOne(equals3).should('equal', 3)
+  })
+
+  it('finds using property', () => {
+    const values = [{ name: 'Joe' }]
+    setTimeout(() => {
+      values.push({ name: 'Anna' })
+    }, 1000)
+    cy.wrap(values).findOne({ name: 'Anna' }).should('exist')
+  })
+})