@digital-realty/ix-grid 1.3.2 → 1.4.1-alpha.2

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.
Files changed (71) hide show
  1. package/dist/IxGrid.js +1 -1
  2. package/dist/IxGrid.js.map +1 -1
  3. package/dist/components/IxGridRowFilter.js +1 -1
  4. package/dist/components/IxGridRowFilter.js.map +1 -1
  5. package/dist/ix-grid.min.js +1 -1
  6. package/package.json +6 -6
  7. package/rollup.config.mjs +36 -2
  8. package/src/IxGrid.d.ts +148 -0
  9. package/src/IxGrid.js +784 -0
  10. package/src/IxGrid.js.map +1 -0
  11. package/src/IxGrid.ts +1 -1
  12. package/src/IxGridNav.d.ts +2 -0
  13. package/src/IxGridNav.js +8 -0
  14. package/src/IxGridNav.js.map +1 -0
  15. package/src/components/IxGridColumnFilter.d.ts +35 -0
  16. package/src/components/IxGridColumnFilter.js +249 -0
  17. package/src/components/IxGridColumnFilter.js.map +1 -0
  18. package/src/components/IxGridDownloadMenu.d.ts +17 -0
  19. package/src/components/IxGridDownloadMenu.js +98 -0
  20. package/src/components/IxGridDownloadMenu.js.map +1 -0
  21. package/src/components/IxGridNav.d.ts +16 -0
  22. package/src/components/IxGridNav.js +103 -0
  23. package/src/components/IxGridNav.js.map +1 -0
  24. package/src/components/IxGridNoRows.d.ts +10 -0
  25. package/src/components/IxGridNoRows.js +74 -0
  26. package/src/components/IxGridNoRows.js.map +1 -0
  27. package/src/components/IxGridRowFilter.d.ts +55 -0
  28. package/src/components/IxGridRowFilter.js +441 -0
  29. package/src/components/IxGridRowFilter.js.map +1 -0
  30. package/src/components/IxGridRowFilter.ts +1 -1
  31. package/src/components/IxPagination.d.ts +14 -0
  32. package/src/components/IxPagination.js +107 -0
  33. package/src/components/IxPagination.js.map +1 -0
  34. package/src/components/grid-column-filter-styles.d.ts +1 -0
  35. package/src/components/grid-column-filter-styles.js +89 -0
  36. package/src/components/grid-column-filter-styles.js.map +1 -0
  37. package/src/components/grid-row-filter-styles.d.ts +1 -0
  38. package/src/components/grid-row-filter-styles.js +311 -0
  39. package/src/components/grid-row-filter-styles.js.map +1 -0
  40. package/src/components/ix-grid-no-rows.d.ts +1 -0
  41. package/src/components/ix-grid-no-rows.js +2 -0
  42. package/src/components/ix-grid-no-rows.js.map +1 -0
  43. package/src/components/pagination-styles.d.ts +1 -0
  44. package/src/components/pagination-styles.js +84 -0
  45. package/src/components/pagination-styles.js.map +1 -0
  46. package/src/grid-view-styles.d.ts +1 -0
  47. package/src/grid-view-styles.js +283 -0
  48. package/src/grid-view-styles.js.map +1 -0
  49. package/src/index.d.ts +3 -0
  50. package/src/index.js +3 -0
  51. package/src/index.js.map +1 -0
  52. package/src/ix-grid-copy.d.ts +12 -0
  53. package/src/ix-grid-copy.js +12 -0
  54. package/src/ix-grid-copy.js.map +1 -0
  55. package/src/ix-grid-nav.d.ts +1 -0
  56. package/src/ix-grid-nav.js +2 -0
  57. package/src/ix-grid-nav.js.map +1 -0
  58. package/src/ix-grid-no-rows.d.ts +1 -0
  59. package/src/ix-grid-no-rows.js +2 -0
  60. package/src/ix-grid-no-rows.js.map +1 -0
  61. package/src/ix-grid.d.ts +1 -0
  62. package/src/ix-grid.js +2 -0
  63. package/src/ix-grid.js.map +1 -0
  64. package/src/models/IxGridDownloadMenuItemModel.d.ts +7 -0
  65. package/src/models/IxGridDownloadMenuItemModel.js +1 -0
  66. package/src/models/IxGridDownloadMenuItemModel.js.map +1 -0
  67. package/src/test/ix-grid-column-filter.test.js +41 -0
  68. package/src/test/ix-grid-row-filter.test.js +281 -0
  69. package/src/test/ix-grid.test.js +391 -0
  70. package/src/test/ix-pagination.test.js +58 -0
  71. package/web-test-runner.config.mjs +1 -1
@@ -0,0 +1,281 @@
1
+ /* eslint-disable no-restricted-globals */
2
+ import { aTimeout, elementUpdated, expect, fixture } from '@open-wc/testing';
3
+ import { html } from 'lit';
4
+ import sinon from 'sinon';
5
+ import { IxGridRowFilter } from '../components/IxGridRowFilter.js';
6
+ const columns = [
7
+ {
8
+ name: 'firstName',
9
+ header: 'First name',
10
+ filterable: true,
11
+ filterOperators: ['contains', 'equals'],
12
+ },
13
+ {
14
+ name: 'lastName',
15
+ header: 'Last name',
16
+ filterable: true,
17
+ filterOperators: ['contains', 'equals'],
18
+ },
19
+ {
20
+ name: 'middleName',
21
+ header: 'Middle name',
22
+ filterable: false,
23
+ filterOperators: ['contains', 'equals'],
24
+ },
25
+ {
26
+ name: 'email',
27
+ header: 'Email',
28
+ filterable: true,
29
+ filterOperators: ['contains', 'equals'],
30
+ },
31
+ ];
32
+ const filters = [
33
+ {
34
+ columnField: 'firstName',
35
+ operatorValue: 'contains',
36
+ value: 'John',
37
+ },
38
+ ];
39
+ describe('IxGridRowFilter', () => {
40
+ it('should render the grid row filter', async () => {
41
+ const el = await fixture(html `<ix-grid-row-filter .columns=${columns}></ix-grid-row-filter>`);
42
+ expect(el).to.be.instanceOf(IxGridRowFilter);
43
+ });
44
+ it('renders and popluates the grid row filter', async () => {
45
+ var _a, _b, _c;
46
+ const el = await fixture(html `<ix-grid-row-filter
47
+ .columns=${columns}
48
+ .filters=${filters}
49
+ ></ix-grid-row-filter>`);
50
+ const openMenu = (_a = el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.filter-button');
51
+ openMenu.click();
52
+ await elementUpdated(el);
53
+ const columnOptions = (_b = el.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelectorAll('select[data-v="firstName"] option');
54
+ expect(columnOptions === null || columnOptions === void 0 ? void 0 : columnOptions.length).to.equal(3);
55
+ const filterOperatorInput = (_c = el.shadowRoot) === null || _c === void 0 ? void 0 : _c.querySelector('select.filterOperatorInput');
56
+ filterOperatorInput.value = 'equals';
57
+ filterOperatorInput.dispatchEvent(new Event('change', {
58
+ bubbles: true,
59
+ cancelable: true,
60
+ }));
61
+ await elementUpdated(el);
62
+ const selectedOperator = filterOperatorInput.querySelector('option[selected]');
63
+ expect(selectedOperator === null || selectedOperator === void 0 ? void 0 : selectedOperator.value).to.equal('equals');
64
+ });
65
+ it('should debounce the filter input', async () => {
66
+ var _a, _b;
67
+ const debounceTime = 100;
68
+ const el = await fixture(html `<ix-grid-row-filter
69
+ .columns=${columns}
70
+ .filters=${filters}
71
+ .filterValueChangeDebounceTime=${debounceTime}
72
+ ></ix-grid-row-filter>`);
73
+ const openMenu = (_a = el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.filter-button');
74
+ openMenu.click();
75
+ await elementUpdated(el);
76
+ const filterInput = (_b = el.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('div.filterValueField input');
77
+ const debouncedOnFilterValueChangeSpy = sinon.spy(el, 'debouncedOnFilterValueChange');
78
+ const onFilterValueChangeSpy = sinon.spy(el, 'onfilterValueChange');
79
+ for (let i = 0; i < 5; i += 1) {
80
+ filterInput.dispatchEvent(new InputEvent('input', {
81
+ bubbles: true,
82
+ cancelable: true,
83
+ composed: true,
84
+ }));
85
+ }
86
+ expect(debouncedOnFilterValueChangeSpy).to.have.been.called;
87
+ expect(onFilterValueChangeSpy).to.not.have.been.called;
88
+ await aTimeout(debounceTime);
89
+ expect(onFilterValueChangeSpy).to.have.been.calledOnce;
90
+ });
91
+ describe('Filter type input', () => {
92
+ it('should render a string input control for non-supplied data type', async () => {
93
+ var _a, _b;
94
+ const columnsWithDataType = [
95
+ {
96
+ name: 'firstName',
97
+ header: 'First name',
98
+ filterable: true,
99
+ },
100
+ ];
101
+ const el = await fixture(html `<ix-grid-row-filter
102
+ .columns=${columnsWithDataType}
103
+ ></ix-grid-row-filter>`);
104
+ const openMenu = (_a = el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.filter-button');
105
+ openMenu === null || openMenu === void 0 ? void 0 : openMenu.click();
106
+ await elementUpdated(el);
107
+ const filterInput = (_b = el.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('div.filterValueField input');
108
+ expect(filterInput).to.exist;
109
+ expect(filterInput).to.be.instanceOf(HTMLInputElement);
110
+ });
111
+ it('should render a string input control for string data type', async () => {
112
+ var _a, _b;
113
+ const columnsWithDataType = [
114
+ {
115
+ name: 'firstName',
116
+ header: 'First name',
117
+ filterable: true,
118
+ dataType: 'string',
119
+ },
120
+ ];
121
+ const el = await fixture(html `<ix-grid-row-filter
122
+ .columns=${columnsWithDataType}
123
+ ></ix-grid-row-filter>`);
124
+ const openMenu = (_a = el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.filter-button');
125
+ openMenu === null || openMenu === void 0 ? void 0 : openMenu.click();
126
+ await elementUpdated(el);
127
+ const filterInput = (_b = el.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('div.filterValueField input');
128
+ expect(filterInput).to.exist;
129
+ expect(filterInput).to.be.instanceOf(HTMLInputElement);
130
+ });
131
+ it('should render a date input control for dateTime data type', async () => {
132
+ var _a, _b;
133
+ const columnsWithDataType = [
134
+ {
135
+ name: 'createdDate',
136
+ header: 'Created Date',
137
+ filterable: true,
138
+ dataType: 'dateTime',
139
+ },
140
+ ];
141
+ const el = await fixture(html `<ix-grid-row-filter
142
+ .columns=${columnsWithDataType}
143
+ ></ix-grid-row-filter>`);
144
+ const openMenu = (_a = el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.filter-button');
145
+ openMenu === null || openMenu === void 0 ? void 0 : openMenu.click();
146
+ await elementUpdated(el);
147
+ const dateInput = (_b = el.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('div.filterValueField ix-date');
148
+ expect(dateInput).to.exist;
149
+ expect(dateInput).to.be.instanceOf(HTMLElement);
150
+ });
151
+ it('should render nothing when an unknown data type is used', async () => {
152
+ var _a, _b;
153
+ const columnsWithDataType = [
154
+ {
155
+ name: 'unknownTypeField',
156
+ header: 'Unknown Type',
157
+ filterable: true,
158
+ dataType: 'unknownType',
159
+ },
160
+ ];
161
+ const el = await fixture(html `<ix-grid-row-filter
162
+ .columns=${columnsWithDataType}
163
+ ></ix-grid-row-filter>`);
164
+ const openMenu = (_a = el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.filter-button');
165
+ openMenu === null || openMenu === void 0 ? void 0 : openMenu.click();
166
+ await elementUpdated(el);
167
+ const inputField = (_b = el.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('div.filterValueField input');
168
+ expect(inputField).to.be.null;
169
+ });
170
+ it('should build filter from URL when readParamsFromURL is True', async () => {
171
+ var _a, _b, _c, _d, _e, _f;
172
+ const columnsWithDataType = [
173
+ {
174
+ name: 'firstName',
175
+ header: 'First name',
176
+ filterable: true,
177
+ dataType: 'string',
178
+ filterOperators: ['contains', 'equals'],
179
+ },
180
+ {
181
+ name: 'lastName',
182
+ header: 'Last Name',
183
+ filterable: true,
184
+ dataType: 'string',
185
+ filterOperators: ['equals'],
186
+ },
187
+ {
188
+ name: 'createdDate',
189
+ header: 'Created Date',
190
+ filterable: true,
191
+ dataType: 'dateTime',
192
+ filterOperators: ['equals'],
193
+ },
194
+ ];
195
+ const el = (await fixture(html `<ix-grid-row-filter
196
+ .columns=${columnsWithDataType}
197
+ .readParamsFromURL=${true}
198
+ ></ix-grid-row-filter>`));
199
+ const url = 'firstName_contains=John&createdDate_equals=2021-09-01';
200
+ history.pushState(null, '', `${location.pathname}?${url}`);
201
+ el.firstUpdated();
202
+ const openMenu = (_a = el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.filter-button');
203
+ openMenu === null || openMenu === void 0 ? void 0 : openMenu.click();
204
+ await elementUpdated(el);
205
+ const formFilters = (_b = el.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelectorAll('.filter-form');
206
+ const filter1Name = (_c = formFilters[0]
207
+ .querySelector('.filterColumnField')
208
+ .querySelector('select')
209
+ .attributes.getNamedItem('data-v')) === null || _c === void 0 ? void 0 : _c.value;
210
+ const filter1Operator = (_d = formFilters[0]
211
+ .querySelector('.filterOperatorField')
212
+ .querySelector('select')
213
+ .attributes.getNamedItem('data-v')) === null || _d === void 0 ? void 0 : _d.value;
214
+ const filter1Value = formFilters[0]
215
+ .querySelector('.filterValueField')
216
+ .querySelector('input').value;
217
+ const filter2Name = (_e = formFilters[1]
218
+ .querySelector('.filterColumnField')
219
+ .querySelector('select')
220
+ .attributes.getNamedItem('data-v')) === null || _e === void 0 ? void 0 : _e.value;
221
+ const filter2Operator = (_f = formFilters[1]
222
+ .querySelector('.filterOperatorField')
223
+ .querySelector('select')
224
+ .attributes.getNamedItem('data-v')) === null || _f === void 0 ? void 0 : _f.value;
225
+ const filter2Value = formFilters[1]
226
+ .querySelector('.filterValueField')
227
+ .querySelector('ix-date').value;
228
+ expect(formFilters.length).to.be.eq(2);
229
+ expect(filter1Name).to.be.eq('firstName');
230
+ expect(filter1Operator).to.be.eq('contains');
231
+ expect(filter1Value).to.be.eq('John');
232
+ expect(filter2Name).to.be.eq('createdDate');
233
+ expect(filter2Operator).to.be.eq('equals');
234
+ expect(filter2Value).to.be.eq('2021-09-01');
235
+ });
236
+ it('should use maxDate for date inputs if provided', async () => {
237
+ var _a, _b;
238
+ const columnsWithDataType = [
239
+ {
240
+ name: 'dateField',
241
+ header: 'Date Field',
242
+ filterable: true,
243
+ filterOperators: ['='],
244
+ dataType: 'dateTime',
245
+ },
246
+ ];
247
+ const el = await fixture(html `<ix-grid-row-filter
248
+ .columns=${columnsWithDataType}
249
+ .maxDate=${'2025-12-31'}
250
+ ></ix-grid-row-filter>`);
251
+ const openMenu = (_a = el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.filter-button');
252
+ openMenu === null || openMenu === void 0 ? void 0 : openMenu.click();
253
+ await elementUpdated(el);
254
+ const dateInput = (_b = el.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('div.filterValueField ix-date');
255
+ expect(dateInput).to.exist;
256
+ expect(dateInput === null || dateInput === void 0 ? void 0 : dateInput.getAttribute('max')).to.equal('2025-12-31');
257
+ });
258
+ it('should fallback to default maxDate if maxDate is not provided', async () => {
259
+ var _a, _b;
260
+ const columnsWithDataType = [
261
+ {
262
+ name: 'dateField',
263
+ header: 'Date Field',
264
+ filterable: true,
265
+ filterOperators: ['='],
266
+ dataType: 'dateTime',
267
+ },
268
+ ];
269
+ const el = await fixture(html `<ix-grid-row-filter
270
+ .columns=${columnsWithDataType}
271
+ ></ix-grid-row-filter>`);
272
+ const openMenu = (_a = el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.filter-button');
273
+ openMenu === null || openMenu === void 0 ? void 0 : openMenu.click();
274
+ await elementUpdated(el);
275
+ const dateInput = (_b = el.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('div.filterValueField ix-date');
276
+ expect(dateInput).to.exist;
277
+ const today = new Date().toISOString().split('T')[0];
278
+ expect(dateInput === null || dateInput === void 0 ? void 0 : dateInput.getAttribute('max')).to.equal(today);
279
+ });
280
+ });
281
+ });
@@ -0,0 +1,391 @@
1
+ /* eslint-disable no-restricted-globals */
2
+ import { expect, fixture, oneEvent } from '@open-wc/testing';
3
+ import { html } from 'lit';
4
+ import { IxGridRowFilter } from '../components/IxGridRowFilter.js';
5
+ import '../ix-grid-no-rows.js';
6
+ import '../ix-grid.js';
7
+ const rows = [
8
+ {
9
+ name: 'one',
10
+ },
11
+ {
12
+ name: 'two',
13
+ },
14
+ {
15
+ name: 'three',
16
+ },
17
+ ];
18
+ const columns = [
19
+ {
20
+ name: 'one',
21
+ header: 'one',
22
+ bodyRenderer: row => html ` <span>${row.name}</span>`,
23
+ filterable: true,
24
+ filterOperators: ['equals'],
25
+ },
26
+ {
27
+ name: 'two',
28
+ header: 'one',
29
+ bodyRenderer: row => html ` <span>${row.name}</span>`,
30
+ filterable: true,
31
+ filterOperators: ['equals', 'contains'],
32
+ },
33
+ {
34
+ name: 'three',
35
+ header: 'one',
36
+ bodyRenderer: row => html ` <span>${row.name}</span>`,
37
+ filterable: true,
38
+ filterOperators: ['equals', 'contains'],
39
+ },
40
+ ];
41
+ describe('IxGrid', () => {
42
+ it('renders a grid', async () => {
43
+ const el = await fixture(html `<ix-grid></ix-grid>`);
44
+ expect(el).to.not.be.null;
45
+ });
46
+ it('renders the correct number of rows', async () => {
47
+ const el = await fixture(html `<ix-grid
48
+ .columns=${columns}
49
+ .rows=${rows}
50
+ ></ix-grid>`);
51
+ expect(rows.length).to.equal(3);
52
+ expect(el).to.not.be.null;
53
+ });
54
+ it('renders no rows component state', async () => {
55
+ var _a;
56
+ const el = await fixture(html `<ix-grid
57
+ .columns=${columns}
58
+ .rows=${[]}
59
+ ><ix-grid-no-rows slot="no-rows"></ix-grid-no-rows
60
+ ></ix-grid>`);
61
+ const noRows = (_a = el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('slot[name="no-rows"]');
62
+ expect(noRows).to.not.be.null;
63
+ });
64
+ it('renders an ix-grid-row-filter', async () => {
65
+ var _a;
66
+ const el = await fixture(html `<ix-grid
67
+ .columns=${columns}
68
+ .rows=${rows}
69
+ .filterValueChangeDebounceTime=${1000}
70
+ ></ix-grid>`);
71
+ const rowFilter = (_a = el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('ix-grid-row-filter');
72
+ expect(rowFilter).to.be.instanceOf(IxGridRowFilter);
73
+ expect(rowFilter.filterValueChangeDebounceTime).to.equal(1000);
74
+ });
75
+ it('resets pagination upon filter change', async () => {
76
+ var _a, _b;
77
+ const el = await fixture(html `<ix-grid
78
+ .columns=${columns}
79
+ .rows=${rows}
80
+ ></ix-grid>`);
81
+ const pagination = (_a = el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('ix-pagination');
82
+ pagination.dispatchEvent(new CustomEvent('updatePagination', {
83
+ detail: {
84
+ page: 2,
85
+ pageSize: 10,
86
+ },
87
+ bubbles: true,
88
+ composed: true,
89
+ }));
90
+ expect(el.page).to.equal(2);
91
+ const rowFilter = (_b = el.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('ix-grid-row-filter');
92
+ rowFilter.dispatchEvent(new CustomEvent('rowFilter', {
93
+ detail: {
94
+ resetPage: true,
95
+ },
96
+ bubbles: true,
97
+ composed: true,
98
+ }));
99
+ expect(el.page).to.equal(1);
100
+ });
101
+ it('should read and set sort, order, page and page size from URL if readParamsFromURL is set to true', async () => {
102
+ const el = await fixture(html `<ix-grid
103
+ .columns=${columns}
104
+ .rows=${rows}
105
+ .readParamsFromURL=${true}
106
+ ></ix-grid>`);
107
+ const url = '?sort=lastName&order=asc&page=2&size=5';
108
+ history.pushState(null, '', `${location.pathname}${url}`);
109
+ el.firstUpdated();
110
+ expect(el.page).to.be.eq(2);
111
+ expect(el.pageSize).to.be.eq(5);
112
+ expect(el.sortedColumn).to.be.eq('lastName');
113
+ expect(el.sortDirection).to.be.eq('asc');
114
+ });
115
+ it('should set sort, order, page, page size and filters in the URL when addParamsToURL is set to true', async () => {
116
+ var _a, _b;
117
+ const columnsWithFilters = [
118
+ {
119
+ name: 'firstName',
120
+ header: 'First name',
121
+ filterable: true,
122
+ dataType: 'string',
123
+ filterOperators: ['contains', 'equals'],
124
+ },
125
+ {
126
+ name: 'lastName',
127
+ header: 'Last Name',
128
+ filterable: true,
129
+ dataType: 'string',
130
+ filterOperators: ['equals'],
131
+ },
132
+ {
133
+ name: 'createdDate',
134
+ header: 'Created Date',
135
+ filterable: true,
136
+ dataType: 'dateTime',
137
+ filterOperators: ['equals'],
138
+ },
139
+ ];
140
+ const existingSearchParams = '?realUsername=Earl&userAge=30';
141
+ history.pushState(null, '', `${location.pathname}${existingSearchParams}`);
142
+ const el = await fixture(html `<ix-grid
143
+ .columns=${columnsWithFilters}
144
+ .addParamsToURL=${true}
145
+ ></ix-grid>`);
146
+ const rowFilter = (_a = el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('ix-grid-row-filter');
147
+ rowFilter.dispatchEvent(new CustomEvent('rowFilter', {
148
+ detail: {
149
+ filters: [
150
+ {
151
+ columnField: 'firstName',
152
+ operatorValue: 'contains',
153
+ value: 'test',
154
+ },
155
+ {
156
+ columnField: 'createdDate',
157
+ operatorValue: 'equals',
158
+ value: '2024-10-10',
159
+ },
160
+ ],
161
+ },
162
+ bubbles: true,
163
+ composed: true,
164
+ }));
165
+ el.sortedColumn = 'firstName';
166
+ el.sortDirection = 'desc';
167
+ const pagination = (_b = el.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('ix-pagination');
168
+ pagination.dispatchEvent(new CustomEvent('updatePagination', {
169
+ detail: {
170
+ page: 3,
171
+ pageSize: 5,
172
+ },
173
+ bubbles: true,
174
+ composed: true,
175
+ }));
176
+ expect(location.search).to.be.eq(`${existingSearchParams}&sort=firstName&order=desc&page=3&size=5&firstName_contains=test&createdDate_equals=2024-10-10`);
177
+ });
178
+ describe('IxGrid updateSort', () => {
179
+ it('should update URL with sort, order, page, pageSize, filters', async () => {
180
+ history.replaceState(null, '', '/?userId=123&token=abc');
181
+ const updateSortColumns = [
182
+ {
183
+ name: 'name',
184
+ header: 'Name',
185
+ filterable: true,
186
+ filterOperators: ['contains'],
187
+ sortable: true,
188
+ },
189
+ ];
190
+ const el = await fixture(html `
191
+ <ix-grid
192
+ .columns=${updateSortColumns}
193
+ .rows=${[]}
194
+ .addParamsToURL=${true}
195
+ .preservedQueryParamKeys=${['userId', 'token']}
196
+ ></ix-grid>
197
+ `);
198
+ await el.updateComplete;
199
+ await el.handleSort('name');
200
+ const url = new URL(window.location.href);
201
+ const { searchParams } = url;
202
+ expect(searchParams.get('sort')).to.equal('name');
203
+ expect(searchParams.get('order')).to.equal('asc');
204
+ expect(searchParams.get('page')).to.equal('1');
205
+ expect(searchParams.get('size')).to.equal('10');
206
+ expect(searchParams.get('userId')).to.equal('123');
207
+ expect(searchParams.get('token')).to.equal('abc');
208
+ });
209
+ });
210
+ describe('IxGrid LocalStorage Persistence', () => {
211
+ beforeEach(() => {
212
+ localStorage.clear();
213
+ });
214
+ it('should reset localStorage value if columns length does not match', async () => {
215
+ const tempEl = await fixture(html `<ix-grid .columns=${columns} localStorageID="testGrid"></ix-grid>`);
216
+ const localStorageKey = tempEl.columnsLocalStorageKey;
217
+ // Simulating a user fully deleting and adding a fake column to their local storage
218
+ localStorage.setItem(localStorageKey, JSON.stringify([{ name: 'six', header: 'six' }]));
219
+ const el = await fixture(html `<ix-grid .columns=${columns} localStorageID="testGrid"></ix-grid>`);
220
+ await el.updateComplete;
221
+ const storedColumns = JSON.parse(localStorage.getItem(el.columnsLocalStorageKey) || '[]');
222
+ expect(storedColumns.length).to.equal(columns.length);
223
+ expect(storedColumns.map(c => c.name)).to.deep.equal(columns.map(c => c.name));
224
+ });
225
+ it('should reset localStorage value if it contains a column that does not exist in the provided columns', async () => {
226
+ const tempEl = await fixture(html `<ix-grid .columns=${columns} localStorageID="testGrid"></ix-grid>`);
227
+ const localStorageKey = tempEl.columnsLocalStorageKey;
228
+ // Simulating a user adding an additional column to local storage
229
+ localStorage.setItem(localStorageKey, JSON.stringify([
230
+ ...columns,
231
+ { name: 'nonexistent', header: 'Nonexistent' },
232
+ ]));
233
+ const el = await fixture(html `<ix-grid .columns=${columns} localStorageID="testGrid"></ix-grid>`);
234
+ await el.updateComplete;
235
+ const storedColumns = JSON.parse(localStorage.getItem(el.columnsLocalStorageKey) || '[]');
236
+ expect(storedColumns.length).to.equal(columns.length);
237
+ expect(storedColumns.map(c => c.name)).to.deep.equal(columns.map(c => c.name));
238
+ });
239
+ it('should delete localStorage if a provided column does not exist in localStorage and not create new local storage', async () => {
240
+ const initialColumns = columns.slice(0, -1);
241
+ const tempEl = await fixture(html `<ix-grid
242
+ .columns=${initialColumns}
243
+ localStorageID="testGrid"
244
+ ></ix-grid>`);
245
+ const localStorageKey = tempEl.columnsLocalStorageKey;
246
+ // Simulating a user deleting most columns from local storage
247
+ localStorage.setItem(localStorageKey, JSON.stringify(initialColumns));
248
+ const el = await fixture(html `<ix-grid .columns=${columns} localStorageID="testGrid"></ix-grid>`);
249
+ await el.updateComplete;
250
+ expect(localStorage.getItem(el.columnsLocalStorageKey)).to.equal(null);
251
+ });
252
+ it('should reorder columns based on table header flex order', async () => {
253
+ var _a;
254
+ const el = await fixture(html `<ix-grid
255
+ .columns=${columns}
256
+ .rows=${rows}
257
+ localStorageID="testGrid"
258
+ ></ix-grid>`);
259
+ await el.updateComplete;
260
+ const headerCells = (_a = el.grid.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelectorAll('th');
261
+ if (!headerCells || headerCells.length !== columns.length) {
262
+ throw new Error('Table headers not found or do not match expected count');
263
+ }
264
+ const orderMap = [2, 0, 1];
265
+ headerCells.forEach((th, index) => {
266
+ const thElement = th;
267
+ thElement.style.order = orderMap[index].toString();
268
+ });
269
+ await el.updateComplete;
270
+ await el.reorderColumnsFromTable();
271
+ const reorderedColumns = [columns[1], columns[2], columns[0]];
272
+ const storedColumns = JSON.parse(localStorage.getItem(el.columnsLocalStorageKey) || '[]');
273
+ expect(storedColumns.map(c => c.name)).to.deep.equal(reorderedColumns.map(c => c.name));
274
+ });
275
+ it('should reorder columns when a filter reorder event is triggered', async () => {
276
+ const el = await fixture(html `<ix-grid .columns=${columns} localStorageID="testGrid"></ix-grid>`);
277
+ await el.updateComplete;
278
+ const reorderedColumns = [columns[2], columns[0], columns[1]];
279
+ const event = new CustomEvent('columnFilter', {
280
+ detail: { reorderedColumns },
281
+ bubbles: true,
282
+ composed: true,
283
+ });
284
+ el.reorderColumnsFromFilter(event);
285
+ await el.updateComplete;
286
+ const storedColumns = JSON.parse(localStorage.getItem(el.columnsLocalStorageKey) || '[]');
287
+ expect(el.displayColumns.map(c => c.name)).to.deep.equal(reorderedColumns.map(c => c.name));
288
+ expect(storedColumns.map(c => c.name)).to.deep.equal(reorderedColumns.map(c => c.name));
289
+ });
290
+ });
291
+ describe('IxGrid handlePopState', () => {
292
+ const handlePopStateColumns = [
293
+ {
294
+ name: 'customcolumnfilter',
295
+ header: 'Custom',
296
+ filterable: true,
297
+ filterOperators: ['contains'],
298
+ },
299
+ {
300
+ name: 'id',
301
+ header: 'ID',
302
+ filterable: true,
303
+ filterOperators: ['equals'],
304
+ },
305
+ ];
306
+ it('should handle empty sort/order/page/size in URL', async () => {
307
+ history.replaceState(null, '', '/?sort=&order=&page=&size=');
308
+ const el = await fixture(html `
309
+ <ix-grid
310
+ .columns=${handlePopStateColumns}
311
+ .rows=${[]}
312
+ .readParamsFromURL=${true}
313
+ ></ix-grid>
314
+ `);
315
+ await el.updateComplete;
316
+ const changeEventPromise = oneEvent(el, 'change');
317
+ window.dispatchEvent(new PopStateEvent('popstate'));
318
+ const event = await changeEventPromise;
319
+ expect(el.sortedColumn).to.equal('');
320
+ expect(el.sortDirection).to.equal('');
321
+ expect(el.page).to.equal(1);
322
+ expect(el.pageSize).to.equal(10);
323
+ expect(event.detail.filters).to.deep.equal({});
324
+ });
325
+ it('should handle valid sort/order/page/size in URL', async () => {
326
+ history.replaceState(null, '', '/?sort=id&order=desc&page=1&size=20');
327
+ const el = await fixture(html `
328
+ <ix-grid
329
+ .columns=${handlePopStateColumns}
330
+ .rows=${[]}
331
+ .readParamsFromURL=${true}
332
+ ></ix-grid>
333
+ `);
334
+ await el.updateComplete;
335
+ const changeEventPromise = oneEvent(el, 'change');
336
+ window.dispatchEvent(new PopStateEvent('popstate'));
337
+ const event = await changeEventPromise;
338
+ expect(el.sortedColumn).to.equal('id');
339
+ expect(el.sortDirection).to.equal('desc');
340
+ expect(el.page).to.equal(1);
341
+ expect(el.pageSize).to.equal(20);
342
+ expect(event.detail.filters).to.deep.equal({});
343
+ });
344
+ it('should update filters from URL with custom filter', async () => {
345
+ history.replaceState(null, '', '/?customcolumnfilter_contains=foobar');
346
+ const el = await fixture(html `
347
+ <ix-grid
348
+ .columns=${handlePopStateColumns}
349
+ .rows=${[]}
350
+ .readParamsFromURL=${true}
351
+ ></ix-grid>
352
+ `);
353
+ await el.updateComplete;
354
+ const changeEventPromise = oneEvent(el, 'change');
355
+ window.dispatchEvent(new PopStateEvent('popstate'));
356
+ const event = await changeEventPromise;
357
+ expect(event.detail.filters).to.deep.equal({
358
+ customcolumnfilter: 'foobar',
359
+ });
360
+ });
361
+ it('should dispatch a change event with correct detail', async () => {
362
+ history.replaceState(null, '', '/?sort=id&order=desc&page=1&size=20&customcolumnfilter_contains=foobar');
363
+ const el = await fixture(html `
364
+ <ix-grid
365
+ .columns=${handlePopStateColumns}
366
+ .rows=${[]}
367
+ .readParamsFromURL=${true}
368
+ ></ix-grid>
369
+ `);
370
+ await el.updateComplete;
371
+ const changeEventPromise = oneEvent(el, 'change');
372
+ window.dispatchEvent(new PopStateEvent('popstate'));
373
+ const event = await changeEventPromise;
374
+ expect(event.detail).to.include({
375
+ columnName: 'id',
376
+ sortOrder: 'desc',
377
+ page: 1,
378
+ pageSize: 20,
379
+ });
380
+ expect(event.detail.filters).to.deep.equal({
381
+ customcolumnfilter: 'foobar',
382
+ });
383
+ expect(event.detail.filtersOperators).to.deep.equal([
384
+ {
385
+ columnField: 'customcolumnfilter',
386
+ operator: 'contains',
387
+ },
388
+ ]);
389
+ });
390
+ });
391
+ });