cypress-ag-grid 3.3.4 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # cypress-ag-grid
2
+
3
+ ## 3.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2a7e5d7: Move to monorepo structure to support more node-based testing tools for ag grid interactions and validations
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [2a7e5d7]
12
+ - @kpmck/ag-grid-core@1.0.0
@@ -0,0 +1,28 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <title>AG Grid Animation Wait - AG Owned</title>
7
+ <script>
8
+ window.AG_GRID_LIBRARY_VERSION = "35.0.0";
9
+ window.ANIMATION_WAIT_SCENARIO = "ag-owned";
10
+ </script>
11
+ <script src="https://unpkg.com/ag-grid-enterprise@35.0.0/dist/ag-grid-enterprise.min.noStyle.js"></script>
12
+ <link rel="stylesheet" href="../ag-grid.css">
13
+ <link rel="stylesheet" href="../ag-theme-alpine.css">
14
+ <style>
15
+ body {
16
+ font-family: Arial, sans-serif;
17
+ margin: 16px;
18
+ }
19
+ </style>
20
+ </head>
21
+
22
+ <body>
23
+ <h1>AG Grid Animation Wait - AG Owned</h1>
24
+ <div id="myGrid" style="height: 400px; width: 900px;" class="ag-theme-alpine"></div>
25
+ <script src="./animation-grid.js" type="text/javascript" charset="utf-8"></script>
26
+ </body>
27
+
28
+ </html>
@@ -0,0 +1,123 @@
1
+ const rowData = [
2
+ { make: "Porsche", model: "Boxster", price: 72000 },
3
+ { make: "Tesla", model: "Model 3", price: 48000 },
4
+ { make: "Toyota", model: "Celica", price: 35000 },
5
+ ];
6
+
7
+ const gridOptions = {
8
+ animateRows: true,
9
+ columnDefs: [
10
+ { field: "make" },
11
+ { field: "model" },
12
+ { field: "price" },
13
+ ],
14
+ defaultColDef: {
15
+ resizable: true,
16
+ },
17
+ rowData,
18
+ };
19
+
20
+ const animationProbe = {
21
+ agFinished: false,
22
+ agStarted: false,
23
+ thirdPartyInstalled: false,
24
+ };
25
+
26
+ let extraAnimations = [];
27
+
28
+ function getFirstAgRow() {
29
+ return document.querySelector("#myGrid .ag-center-cols-container .ag-row");
30
+ }
31
+
32
+ function createTrackedAnimation({ target, duration, onFinish, timingDuration = duration }) {
33
+ let resolveFinished;
34
+ const finished = new Promise((resolve) => {
35
+ resolveFinished = resolve;
36
+ });
37
+
38
+ setTimeout(() => {
39
+ if (typeof onFinish === "function") {
40
+ onFinish();
41
+ }
42
+ resolveFinished();
43
+ }, duration);
44
+
45
+ return {
46
+ effect: {
47
+ target,
48
+ getTiming: () => ({
49
+ duration: timingDuration,
50
+ iterations: 1,
51
+ }),
52
+ },
53
+ finished,
54
+ playState: "running",
55
+ };
56
+ }
57
+
58
+ function startAgOwnedAnimation() {
59
+ const row = getFirstAgRow();
60
+ if (!row) {
61
+ throw new Error("Expected an AG Grid row before starting the animation.");
62
+ }
63
+
64
+ animationProbe.agStarted = true;
65
+ animationProbe.agFinished = false;
66
+
67
+ extraAnimations.push(
68
+ createTrackedAnimation({
69
+ target: row,
70
+ duration: 350,
71
+ onFinish: () => {
72
+ animationProbe.agFinished = true;
73
+ },
74
+ })
75
+ );
76
+ }
77
+
78
+ function installThirdPartyAnimationTrap() {
79
+ const grid = document.querySelector("#myGrid");
80
+ const overlayScrollbarsApi = window.OverlayScrollbarsGlobal?.OverlayScrollbars;
81
+ if (typeof overlayScrollbarsApi !== "function") {
82
+ throw new Error("Expected OverlayScrollbarsGlobal.OverlayScrollbars to be available.");
83
+ }
84
+
85
+ overlayScrollbarsApi(grid, {});
86
+
87
+ const handle = grid.querySelector(".os-scrollbar-handle");
88
+ if (!handle) {
89
+ throw new Error("Expected OverlayScrollbars to render an .os-scrollbar-handle element.");
90
+ }
91
+
92
+ extraAnimations.push({
93
+ effect: {
94
+ target: handle,
95
+ getTiming: () => ({
96
+ duration: "auto",
97
+ iterations: 1,
98
+ }),
99
+ },
100
+ finished: new Promise(() => {}),
101
+ playState: "running",
102
+ });
103
+
104
+ animationProbe.thirdPartyInstalled = true;
105
+ }
106
+
107
+ window.__animationProbe = animationProbe;
108
+ window.startAnimationWaitScenario = () => {
109
+ startAgOwnedAnimation();
110
+
111
+ if (window.ANIMATION_WAIT_SCENARIO === "third-party-subtree") {
112
+ installThirdPartyAnimationTrap();
113
+ }
114
+ };
115
+
116
+ const gridElement = document.querySelector("#myGrid");
117
+ agGrid.createGrid(gridElement, gridOptions);
118
+
119
+ const originalGetAnimations = gridElement.getAnimations.bind(gridElement);
120
+ gridElement.getAnimations = (options) => [
121
+ ...originalGetAnimations(options),
122
+ ...extraAnimations,
123
+ ];
@@ -0,0 +1,41 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <title>AG Grid Animation Wait - Third Party Subtree</title>
7
+ <script>
8
+ window.AG_GRID_LIBRARY_VERSION = "35.0.0";
9
+ window.ANIMATION_WAIT_SCENARIO = "third-party-subtree";
10
+ </script>
11
+ <script src="https://unpkg.com/ag-grid-enterprise@35.0.0/dist/ag-grid-enterprise.min.noStyle.js"></script>
12
+ <link rel="stylesheet" href="https://unpkg.com/overlayscrollbars/styles/overlayscrollbars.min.css">
13
+ <link rel="stylesheet" href="../ag-grid.css">
14
+ <link rel="stylesheet" href="../ag-theme-alpine.css">
15
+ <style>
16
+ body {
17
+ font-family: Arial, sans-serif;
18
+ margin: 16px;
19
+ }
20
+
21
+ .os-scrollbar-handle {
22
+ background: rgba(31, 77, 143, 0.25);
23
+ border-radius: 999px;
24
+ height: 12px;
25
+ position: absolute;
26
+ right: 12px;
27
+ top: 12px;
28
+ width: 80px;
29
+ z-index: 10;
30
+ }
31
+ </style>
32
+ </head>
33
+
34
+ <body>
35
+ <h1>AG Grid Animation Wait - Third Party Subtree</h1>
36
+ <div id="myGrid" data-overlayscrollbars-initialize style="height: 400px; width: 900px;" class="ag-theme-alpine"></div>
37
+ <script src="https://unpkg.com/overlayscrollbars/browser/overlayscrollbars.browser.es6.min.js" type="text/javascript"></script>
38
+ <script src="./animation-grid.js" type="text/javascript" charset="utf-8"></script>
39
+ </body>
40
+
41
+ </html>
@@ -0,0 +1,46 @@
1
+ describe("agGridWaitForAnimation", () => {
2
+ it("waits for AG Grid-owned animations to finish", () => {
3
+ cy.visit("../app/animation-wait/ag-owned.html");
4
+ cy.get(".ag-cell", { timeout: 10000 }).should("be.visible");
5
+
6
+ cy.window().then((win) => {
7
+ win.startAnimationWaitScenario();
8
+ win.__animationProbe.waitStartedAt = Date.now();
9
+ });
10
+
11
+ cy.get("#myGrid")
12
+ .agGridWaitForAnimation()
13
+ .then(() => {
14
+ cy.window().then((win) => {
15
+ const elapsedMs = Date.now() - win.__animationProbe.waitStartedAt;
16
+ expect(win.__animationProbe.agStarted).to.equal(true);
17
+ expect(win.__animationProbe.agFinished).to.equal(true);
18
+ expect(elapsedMs).to.be.greaterThan(200);
19
+ expect(elapsedMs).to.be.lessThan(2000);
20
+ });
21
+ });
22
+ });
23
+
24
+ it("ignores third-party subtree animations whose finished promise never resolves", () => {
25
+ cy.visit("../app/animation-wait/third-party-subtree.html");
26
+ cy.get(".ag-cell", { timeout: 10000 }).should("be.visible");
27
+
28
+ cy.window().then((win) => {
29
+ win.startAnimationWaitScenario();
30
+ win.__animationProbe.waitStartedAt = Date.now();
31
+ });
32
+
33
+ cy.get("#myGrid .os-scrollbar-handle").should("exist");
34
+
35
+ cy.get("#myGrid")
36
+ .agGridWaitForAnimation()
37
+ .then(() => {
38
+ cy.window().then((win) => {
39
+ const elapsedMs = Date.now() - win.__animationProbe.waitStartedAt;
40
+ expect(win.__animationProbe.agFinished).to.equal(true);
41
+ expect(win.__animationProbe.thirdPartyInstalled).to.equal(true);
42
+ expect(elapsedMs).to.be.lessThan(2000);
43
+ });
44
+ });
45
+ });
46
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cypress-ag-grid",
3
- "version": "3.3.4",
3
+ "version": "3.4.0",
4
4
  "description": "Cypress plugin to interact with ag grid",
5
5
  "main": "src/index.js",
6
6
  "repository": {
@@ -18,14 +18,21 @@
18
18
  "cypress aggrid"
19
19
  ],
20
20
  "scripts": {
21
- "test": "npx cypress run --headless --spec \"cypress/e2e/*.cy.js\"",
21
+ "test": "npm run test:all",
22
+ "test:all": "npx cypress run --headless --spec \"cypress/e2e/*.cy.js\"",
22
23
  "test:v33": "npx cypress run --headless --spec \"cypress/e2e/*.v33.cy.js\"",
23
24
  "test:v34": "npx cypress run --headless --spec \"cypress/e2e/*.v34.cy.js\"",
24
25
  "test:v35": "npx cypress run --headless --spec \"cypress/e2e/*.v35.cy.js\"",
25
26
  "test:watch": "npx cypress open"
26
27
  },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
27
31
  "author": "Kerry McKeever <kerry@kerrymckeever.com>",
28
32
  "license": "MIT",
33
+ "dependencies": {
34
+ "@kpmck/ag-grid-core": "1.0.0"
35
+ },
29
36
  "devDependencies": {
30
37
  "cypress": "^15.12.0"
31
38
  }
@@ -1,72 +1,41 @@
1
1
  /// <reference types="cypress" />
2
- import { filterOperator } from "./filterOperator.enum";
3
- import { filterTab } from "./menuTab.enum";
4
- import { sort } from "./sort.enum";
5
-
6
- function isRowNotDestroyed(rowElement) {
7
- const rect = rowElement.getBoundingClientRect();
8
- const viewPortRect = rowElement.parentElement.getBoundingClientRect();
9
-
10
- return (
11
- rect.top >= viewPortRect.top &&
12
- rect.left >= viewPortRect.left &&
13
- rect.bottom <= viewPortRect.bottom &&
14
- rect.right <= viewPortRect.right
15
- );
16
- }
17
-
18
- export const agGridWaitForAnimation = async (agGridElement) => {
19
- if (agGridElement.get().length < 1) {
2
+ import {
3
+ extractAgGridData,
4
+ extractAgGridElements,
5
+ filterOperator,
6
+ filterTab,
7
+ sort,
8
+ waitForAgGridAnimation,
9
+ } from "@kpmck/ag-grid-core";
10
+
11
+ function getSingleAgGridRootElement(agGridElement) {
12
+ const rootElements = agGridElement.get();
13
+
14
+ if (rootElements.length < 1) {
20
15
  throw new Error(`Couldn't find the element ${agGridElement}`);
21
16
  }
22
17
 
23
- const animations = agGridElement.get()[0].getAnimations({ subtree: true });
24
-
25
- // Filter out infinite animations (e.g. loading spinners) whose .finished
26
- // promise never resolves per the Web Animations API spec.
27
- const finiteAnimations = animations.filter((animation) => {
28
- const iterations = animation.effect?.getTiming?.()?.iterations;
29
- return iterations !== Infinity;
30
- });
18
+ if (rootElements.length > 1) {
19
+ throw new Error(
20
+ `Selector "${agGridElement.selector}" returned more than 1 element.`
21
+ );
22
+ }
31
23
 
32
- await Promise.all(
33
- finiteAnimations.map(async (animation) => {
34
- try {
35
- await animation.finished;
36
- } catch (error) {
37
- if (error.name === "AbortError") return;
38
- console.error("error", error, error.name);
39
- throw error;
40
- }
41
- })
42
- );
24
+ return rootElements[0];
25
+ }
43
26
 
27
+ export const agGridWaitForAnimation = async (agGridElement) => {
28
+ await waitForAgGridAnimation(getSingleAgGridRootElement(agGridElement));
44
29
  return agGridElement;
45
30
  };
46
31
 
47
- /**
48
- * Uses the attribute value's index and sorts the data accordingly.
49
- * For our purposes, we are getting the attribute with the items' indices and sorting accordingly.
50
- *
51
- * @param {*} index
52
- * @returns
53
- */
54
- function sortElementsByAttributeValue(attribute) {
55
- return (a, b) => {
56
- const contentA = parseInt(a.attributes[attribute].nodeValue, 10).valueOf();
57
- const contentB = parseInt(b.attributes[attribute].nodeValue, 10).valueOf();
58
- return contentA < contentB ? -1 : contentA > contentB ? 1 : 0;
59
- };
60
- }
61
-
62
32
  /**
63
33
  * Retrieves the values from the *displayed* page in ag grid and assigns each value to its respective column name.
64
34
  * @param agGridElement The get() selector for which ag grid table you wish to retrieve.
65
35
  * @param options Provide an array of columns you wish to exclude from the table retrieval.
66
36
  */
67
37
  export const getAgGridData = async (agGridElement, options = {}) => {
68
- await agGridWaitForAnimation(agGridElement);
69
- return _getAgGrid(agGridElement, options, false);
38
+ return extractAgGridData(getSingleAgGridRootElement(agGridElement), options);
70
39
  };
71
40
 
72
41
  /**
@@ -75,127 +44,11 @@ export const getAgGridData = async (agGridElement, options = {}) => {
75
44
  * @param options Provide an array of columns you wish to exclude from the table retrieval.
76
45
  */
77
46
  export const getAgGridElements = async (agGridElement, options = {}) => {
78
- await agGridWaitForAnimation(agGridElement);
79
- return _getAgGrid(agGridElement, options, true);
80
- };
81
-
82
- function _getAgGrid(agGridElement, options = {}, returnElements) {
83
- const agGridColumnSelectors =
84
- ".ag-pinned-left-cols-container^.ag-center-cols-clipper^.ag-center-cols-viewport^.ag-pinned-right-cols-container";
85
- if (agGridElement.get().length > 1)
86
- throw new Error(
87
- `Selector "${agGridElement.selector}" returned more than 1 element.`
88
- );
89
-
90
- const tableElement = agGridElement.get()[0].querySelectorAll(".ag-root")[0];
91
- const agGridSelectors = agGridColumnSelectors.split("^");
92
- const headers = [
93
- ...tableElement.querySelectorAll(".ag-header-row-column [aria-colindex]"),
94
- ]
95
- .sort(sortElementsByAttributeValue("aria-colindex"))
96
- .map((headerElement) => {
97
- // Check if the elements returned are already .ag-header-cell-text elements
98
- // If not, query for that element and return the text content
99
- let headerCells = [
100
- ...headerElement.querySelectorAll(".ag-header-cell-text"),
101
- ];
102
- if (headerCells.length === 0) {
103
- return [headerElement].map((e) => e.textContent.trim());
104
- } else {
105
- return [...headerElement.querySelectorAll(".ag-header-cell-text")].map(
106
- (e) => e.textContent.trim()
107
- );
108
- }
109
- })
110
- .flat();
111
-
112
- let allRows = [];
113
- let rows = [];
114
-
115
- agGridSelectors.forEach((selector) => {
116
- const _rows = [
117
- ...tableElement.querySelectorAll(
118
- `${selector}:not(.ag-hidden) .ag-row:not(.ag-opacity-zero)`
119
- ),
120
- ]
121
- // When animation is enabled, ag-grid destroys rows in 2 phases,
122
- // first it runs an animation to place rows to be destroyed just outside
123
- // the viewport.
124
- // In the second phase those rows are removed from the DOM.
125
- // Because we get here AFTER all animations are finished, it is possible,
126
- // those rows are still in the DOM, but are not visible.
127
- // therefore those rows should be filtered out.
128
- .filter(isRowNotDestroyed)
129
- // Sort rows by their row-index attribute value
130
- .sort(sortElementsByAttributeValue("row-index"))
131
- .map((row) => {
132
- // Sort row cells by their aria-colindex attribute value
133
- // First check if elements returned already contain the aria-colindex
134
- // If not, just query for the .ag-cell
135
- let rowCells = [...row.querySelectorAll(".ag-cell[aria-colindex]")];
136
- if (rowCells.length === 0) {
137
- rowCells = [...row.querySelectorAll(".ag-cell")];
138
- }
139
- const rowIndex = parseInt(
140
- row.attributes["row-index"].nodeValue,
141
- 10
142
- ).valueOf();
143
-
144
- if (allRows[rowIndex]) {
145
- allRows[rowIndex] = [...allRows[rowIndex], ...rowCells];
146
- } else {
147
- allRows[rowIndex] = rowCells;
148
- }
149
- });
150
- });
151
- // Remove any empty arrays before merging
152
- allRows = allRows.filter(function (ele) {
153
- return ele.length;
154
- });
155
-
156
- // Remove duplicate entries from allRows
157
- // In some instances we see cell duplication for non-unique rows
158
- allRows = allRows.map((row) => {
159
- return row.filter((cell, index) => {
160
- return row.indexOf(cell) === index;
161
- });
162
- });
163
-
164
- if (!allRows.length) rows = [];
165
- else {
166
- rows = allRows
167
- .filter((rowCells) => rowCells.length)
168
- .map((rowCells) =>
169
- rowCells
170
- .sort(sortElementsByAttributeValue("aria-colindex"))
171
- .map((e) => {
172
- if (returnElements) {
173
- return e;
174
- } else {
175
- return e.textContent.trim();
176
- }
177
- })
178
- );
179
- }
180
- // if options.rawValues = true, return headers & rows values as arrays instead of mapping as objects
181
- if (options.valuesArray) {
182
- return { headers, rows };
183
- }
184
- // return structured object from headers and rows variables
185
- return rows.map((row) =>
186
- row.reduce((acc, curr, idx) => {
187
- if (
188
- //@ts-ignore
189
- (options.onlyColumns && !options.onlyColumns.includes(headers[idx])) ||
190
- headers[idx] === undefined
191
- ) {
192
- // dont include columns that are not present in onlyColumns, or if the header is undefined
193
- return { ...acc };
194
- }
195
- return { ...acc, [headers[idx]]: curr };
196
- }, {})
47
+ return extractAgGridElements(
48
+ getSingleAgGridRootElement(agGridElement),
49
+ options
197
50
  );
198
- }
51
+ };
199
52
 
200
53
  /**
201
54
  * Retrieve the ag grid column header element based on its column name value
@@ -1,14 +0,0 @@
1
- # Use the latest 2.1 version of CircleCI pipeline process engine.
2
- # See: https://circleci.com/docs/2.0/configuration-reference
3
- version: 2.1
4
-
5
- orbs:
6
- # "cypress-io/cypress@1" installs the latest published
7
- # version "1.x.y" of the orb. We recommend you then use
8
- # the strict explicit version "cypress-io/cypress@1.x.y"
9
- # to lock the version and prevent unexpected CI changes
10
- cypress: cypress-io/cypress@3
11
- workflows:
12
- run_cypress_tests:
13
- jobs:
14
- - cypress/run # "run" job comes from "cypress" orb