@linzjs/step-ag-grid 24.0.0 → 25.0.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/dist/GridTheme.scss +1 -1
- package/dist/index.css +0 -13
- package/dist/src/index.d.ts +2 -0
- package/{src/utils → dist/src/utils/__tests__}/testQuick.ts +1 -1
- package/{src/utils → dist/src/utils/__tests__}/testUtil.ts +1 -1
- package/dist/src/utils/__tests__/vitestUtil.ts +295 -0
- package/dist/{step-ag-grid.cjs.js → step-ag-grid.cjs} +4 -4
- package/dist/step-ag-grid.cjs.map +1 -0
- package/dist/step-ag-grid.esm.js +4 -6
- package/dist/step-ag-grid.esm.js.map +1 -1
- package/dist/vite.config.d.ts +1 -1
- package/package.json +23 -27
- package/src/components/GridCell.test.tsx +2 -0
- package/src/contexts/GridContextProvider.test.tsx +1 -0
- package/src/contexts/GridContextProvider.tsx +1 -2
- package/src/index.ts +2 -0
- package/src/lui/ActionButton.scss +2 -2
- package/src/lui/FormError.scss +3 -4
- package/src/lui/TextInputFormatted.scss +1 -1
- package/src/lui/reactUtils.test.tsx +1 -0
- package/src/lui/timeoutHook.test.tsx +7 -6
- package/src/stories/grid/GridDragRow.stories.tsx +1 -1
- package/src/stories/grid/GridFilterButtons.stories.tsx +1 -1
- package/src/stories/grid/GridNonEditableRow.stories.tsx +1 -1
- package/src/stories/grid/GridPinnedRow.stories.tsx +1 -1
- package/src/stories/grid/GridPopoutContextMenu.stories.tsx +1 -1
- package/src/stories/grid/GridPopoutEditBoolean.stories.tsx +1 -1
- package/src/stories/grid/GridPopoutEditGeneric.stories.tsx +1 -1
- package/src/stories/grid/GridPopoutEditGenericTextArea.stories.tsx +1 -1
- package/src/stories/grid/GridPopoverEditBearing.stories.tsx +1 -1
- package/src/stories/grid/GridPopoverEditDropDown.stories.tsx +1 -1
- package/src/stories/grid/GridPopoverEditMultiSelect.stories.tsx +1 -1
- package/src/stories/grid/GridPopoverEditMultiSelectGrid.stories.tsx +1 -1
- package/src/stories/grid/GridReadOnly.stories.tsx +1 -1
- package/src/stories/grid/interactions/GridKeyboardInteractions.stories.tsx +1 -1
- package/src/styles/Grid.scss +9 -9
- package/src/styles/GridIcon.scss +6 -6
- package/src/styles/GridPopoverMenu.scss +3 -3
- package/src/styles/GridTheme.scss +1 -1
- package/src/styles/react-menu-customisations.scss +26 -41
- package/src/utils/__tests__/storybookTestUtil.ts +4 -0
- package/src/utils/__tests__/testQuick.ts +157 -0
- package/src/utils/__tests__/testUtil.ts +294 -0
- package/src/utils/__tests__/vitestUtil.ts +295 -0
- package/src/utils/bearing.test.ts +3 -1
- package/src/utils/textMatcher.test.ts +2 -1
- package/src/utils/textValidator.test.ts +5 -6
- package/src/utils/util.test.ts +2 -1
- package/dist/src/utils/storybookTestUtil.d.ts +0 -3
- package/dist/src/utils/testQuick.d.ts +0 -62
- package/dist/src/utils/testUtil.d.ts +0 -46
- package/dist/step-ag-grid.cjs.js.map +0 -1
- /package/{src/utils → dist/src/utils/__tests__}/storybookTestUtil.ts +0 -0
|
@@ -1,25 +1,22 @@
|
|
|
1
|
-
@use "../../node_modules/@linzjs/lui/dist/scss/
|
|
2
|
-
@use "../../node_modules/@linzjs/lui/dist/scss/Foundation/Variables/FontVars" as fonts;
|
|
3
|
-
@use "../../node_modules/@linzjs/lui/dist/scss/Foundation/Variables/SpacingVars" as spacing;
|
|
4
|
-
@use "../../node_modules/@linzjs/lui/dist/scss/Foundation/Utilities" as *;
|
|
1
|
+
@use "../../node_modules/@linzjs/lui/dist/scss/Core" as lui;
|
|
5
2
|
|
|
6
3
|
/**
|
|
7
4
|
* Here is where we can override the styles of react-menu
|
|
8
5
|
*/
|
|
9
6
|
.step-ag-grid-react-menu.szh-menu {
|
|
10
7
|
z-index: 900;
|
|
11
|
-
color:
|
|
12
|
-
@include
|
|
8
|
+
color: lui.$base-type-color;
|
|
9
|
+
@include lui.font-regular();
|
|
13
10
|
|
|
14
11
|
div,
|
|
15
12
|
a[role="menuitem"] {
|
|
16
13
|
text-decoration: none;
|
|
17
|
-
color:
|
|
18
|
-
@include
|
|
14
|
+
color: lui.$base-type-color;
|
|
15
|
+
@include lui.font-regular();
|
|
19
16
|
}
|
|
20
17
|
|
|
21
18
|
li[role="menuitem"]:hover {
|
|
22
|
-
background-color:
|
|
19
|
+
background-color: lui.$polar;
|
|
23
20
|
}
|
|
24
21
|
|
|
25
22
|
.szh-menu__header {
|
|
@@ -31,85 +28,73 @@
|
|
|
31
28
|
/**
|
|
32
29
|
* Here are styles for lui select menu
|
|
33
30
|
*/
|
|
34
|
-
.lui-select-menu.szh-menu-container {
|
|
35
|
-
@include breakpoint(sm) {
|
|
36
|
-
position: absolute;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
31
|
.lui-select-menu.szh-menu {
|
|
41
32
|
z-index: 900;
|
|
42
33
|
width: 92vw;
|
|
43
34
|
overflow-y: scroll;
|
|
44
35
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
height: auto;
|
|
48
|
-
width: 240px;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
color: colors.$base-type-color;
|
|
52
|
-
@include fonts.font-light();
|
|
36
|
+
color: lui.$base-type-color;
|
|
37
|
+
@include lui.font-light();
|
|
53
38
|
}
|
|
54
39
|
|
|
55
40
|
.lui-select-menuItem {
|
|
56
|
-
padding:
|
|
41
|
+
padding: lui.$unit-xs lui.$unit-sm lui.$unit-xs lui.$unit-xs;
|
|
57
42
|
display: flex;
|
|
58
43
|
line-height: 1;
|
|
59
44
|
transition: all 0.2s ease-in-out;
|
|
60
|
-
fill:
|
|
45
|
+
fill: lui.$fuscous;
|
|
61
46
|
|
|
62
47
|
&:hover {
|
|
63
|
-
background-color:
|
|
48
|
+
background-color: lui.$polar;
|
|
64
49
|
}
|
|
65
50
|
}
|
|
66
51
|
|
|
67
52
|
.lui-select-menuItem--error {
|
|
68
53
|
p {
|
|
69
|
-
color:
|
|
54
|
+
color: lui.$error;
|
|
70
55
|
}
|
|
71
56
|
|
|
72
57
|
.LuiIcon {
|
|
73
|
-
fill:
|
|
58
|
+
fill: lui.$error;
|
|
74
59
|
}
|
|
75
60
|
}
|
|
76
61
|
|
|
77
62
|
.lui-select-menuItemText {
|
|
78
|
-
@include
|
|
79
|
-
color:
|
|
63
|
+
@include lui.font-regular();
|
|
64
|
+
color: lui.$charcoal;
|
|
80
65
|
margin: 0;
|
|
81
|
-
padding-left:
|
|
66
|
+
padding-left: lui.$unit-lg;
|
|
82
67
|
|
|
83
68
|
// if no icon, give it 4px to align with the group header
|
|
84
69
|
&--noPadding {
|
|
85
|
-
padding-left:
|
|
70
|
+
padding-left: lui.$unit-xxs;
|
|
86
71
|
}
|
|
87
72
|
}
|
|
88
73
|
|
|
89
74
|
.lui-select-icon + .lui-select-menuItemText {
|
|
90
|
-
padding-left:
|
|
75
|
+
padding-left: lui.$unit-xs;
|
|
91
76
|
}
|
|
92
77
|
|
|
93
78
|
.lui-select-menu-group {
|
|
94
79
|
height: 32px;
|
|
95
|
-
color:
|
|
80
|
+
color: lui.$fuscous;
|
|
96
81
|
padding-left: 0.75rem;
|
|
97
82
|
text-transform: none;
|
|
98
|
-
@include
|
|
83
|
+
@include lui.font-semibold();
|
|
99
84
|
}
|
|
100
85
|
|
|
101
86
|
.lui-select-submenu {
|
|
102
87
|
flex: 1;
|
|
103
88
|
|
|
104
89
|
div[role="menuitem"] {
|
|
105
|
-
padding:
|
|
90
|
+
padding: lui.$unit-xs lui.$unit-xs lui.$unit-xs 2.5rem;
|
|
106
91
|
text-decoration: none;
|
|
107
|
-
@include
|
|
92
|
+
@include lui.font-regular();
|
|
108
93
|
height: 40px;
|
|
109
94
|
}
|
|
110
95
|
// overwrite the hover color
|
|
111
96
|
.szh-menu__item--hover {
|
|
112
|
-
background-color:
|
|
97
|
+
background-color: lui.$polar;
|
|
113
98
|
}
|
|
114
99
|
}
|
|
115
100
|
|
|
@@ -119,7 +104,7 @@
|
|
|
119
104
|
div[role="menuitem"] {
|
|
120
105
|
padding-left: 0;
|
|
121
106
|
text-decoration: none;
|
|
122
|
-
@include
|
|
107
|
+
@include lui.font-regular();
|
|
123
108
|
height: 40px;
|
|
124
109
|
}
|
|
125
110
|
}
|
|
@@ -130,6 +115,6 @@
|
|
|
130
115
|
|
|
131
116
|
|
|
132
117
|
.step-ag-grid-react-menu-sub-header-icon {
|
|
133
|
-
margin:
|
|
134
|
-
fill:
|
|
118
|
+
margin: lui.$unit-xs;
|
|
119
|
+
fill: lui.$fuscous;
|
|
135
120
|
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* General query all by selected. Internal use, use quick operations instead.
|
|
3
|
+
*
|
|
4
|
+
* @param selector Selector to use
|
|
5
|
+
* @param container Optional container
|
|
6
|
+
* @return HTMLElement array
|
|
7
|
+
*/
|
|
8
|
+
import { IconName } from '@linzjs/lui/dist/components/LuiIcon/LuiIcon';
|
|
9
|
+
|
|
10
|
+
import { wait } from '../util';
|
|
11
|
+
|
|
12
|
+
const queryAllBySelector = <T extends HTMLElement>(selector: string, container: HTMLElement = document.body): T[] =>
|
|
13
|
+
Array.from(container.querySelectorAll<T>(selector));
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Filters for query quick operations.
|
|
17
|
+
*/
|
|
18
|
+
export interface IQueryQuick {
|
|
19
|
+
testId?: string;
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
21
|
+
tagName?: 'button' | 'span' | 'div' | 'input' | 'textarea' | any;
|
|
22
|
+
text?: string | RegExp;
|
|
23
|
+
icon?: IconName;
|
|
24
|
+
ariaLabel?: string;
|
|
25
|
+
role?: string;
|
|
26
|
+
classes?: string;
|
|
27
|
+
child?: IQueryQuick;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const escapeSelectorParam = (param: string): string => param.replace(/["\\]/g, '\\$&');
|
|
31
|
+
|
|
32
|
+
export const getMatcher = (matcherText: string | RegExp) => {
|
|
33
|
+
const textMatcher =
|
|
34
|
+
typeof matcherText === 'string'
|
|
35
|
+
? (text?: string) => text != null && text.toLowerCase() === matcherText.toLowerCase()
|
|
36
|
+
: (text?: string) => text != null && matcherText.test(text);
|
|
37
|
+
return (e: HTMLElement) => textMatcher(e.innerHTML?.trim()) || textMatcher(e.innerText?.trim());
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build selector for quick operations.
|
|
42
|
+
*
|
|
43
|
+
* @param props Filters to convert to selector.
|
|
44
|
+
* @param container Optional container to search in.
|
|
45
|
+
*/
|
|
46
|
+
const quickSelector = <T extends HTMLElement>(
|
|
47
|
+
props: IQueryQuick,
|
|
48
|
+
container?: HTMLElement,
|
|
49
|
+
): { selector: string; els: T[] } => {
|
|
50
|
+
let selector = '';
|
|
51
|
+
let lastIQueryQuick = props;
|
|
52
|
+
for (let loop: IQueryQuick | undefined = props; loop; loop = loop.child) {
|
|
53
|
+
lastIQueryQuick = loop;
|
|
54
|
+
loop.tagName && (selector += loop.tagName);
|
|
55
|
+
loop.ariaLabel && (selector += `[aria-label='${escapeSelectorParam(loop.ariaLabel)}']`);
|
|
56
|
+
loop.role && (selector += `[role="${escapeSelectorParam(loop.role)}"]`);
|
|
57
|
+
loop.testId && (selector += `[data-testid="${escapeSelectorParam(loop.testId)}"]`);
|
|
58
|
+
loop.icon && (selector += `[data-icon='${loop.icon}']`);
|
|
59
|
+
loop.classes && (selector += loop.classes);
|
|
60
|
+
selector += ' ';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (selector.trim() == '') {
|
|
64
|
+
throw 'get/query/findQuick needs at least one defined parameter';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let els = queryAllBySelector<T>(selector, container);
|
|
68
|
+
|
|
69
|
+
if (lastIQueryQuick.text != null) {
|
|
70
|
+
const matcher = getMatcher(lastIQueryQuick.text);
|
|
71
|
+
els = els.filter(matcher);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
selector: [
|
|
76
|
+
selector,
|
|
77
|
+
typeof lastIQueryQuick.text === 'string' ? JSON.stringify(lastIQueryQuick.text) : lastIQueryQuick.text,
|
|
78
|
+
]
|
|
79
|
+
.filter((r) => r)
|
|
80
|
+
.join(' '),
|
|
81
|
+
els,
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Query by filter.
|
|
87
|
+
*
|
|
88
|
+
* @param filter Filter
|
|
89
|
+
* @param container Optional container to look in
|
|
90
|
+
* @return HTMLElement if found else null
|
|
91
|
+
*/
|
|
92
|
+
export const queryQuick = <T extends HTMLElement>(filter: IQueryQuick, container?: HTMLElement): T | null => {
|
|
93
|
+
const { els, selector } = quickSelector<T>(filter, container);
|
|
94
|
+
if (els.length > 1) {
|
|
95
|
+
throw `Found multiple(${els.length}) elements by selector ${selector}\n${els
|
|
96
|
+
.map((el, index) => `${index}: ${el.parentElement?.innerHTML}\n\n`)
|
|
97
|
+
.join()}`;
|
|
98
|
+
}
|
|
99
|
+
return els[0] ?? null;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Query all by filter.
|
|
104
|
+
*
|
|
105
|
+
* @param filter Filter
|
|
106
|
+
* @param container Optional container to look in
|
|
107
|
+
* @return HTMLElement array
|
|
108
|
+
*/
|
|
109
|
+
export const queryAllQuick = <T extends HTMLElement>(filter: IQueryQuick, container?: HTMLElement): T[] =>
|
|
110
|
+
quickSelector<T>(filter, container)?.els;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get all by filter.
|
|
114
|
+
*
|
|
115
|
+
* @param filter Filter
|
|
116
|
+
* @param container Optional container to look in
|
|
117
|
+
* @return HTMLElement array. Throws exception if nothing is found.
|
|
118
|
+
*/
|
|
119
|
+
export const getAllQuick = <T extends HTMLElement>(filter: IQueryQuick, container?: HTMLElement): T[] => {
|
|
120
|
+
const els = queryAllQuick(filter, container);
|
|
121
|
+
if (els.length == 0) {
|
|
122
|
+
throw Error(`getAllQuick not found, selector: ${quickSelector(filter).selector}`);
|
|
123
|
+
}
|
|
124
|
+
return els as T[];
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get by filter.
|
|
129
|
+
*
|
|
130
|
+
* @param filter Filter
|
|
131
|
+
* @param container Optional container to look in
|
|
132
|
+
* @return HTMLElement. Throws exception if not found.
|
|
133
|
+
*/
|
|
134
|
+
export const getQuick = (filter: IQueryQuick, container?: HTMLElement): HTMLElement => {
|
|
135
|
+
const el = queryQuick(filter, container);
|
|
136
|
+
if (el == null) {
|
|
137
|
+
throw Error(`getQuick not found, selector: ${quickSelector(filter).selector}`);
|
|
138
|
+
}
|
|
139
|
+
return el;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Find by filter. Waits up to 5 seconds to find filter.
|
|
144
|
+
*
|
|
145
|
+
* @param filter Filter
|
|
146
|
+
* @param container Optional container to look in
|
|
147
|
+
* @return HTMLElement. Throws exception if not found.
|
|
148
|
+
*/
|
|
149
|
+
export const findQuick = async <T extends HTMLElement>(filter: IQueryQuick, container?: HTMLElement): Promise<T> => {
|
|
150
|
+
const endTime = Date.now() + 5000;
|
|
151
|
+
while (Date.now() < endTime) {
|
|
152
|
+
const el = queryQuick<T>(filter, container);
|
|
153
|
+
if (el) return el;
|
|
154
|
+
await wait(50);
|
|
155
|
+
}
|
|
156
|
+
throw Error(`findQuick not found, selector: ${quickSelector(filter).selector}`);
|
|
157
|
+
};
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { act, waitFor, within } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { isEqual } from 'lodash-es';
|
|
4
|
+
|
|
5
|
+
import { findQuick, getAllQuick, getMatcher, getQuick, IQueryQuick, queryQuick } from './testQuick';
|
|
6
|
+
|
|
7
|
+
let user = userEvent;
|
|
8
|
+
/**
|
|
9
|
+
* allow external userEvent to be used
|
|
10
|
+
* @param customisedUserEvent
|
|
11
|
+
*/
|
|
12
|
+
export const setUpUserEvent = (customisedUserEvent: any) => {
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
14
|
+
user = customisedUserEvent;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const countRows = (within?: HTMLElement): number => {
|
|
18
|
+
return getAllQuick({ tagName: `div[row-id]:not(:empty)` }, within).length;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const findRowByIndex = async (rowIndex: number | string, within?: HTMLElement): Promise<HTMLDivElement> => {
|
|
22
|
+
await waitFor(() => {
|
|
23
|
+
expect(getAllQuick({ classes: '.ag-row' }).length > 0).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
//if this is not wrapped in an act console errors are logged during testing
|
|
26
|
+
let row!: HTMLDivElement;
|
|
27
|
+
await act(async () => {
|
|
28
|
+
row = await findQuick<HTMLDivElement>(
|
|
29
|
+
{ tagName: `.ag-center-cols-container div[row-index='${rowIndex}']:not(:empty)` },
|
|
30
|
+
within,
|
|
31
|
+
);
|
|
32
|
+
let combineChildren = [...Array.from(row.children)];
|
|
33
|
+
|
|
34
|
+
const leftCols = queryQuick<HTMLDivElement>(
|
|
35
|
+
{ tagName: `.ag-pinned-left-cols-container div[row-index='${rowIndex}']` },
|
|
36
|
+
within,
|
|
37
|
+
);
|
|
38
|
+
if (leftCols) {
|
|
39
|
+
combineChildren = [...Array.from(leftCols.children), ...combineChildren];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const rightCols = queryQuick<HTMLDivElement>(
|
|
43
|
+
{ tagName: `.ag-pinned-right-cols-container div[row-index='${rowIndex}']` },
|
|
44
|
+
within,
|
|
45
|
+
);
|
|
46
|
+
if (rightCols) {
|
|
47
|
+
combineChildren = [...Array.from(rightCols.children), ...combineChildren];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
row.replaceChildren(...combineChildren);
|
|
51
|
+
});
|
|
52
|
+
return row;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const findRow = async (rowId: number | string, within?: HTMLElement): Promise<HTMLDivElement> => {
|
|
56
|
+
await waitFor(() => {
|
|
57
|
+
expect(getAllQuick({ classes: '.ag-row' }).length > 0).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
//if this is not wrapped in an act console errors are logged during testing
|
|
60
|
+
let row!: HTMLDivElement;
|
|
61
|
+
await act(async () => {
|
|
62
|
+
row = await findQuick<HTMLDivElement>(
|
|
63
|
+
{ tagName: `.ag-center-cols-container div[row-id='${rowId}']:not(:empty)` },
|
|
64
|
+
within,
|
|
65
|
+
);
|
|
66
|
+
let combineChildren = [...Array.from(row.children)];
|
|
67
|
+
|
|
68
|
+
const leftCols = queryQuick<HTMLDivElement>(
|
|
69
|
+
{ tagName: `.ag-pinned-left-cols-container div[row-id='${rowId}']` },
|
|
70
|
+
within,
|
|
71
|
+
);
|
|
72
|
+
if (leftCols) {
|
|
73
|
+
combineChildren = [...Array.from(leftCols.children), ...combineChildren];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const rightCols = queryQuick<HTMLDivElement>(
|
|
77
|
+
{ tagName: `.ag-pinned-right-cols-container div[row-id='${rowId}']` },
|
|
78
|
+
within,
|
|
79
|
+
);
|
|
80
|
+
if (rightCols) {
|
|
81
|
+
combineChildren = [...Array.from(rightCols.children), ...combineChildren];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
row.replaceChildren(...combineChildren);
|
|
85
|
+
});
|
|
86
|
+
return row;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const queryRow = (rowId: number | string, within?: HTMLElement): HTMLDivElement | null => {
|
|
90
|
+
return queryQuick<HTMLDivElement>({ tagName: `div[row-id='${rowId}']:not(:empty)` }, within);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const _selectRow = async (
|
|
94
|
+
select: 'select' | 'deselect' | 'toggle',
|
|
95
|
+
rowId: string | number,
|
|
96
|
+
within?: HTMLElement,
|
|
97
|
+
): Promise<void> => {
|
|
98
|
+
const row = await findRow(rowId, within);
|
|
99
|
+
const isSelected = row.className.includes('ag-row-selected');
|
|
100
|
+
if (select === 'toggle' || (select === 'select' && !isSelected) || (select === 'deselect' && isSelected)) {
|
|
101
|
+
const cell = await findCell(rowId, 'selection', within);
|
|
102
|
+
await user.click(cell);
|
|
103
|
+
await waitFor(async () => {
|
|
104
|
+
const row = await findRow(rowId, within);
|
|
105
|
+
const nowSelected = row.className.includes('ag-row-selected');
|
|
106
|
+
if (nowSelected === isSelected) throw `Row ${rowId} won't select`;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const selectRow = async (rowId: string | number, within?: HTMLElement): Promise<void> =>
|
|
112
|
+
_selectRow('select', rowId, within);
|
|
113
|
+
|
|
114
|
+
export const deselectRow = async (rowId: string | number, within?: HTMLElement): Promise<void> =>
|
|
115
|
+
_selectRow('deselect', rowId, within);
|
|
116
|
+
|
|
117
|
+
export const findCell = async (rowId: number | string, colId: string, within?: HTMLElement): Promise<HTMLElement> => {
|
|
118
|
+
const row = await findRow(rowId, within);
|
|
119
|
+
return await findQuick({ tagName: `[col-id='${colId}']` }, row);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const findCellContains = async (
|
|
123
|
+
rowId: number | string,
|
|
124
|
+
colId: string,
|
|
125
|
+
text: string | RegExp,
|
|
126
|
+
within?: HTMLElement,
|
|
127
|
+
) => {
|
|
128
|
+
return await waitFor(
|
|
129
|
+
async () => {
|
|
130
|
+
const row = await findRow(rowId, within);
|
|
131
|
+
return getQuick({ tagName: `[col-id='${colId}']`, text }, row);
|
|
132
|
+
},
|
|
133
|
+
{ timeout: 10000 },
|
|
134
|
+
);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export const selectCell = async (rowId: string | number, colId: string, within?: HTMLElement): Promise<void> => {
|
|
138
|
+
const cell = await findCell(rowId, colId, within);
|
|
139
|
+
await user.click(cell);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export const editCell = async (rowId: number | string, colId: string, within?: HTMLElement): Promise<void> => {
|
|
143
|
+
await waitFor(
|
|
144
|
+
async () => {
|
|
145
|
+
const cell = await findCell(rowId, colId, within);
|
|
146
|
+
await user.dblClick(cell);
|
|
147
|
+
await waitFor(findOpenPopover, { timeout: 1000 });
|
|
148
|
+
},
|
|
149
|
+
{ timeout: 10000 },
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const isCellReadOnly = async (rowId: number | string, colId: string, within?: HTMLElement): Promise<boolean> => {
|
|
154
|
+
const cell = await findCell(rowId, colId, within);
|
|
155
|
+
return cell.className.includes('GridCell-readonly');
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export const findOpenPopover = () => findQuick({ classes: '.szh-menu--state-open' });
|
|
159
|
+
|
|
160
|
+
export const queryMenuOption = async (menuOptionText: string | RegExp): Promise<HTMLElement | null> => {
|
|
161
|
+
const openMenu = await findOpenPopover();
|
|
162
|
+
const els = await within(openMenu).findAllByRole('menuitem');
|
|
163
|
+
const matcher = getMatcher(menuOptionText);
|
|
164
|
+
const result = els.find(matcher);
|
|
165
|
+
return result ?? null;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const findMenuOption = async (menuOptionText: string | RegExp): Promise<HTMLElement> => {
|
|
169
|
+
return await waitFor(
|
|
170
|
+
async () => {
|
|
171
|
+
const menuOption = await queryMenuOption(menuOptionText);
|
|
172
|
+
if (menuOption == null) {
|
|
173
|
+
throw Error(`Unable to find menu option ${menuOptionText}`);
|
|
174
|
+
}
|
|
175
|
+
return menuOption;
|
|
176
|
+
},
|
|
177
|
+
{ timeout: 5000 },
|
|
178
|
+
);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export const validateMenuOptions = async (
|
|
182
|
+
rowId: number | string,
|
|
183
|
+
colId: string,
|
|
184
|
+
expectedMenuOptions: Array<string>,
|
|
185
|
+
): Promise<boolean> => {
|
|
186
|
+
await editCell(rowId, colId);
|
|
187
|
+
const openMenu = await findOpenPopover();
|
|
188
|
+
const actualOptions = (await within(openMenu).findAllByRole('menuitem')).map((menuItem) => menuItem.textContent);
|
|
189
|
+
return isEqual(actualOptions, expectedMenuOptions);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export const clickMenuOption = async (menuOptionText: string | RegExp): Promise<void> => {
|
|
193
|
+
const menuOption = await findMenuOption(menuOptionText);
|
|
194
|
+
await user.click(menuOption);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export const openAndClickMenuOption = async (
|
|
198
|
+
rowId: number | string,
|
|
199
|
+
colId: string,
|
|
200
|
+
menuOptionText: string | RegExp,
|
|
201
|
+
within?: HTMLElement,
|
|
202
|
+
): Promise<void> => {
|
|
203
|
+
await editCell(rowId, colId, within);
|
|
204
|
+
await clickMenuOption(menuOptionText);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export const openAndFindMenuOption = async (
|
|
208
|
+
rowId: number | string,
|
|
209
|
+
colId: string,
|
|
210
|
+
menuOptionText: string | RegExp,
|
|
211
|
+
within?: HTMLElement,
|
|
212
|
+
): Promise<HTMLElement> => {
|
|
213
|
+
await editCell(rowId, colId, within);
|
|
214
|
+
return await findMenuOption(menuOptionText);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
export const getMultiSelectOptions = async () => {
|
|
218
|
+
const openMenu = await findOpenPopover();
|
|
219
|
+
return getAllQuick<HTMLInputElement>({ role: 'menuitem', child: { tagName: 'input,textarea' } }, openMenu).map(
|
|
220
|
+
(input) => {
|
|
221
|
+
return {
|
|
222
|
+
v: input.value,
|
|
223
|
+
c: input.checked ?? true,
|
|
224
|
+
};
|
|
225
|
+
},
|
|
226
|
+
);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export const findMultiSelectOption = async (value: string): Promise<HTMLElement> => {
|
|
230
|
+
const openMenu = await findOpenPopover();
|
|
231
|
+
return getQuick({ role: 'menuitem', child: { tagName: `input[value='${value}']` } }, openMenu);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export const clickMultiSelectOption = async (value: string): Promise<void> => {
|
|
235
|
+
const menuItem = await findMultiSelectOption(value);
|
|
236
|
+
menuItem.parentElement && (await user.click(menuItem.parentElement));
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const typeInput = async (value: string, filter: IQueryQuick): Promise<void> => {
|
|
240
|
+
const openMenu = await findOpenPopover();
|
|
241
|
+
const input = await findQuick(filter, openMenu);
|
|
242
|
+
await user.clear(input);
|
|
243
|
+
//'typing' an empty string will cause a console error, and it's also unnecessary after the previous clear call
|
|
244
|
+
if (value.length > 0) {
|
|
245
|
+
await user.type(input, value);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export const typeOnlyInput = async (value: string): Promise<void> =>
|
|
250
|
+
typeInput(value, { child: { tagName: "input[type='text'], textarea" } });
|
|
251
|
+
|
|
252
|
+
export const typeInputByLabel = async (value: string, labelText: string): Promise<void> => {
|
|
253
|
+
const labels = getAllQuick({ child: { tagName: 'label' } }).filter((l) => l.textContent === labelText);
|
|
254
|
+
if (labels.length === 0) {
|
|
255
|
+
throw Error(`Label not found for text: ${labelText}`);
|
|
256
|
+
}
|
|
257
|
+
if (labels.length > 1) {
|
|
258
|
+
throw Error(`Multiple labels found for text: ${labelText}`);
|
|
259
|
+
}
|
|
260
|
+
const inputId = labels[0].getAttribute('for');
|
|
261
|
+
await typeInput(value, { child: { tagName: `input[id='${inputId}'], textarea[id='${inputId}']` } });
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
export const typeInputByPlaceholder = async (value: string, placeholder: string): Promise<void> =>
|
|
265
|
+
typeInput(value, {
|
|
266
|
+
child: { tagName: `input[placeholder='${placeholder}'], textarea[placeholder='${placeholder}']` },
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
export const typeOtherInput = async (value: string): Promise<void> =>
|
|
270
|
+
typeInput(value, { classes: '.subComponent', child: { tagName: "input[type='text']" } });
|
|
271
|
+
|
|
272
|
+
export const typeOtherTextArea = async (value: string): Promise<void> =>
|
|
273
|
+
typeInput(value, { classes: '.subComponent', child: { tagName: 'textarea' } });
|
|
274
|
+
|
|
275
|
+
export const closeMenu = () => user.click(document.body);
|
|
276
|
+
export const closePopover = () => user.click(document.body);
|
|
277
|
+
|
|
278
|
+
export const findActionButton = (text: string, container?: HTMLElement): Promise<HTMLElement> =>
|
|
279
|
+
findQuick({ tagName: 'button', child: { classes: '.ActionButton-minimalAreaDisplay', text: text } }, container);
|
|
280
|
+
|
|
281
|
+
export const clickActionButton = async (text: string, container?: HTMLElement): Promise<void> => {
|
|
282
|
+
const button = await findActionButton(text, container);
|
|
283
|
+
await user.click(button);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
export const waitForGridReady = async (props?: { grid?: HTMLElement; timeout?: number }) =>
|
|
287
|
+
waitFor(() => expect(getAllQuick({ classes: '.Grid-ready' }, props?.grid)).toBeDefined(), {
|
|
288
|
+
timeout: props?.timeout ?? 5000,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
export const waitForGridRows = async (props?: { grid?: HTMLElement; timeout?: number }) =>
|
|
292
|
+
waitFor(() => expect(getAllQuick({ classes: '.ag-row' }, props?.grid).length > 0).toBe(true), {
|
|
293
|
+
timeout: props?.timeout ?? 5000,
|
|
294
|
+
});
|