@squiz/resource-browser 1.32.1-alpha.12

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 (160) hide show
  1. package/.storybook/main.ts +23 -0
  2. package/.storybook/preview-head.html +15 -0
  3. package/.storybook/preview.ts +16 -0
  4. package/build.js +21 -0
  5. package/jest.config.ts +18 -0
  6. package/lib/Icons/Generics/ArrowDown.d.ts +15 -0
  7. package/lib/Icons/Generics/ArrowDown.js +23 -0
  8. package/lib/Icons/Generics/ArrowRight.d.ts +15 -0
  9. package/lib/Icons/Generics/ArrowRight.js +23 -0
  10. package/lib/Icons/Generics/Close.d.ts +15 -0
  11. package/lib/Icons/Generics/Close.js +23 -0
  12. package/lib/Icons/Generics/GenericIconMap.d.ts +10 -0
  13. package/lib/Icons/Generics/GenericIconMap.js +14 -0
  14. package/lib/Icons/Generics/ResourceSelect.d.ts +15 -0
  15. package/lib/Icons/Generics/ResourceSelect.js +28 -0
  16. package/lib/Icons/Generics/Root.d.ts +15 -0
  17. package/lib/Icons/Generics/Root.js +23 -0
  18. package/lib/Icons/Generics/Selected.d.ts +15 -0
  19. package/lib/Icons/Generics/Selected.js +23 -0
  20. package/lib/Icons/Generics/index.d.ts +6 -0
  21. package/lib/Icons/Generics/index.js +19 -0
  22. package/lib/Icons/Icon.d.ts +47 -0
  23. package/lib/Icons/Icon.js +44 -0
  24. package/lib/Icons/MatrixResources/Audio.d.ts +15 -0
  25. package/lib/Icons/MatrixResources/Audio.js +28 -0
  26. package/lib/Icons/MatrixResources/Excel.d.ts +15 -0
  27. package/lib/Icons/MatrixResources/Excel.js +27 -0
  28. package/lib/Icons/MatrixResources/Folder.d.ts +15 -0
  29. package/lib/Icons/MatrixResources/Folder.js +24 -0
  30. package/lib/Icons/MatrixResources/GenericFile.d.ts +15 -0
  31. package/lib/Icons/MatrixResources/GenericFile.js +28 -0
  32. package/lib/Icons/MatrixResources/Image.d.ts +15 -0
  33. package/lib/Icons/MatrixResources/Image.js +26 -0
  34. package/lib/Icons/MatrixResources/MatrixResourceMap.d.ts +15 -0
  35. package/lib/Icons/MatrixResources/MatrixResourceMap.js +19 -0
  36. package/lib/Icons/MatrixResources/Page.d.ts +15 -0
  37. package/lib/Icons/MatrixResources/Page.js +30 -0
  38. package/lib/Icons/MatrixResources/Pdf.d.ts +15 -0
  39. package/lib/Icons/MatrixResources/Pdf.js +31 -0
  40. package/lib/Icons/MatrixResources/Powerpoint.d.ts +15 -0
  41. package/lib/Icons/MatrixResources/Powerpoint.js +28 -0
  42. package/lib/Icons/MatrixResources/Site.d.ts +15 -0
  43. package/lib/Icons/MatrixResources/Site.js +30 -0
  44. package/lib/Icons/MatrixResources/Video.d.ts +15 -0
  45. package/lib/Icons/MatrixResources/Video.js +24 -0
  46. package/lib/Icons/MatrixResources/Word.d.ts +17 -0
  47. package/lib/Icons/MatrixResources/Word.js +28 -0
  48. package/lib/Icons/MatrixResources/index.d.ts +11 -0
  49. package/lib/Icons/MatrixResources/index.js +29 -0
  50. package/lib/Modal/Modal.d.ts +11 -0
  51. package/lib/Modal/Modal.js +46 -0
  52. package/lib/Modal/ModalOpeningButton.d.ts +10 -0
  53. package/lib/Modal/ModalOpeningButton.js +13 -0
  54. package/lib/Modal/ModalTrigger.d.ts +9 -0
  55. package/lib/Modal/ModalTrigger.js +24 -0
  56. package/lib/PreviewPanel/PreviewModal.d.ts +11 -0
  57. package/lib/PreviewPanel/PreviewModal.js +81 -0
  58. package/lib/PreviewPanel/PreviewPanel.d.ts +16 -0
  59. package/lib/PreviewPanel/PreviewPanel.js +87 -0
  60. package/lib/PreviewPanel/details/MatrixResource.d.ts +12 -0
  61. package/lib/PreviewPanel/details/MatrixResource.js +41 -0
  62. package/lib/ResourceBreadcrumb/ResourceBreadcrumb.d.ts +9 -0
  63. package/lib/ResourceBreadcrumb/ResourceBreadcrumb.js +20 -0
  64. package/lib/ResourceItem/ResourceItem.d.ts +19 -0
  65. package/lib/ResourceItem/ResourceItem.js +26 -0
  66. package/lib/ResourceList/ResourceList.d.ts +14 -0
  67. package/lib/ResourceList/ResourceList.js +51 -0
  68. package/lib/ResourcePickerContainer/ResourcePickerContainer.d.ts +15 -0
  69. package/lib/ResourcePickerContainer/ResourcePickerContainer.js +145 -0
  70. package/lib/Skeleton/List/SkeletonList.d.ts +6 -0
  71. package/lib/Skeleton/List/SkeletonList.js +13 -0
  72. package/lib/Skeleton/ListItem/SkeletonListItem.d.ts +2 -0
  73. package/lib/Skeleton/ListItem/SkeletonListItem.js +15 -0
  74. package/lib/SourceDropdown/SourceDropdown.d.ts +9 -0
  75. package/lib/SourceDropdown/SourceDropdown.js +106 -0
  76. package/lib/SourceList/SourceList.d.ts +14 -0
  77. package/lib/SourceList/SourceList.js +58 -0
  78. package/lib/Spinner/Spinner.d.ts +8 -0
  79. package/lib/Spinner/Spinner.js +12 -0
  80. package/lib/index.css +968 -0
  81. package/lib/index.d.ts +37 -0
  82. package/lib/index.js +15 -0
  83. package/lib/uuid.d.ts +1 -0
  84. package/lib/uuid.js +8 -0
  85. package/package.json +74 -0
  86. package/postcss.config.js +11 -0
  87. package/src/Icons/Generics/ArrowDown.tsx +27 -0
  88. package/src/Icons/Generics/ArrowRight.tsx +27 -0
  89. package/src/Icons/Generics/Close.tsx +26 -0
  90. package/src/Icons/Generics/GenericIconMap.ts +14 -0
  91. package/src/Icons/Generics/ResourceSelect.tsx +40 -0
  92. package/src/Icons/Generics/Root.tsx +24 -0
  93. package/src/Icons/Generics/Selected.tsx +27 -0
  94. package/src/Icons/Generics/index.tsx +7 -0
  95. package/src/Icons/Icon.spec.tsx +62 -0
  96. package/src/Icons/Icon.stories.tsx +105 -0
  97. package/src/Icons/Icon.tsx +61 -0
  98. package/src/Icons/MatrixResources/Audio.tsx +30 -0
  99. package/src/Icons/MatrixResources/Excel.tsx +29 -0
  100. package/src/Icons/MatrixResources/Folder.tsx +29 -0
  101. package/src/Icons/MatrixResources/GenericFile.tsx +34 -0
  102. package/src/Icons/MatrixResources/Image.tsx +36 -0
  103. package/src/Icons/MatrixResources/MatrixResourceMap.ts +19 -0
  104. package/src/Icons/MatrixResources/Page.tsx +33 -0
  105. package/src/Icons/MatrixResources/Pdf.tsx +34 -0
  106. package/src/Icons/MatrixResources/Powerpoint.tsx +34 -0
  107. package/src/Icons/MatrixResources/Site.tsx +37 -0
  108. package/src/Icons/MatrixResources/Video.tsx +27 -0
  109. package/src/Icons/MatrixResources/Word.tsx +30 -0
  110. package/src/Icons/MatrixResources/index.tsx +12 -0
  111. package/src/Modal/Modal.spec.tsx +244 -0
  112. package/src/Modal/Modal.tsx +58 -0
  113. package/src/Modal/ModalContainer.stories.tsx +33 -0
  114. package/src/Modal/ModalOpeningButton.tsx +20 -0
  115. package/src/Modal/ModalTrigger.tsx +45 -0
  116. package/src/PreviewPanel/PreviewModal.spec.tsx +164 -0
  117. package/src/PreviewPanel/PreviewModal.tsx +92 -0
  118. package/src/PreviewPanel/PreviewPanel.spec.tsx +197 -0
  119. package/src/PreviewPanel/PreviewPanel.stories.tsx +61 -0
  120. package/src/PreviewPanel/PreviewPanel.tsx +123 -0
  121. package/src/PreviewPanel/details/MatrixResource.tsx +59 -0
  122. package/src/ResourceBreadcrumb/ResourceBreadcrumb.spec.tsx +76 -0
  123. package/src/ResourceBreadcrumb/ResourceBreadcrumb.stories.tsx +24 -0
  124. package/src/ResourceBreadcrumb/ResourceBreadcrumb.tsx +39 -0
  125. package/src/ResourceBreadcrumb/sample-hierarchy.json +23 -0
  126. package/src/ResourceItem/ResourceItem.spec.tsx +69 -0
  127. package/src/ResourceItem/ResourceItem.tsx +82 -0
  128. package/src/ResourceList/ResourceList.spec.tsx +196 -0
  129. package/src/ResourceList/ResourceList.stories.tsx +40 -0
  130. package/src/ResourceList/ResourceList.tsx +74 -0
  131. package/src/ResourceList/sample-resources.json +75 -0
  132. package/src/ResourcePickerContainer/ResourcePickerContainer.spec.tsx +706 -0
  133. package/src/ResourcePickerContainer/ResourcePickerContainer.stories.tsx +56 -0
  134. package/src/ResourcePickerContainer/ResourcePickerContainer.tsx +224 -0
  135. package/src/Skeleton/List/SkeletonList.spec.tsx +18 -0
  136. package/src/Skeleton/List/SkeletonList.stories.tsx +15 -0
  137. package/src/Skeleton/List/SkeletonList.tsx +16 -0
  138. package/src/Skeleton/ListItem/SkeletonListItem.stories.tsx +15 -0
  139. package/src/Skeleton/ListItem/SkeletonListItem.tsx +14 -0
  140. package/src/SourceDropdown/SourceDropdown.spec.tsx +263 -0
  141. package/src/SourceDropdown/SourceDropdown.stories.tsx +36 -0
  142. package/src/SourceDropdown/SourceDropdown.tsx +175 -0
  143. package/src/SourceDropdown/sample-sources.json +110 -0
  144. package/src/SourceList/SourceList.spec.tsx +224 -0
  145. package/src/SourceList/SourceList.stories.tsx +40 -0
  146. package/src/SourceList/SourceList.tsx +93 -0
  147. package/src/SourceList/sample-sources.json +110 -0
  148. package/src/Spinner/Spinner.spec.tsx +18 -0
  149. package/src/Spinner/Spinner.stories.tsx +26 -0
  150. package/src/Spinner/Spinner.tsx +18 -0
  151. package/src/Spinner/_spinner.scss +11 -0
  152. package/src/__mocks__/JestHelpers.ts +65 -0
  153. package/src/__mocks__/jestHelpers.spec.ts +38 -0
  154. package/src/__mocks__/styleMock.ts +1 -0
  155. package/src/index.scss +7 -0
  156. package/src/index.stories.tsx +70 -0
  157. package/src/index.tsx +71 -0
  158. package/src/uuid.ts +7 -0
  159. package/tailwind.config.cjs +84 -0
  160. package/tsconfig.json +22 -0
@@ -0,0 +1,706 @@
1
+ /* eslint-disable @typescript-eslint/no-empty-function */
2
+ import React from 'react';
3
+ import { screen, render, waitFor, within } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+
6
+ import { Context as ResponsiveContext } from 'react-responsive';
7
+ import { OverlayTriggerState } from 'react-stately';
8
+
9
+ import type { Hierarchy } from '../index';
10
+ import ResourcePickerContainer from './ResourcePickerContainer';
11
+
12
+ import SourceList, { SourceListProps } from '../SourceList/SourceList'; // Import Functional Component
13
+ jest.mock('../SourceList/SourceList'); // Mock the Functional Component
14
+ const ActualSourceList = jest.requireActual('../SourceList/SourceList').default; // Grab the real copy of it as normally we don't want the mock
15
+ const MockSourceList = SourceList as jest.MockedFunction<typeof SourceList>; // Cast the mocked function so TS stops complaining
16
+ MockSourceList.mockImplementation(ActualSourceList); // Return the actual function unless overridden
17
+
18
+ import ResourceList, { ResourceListProps } from '../ResourceList/ResourceList'; // Import Functional Component
19
+ jest.mock('../ResourceList/ResourceList'); // Mock the Functional Component
20
+ const ActualResourceList = jest.requireActual('../ResourceList/ResourceList').default; // Grab the real copy of it as normally we don't want the mock
21
+ const MockResourceList = ResourceList as jest.MockedFunction<typeof ResourceList>; // Cast the mocked function so TS stops complaining
22
+ MockResourceList.mockImplementation(ActualResourceList); // Return the actual function unless overridden
23
+
24
+ import PreviewPanel, { PreviewPanelProps } from '../PreviewPanel/PreviewPanel'; // Import Functional Component
25
+ jest.mock('../PreviewPanel/PreviewPanel'); // Mock the Functional Component
26
+ const ActualPreviewPanel = jest.requireActual('../PreviewPanel/PreviewPanel').default; // Grab the real copy of it as normally we don't want the mock
27
+ const MockPreviewPanel = PreviewPanel as jest.MockedFunction<typeof PreviewPanel>; // Cast the mocked function so TS stops complaining
28
+ MockPreviewPanel.mockImplementation(ActualPreviewPanel); // Return the actual function unless overridden
29
+
30
+ import ResourceBreadcrumb, { ResourceBreadcrumbProps } from '../ResourceBreadcrumb/ResourceBreadcrumb'; // Import Functional Component
31
+ jest.mock('../ResourceBreadcrumb/ResourceBreadcrumb'); // Mock the Functional Component
32
+ const ActualResourceBreadcrumb = jest.requireActual('../ResourceBreadcrumb/ResourceBreadcrumb').default; // Grab the real copy of it as normally we don't want the mock
33
+ const MockResourceBreadcrumb = ResourceBreadcrumb as jest.MockedFunction<typeof ResourceBreadcrumb>; // Cast the mocked function so TS stops complaining
34
+ MockResourceBreadcrumb.mockImplementation(ActualResourceBreadcrumb); // Return the actual function unless overridden
35
+
36
+ const baseProps = {
37
+ title: 'Testing',
38
+ titleAriaProps: {},
39
+ allowedTypes: undefined,
40
+ onClose: () => {},
41
+ onRequestSources: () => {
42
+ return Promise.resolve([
43
+ {
44
+ id: '1',
45
+ name: 'Test system',
46
+ nodes: [
47
+ {
48
+ id: {
49
+ id: '1',
50
+ source: '1',
51
+ },
52
+ type: 'site',
53
+ selected: false,
54
+ label: 'Test Website',
55
+ childCount: 21,
56
+ },
57
+ ],
58
+ },
59
+ ]);
60
+ },
61
+ onRequestChildren: () => {
62
+ return Promise.resolve([
63
+ {
64
+ id: {
65
+ id: '1',
66
+ source: '1',
67
+ },
68
+ type: 'page',
69
+ selected: false,
70
+ label: 'Test Page',
71
+ childCount: 0,
72
+ },
73
+ ]);
74
+ },
75
+ onRequestResource: () => {
76
+ return Promise.resolve({
77
+ type: 'page',
78
+ name: 'Products',
79
+ properties: new Map([
80
+ ['assetId', '12345'],
81
+ ['status', 'UnderConstruction'],
82
+ ]),
83
+ });
84
+ },
85
+ onChange: () => {},
86
+ };
87
+
88
+ describe('ResourcePickerContainer', () => {
89
+ it('Queries onRequestSources for source list on startup', async () => {
90
+ const onRequestSources = jest.fn(() => {
91
+ return Promise.resolve([]);
92
+ });
93
+
94
+ const { getByLabelText } = render(<ResourcePickerContainer {...baseProps} onRequestSources={onRequestSources} />);
95
+ await waitFor(() => {
96
+ expect(getByLabelText('Source list')).toBeInTheDocument();
97
+ });
98
+
99
+ expect(onRequestSources).toHaveBeenCalled();
100
+ });
101
+
102
+ it('Renders provided sources from onRequestSources', async () => {
103
+ const onRequestSources = jest.fn(() => {
104
+ return Promise.resolve([
105
+ {
106
+ id: '1',
107
+ name: 'Test system 1',
108
+ nodes: [
109
+ {
110
+ id: {
111
+ id: '1',
112
+ source: '1',
113
+ },
114
+ type: 'site',
115
+ selected: false,
116
+ label: 'Test Website 1',
117
+ childCount: 21,
118
+ },
119
+ ],
120
+ },
121
+ {
122
+ id: '2',
123
+ name: 'Test system 2',
124
+ nodes: [
125
+ {
126
+ id: {
127
+ id: '1',
128
+ source: '2',
129
+ },
130
+ type: 'site',
131
+ selected: false,
132
+ label: 'Test Website 2',
133
+ childCount: 21,
134
+ },
135
+ {
136
+ id: {
137
+ id: '2',
138
+ source: '2',
139
+ },
140
+ type: 'site',
141
+ selected: false,
142
+ label: 'Test Website 3',
143
+ childCount: 21,
144
+ },
145
+ ],
146
+ },
147
+ ]);
148
+ });
149
+
150
+ const { getAllByText } = render(<ResourcePickerContainer {...baseProps} onRequestSources={onRequestSources} />);
151
+ await waitFor(() => {
152
+ expect(getAllByText('Test system 1')[0]).toBeInTheDocument();
153
+ });
154
+
155
+ await waitFor(() => {
156
+ const sourceList = screen.getByLabelText('Source list');
157
+ expect(within(sourceList).getByText('Test system 1')).toBeTruthy();
158
+ expect(within(sourceList).getByText('Test Website 1')).toBeTruthy();
159
+
160
+ expect(within(sourceList).getByText('Test system 2')).toBeTruthy();
161
+ expect(within(sourceList).getByText('Test Website 2')).toBeTruthy();
162
+ expect(within(sourceList).getByText('Test Website 3')).toBeTruthy();
163
+ });
164
+ });
165
+
166
+ it('Selecting a child count drills down', async () => {
167
+ const onRequestChildren = jest.fn(() => {
168
+ return Promise.resolve([]);
169
+ });
170
+
171
+ // Component has finished initial render
172
+ const { getAllByText } = render(<ResourcePickerContainer {...baseProps} onRequestChildren={onRequestChildren} />);
173
+ await waitFor(() => {
174
+ expect(getAllByText('Test system')[0]).toBeInTheDocument();
175
+ });
176
+
177
+ // Click the drill down counter
178
+ const user = userEvent.setup();
179
+ user.click(screen.getByRole('button', { name: 'Drill down to Test Website children' }));
180
+
181
+ await waitFor(() => {
182
+ expect(onRequestChildren).toHaveBeenCalled();
183
+ });
184
+ });
185
+
186
+ it('Drill down renders provided resources from onRequestChildren', async () => {
187
+ const onRequestChildren = jest.fn(() => {
188
+ return Promise.resolve([
189
+ {
190
+ id: {
191
+ id: '1',
192
+ source: '1',
193
+ },
194
+ type: 'page',
195
+ selected: false,
196
+ label: 'Test Page 1',
197
+ childCount: 0,
198
+ },
199
+ {
200
+ id: {
201
+ id: '2',
202
+ source: '1',
203
+ },
204
+ type: 'page',
205
+ selected: false,
206
+ label: 'Test Page 2',
207
+ childCount: 0,
208
+ },
209
+ {
210
+ id: {
211
+ id: '3',
212
+ source: '1',
213
+ },
214
+ type: 'folder',
215
+ selected: false,
216
+ label: 'Test Page 3',
217
+ childCount: 31,
218
+ },
219
+ ]);
220
+ });
221
+
222
+ // Component has finished initial render
223
+ const { getAllByText } = render(<ResourcePickerContainer {...baseProps} onRequestChildren={onRequestChildren} />);
224
+ await waitFor(() => {
225
+ expect(getAllByText('Test system')[0]).toBeInTheDocument();
226
+ });
227
+
228
+ // Click the drill down counter
229
+ const user = userEvent.setup();
230
+ user.click(screen.getByRole('button', { name: 'Drill down to Test Website children' }));
231
+
232
+ await waitFor(() => {
233
+ expect(screen.getByText('Test Page 1')).toBeTruthy();
234
+ expect(screen.getByText('Test Page 2')).toBeTruthy();
235
+ expect(screen.getByText('Test Page 3')).toBeTruthy();
236
+ });
237
+ });
238
+
239
+ it('Selecting a node shows its properties in the details panel', async () => {
240
+ const { getByText, getAllByText } = render(<ResourcePickerContainer {...baseProps} />);
241
+ await waitFor(() => {
242
+ expect(getAllByText('Test system')[0]).toBeInTheDocument();
243
+ });
244
+
245
+ // Is on base page
246
+ expect(screen.getByLabelText('Source list')).toBeTruthy();
247
+
248
+ // Click the drill down counter
249
+ const user = userEvent.setup();
250
+ user.click(screen.getByRole('button', { name: 'site Test Website' }));
251
+
252
+ await waitFor(() => {
253
+ expect(getByText('Products')).toBeInTheDocument();
254
+ });
255
+
256
+ await waitFor(() => {
257
+ expect(screen.getByText('Products')).toBeTruthy();
258
+ expect(screen.getByText('page')).toBeTruthy();
259
+ expect(screen.getByText('#12345')).toBeTruthy();
260
+ });
261
+ });
262
+
263
+ it('Mobile: Selecting a node shows its properties in the details panel', async () => {
264
+ const { getByText, getAllByText } = render(
265
+ <ResponsiveContext.Provider value={{ width: 360 }}>
266
+ <ResourcePickerContainer {...baseProps} />
267
+ </ResponsiveContext.Provider>,
268
+ );
269
+ await waitFor(() => {
270
+ expect(getAllByText('Test system')[0]).toBeInTheDocument();
271
+ });
272
+
273
+ // Click the drill down counter
274
+ const user = userEvent.setup();
275
+ user.click(screen.getByRole('button', { name: 'site Test Website' }));
276
+
277
+ await waitFor(() => {
278
+ expect(getByText('Products')).toBeInTheDocument();
279
+ });
280
+
281
+ await waitFor(() => {
282
+ expect(screen.getByText('Products')).toBeTruthy();
283
+ expect(screen.getByText('page')).toBeTruthy();
284
+ expect(screen.getByText('#12345')).toBeTruthy();
285
+ });
286
+ });
287
+
288
+ it('Closing the details panel deselects the node', async () => {
289
+ const { getByText, getAllByText } = render(
290
+ <ResponsiveContext.Provider value={{ width: 360 }}>
291
+ <ResourcePickerContainer {...baseProps} />
292
+ </ResponsiveContext.Provider>,
293
+ );
294
+ await waitFor(() => {
295
+ expect(getAllByText('Test system')[0]).toBeInTheDocument();
296
+ });
297
+
298
+ // Click the drill down counter
299
+ const user = userEvent.setup();
300
+ user.click(screen.getByRole('button', { name: 'site Test Website' }));
301
+ await waitFor(() => {
302
+ expect(getByText('Products')).toBeInTheDocument();
303
+ });
304
+
305
+ user.click(screen.getByRole('button', { name: 'Close details' }));
306
+
307
+ await waitFor(() => {
308
+ // Is now on child page
309
+ expect(screen.queryByText('Products')).toBeFalsy();
310
+ expect(screen.queryByText('page')).toBeFalsy();
311
+ expect(screen.queryByText('#12345')).toBeFalsy();
312
+ });
313
+ });
314
+
315
+ it('Passes modal state down to child components: SourceList', async () => {
316
+ let mockModalState: OverlayTriggerState | null = null;
317
+
318
+ MockSourceList.mockImplementation((args: SourceListProps) => {
319
+ mockModalState = args.previewModalState;
320
+ return ActualSourceList(args);
321
+ });
322
+
323
+ render(<ResourcePickerContainer {...baseProps} />);
324
+
325
+ expect(mockModalState).toEqual(
326
+ expect.objectContaining({
327
+ close: expect.any(Function),
328
+ isOpen: expect.any(Boolean),
329
+ open: expect.any(Function),
330
+ setOpen: expect.any(Function),
331
+ toggle: expect.any(Function),
332
+ }),
333
+ );
334
+ });
335
+
336
+ it('Passes modal state down to child components: ResourceList', async () => {
337
+ let mockModalState: OverlayTriggerState | null = null;
338
+
339
+ MockResourceList.mockImplementation((args: ResourceListProps) => {
340
+ mockModalState = args.previewModalState;
341
+ return ActualResourceList(args);
342
+ });
343
+
344
+ const { getByText, getAllByText } = render(<ResourcePickerContainer {...baseProps} />);
345
+ await waitFor(() => {
346
+ expect(getAllByText('Test system')[0]).toBeInTheDocument();
347
+ });
348
+
349
+ // Click the drill down counter
350
+ const user = userEvent.setup();
351
+ user.click(screen.getByRole('button', { name: 'Drill down to Test Website children' }));
352
+ await waitFor(() => {
353
+ expect(getByText('Test Page')).toBeInTheDocument();
354
+ });
355
+
356
+ expect(mockModalState).toEqual(
357
+ expect.objectContaining({
358
+ close: expect.any(Function),
359
+ isOpen: expect.any(Boolean),
360
+ open: expect.any(Function),
361
+ setOpen: expect.any(Function),
362
+ toggle: expect.any(Function),
363
+ }),
364
+ );
365
+ });
366
+
367
+ it('Passes modal state down to child components: Details Panel', async () => {
368
+ let mockModalState: OverlayTriggerState | null = null;
369
+
370
+ MockPreviewPanel.mockImplementation((args: PreviewPanelProps) => {
371
+ mockModalState = args.modalState;
372
+ return ActualPreviewPanel(args);
373
+ });
374
+
375
+ const { getAllByText } = render(<ResourcePickerContainer {...baseProps} />);
376
+ await waitFor(() => {
377
+ expect(getAllByText('Test system')[0]).toBeInTheDocument();
378
+ });
379
+
380
+ const user = userEvent.setup();
381
+ user.click(screen.getByRole('button', { name: 'site Test Website' }));
382
+
383
+ expect(mockModalState).toEqual(
384
+ expect.objectContaining({
385
+ close: expect.any(Function),
386
+ isOpen: expect.any(Boolean),
387
+ open: expect.any(Function),
388
+ setOpen: expect.any(Function),
389
+ toggle: expect.any(Function),
390
+ }),
391
+ );
392
+ });
393
+
394
+ it('Breadcrumb hierarchy is added to on drill down', async () => {
395
+ let mockHierarchy: Array<Hierarchy> = [];
396
+
397
+ MockResourceBreadcrumb.mockImplementation((args: ResourceBreadcrumbProps) => {
398
+ mockHierarchy = args.hierarchy;
399
+ return ActualResourceBreadcrumb(args);
400
+ });
401
+
402
+ const { getByLabelText, getAllByText } = render(<ResourcePickerContainer {...baseProps} />);
403
+ await waitFor(() => {
404
+ expect(getAllByText('Test system')[0]).toBeInTheDocument();
405
+ });
406
+
407
+ const user = userEvent.setup();
408
+ user.click(screen.getByRole('button', { name: 'Drill down to Test Website children' }));
409
+
410
+ // Breadcrumb is showing
411
+ await waitFor(() => {
412
+ expect(getByLabelText('Resource breadcrumb')).toBeInTheDocument();
413
+ });
414
+
415
+ // Expect the clicked child was added to breadcrumb
416
+ expect(mockHierarchy).toEqual([{ id: { id: '1', source: '1' }, label: 'Test Website' }]);
417
+ });
418
+
419
+ it('Multiple hierarchy is tracked on on drill down', async () => {
420
+ let mockHierarchy: Array<Hierarchy> = [];
421
+
422
+ MockResourceBreadcrumb.mockImplementation((args: ResourceBreadcrumbProps) => {
423
+ mockHierarchy = args.hierarchy;
424
+ return ActualResourceBreadcrumb(args);
425
+ });
426
+
427
+ const onRequestChildren = jest
428
+ .fn()
429
+ .mockImplementationOnce(() => {
430
+ return Promise.resolve([
431
+ {
432
+ id: {
433
+ id: '11',
434
+ source: '1',
435
+ },
436
+ type: 'page',
437
+ selected: false,
438
+ label: 'Test Page',
439
+ childCount: 4,
440
+ },
441
+ ]);
442
+ })
443
+ .mockImplementationOnce(() => {
444
+ return Promise.resolve([
445
+ {
446
+ id: {
447
+ id: '111',
448
+ source: '1',
449
+ },
450
+ type: 'page',
451
+ selected: false,
452
+ label: 'Second Page',
453
+ childCount: 0,
454
+ },
455
+ ]);
456
+ });
457
+
458
+ const { getByLabelText, getByText, getAllByText } = render(
459
+ <ResourcePickerContainer {...baseProps} onRequestChildren={onRequestChildren} />,
460
+ );
461
+ await waitFor(() => {
462
+ expect(getAllByText('Test system')[0]).toBeInTheDocument();
463
+ });
464
+
465
+ const user = userEvent.setup();
466
+ user.click(screen.getByRole('button', { name: 'Drill down to Test Website children' }));
467
+
468
+ // Breadcrumb is showing
469
+ await waitFor(() => {
470
+ expect(getByLabelText('Resource breadcrumb')).toBeInTheDocument();
471
+ });
472
+
473
+ // Drill down again
474
+ user.click(screen.getByRole('button', { name: 'Drill down to Test Page children' }));
475
+
476
+ await waitFor(() => {
477
+ expect(getByText('Second Page')).toBeInTheDocument();
478
+ });
479
+
480
+ // Expect the clicked child was added to breadcrumb
481
+ expect(mockHierarchy).toEqual([
482
+ {
483
+ id: {
484
+ id: '1',
485
+ source: '1',
486
+ },
487
+ label: 'Test Website',
488
+ },
489
+ {
490
+ id: {
491
+ id: '11',
492
+ source: '1',
493
+ },
494
+ label: 'Test Page',
495
+ },
496
+ ]);
497
+ });
498
+
499
+ it('Can track multiple hierarchy jumping back', async () => {
500
+ let mockHierarchy: Array<Hierarchy> = [];
501
+
502
+ MockResourceBreadcrumb.mockImplementation((args: ResourceBreadcrumbProps) => {
503
+ mockHierarchy = args.hierarchy;
504
+ return ActualResourceBreadcrumb(args);
505
+ });
506
+
507
+ const onRequestChildren = jest
508
+ .fn()
509
+ .mockImplementationOnce(() => {
510
+ return Promise.resolve([
511
+ {
512
+ id: {
513
+ id: '11',
514
+ source: '1',
515
+ },
516
+ type: 'page',
517
+ selected: false,
518
+ label: 'Test Page',
519
+ childCount: 4,
520
+ },
521
+ ]);
522
+ })
523
+ .mockImplementationOnce(() => {
524
+ return Promise.resolve([
525
+ {
526
+ id: {
527
+ id: '111',
528
+ source: '1',
529
+ },
530
+ type: 'page',
531
+ selected: false,
532
+ label: 'Second Page',
533
+ childCount: 0,
534
+ },
535
+ ]);
536
+ })
537
+ .mockImplementationOnce(() => {
538
+ return Promise.resolve([
539
+ {
540
+ id: {
541
+ id: '11',
542
+ source: '1',
543
+ },
544
+ type: 'page',
545
+ selected: false,
546
+ label: 'Test Page',
547
+ childCount: 4,
548
+ },
549
+ ]);
550
+ });
551
+
552
+ const { getByLabelText, getByText, getAllByText, queryByText } = render(
553
+ <ResourcePickerContainer {...baseProps} onRequestChildren={onRequestChildren} />,
554
+ );
555
+ await waitFor(() => {
556
+ expect(getAllByText('Test system')[0]).toBeInTheDocument();
557
+ });
558
+
559
+ const user = userEvent.setup();
560
+ user.click(screen.getByRole('button', { name: 'Drill down to Test Website children' }));
561
+
562
+ // Breadcrumb is showing
563
+ await waitFor(() => {
564
+ expect(getByLabelText('Resource breadcrumb')).toBeInTheDocument();
565
+ });
566
+
567
+ // Drill down again
568
+ user.click(screen.getByRole('button', { name: 'Drill down to Test Page children' }));
569
+
570
+ await waitFor(() => {
571
+ expect(getByText('Second Page')).toBeInTheDocument();
572
+ });
573
+
574
+ // Click on previous breadcrumb item
575
+ const breadcrumb = screen.getByLabelText('Resource breadcrumb');
576
+ user.click(within(breadcrumb).getByRole('button', { name: 'Test Website' }));
577
+
578
+ await waitFor(() => {
579
+ expect(queryByText('Second Page')).not.toBeInTheDocument();
580
+ });
581
+
582
+ // Expect the clicked child was added to breadcrumb
583
+ expect(mockHierarchy).toEqual([
584
+ {
585
+ id: {
586
+ id: '1',
587
+ source: '1',
588
+ },
589
+ label: 'Test Website',
590
+ },
591
+ ]);
592
+ });
593
+
594
+ it('Multiple hierarchy can handle reset to new source', async () => {
595
+ let mockHierarchy: Array<Hierarchy> = [];
596
+
597
+ MockResourceBreadcrumb.mockImplementation((args: ResourceBreadcrumbProps) => {
598
+ mockHierarchy = args.hierarchy;
599
+ return ActualResourceBreadcrumb(args);
600
+ });
601
+
602
+ const onRequestChildren = jest
603
+ .fn()
604
+ .mockImplementationOnce(() => {
605
+ return Promise.resolve([
606
+ {
607
+ id: {
608
+ id: '11',
609
+ source: '1',
610
+ },
611
+ type: 'page',
612
+ selected: false,
613
+ label: 'Test Page',
614
+ childCount: 4,
615
+ },
616
+ ]);
617
+ })
618
+ .mockImplementationOnce(() => {
619
+ return Promise.resolve([
620
+ {
621
+ id: {
622
+ id: '111',
623
+ source: '1',
624
+ },
625
+ type: 'page',
626
+ selected: false,
627
+ label: 'Second Page',
628
+ childCount: 0,
629
+ },
630
+ ]);
631
+ });
632
+
633
+ const onRequestSources = jest.fn().mockImplementation(() => {
634
+ return Promise.resolve([
635
+ {
636
+ id: '1',
637
+ name: 'Test system',
638
+ nodes: [
639
+ {
640
+ id: {
641
+ id: '1',
642
+ source: '1',
643
+ },
644
+ type: 'site',
645
+ selected: false,
646
+ label: 'Test Website',
647
+ childCount: 21,
648
+ },
649
+ {
650
+ id: {
651
+ id: '2',
652
+ source: '1',
653
+ },
654
+ type: 'site',
655
+ selected: false,
656
+ label: 'Second Website',
657
+ childCount: 21,
658
+ },
659
+ ],
660
+ },
661
+ ]);
662
+ });
663
+
664
+ const { getByLabelText, getAllByText, getByText, getByRole } = render(
665
+ <ResourcePickerContainer
666
+ {...baseProps}
667
+ onRequestSources={onRequestSources}
668
+ onRequestChildren={onRequestChildren}
669
+ />,
670
+ );
671
+ await waitFor(() => {
672
+ expect(getAllByText('Test system')[0]).toBeInTheDocument();
673
+ });
674
+
675
+ const user = userEvent.setup();
676
+ user.click(screen.getByRole('button', { name: 'Drill down to Test Website children' }));
677
+
678
+ // Breadcrumb is showing
679
+ await waitFor(() => {
680
+ expect(getByLabelText('Resource breadcrumb')).toBeInTheDocument();
681
+ });
682
+
683
+ user.click(screen.getByRole('button', { name: 'Source quick select' }));
684
+
685
+ await waitFor(() => {
686
+ expect(getByRole('button', { name: 'site Second Website' })).toBeInTheDocument();
687
+ });
688
+
689
+ user.click(getByRole('button', { name: 'site Second Website' }));
690
+
691
+ await waitFor(() => {
692
+ expect(getByText('Second Page')).toBeInTheDocument();
693
+ });
694
+
695
+ // Expect the clicked child was added to breadcrumb
696
+ expect(mockHierarchy).toEqual([
697
+ {
698
+ id: {
699
+ id: '2',
700
+ source: '1',
701
+ },
702
+ label: 'Second Website',
703
+ },
704
+ ]);
705
+ });
706
+ });