Repository: bahmutov/cypress-map
Pull request #13 (2023-01-19 21:56:49)

feat: add cy.table command


Patch:
diff --git a/README.md b/README.md
index f68fc5f..b7c8621 100644
--- a/README.md
+++ b/README.md
@@ -152,6 +152,40 @@ cy.get('#items li').at(-1).its('innerText').should('equal', 'fifth')
 
 See [at.cy.js](./cypress/e2e/at.cy.js)
 
+### table
+
+Extracts all cells from the current subject table. Yields a 2D array of strings.
+
+```js
+cy.get('table').table()
+```
+
+You can slice the table to yield just a region `.table(x, y, w, h)`
+
+![Table](./images/table.png)
+
+For example, you can get 2 by 2 subregion
+
+```js
+cy.get('table')
+  .table(0, 2, 2, 2)
+  .should('deep.equal', [
+    ['Cary', '30'],
+    ['Joe', '28'],
+  ])
+```
+
+See the spec [table.cy.js](./cypress/e2e/table.cy.js) for more examples.
+
+**Tip:** to get just the headings row, combine `.table` and `.its` queries
+
+```js
+cy.get('table')
+  .table(0, 0, 3, 1)
+  .its(0)
+  .should('deep.equal', ['Name', 'Age', 'Date (YYYY-MM-DD)'])
+```
+
 ## cy.invoke vs cy.map vs cy.mapInvoke
 
 Here are a few examples to clarify the different between the `cy.invoke`, `cy.map`, and `cy.mapInvoke` query commands, see [diff.cy.js](./cypress/e2e/diff.cy.js)
diff --git a/commands/index.d.ts b/commands/index.d.ts
index 67718c7..e7d9c79 100644
--- a/commands/index.d.ts
+++ b/commands/index.d.ts
@@ -111,5 +111,14 @@ declare namespace Cypress {
      *  cy.wrap(arr).print('array length {0.length}')
      */
     print(format?: string | function): Chainable<any>
+
+    /**
+     * Collects all cells from the table subject into a 2D array of strings.
+     * You can slice the array into a smaller region, like a single row, column,
+     * or a 2D region.
+     * @example cy.get('table').table()
+     * @example cy.get('table').table(0, 0, 2, 2)
+     */
+    table(x?: number, y?: number, w?: number, h?: number): Chainable<string[][]>
   }
 }
diff --git a/commands/index.js b/commands/index.js
index bb4d6da..766345b 100644
--- a/commands/index.js
+++ b/commands/index.js
@@ -10,3 +10,4 @@ import './primo'
 import './prop'
 import './at'
 import './print'
+import './table'
diff --git a/commands/table.js b/commands/table.js
new file mode 100644
index 0000000..966dea0
--- /dev/null
+++ b/commands/table.js
@@ -0,0 +1,35 @@
+/// <reference types="cypress" />
+
+Cypress.Commands.addQuery('table', (x, y, w, h) => {
+  let message
+  if (typeof x === 'number' && typeof y === 'number') {
+    message = `x:${x},y:${y}`
+  }
+  if (typeof w === 'number') {
+    message += `,w:${w}`
+  }
+  if (typeof h === 'number') {
+    message += `,h:${h}`
+  }
+  const log = Cypress.log({ name: 'table', message })
+
+  return ($table) => {
+    const cells = Cypress._.map($table.find('tr'), (tr) => {
+      return Cypress._.map(tr.children, 'innerText')
+    })
+
+    if (typeof x === 'number' && typeof y === 'number') {
+      if (typeof w === 'undefined' && cells.length) {
+        w = cells[0].length
+      }
+      if (typeof h === 'undefined') {
+        h = cells.length
+      }
+      const slice = cells.slice(y, y + h).map((row) => {
+        return row.slice(x, x + w)
+      })
+      return slice
+    }
+    return cells
+  }
+})


Test patch:
diff --git a/cypress/e2e/table.cy.js b/cypress/e2e/table.cy.js
index f371b76..7381521 100644
--- a/cypress/e2e/table.cy.js
+++ b/cypress/e2e/table.cy.js
@@ -4,9 +4,12 @@
 // import cypress-map plugin
 import '../../commands'
 
-describe('table', { viewportWidth: 400, viewportHeight: 400 }, () => {
-  it('checks the column of cells', () => {
+describe('table', { viewportWidth: 300, viewportHeight: 200 }, () => {
+  beforeEach(() => {
     cy.visit('cypress/table.html')
+  })
+
+  it('checks the column of cells', () => {
     cy.contains('table#people thead td:nth-child(2)', 'Age')
     cy.get('table#people tbody td:nth-child(2)')
       .should('have.length', 4)
@@ -14,4 +17,56 @@ describe('table', { viewportWidth: 400, viewportHeight: 400 }, () => {
       .map(Number)
       .should('deep.equal', [20, 30, 28, 22])
   })
+
+  it('gets the entire table', () => {
+    cy.get('table')
+      .table()
+      .should('deep.equal', [
+        ['Name', 'Age', 'Date (YYYY-MM-DD)'],
+        ['Dave', '20', '2023-12-23'],
+        ['Cary', '30', '2024-01-24'],
+        ['Joe', '28', '2022-02-25'],
+        ['Anna', '22', '2027-03-26'],
+      ])
+  })
+
+  it('gets the headings', () => {
+    cy.get('table')
+      .table(0, 0, 3, 1)
+      .its(0)
+      .print()
+      .should('deep.equal', ['Name', 'Age', 'Date (YYYY-MM-DD)'])
+  })
+
+  it('gets the headings row', () => {
+    cy.get('table')
+      .table(0, 0)
+      .its(0)
+      .print()
+      .should('deep.equal', ['Name', 'Age', 'Date (YYYY-MM-DD)'])
+  })
+
+  it('gets the first column', () => {
+    cy.get('table')
+      .table(0, 0, 1, 5)
+      .print()
+      .should('deep.equal', [['Name'], ['Dave'], ['Cary'], ['Joe'], ['Anna']])
+  })
+
+  it('gets the entire first column', () => {
+    cy.get('table')
+      .table(0, 0, 1)
+      .print()
+      .should('deep.equal', [['Name'], ['Dave'], ['Cary'], ['Joe'], ['Anna']])
+  })
+
+  it('gets a region of the table', () => {
+    cy.get('table')
+      .table(0, 2, 2, 2)
+      .print()
+      .should('deep.equal', [
+        ['Cary', '30'],
+        ['Joe', '28'],
+      ])
+  })
 })