orc-shared 5.10.0-dev.7 → 5.10.0-dev.9

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.
@@ -0,0 +1,551 @@
1
+ import React, { useRef } from "react";
2
+ import { Provider } from "react-redux";
3
+ import Immutable from "immutable";
4
+ import sinon from "sinon";
5
+ import { spyOnConsole } from "../utils/testUtils";
6
+ import { mount } from "enzyme";
7
+ import useInMemoryPaging from "./useInMemoryPaging";
8
+ import { buildHeaderAndRowFromConfig, Table, TableProps } from "../components/MaterialUI/DataDisplay";
9
+
10
+ const getColumnDefs = () => [
11
+ {
12
+ fieldName: "name",
13
+ label: "name",
14
+ },
15
+ ];
16
+
17
+ const TestComp = ({
18
+ stateName,
19
+ allRecords,
20
+ sortAndFilterFn,
21
+ initialSort = {},
22
+ initialFilters = {},
23
+ tableRef = undefined,
24
+ }) => {
25
+ const internalTableRef = useRef(null);
26
+
27
+ const params = {
28
+ viewStateName: stateName,
29
+ tableRef: tableRef === undefined ? internalTableRef : tableRef,
30
+ records: allRecords,
31
+ pageSize: 20,
32
+ initialSort: initialSort,
33
+ initialFilters: initialFilters,
34
+ sortAndFilterFn: sortAndFilterFn,
35
+ };
36
+
37
+ if (params.initialSort === null) {
38
+ delete params.initialSort;
39
+ }
40
+
41
+ if (params.initialFilters === null) {
42
+ delete params.initialFilters;
43
+ }
44
+
45
+ const {
46
+ rows: pagedRows,
47
+ scrollLoader,
48
+ currentPage,
49
+ filters,
50
+ sorting,
51
+ setFilter,
52
+ setSort,
53
+ totalCount,
54
+ } = useInMemoryPaging(params);
55
+
56
+ const onInternalFilter = () => {
57
+ setFilter({ search: "n10" });
58
+ };
59
+
60
+ const onInternalSort = () => {
61
+ setSort({ sortBy: "name" });
62
+ };
63
+
64
+ const onScrollPage1 = () => {
65
+ scrollLoader(1);
66
+ };
67
+
68
+ const onScrollPage2 = () => {
69
+ scrollLoader(2);
70
+ };
71
+
72
+ const tableProps = new TableProps();
73
+ tableProps.set(TableProps.propNames.stickyHeader, true);
74
+ tableProps.set(TableProps.propNames.withoutTopBorder, true);
75
+ const columnDefs = getColumnDefs();
76
+
77
+ const { headers, rows } = buildHeaderAndRowFromConfig(columnDefs, pagedRows, true, "id");
78
+
79
+ return (
80
+ <div>
81
+ <div data-qa="totalCount">{totalCount}</div>
82
+ <div data-qa="filters">{JSON.stringify(filters)}</div>
83
+ <div data-qa="sorting">{JSON.stringify(sorting)}</div>
84
+ <div data-qa="pagedRows">{JSON.stringify(pagedRows)}</div>
85
+
86
+ <input type="button" data-qa="filter" value="filter" onClick={onInternalFilter} />
87
+ <input type="button" data-qa="sort" value="sort" onClick={onInternalSort} />
88
+ <input type="button" data-qa="scrollPage1" value="scrollPage1" onClick={onScrollPage1} />
89
+ <input type="button" data-qa="scrollPage2" value="scrollPage2" onClick={onScrollPage2} />
90
+
91
+ <Table
92
+ headers={headers}
93
+ rows={rows}
94
+ tableProps={tableProps}
95
+ scrollLoader={scrollLoader}
96
+ latestPage={currentPage}
97
+ pageLength={20}
98
+ />
99
+ </div>
100
+ );
101
+ };
102
+
103
+ const sortAndFilter = ({ list, filters, sorting }) => {
104
+ const { searchTerm } = filters;
105
+ const { sortBy = "name" } = sorting;
106
+
107
+ list = [...list];
108
+ list.sort((a, b) => a[sortBy]?.localeCompare(b[sortBy]));
109
+
110
+ if (searchTerm) {
111
+ list = list.filter(c => c.name?.toLowerCase()?.includes(searchTerm));
112
+ }
113
+
114
+ return list;
115
+ };
116
+
117
+ const standardListRecords = Array.from(Array(50).keys()).map(k => ({ name: "n" + k }));
118
+ const immutableListRecord = Immutable.fromJS(standardListRecords);
119
+
120
+ describe.each([
121
+ ["useInMemoryPaging with standard list", standardListRecords],
122
+ ["useInMemoryPaging with Immutable list", immutableListRecord],
123
+ ])("%s", (title, listValues) => {
124
+ spyOnConsole(["warn", "error"]);
125
+
126
+ let state, store;
127
+
128
+ beforeEach(() => {
129
+ state = Immutable.fromJS({});
130
+ store = {
131
+ getState: () => state,
132
+ subscribe: () => {},
133
+ dispatch: sinon.spy().named("dispatch"),
134
+ };
135
+ });
136
+
137
+ it("initial state", () => {
138
+ const component = (
139
+ <Provider store={store}>
140
+ <TestComp allRecords={listValues} stateName={"inMemory"} sortAndFilterFn={sortAndFilter} />
141
+ </Provider>
142
+ );
143
+
144
+ const mountedComponent = mount(component);
145
+ const totalCountDiv = mountedComponent.find("[data-qa='totalCount']").at(0);
146
+ const filtersDiv = mountedComponent.find("[data-qa='filters']").at(0);
147
+ const sortingDiv = mountedComponent.find("[data-qa='sorting']").at(0);
148
+ const pagedRowsDiv = mountedComponent.find("[data-qa='pagedRows']").at(0);
149
+
150
+ expect(totalCountDiv.text(), "to equal", "50");
151
+ expect(filtersDiv.text(), "to equal", "{}");
152
+ expect(sortingDiv.text(), "to equal", "{}");
153
+ expect(
154
+ pagedRowsDiv.text(),
155
+ "to equal",
156
+ '[{"name":"n0"},{"name":"n1"},{"name":"n10"},{"name":"n11"},{"name":"n12"},{"name":"n13"},{"name":"n14"},{"name":"n15"},{"name":"n16"},{"name":"n17"},{"name":"n18"},{"name":"n19"},{"name":"n2"},{"name":"n20"},{"name":"n21"},{"name":"n22"},{"name":"n23"},{"name":"n24"},{"name":"n25"},{"name":"n26"}]',
157
+ );
158
+ });
159
+
160
+ it("scrollLoader changes nothing if page is less than next page", () => {
161
+ state = state.setIn(
162
+ ["view", "inMemory"],
163
+ Immutable.fromJS({
164
+ currentPage: 1,
165
+ nextPageToLoad: 1,
166
+ }),
167
+ );
168
+
169
+ const component = (
170
+ <Provider store={store}>
171
+ <TestComp allRecords={listValues} stateName={"inMemory"} sortAndFilterFn={sortAndFilter} />
172
+ </Provider>
173
+ );
174
+
175
+ const mountedComponent = mount(component);
176
+ const onScrollPage1Btn = mountedComponent.find("[data-qa='scrollPage1']").at(0);
177
+ onScrollPage1Btn.simulate("click");
178
+
179
+ expect(store.dispatch, "was not called");
180
+ });
181
+
182
+ it("scrollLoader changes view state if page is greater than next page", () => {
183
+ state = state.setIn(
184
+ ["view", "inMemory"],
185
+ Immutable.fromJS({
186
+ currentPage: 1,
187
+ nextPageToLoad: 1,
188
+ }),
189
+ );
190
+
191
+ const component = (
192
+ <Provider store={store}>
193
+ <TestComp allRecords={listValues} stateName={"inMemory"} sortAndFilterFn={sortAndFilter} />
194
+ </Provider>
195
+ );
196
+
197
+ const mountedComponent = mount(component);
198
+ const onScrollPage2Btn = mountedComponent.find("[data-qa='scrollPage2']").at(0);
199
+ onScrollPage2Btn.simulate("click");
200
+
201
+ expect(store.dispatch, "to have a call satisfying", {
202
+ args: [
203
+ {
204
+ type: "VIEW_STATE_SET_FIELD",
205
+ payload: { name: "inMemory", field: "currentPage", value: 2 },
206
+ },
207
+ ],
208
+ });
209
+ expect(store.dispatch, "to have a call satisfying", {
210
+ args: [
211
+ {
212
+ type: "VIEW_STATE_SET_FIELD",
213
+ payload: { name: "inMemory", field: "nextPageToLoad", value: 2 },
214
+ },
215
+ ],
216
+ });
217
+ });
218
+
219
+ it("validate default values for filters and sorting", () => {
220
+ state = state.setIn(
221
+ ["view", "inMemory"],
222
+ Immutable.fromJS({
223
+ currentPage: 1,
224
+ nextPageToLoad: 1,
225
+ }),
226
+ );
227
+
228
+ const component = (
229
+ <Provider store={store}>
230
+ <TestComp
231
+ allRecords={listValues}
232
+ stateName={"inMemory"}
233
+ sortAndFilterFn={sortAndFilter}
234
+ initialSort={null}
235
+ initialFilters={null}
236
+ />
237
+ </Provider>
238
+ );
239
+
240
+ const mountedComponent = mount(component);
241
+ const totalCountDiv = mountedComponent.find("[data-qa='totalCount']").at(0);
242
+ const filtersDiv = mountedComponent.find("[data-qa='filters']").at(0);
243
+ const sortingDiv = mountedComponent.find("[data-qa='sorting']").at(0);
244
+ const pagedRowsDiv = mountedComponent.find("[data-qa='pagedRows']").at(0);
245
+
246
+ expect(totalCountDiv.text(), "to equal", "50");
247
+ expect(filtersDiv.text(), "to equal", "{}");
248
+ expect(sortingDiv.text(), "to equal", "{}");
249
+ expect(
250
+ pagedRowsDiv.text(),
251
+ "to equal",
252
+ '[{"name":"n0"},{"name":"n1"},{"name":"n10"},{"name":"n11"},{"name":"n12"},{"name":"n13"},{"name":"n14"},{"name":"n15"},{"name":"n16"},{"name":"n17"},{"name":"n18"},{"name":"n19"},{"name":"n2"},{"name":"n20"},{"name":"n21"},{"name":"n22"},{"name":"n23"},{"name":"n24"},{"name":"n25"},{"name":"n26"}]',
253
+ );
254
+ });
255
+
256
+ it("displays first page with a real sortAndFilterFn", () => {
257
+ state = state.setIn(
258
+ ["view", "inMemory"],
259
+ Immutable.fromJS({
260
+ currentPage: 1,
261
+ nextPageToLoad: 1,
262
+ }),
263
+ );
264
+
265
+ const sortAndFilterCustom = ({ list, filters, sorting }) => {
266
+ const { searchTerm } = filters;
267
+ const { sortBy = "name" } = sorting;
268
+
269
+ list = [...list];
270
+ list.sort((a, b) => a[sortBy]?.localeCompare(b[sortBy]));
271
+
272
+ if (searchTerm) {
273
+ list = list.filter(c => c.name?.toLowerCase()?.includes(searchTerm));
274
+ }
275
+
276
+ return list.slice(0, 10);
277
+ };
278
+
279
+ const component = (
280
+ <Provider store={store}>
281
+ <TestComp allRecords={listValues} stateName={"inMemory"} sortAndFilterFn={sortAndFilterCustom} />
282
+ </Provider>
283
+ );
284
+
285
+ const mountedComponent = mount(component);
286
+ const totalCountDiv = mountedComponent.find("[data-qa='totalCount']").at(0);
287
+ const filtersDiv = mountedComponent.find("[data-qa='filters']").at(0);
288
+ const sortingDiv = mountedComponent.find("[data-qa='sorting']").at(0);
289
+ const pagedRowsDiv = mountedComponent.find("[data-qa='pagedRows']").at(0);
290
+
291
+ expect(totalCountDiv.text(), "to equal", "10");
292
+ expect(filtersDiv.text(), "to equal", "{}");
293
+ expect(sortingDiv.text(), "to equal", "{}");
294
+ expect(
295
+ pagedRowsDiv.text(),
296
+ "to equal",
297
+ '[{"name":"n0"},{"name":"n1"},{"name":"n10"},{"name":"n11"},{"name":"n12"},{"name":"n13"},{"name":"n14"},{"name":"n15"},{"name":"n16"},{"name":"n17"}]',
298
+ );
299
+ });
300
+
301
+ it("displays 2nd page", () => {
302
+ state = state.setIn(
303
+ ["view", "inMemory"],
304
+ Immutable.fromJS({
305
+ currentPage: 2,
306
+ nextPageToLoad: 2,
307
+ }),
308
+ );
309
+
310
+ const component = (
311
+ <Provider store={store}>
312
+ <TestComp allRecords={listValues} stateName={"inMemory"} sortAndFilterFn={sortAndFilter} />
313
+ </Provider>
314
+ );
315
+
316
+ const mountedComponent = mount(component);
317
+ const totalCountDiv = mountedComponent.find("[data-qa='totalCount']").at(0);
318
+ const filtersDiv = mountedComponent.find("[data-qa='filters']").at(0);
319
+ const sortingDiv = mountedComponent.find("[data-qa='sorting']").at(0);
320
+ const pagedRowsDiv = mountedComponent.find("[data-qa='pagedRows']").at(0);
321
+
322
+ expect(totalCountDiv.text(), "to equal", "50");
323
+ expect(filtersDiv.text(), "to equal", "{}");
324
+ expect(sortingDiv.text(), "to equal", "{}");
325
+ expect(
326
+ pagedRowsDiv.text(),
327
+ "to equal",
328
+ '[{"name":"n0"},{"name":"n1"},{"name":"n10"},{"name":"n11"},{"name":"n12"},{"name":"n13"},{"name":"n14"},{"name":"n15"},{"name":"n16"},{"name":"n17"},{"name":"n18"},{"name":"n19"},{"name":"n2"},{"name":"n20"},{"name":"n21"},{"name":"n22"},{"name":"n23"},{"name":"n24"},{"name":"n25"},{"name":"n26"},{"name":"n27"},{"name":"n28"},{"name":"n29"},{"name":"n3"},{"name":"n30"},{"name":"n31"},{"name":"n32"},{"name":"n33"},{"name":"n34"},{"name":"n35"},{"name":"n36"},{"name":"n37"},{"name":"n38"},{"name":"n39"},{"name":"n4"},{"name":"n40"},{"name":"n41"},{"name":"n42"},{"name":"n43"},{"name":"n44"}]',
329
+ );
330
+ });
331
+
332
+ it("scrollTableRef raises warning if tableRef is null", () => {
333
+ state = state.setIn(
334
+ ["view", "inMemory"],
335
+ Immutable.fromJS({
336
+ currentPage: 2,
337
+ nextPageToLoad: 2,
338
+ }),
339
+ );
340
+
341
+ const component = (
342
+ <Provider store={store}>
343
+ <TestComp allRecords={listValues} stateName={"inMemory"} sortAndFilterFn={sortAndFilter} tableRef={null} />
344
+ </Provider>
345
+ );
346
+
347
+ const mountedComponent = mount(component);
348
+ const filterBtn = mountedComponent.find("[data-qa='filter']").at(0);
349
+ filterBtn.simulate("click");
350
+
351
+ expect(console.error, "to have a call satisfying", {
352
+ args: ["[useInMemoryPaging]: tableRef was not specified or tableRef.current is null."],
353
+ });
354
+ });
355
+
356
+ it("scrollTableRef raises warning if tableRef.current is null", () => {
357
+ state = state.setIn(
358
+ ["view", "inMemory"],
359
+ Immutable.fromJS({
360
+ currentPage: 2,
361
+ nextPageToLoad: 2,
362
+ }),
363
+ );
364
+
365
+ const tableRef = {
366
+ current: null,
367
+ };
368
+
369
+ const component = (
370
+ <Provider store={store}>
371
+ <TestComp allRecords={listValues} stateName={"inMemory"} sortAndFilterFn={sortAndFilter} tableRef={tableRef} />
372
+ </Provider>
373
+ );
374
+
375
+ const mountedComponent = mount(component);
376
+ const filterBtn = mountedComponent.find("[data-qa='filter']").at(0);
377
+ filterBtn.simulate("click");
378
+
379
+ expect(console.error, "to have a call satisfying", {
380
+ args: ["[useInMemoryPaging]: tableRef was not specified or tableRef.current is null."],
381
+ });
382
+ });
383
+
384
+ it("scrollTableRef scrolls to top if tableRef.current is not null", () => {
385
+ state = state.setIn(
386
+ ["view", "inMemory"],
387
+ Immutable.fromJS({
388
+ currentPage: 2,
389
+ nextPageToLoad: 2,
390
+ }),
391
+ );
392
+
393
+ const tableRef = {
394
+ current: {
395
+ scrollToTop: sinon.spy().named("tableRefcurrent"),
396
+ },
397
+ };
398
+
399
+ const component = (
400
+ <Provider store={store}>
401
+ <TestComp allRecords={listValues} stateName={"inMemory"} sortAndFilterFn={sortAndFilter} tableRef={tableRef} />
402
+ </Provider>
403
+ );
404
+
405
+ const mountedComponent = mount(component);
406
+ const filterBtn = mountedComponent.find("[data-qa='filter']").at(0);
407
+ filterBtn.simulate("click");
408
+
409
+ expect(tableRef.current.scrollToTop, "was called");
410
+ });
411
+
412
+ it("changing filter reset state and tableRef", () => {
413
+ state = state.setIn(
414
+ ["view", "inMemory"],
415
+ Immutable.fromJS({
416
+ currentPage: 2,
417
+ nextPageToLoad: 2,
418
+ }),
419
+ );
420
+
421
+ const tableRef = {
422
+ current: {
423
+ scrollToTop: sinon.spy().named("tableRefcurrent"),
424
+ },
425
+ };
426
+
427
+ const component = (
428
+ <Provider store={store}>
429
+ <TestComp allRecords={listValues} stateName={"inMemory"} sortAndFilterFn={sortAndFilter} tableRef={tableRef} />
430
+ </Provider>
431
+ );
432
+
433
+ const mountedComponent = mount(component);
434
+ const filterBtn = mountedComponent.find("[data-qa='filter']").at(0);
435
+ filterBtn.simulate("click");
436
+
437
+ expect(tableRef.current.scrollToTop, "was called");
438
+ expect(store.dispatch, "to have a call satisfying", {
439
+ args: [
440
+ {
441
+ type: "VIEW_STATE_SET_FIELD",
442
+ payload: { name: "inMemory", field: "filters", value: { search: "n10" } },
443
+ },
444
+ ],
445
+ });
446
+ expect(store.dispatch, "to have a call satisfying", {
447
+ args: [
448
+ {
449
+ type: "VIEW_STATE_SET_FIELD",
450
+ payload: { name: "inMemory", field: "currentPage", value: 1 },
451
+ },
452
+ ],
453
+ });
454
+ expect(store.dispatch, "to have a call satisfying", {
455
+ args: [
456
+ {
457
+ type: "VIEW_STATE_SET_FIELD",
458
+ payload: { name: "inMemory", field: "nextPageToLoad", value: 1 },
459
+ },
460
+ ],
461
+ });
462
+ });
463
+
464
+ it("changing sorting reset state and tableRef", () => {
465
+ state = state.setIn(
466
+ ["view", "inMemory"],
467
+ Immutable.fromJS({
468
+ currentPage: 2,
469
+ nextPageToLoad: 2,
470
+ }),
471
+ );
472
+
473
+ const tableRef = {
474
+ current: {
475
+ scrollToTop: sinon.spy().named("tableRefcurrent"),
476
+ },
477
+ };
478
+
479
+ const component = (
480
+ <Provider store={store}>
481
+ <TestComp allRecords={listValues} stateName={"inMemory"} sortAndFilterFn={sortAndFilter} tableRef={tableRef} />
482
+ </Provider>
483
+ );
484
+
485
+ const mountedComponent = mount(component);
486
+ const sortingBtn = mountedComponent.find("[data-qa='sort']").at(0);
487
+ sortingBtn.simulate("click");
488
+
489
+ expect(tableRef.current.scrollToTop, "was called");
490
+ expect(store.dispatch, "to have a call satisfying", {
491
+ args: [
492
+ {
493
+ type: "VIEW_STATE_SET_FIELD",
494
+ payload: { name: "inMemory", field: "sorting", value: { sortBy: "name" } },
495
+ },
496
+ ],
497
+ });
498
+ expect(store.dispatch, "to have a call satisfying", {
499
+ args: [
500
+ {
501
+ type: "VIEW_STATE_SET_FIELD",
502
+ payload: { name: "inMemory", field: "currentPage", value: 1 },
503
+ },
504
+ ],
505
+ });
506
+ expect(store.dispatch, "to have a call satisfying", {
507
+ args: [
508
+ {
509
+ type: "VIEW_STATE_SET_FIELD",
510
+ payload: { name: "inMemory", field: "nextPageToLoad", value: 1 },
511
+ },
512
+ ],
513
+ });
514
+ });
515
+
516
+ it("mutating sortAndFilterFn should display a warning", () => {
517
+ state = state.setIn(
518
+ ["view", "inMemory"],
519
+ Immutable.fromJS({
520
+ currentPage: 2,
521
+ nextPageToLoad: 2,
522
+ }),
523
+ );
524
+
525
+ const tableRef = {
526
+ current: {
527
+ scrollToTop: sinon.spy().named("tableRefcurrent"),
528
+ },
529
+ };
530
+
531
+ const component = (
532
+ <Provider store={store}>
533
+ <TestComp allRecords={listValues} stateName={"inMemory"} sortAndFilterFn={sortAndFilter} tableRef={tableRef} />
534
+ </Provider>
535
+ );
536
+
537
+ const mountedComponent = mount(component);
538
+ mountedComponent.setProps({
539
+ children: (
540
+ <TestComp allRecords={listValues} stateName={"inMemory"} sortAndFilterFn={list => list} tableRef={tableRef} />
541
+ ),
542
+ });
543
+ mountedComponent.update();
544
+
545
+ expect(console.warn, "to have a call satisfying", {
546
+ args: [
547
+ "[useInMemoryPaging]: a different value for sortAndFilterFn was detected between renders, ensure that it never changes (define it outside of your component).",
548
+ ],
549
+ });
550
+ });
551
+ });