@patternfly/react-data-view 5.0.1 → 5.1.1

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 (85) hide show
  1. package/dist/cjs/DataView/DataView.d.ts +4 -0
  2. package/dist/cjs/DataView/DataView.js +7 -1
  3. package/dist/cjs/DataViewEventsContext/DataViewEventsContext.d.ts +16 -0
  4. package/dist/cjs/DataViewEventsContext/DataViewEventsContext.js +62 -0
  5. package/dist/cjs/DataViewEventsContext/DataViewEventsContext.test.d.ts +1 -0
  6. package/dist/cjs/DataViewEventsContext/DataViewEventsContext.test.js +72 -0
  7. package/dist/cjs/DataViewEventsContext/index.d.ts +2 -0
  8. package/dist/cjs/DataViewEventsContext/index.js +23 -0
  9. package/dist/cjs/DataViewTable/DataViewTable.d.ts +37 -0
  10. package/dist/cjs/DataViewTable/DataViewTable.js +59 -0
  11. package/dist/cjs/DataViewTable/DataViewTable.test.d.ts +1 -0
  12. package/dist/cjs/DataViewTable/DataViewTable.test.js +27 -0
  13. package/dist/cjs/DataViewTable/index.d.ts +2 -0
  14. package/dist/cjs/DataViewTable/index.js +23 -0
  15. package/dist/cjs/Hooks/pagination.d.ts +13 -1
  16. package/dist/cjs/Hooks/pagination.js +36 -4
  17. package/dist/cjs/Hooks/pagination.test.js +53 -1
  18. package/dist/cjs/Hooks/selection.d.ts +3 -3
  19. package/dist/cjs/Hooks/selection.js +4 -6
  20. package/dist/cjs/Hooks/selection.test.js +4 -4
  21. package/dist/cjs/InternalContext/InternalContext.d.ts +17 -0
  22. package/dist/cjs/InternalContext/InternalContext.js +35 -0
  23. package/dist/cjs/InternalContext/InternalContext.test.d.ts +1 -0
  24. package/dist/cjs/InternalContext/InternalContext.test.js +55 -0
  25. package/dist/cjs/InternalContext/index.d.ts +2 -0
  26. package/dist/cjs/InternalContext/index.js +23 -0
  27. package/dist/cjs/index.d.ts +6 -0
  28. package/dist/cjs/index.js +11 -2
  29. package/dist/dynamic/DataViewEventsContext/package.json +1 -0
  30. package/dist/dynamic/DataViewTable/package.json +1 -0
  31. package/dist/dynamic/InternalContext/package.json +1 -0
  32. package/dist/esm/DataView/DataView.d.ts +4 -0
  33. package/dist/esm/DataView/DataView.js +7 -1
  34. package/dist/esm/DataViewEventsContext/DataViewEventsContext.d.ts +16 -0
  35. package/dist/esm/DataViewEventsContext/DataViewEventsContext.js +34 -0
  36. package/dist/esm/DataViewEventsContext/DataViewEventsContext.test.d.ts +1 -0
  37. package/dist/esm/DataViewEventsContext/DataViewEventsContext.test.js +67 -0
  38. package/dist/esm/DataViewEventsContext/index.d.ts +2 -0
  39. package/dist/esm/DataViewEventsContext/index.js +2 -0
  40. package/dist/esm/DataViewTable/DataViewTable.d.ts +37 -0
  41. package/dist/esm/DataViewTable/DataViewTable.js +49 -0
  42. package/dist/esm/DataViewTable/DataViewTable.test.d.ts +1 -0
  43. package/dist/esm/DataViewTable/DataViewTable.test.js +22 -0
  44. package/dist/esm/DataViewTable/index.d.ts +2 -0
  45. package/dist/esm/DataViewTable/index.js +2 -0
  46. package/dist/esm/Hooks/pagination.d.ts +13 -1
  47. package/dist/esm/Hooks/pagination.js +36 -4
  48. package/dist/esm/Hooks/pagination.test.js +53 -1
  49. package/dist/esm/Hooks/selection.d.ts +3 -3
  50. package/dist/esm/Hooks/selection.js +4 -6
  51. package/dist/esm/Hooks/selection.test.js +4 -4
  52. package/dist/esm/InternalContext/InternalContext.d.ts +17 -0
  53. package/dist/esm/InternalContext/InternalContext.js +7 -0
  54. package/dist/esm/InternalContext/InternalContext.test.d.ts +1 -0
  55. package/dist/esm/InternalContext/InternalContext.test.js +50 -0
  56. package/dist/esm/InternalContext/index.d.ts +2 -0
  57. package/dist/esm/InternalContext/index.js +2 -0
  58. package/dist/esm/index.d.ts +6 -0
  59. package/dist/esm/index.js +6 -0
  60. package/package.json +1 -1
  61. package/patternfly-docs/content/extensions/data-view/examples/Components/Components.md +33 -3
  62. package/patternfly-docs/content/extensions/data-view/examples/Components/DataViewTableExample.tsx +47 -0
  63. package/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsContext.md +32 -0
  64. package/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsExample.tsx +108 -0
  65. package/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md +15 -5
  66. package/patternfly-docs/content/extensions/data-view/examples/Functionality/PaginationExample.tsx +20 -29
  67. package/patternfly-docs/content/extensions/data-view/examples/Functionality/SelectionExample.tsx +9 -40
  68. package/patternfly-docs/content/extensions/data-view/examples/Layout/Layout.md +1 -0
  69. package/patternfly-docs/content/extensions/data-view/examples/Layout/PredefinedLayoutExample.tsx +14 -45
  70. package/src/DataView/DataView.tsx +14 -2
  71. package/src/DataViewEventsContext/DataViewEventsContext.test.tsx +105 -0
  72. package/src/DataViewEventsContext/DataViewEventsContext.tsx +70 -0
  73. package/src/DataViewEventsContext/index.ts +2 -0
  74. package/src/DataViewTable/DataViewTable.test.tsx +37 -0
  75. package/src/DataViewTable/DataViewTable.tsx +96 -0
  76. package/src/DataViewTable/__snapshots__/DataViewTable.test.tsx.snap +320 -0
  77. package/src/DataViewTable/index.ts +2 -0
  78. package/src/Hooks/pagination.test.tsx +81 -1
  79. package/src/Hooks/pagination.ts +71 -15
  80. package/src/Hooks/selection.test.tsx +5 -5
  81. package/src/Hooks/selection.ts +6 -7
  82. package/src/InternalContext/InternalContext.test.tsx +88 -0
  83. package/src/InternalContext/InternalContext.tsx +35 -0
  84. package/src/InternalContext/index.ts +2 -0
  85. package/src/index.ts +9 -0
@@ -0,0 +1,320 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`DataViewTable component should render correctly 1`] = `
4
+ <div>
5
+ <table
6
+ aria-label="Repositories table"
7
+ class="pf-v5-c-table pf-m-grid-md"
8
+ data-ouia-component-id="TableExample"
9
+ data-ouia-component-type="PF5/Table"
10
+ data-ouia-safe="true"
11
+ role="grid"
12
+ >
13
+ <thead
14
+ class="pf-v5-c-table__thead"
15
+ data-ouia-component-id="TableExample-thead"
16
+ >
17
+ <tr
18
+ class="pf-v5-c-table__tr"
19
+ data-ouia-component-id="TableExample-tr-head"
20
+ data-ouia-component-type="PF5/TableRow"
21
+ data-ouia-safe="true"
22
+ >
23
+ <th
24
+ class="pf-v5-c-table__th"
25
+ data-ouia-component-id="TableExample-th-0"
26
+ scope="col"
27
+ tabindex="-1"
28
+ >
29
+ Repositories
30
+ </th>
31
+ <th
32
+ class="pf-v5-c-table__th"
33
+ data-ouia-component-id="TableExample-th-1"
34
+ scope="col"
35
+ tabindex="-1"
36
+ >
37
+ Branches
38
+ </th>
39
+ <th
40
+ class="pf-v5-c-table__th"
41
+ data-ouia-component-id="TableExample-th-2"
42
+ scope="col"
43
+ tabindex="-1"
44
+ >
45
+ Pull requests
46
+ </th>
47
+ <th
48
+ class="pf-v5-c-table__th"
49
+ data-ouia-component-id="TableExample-th-3"
50
+ scope="col"
51
+ tabindex="-1"
52
+ >
53
+ Workspaces
54
+ </th>
55
+ <th
56
+ class="pf-v5-c-table__th"
57
+ data-ouia-component-id="TableExample-th-4"
58
+ scope="col"
59
+ tabindex="-1"
60
+ >
61
+ Last commit
62
+ </th>
63
+ </tr>
64
+ </thead>
65
+ <tbody
66
+ class="pf-v5-c-table__tbody"
67
+ role="rowgroup"
68
+ >
69
+ <tr
70
+ class="pf-v5-c-table__tr"
71
+ data-ouia-component-id="TableExample-tr-0"
72
+ data-ouia-component-type="PF5/TableRow"
73
+ data-ouia-safe="true"
74
+ >
75
+ <td
76
+ class="pf-v5-c-table__td"
77
+ data-ouia-component-id="TableExample-td-0-0"
78
+ tabindex="-1"
79
+ >
80
+ one
81
+ </td>
82
+ <td
83
+ class="pf-v5-c-table__td"
84
+ data-ouia-component-id="TableExample-td-0-1"
85
+ tabindex="-1"
86
+ >
87
+ two
88
+ </td>
89
+ <td
90
+ class="pf-v5-c-table__td"
91
+ data-ouia-component-id="TableExample-td-0-2"
92
+ tabindex="-1"
93
+ >
94
+ three
95
+ </td>
96
+ <td
97
+ class="pf-v5-c-table__td"
98
+ data-ouia-component-id="TableExample-td-0-3"
99
+ tabindex="-1"
100
+ >
101
+ four
102
+ </td>
103
+ <td
104
+ class="pf-v5-c-table__td"
105
+ data-ouia-component-id="TableExample-td-0-4"
106
+ tabindex="-1"
107
+ >
108
+ five
109
+ </td>
110
+ </tr>
111
+ <tr
112
+ class="pf-v5-c-table__tr"
113
+ data-ouia-component-id="TableExample-tr-1"
114
+ data-ouia-component-type="PF5/TableRow"
115
+ data-ouia-safe="true"
116
+ >
117
+ <td
118
+ class="pf-v5-c-table__td"
119
+ data-ouia-component-id="TableExample-td-1-0"
120
+ tabindex="-1"
121
+ >
122
+ one - 2
123
+ </td>
124
+ <td
125
+ class="pf-v5-c-table__td"
126
+ data-ouia-component-id="TableExample-td-1-1"
127
+ tabindex="-1"
128
+ />
129
+ <td
130
+ class="pf-v5-c-table__td"
131
+ data-ouia-component-id="TableExample-td-1-2"
132
+ tabindex="-1"
133
+ />
134
+ <td
135
+ class="pf-v5-c-table__td"
136
+ data-ouia-component-id="TableExample-td-1-3"
137
+ tabindex="-1"
138
+ >
139
+ four - 2
140
+ </td>
141
+ <td
142
+ class="pf-v5-c-table__td"
143
+ data-ouia-component-id="TableExample-td-1-4"
144
+ tabindex="-1"
145
+ >
146
+ five - 2
147
+ </td>
148
+ </tr>
149
+ <tr
150
+ class="pf-v5-c-table__tr"
151
+ data-ouia-component-id="TableExample-tr-2"
152
+ data-ouia-component-type="PF5/TableRow"
153
+ data-ouia-safe="true"
154
+ >
155
+ <td
156
+ class="pf-v5-c-table__td"
157
+ data-ouia-component-id="TableExample-td-2-0"
158
+ tabindex="-1"
159
+ >
160
+ one - 3
161
+ </td>
162
+ <td
163
+ class="pf-v5-c-table__td"
164
+ data-ouia-component-id="TableExample-td-2-1"
165
+ tabindex="-1"
166
+ >
167
+ two - 3
168
+ </td>
169
+ <td
170
+ class="pf-v5-c-table__td"
171
+ data-ouia-component-id="TableExample-td-2-2"
172
+ tabindex="-1"
173
+ >
174
+ three - 3
175
+ </td>
176
+ <td
177
+ class="pf-v5-c-table__td"
178
+ data-ouia-component-id="TableExample-td-2-3"
179
+ tabindex="-1"
180
+ >
181
+ four - 3
182
+ </td>
183
+ <td
184
+ class="pf-v5-c-table__td"
185
+ data-ouia-component-id="TableExample-td-2-4"
186
+ tabindex="-1"
187
+ >
188
+ five - 3
189
+ </td>
190
+ </tr>
191
+ <tr
192
+ class="pf-v5-c-table__tr"
193
+ data-ouia-component-id="TableExample-tr-3"
194
+ data-ouia-component-type="PF5/TableRow"
195
+ data-ouia-safe="true"
196
+ >
197
+ <td
198
+ class="pf-v5-c-table__td"
199
+ data-ouia-component-id="TableExample-td-3-0"
200
+ tabindex="-1"
201
+ >
202
+ one - 4
203
+ </td>
204
+ <td
205
+ class="pf-v5-c-table__td"
206
+ data-ouia-component-id="TableExample-td-3-1"
207
+ tabindex="-1"
208
+ >
209
+ two - 4
210
+ </td>
211
+ <td
212
+ class="pf-v5-c-table__td"
213
+ data-ouia-component-id="TableExample-td-3-2"
214
+ tabindex="-1"
215
+ >
216
+ null
217
+ </td>
218
+ <td
219
+ class="pf-v5-c-table__td"
220
+ data-ouia-component-id="TableExample-td-3-3"
221
+ tabindex="-1"
222
+ >
223
+ four - 4
224
+ </td>
225
+ <td
226
+ class="pf-v5-c-table__td"
227
+ data-ouia-component-id="TableExample-td-3-4"
228
+ tabindex="-1"
229
+ >
230
+ five - 4
231
+ </td>
232
+ </tr>
233
+ <tr
234
+ class="pf-v5-c-table__tr"
235
+ data-ouia-component-id="TableExample-tr-4"
236
+ data-ouia-component-type="PF5/TableRow"
237
+ data-ouia-safe="true"
238
+ >
239
+ <td
240
+ class="pf-v5-c-table__td"
241
+ data-ouia-component-id="TableExample-td-4-0"
242
+ tabindex="-1"
243
+ >
244
+ one - 5
245
+ </td>
246
+ <td
247
+ class="pf-v5-c-table__td"
248
+ data-ouia-component-id="TableExample-td-4-1"
249
+ tabindex="-1"
250
+ >
251
+ two - 5
252
+ </td>
253
+ <td
254
+ class="pf-v5-c-table__td"
255
+ data-ouia-component-id="TableExample-td-4-2"
256
+ tabindex="-1"
257
+ >
258
+ three - 5
259
+ </td>
260
+ <td
261
+ class="pf-v5-c-table__td"
262
+ data-ouia-component-id="TableExample-td-4-3"
263
+ tabindex="-1"
264
+ >
265
+ four - 5
266
+ </td>
267
+ <td
268
+ class="pf-v5-c-table__td"
269
+ data-ouia-component-id="TableExample-td-4-4"
270
+ tabindex="-1"
271
+ >
272
+ five - 5
273
+ </td>
274
+ </tr>
275
+ <tr
276
+ class="pf-v5-c-table__tr"
277
+ data-ouia-component-id="TableExample-tr-5"
278
+ data-ouia-component-type="PF5/TableRow"
279
+ data-ouia-safe="true"
280
+ >
281
+ <td
282
+ class="pf-v5-c-table__td"
283
+ data-ouia-component-id="TableExample-td-5-0"
284
+ tabindex="-1"
285
+ >
286
+ one - 6
287
+ </td>
288
+ <td
289
+ class="pf-v5-c-table__td"
290
+ data-ouia-component-id="TableExample-td-5-1"
291
+ tabindex="-1"
292
+ >
293
+ two - 6
294
+ </td>
295
+ <td
296
+ class="pf-v5-c-table__td"
297
+ data-ouia-component-id="TableExample-td-5-2"
298
+ tabindex="-1"
299
+ >
300
+ three - 6
301
+ </td>
302
+ <td
303
+ class="pf-v5-c-table__td"
304
+ data-ouia-component-id="TableExample-td-5-3"
305
+ tabindex="-1"
306
+ >
307
+ four - 6
308
+ </td>
309
+ <td
310
+ class="pf-v5-c-table__td"
311
+ data-ouia-component-id="TableExample-td-5-4"
312
+ tabindex="-1"
313
+ >
314
+ five - 6
315
+ </td>
316
+ </tr>
317
+ </tbody>
318
+ </table>
319
+ </div>
320
+ `;
@@ -0,0 +1,2 @@
1
+ export { default } from './DataViewTable';
2
+ export * from './DataViewTable';
@@ -49,9 +49,89 @@ describe('useDataViewPagination', () => {
49
49
  expect(result.current).toEqual({
50
50
  onPerPageSelect: expect.any(Function),
51
51
  onSetPage: expect.any(Function),
52
- page: 3,
52
+ page: 1,
53
53
  perPage: 50
54
54
  })
55
55
  });
56
56
 
57
+ it('should read pagination state from URL', () => {
58
+ const mockSearchParams = new URLSearchParams('page=2&perPage=10');
59
+ const { result } = renderHook(() =>
60
+ useDataViewPagination({
61
+ searchParams: mockSearchParams,
62
+ setSearchParams: jest.fn(),
63
+ page: 1,
64
+ perPage: 5,
65
+ })
66
+ );
67
+
68
+ expect(result.current).toEqual({
69
+ onPerPageSelect: expect.any(Function),
70
+ onSetPage: expect.any(Function),
71
+ page: 2,
72
+ perPage: 10,
73
+ });
74
+ });
75
+
76
+ it('should set pagination state in URL when page changes', () => {
77
+ const mockSetSearchParams = jest.fn();
78
+ const { result } = renderHook(() =>
79
+ useDataViewPagination({
80
+ searchParams: new URLSearchParams(),
81
+ setSearchParams: mockSetSearchParams,
82
+ page: 1,
83
+ perPage: 5,
84
+ })
85
+ );
86
+
87
+ expect(mockSetSearchParams).toHaveBeenNthCalledWith(
88
+ 1,
89
+ new URLSearchParams('page=1&perPage=5')
90
+ );
91
+
92
+ act(() => {
93
+ result.current.onSetPage(undefined, 4);
94
+ });
95
+
96
+ expect(mockSetSearchParams).toHaveBeenNthCalledWith(
97
+ 2,
98
+ new URLSearchParams('page=4&perPage=5')
99
+ );
100
+ });
101
+
102
+ it('should set pagination state in URL when perPage changes', () => {
103
+ const mockSetSearchParams = jest.fn();
104
+ const { result } = renderHook(() =>
105
+ useDataViewPagination({
106
+ searchParams: new URLSearchParams('page=2'),
107
+ setSearchParams: mockSetSearchParams,
108
+ page: 1,
109
+ perPage: 5,
110
+ })
111
+ );
112
+
113
+ act(() => {
114
+ result.current.onPerPageSelect(undefined, 20);
115
+ });
116
+
117
+ expect(mockSetSearchParams).toHaveBeenCalledWith(
118
+ new URLSearchParams('page=1&perPage=20')
119
+ );
120
+ });
121
+
122
+ it('should initialize URL with default values if not present', () => {
123
+ const mockSetSearchParams = jest.fn();
124
+ renderHook(() =>
125
+ useDataViewPagination({
126
+ searchParams: new URLSearchParams(),
127
+ setSearchParams: mockSetSearchParams,
128
+ page: 1,
129
+ perPage: 5,
130
+ })
131
+ );
132
+
133
+ expect(mockSetSearchParams).toHaveBeenCalledWith(
134
+ new URLSearchParams('page=1&perPage=5')
135
+ );
136
+ });
57
137
  });
@@ -1,31 +1,87 @@
1
- import { useState } from "react";
1
+ import { useState, useEffect } from "react";
2
+
3
+ export enum PaginationParams {
4
+ PAGE = 'page',
5
+ PER_PAGE = 'perPage'
6
+ }
2
7
 
3
8
  export interface UseDataViewPaginationProps {
4
9
  /** Initial page */
5
10
  page?: number;
6
11
  /** Items per page */
7
12
  perPage: number;
13
+ /** Current search parameters as a string */
14
+ searchParams?: URLSearchParams;
15
+ /** Function to set search parameters */
16
+ setSearchParams?: (params: URLSearchParams) => void;
17
+ /** Custom URL parameter name for page */
18
+ pageParam?: string;
19
+ /** Custom URL parameter name for per page */
20
+ perPageParam?: string;
8
21
  }
9
22
 
10
23
  export interface DataViewPaginationProps extends UseDataViewPaginationProps {
11
24
  /** Current page number */
12
- page: number;
25
+ page: number;
13
26
  }
14
27
 
15
- export const useDataViewPagination = ({ page = 1, perPage }: UseDataViewPaginationProps) => {
16
- const [ state, setState ] = useState({ page, perPage });
17
-
18
- const onPerPageSelect = (_event: React.MouseEvent | React.KeyboardEvent | MouseEvent | undefined, newPerPage: number) => {
19
- setState(prev => ({ ...prev, perPage: newPerPage }));
20
- }
21
-
22
- const onSetPage = (_event: React.MouseEvent | React.KeyboardEvent | MouseEvent | undefined, newPage: number) => {
28
+ export const useDataViewPagination = ({
29
+ page = 1,
30
+ perPage,
31
+ searchParams,
32
+ setSearchParams,
33
+ pageParam = PaginationParams.PAGE,
34
+ perPageParam = PaginationParams.PER_PAGE,
35
+ }: UseDataViewPaginationProps) => {
36
+ const [ state, setState ] = useState({
37
+ page: parseInt(searchParams?.get(pageParam) || `${page}`),
38
+ perPage: parseInt(searchParams?.get(perPageParam) || `${perPage}`),
39
+ });
40
+
41
+ const updateSearchParams = (page: number, perPage: number) => {
42
+ if (searchParams && setSearchParams) {
43
+ const params = new URLSearchParams(searchParams);
44
+ params.set(pageParam, `${page}`);
45
+ params.set(perPageParam, `${perPage}`);
46
+ setSearchParams(params);
47
+ }
48
+ };
49
+
50
+ useEffect(() => {
51
+ // Make sure search params are loaded or set if not present on mount
52
+ updateSearchParams(state.page, state.perPage);
53
+ // eslint-disable-next-line react-hooks/exhaustive-deps
54
+ }, []);
55
+
56
+ useEffect(() => {
57
+ // Listen on URL params changes
58
+ const currentPage = parseInt(searchParams?.get(pageParam) || `${state.page}`);
59
+ const currentPerPage = parseInt(searchParams?.get(perPageParam) || `${state.perPage}`);
60
+ if (currentPage !== state.page || currentPerPage !== state.perPage) {
61
+ setState({ page: currentPage, perPage: currentPerPage });
62
+ }
63
+ // eslint-disable-next-line react-hooks/exhaustive-deps
64
+ }, [ searchParams?.toString() ]);
65
+
66
+ const onPerPageSelect = (
67
+ _event: React.MouseEvent | React.KeyboardEvent | MouseEvent | undefined,
68
+ newPerPage: number
69
+ ) => {
70
+ updateSearchParams(1, newPerPage);
71
+ setState({ perPage: newPerPage, page: 1 });
72
+ };
73
+
74
+ const onSetPage = (
75
+ _event: React.MouseEvent | React.KeyboardEvent | MouseEvent | undefined,
76
+ newPage: number
77
+ ) => {
78
+ updateSearchParams(newPage, state.perPage);
23
79
  setState(prev => ({ ...prev, page: newPage }));
24
- }
25
-
80
+ };
81
+
26
82
  return {
27
83
  ...state,
28
84
  onPerPageSelect,
29
- onSetPage
30
- }
31
- }
85
+ onSetPage,
86
+ };
87
+ };
@@ -4,7 +4,7 @@ import { useDataViewSelection } from './selection';
4
4
 
5
5
  describe('useDataViewSelection', () => {
6
6
  it('should get initial state correctly - no initialSelected', () => {
7
- const { result } = renderHook(() => useDataViewSelection({}))
7
+ const { result } = renderHook(() => useDataViewSelection({ matchOption: (a, b) => a.id === b.id }))
8
8
  expect(result.current).toEqual({
9
9
  selected: [],
10
10
  onSelect: expect.any(Function),
@@ -14,7 +14,7 @@ describe('useDataViewSelection', () => {
14
14
 
15
15
  it('should get initial state correctly - with initialSelected', () => {
16
16
  const initialSelected = [ { id: 1, name: 'test1' } ];
17
- const { result } = renderHook(() => useDataViewSelection({ initialSelected }))
17
+ const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a.id === b.id }))
18
18
  expect(result.current).toEqual({
19
19
  selected: initialSelected,
20
20
  onSelect: expect.any(Function),
@@ -24,7 +24,7 @@ describe('useDataViewSelection', () => {
24
24
 
25
25
  it('should select items correctly - objects', async () => {
26
26
  const initialSelected = [ { id: 1, name: 'test1' } ];
27
- const { result } = renderHook(() => useDataViewSelection({ initialSelected }))
27
+ const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a.id === b.id }))
28
28
 
29
29
  await act(async () => {
30
30
  result.current.onSelect(true, { id: 2, name: 'test2' });
@@ -34,7 +34,7 @@ describe('useDataViewSelection', () => {
34
34
 
35
35
  it('should deselect items correctly - strings', async () => {
36
36
  const initialSelected = [ 'test1', 'test2' ];
37
- const { result } = renderHook(() => useDataViewSelection({ initialSelected }))
37
+ const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a === b }))
38
38
 
39
39
  await act(async () => {
40
40
  result.current.onSelect(false, 'test2');
@@ -44,7 +44,7 @@ describe('useDataViewSelection', () => {
44
44
 
45
45
  it('should check if item is selected correctly - objects', () => {
46
46
  const initialSelected = [ { id: 1, name: 'test1' }, { id: 2, name: 'test2' } ];
47
- const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a,b) => a.id === b.id }))
47
+ const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a.id === b.id }))
48
48
 
49
49
  expect(result.current.isSelected({ id: 1, name: 'test1' })).toBe(true);
50
50
  expect(result.current.isSelected({ id: 3, name: 'test2' })).toBe(false);
@@ -2,18 +2,17 @@
2
2
  import { useState } from "react";
3
3
 
4
4
  export interface UseDataViewSelectionProps {
5
+ /** Function to compare items when checking if item is selected */
6
+ matchOption: (item: any, another: any) => boolean;
5
7
  /** Array of initially selected entries */
6
8
  initialSelected?: (any)[];
7
- /** Function to compare items when checking if entry is selected */
8
- matchOption?: (item: any, another: any) => boolean;
9
9
  }
10
10
 
11
- export const useDataViewSelection = (props: UseDataViewSelectionProps) => {
12
- const [ selected, setSelected ] = useState<any[]>(props.initialSelected ?? []);
13
- const matchOption = props.matchOption ? props.matchOption : (option, another) => (option === another);
11
+ export const useDataViewSelection = ({ matchOption, initialSelected = [] }: UseDataViewSelectionProps) => {
12
+ const [ selected, setSelected ] = useState<any[]>(initialSelected);
14
13
 
15
14
  const onSelect = (isSelecting: boolean, items?: any[] | any) => {
16
- isSelecting ?
15
+ isSelecting && items ?
17
16
  setSelected(prev => {
18
17
  const newSelectedItems = [ ...prev ];
19
18
  (Array.isArray(items) ? items : [ items ]).forEach(newItem => !prev.some(prevItem => matchOption(prevItem, newItem)) && newSelectedItems.push(newItem));
@@ -22,7 +21,7 @@ export const useDataViewSelection = (props: UseDataViewSelectionProps) => {
22
21
  : setSelected(items ? prev => prev.filter(prevSelected => !(Array.isArray(items) ? items : [ items ]).some(item => matchOption(item, prevSelected))) : []);
23
22
  };
24
23
 
25
- const isSelected = (item: any): boolean => props?.matchOption ? Boolean(selected.find(selected => matchOption(selected, item))) : selected.includes(item);
24
+ const isSelected = (item: any): boolean => Boolean(selected.find(selected => matchOption(selected, item)));
26
25
 
27
26
  return {
28
27
  selected,
@@ -0,0 +1,88 @@
1
+ import React from 'react';
2
+ import { render, fireEvent } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import { InternalContextProvider, useInternalContext, DataViewSelection } from './InternalContext';
5
+
6
+ describe('InternalContext', () => {
7
+ const mockSelection: DataViewSelection = {
8
+ onSelect: jest.fn(),
9
+ isSelected: jest.fn(),
10
+ isSelectDisabled: jest.fn(),
11
+ };
12
+
13
+ test('should provide context value and allow consuming it', () => {
14
+ const TestComponent = () => {
15
+ const { selection } = useInternalContext();
16
+
17
+ return (
18
+ <div>
19
+ <button onClick={() => selection?.onSelect(true, [ 'item1' ])}>Select item</button>
20
+ <span>{selection?.isSelected('item1') ? 'Selected' : 'Not selected'}</span>
21
+ </div>
22
+ );
23
+ };
24
+
25
+ const { getByText } = render(
26
+ <InternalContextProvider selection={mockSelection}>
27
+ <TestComponent />
28
+ </InternalContextProvider>
29
+ );
30
+
31
+ fireEvent.click(getByText('Select item'));
32
+ expect(mockSelection.onSelect).toHaveBeenCalledWith(true, [ 'item1' ]);
33
+ });
34
+
35
+ test('should handle selection state correctly', () => {
36
+ const mockSelectionState = {
37
+ ...mockSelection,
38
+ isSelected: jest.fn((item) => item === 'item1'),
39
+ };
40
+
41
+ const TestComponent = () => {
42
+ const { selection } = useInternalContext();
43
+
44
+ return (
45
+ <div>
46
+ <span>{selection?.isSelected('item1') ? 'Item 1 is selected' : 'Item 1 is not selected'}</span>
47
+ <span>{selection?.isSelected('item2') ? 'Item 2 is selected' : 'Item 2 is not selected'}</span>
48
+ </div>
49
+ );
50
+ };
51
+
52
+ const { getByText } = render(
53
+ <InternalContextProvider selection={mockSelectionState}>
54
+ <TestComponent />
55
+ </InternalContextProvider>
56
+ );
57
+
58
+ expect(getByText('Item 1 is selected')).toBeInTheDocument();
59
+ expect(getByText('Item 2 is not selected')).toBeInTheDocument();
60
+ });
61
+
62
+ test('should handle selection disabled correctly', () => {
63
+ const mockSelectionWithDisabled = {
64
+ ...mockSelection,
65
+ isSelectDisabled: jest.fn((item) => item === 'item3'),
66
+ };
67
+
68
+ const TestComponent = () => {
69
+ const { selection } = useInternalContext();
70
+
71
+ return (
72
+ <div>
73
+ <span>{selection?.isSelectDisabled?.('item3') ? 'Item 3 is disabled' : 'Item 3 is enabled'}</span>
74
+ <span>{selection?.isSelectDisabled?.('item1') ? 'Item 1 is disabled' : 'Item 1 is enabled'}</span>
75
+ </div>
76
+ );
77
+ };
78
+
79
+ const { getByText } = render(
80
+ <InternalContextProvider selection={mockSelectionWithDisabled}>
81
+ <TestComponent />
82
+ </InternalContextProvider>
83
+ );
84
+
85
+ expect(getByText('Item 3 is disabled')).toBeInTheDocument();
86
+ expect(getByText('Item 1 is enabled')).toBeInTheDocument();
87
+ });
88
+ });