@squiz/resource-browser 1.69.2 → 2.1.9-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/CHANGELOG.md +89 -38
  2. package/LICENSE.md +15 -0
  3. package/README.md +9 -0
  4. package/jest.config.ts +22 -21
  5. package/lib/Hooks/useSelectedState.d.ts +15 -0
  6. package/lib/Hooks/useSelectedState.js +16 -0
  7. package/lib/Hooks/useSources.d.ts +6 -6
  8. package/lib/Hooks/useSources.js +26 -1
  9. package/lib/MainContainer/MainContainer.d.ts +17 -0
  10. package/lib/MainContainer/MainContainer.js +61 -0
  11. package/lib/Plugin/Plugin.d.ts +13 -0
  12. package/lib/Plugin/Plugin.js +17 -0
  13. package/lib/ResourceBrowserContext/ResourceBrowserContext.d.ts +2 -3
  14. package/lib/ResourceBrowserContext/ResourceBrowserContext.js +4 -17
  15. package/lib/ResourceBrowserInput/ResourceBrowserInput.d.ts +24 -0
  16. package/lib/ResourceBrowserInput/ResourceBrowserInput.js +16 -0
  17. package/lib/ResourcePicker/ResourcePicker.d.ts +6 -4
  18. package/lib/ResourcePicker/ResourcePicker.js +14 -8
  19. package/lib/ResourcePicker/States/Selected.d.ts +10 -4
  20. package/lib/ResourcePicker/States/Selected.js +11 -32
  21. package/lib/SourceDropdown/SourceDropdown.d.ts +5 -11
  22. package/lib/SourceDropdown/SourceDropdown.js +20 -99
  23. package/lib/SourceList/SourceList.d.ts +5 -16
  24. package/lib/SourceList/SourceList.js +14 -75
  25. package/lib/index.css +42 -202
  26. package/lib/index.d.ts +7 -7
  27. package/lib/index.js +69 -13
  28. package/lib/types.d.ts +41 -59
  29. package/package.json +82 -80
  30. package/src/Hooks/useSelectedState.spec.ts +46 -0
  31. package/src/Hooks/useSelectedState.ts +22 -0
  32. package/src/Hooks/useSources.spec.ts +60 -13
  33. package/src/Hooks/useSources.ts +35 -5
  34. package/src/Icons/CircledLoopIcon.tsx +8 -8
  35. package/src/MainContainer/MainContainer.spec.tsx +203 -0
  36. package/src/MainContainer/MainContainer.stories.tsx +62 -0
  37. package/src/MainContainer/MainContainer.tsx +101 -0
  38. package/src/Plugin/Plugin.spec.tsx +46 -0
  39. package/src/Plugin/Plugin.tsx +20 -0
  40. package/src/ResourceBrowserContext/ResourceBrowserContext.spec.tsx +65 -106
  41. package/src/ResourceBrowserContext/ResourceBrowserContext.tsx +24 -39
  42. package/src/ResourceBrowserInput/ResourceBrowserInput.spec.tsx +192 -0
  43. package/src/ResourceBrowserInput/ResourceBrowserInput.tsx +81 -0
  44. package/src/ResourcePicker/ResourcePicker.spec.tsx +159 -116
  45. package/src/ResourcePicker/ResourcePicker.stories.tsx +28 -24
  46. package/src/ResourcePicker/ResourcePicker.tsx +79 -59
  47. package/src/ResourcePicker/States/Error.tsx +8 -8
  48. package/src/ResourcePicker/States/Loading.tsx +3 -3
  49. package/src/ResourcePicker/States/Selected.tsx +66 -73
  50. package/src/ResourcePicker/mock-image-resource.json +25 -47
  51. package/src/ResourcePicker/mock-resource.json +11 -13
  52. package/src/ResourcePicker/resource-picker.scss +13 -13
  53. package/src/SourceDropdown/SourceDropdown.spec.tsx +65 -391
  54. package/src/SourceDropdown/SourceDropdown.stories.tsx +21 -24
  55. package/src/SourceDropdown/SourceDropdown.tsx +80 -258
  56. package/src/SourceList/SourceList.spec.tsx +37 -430
  57. package/src/SourceList/SourceList.stories.tsx +17 -37
  58. package/src/SourceList/SourceList.tsx +28 -155
  59. package/src/__mocks__/MockModels.ts +56 -25
  60. package/src/__mocks__/PluginExample.tsx +98 -0
  61. package/src/__mocks__/StorybookHelpers.tsx +141 -0
  62. package/src/__mocks__/renderWithContext.tsx +14 -18
  63. package/src/__mocks__/sample-sources.json +32 -0
  64. package/src/index.scss +18 -8
  65. package/src/index.spec.tsx +277 -99
  66. package/src/index.stories.tsx +65 -39
  67. package/src/index.tsx +119 -57
  68. package/src/types.ts +54 -63
  69. package/tailwind.config.cjs +92 -92
  70. package/vite.config.js +12 -12
  71. package/lib/Hooks/useCategorisedSources.d.ts +0 -14
  72. package/lib/Hooks/useCategorisedSources.js +0 -38
  73. package/lib/Hooks/useChildResources.d.ts +0 -16
  74. package/lib/Hooks/useChildResources.js +0 -13
  75. package/lib/Hooks/usePreselectedResourcePath.d.ts +0 -20
  76. package/lib/Hooks/usePreselectedResourcePath.js +0 -31
  77. package/lib/Hooks/useRecentLocations.d.ts +0 -5
  78. package/lib/Hooks/useRecentLocations.js +0 -38
  79. package/lib/Hooks/useRecentResourcesPaths.d.ts +0 -20
  80. package/lib/Hooks/useRecentResourcesPaths.js +0 -30
  81. package/lib/Hooks/useResource.d.ts +0 -28
  82. package/lib/Hooks/useResource.js +0 -25
  83. package/lib/Hooks/useResourcePath.d.ts +0 -16
  84. package/lib/Hooks/useResourcePath.js +0 -64
  85. package/lib/Icons/HistoryIcon.d.ts +0 -4
  86. package/lib/Icons/HistoryIcon.js +0 -13
  87. package/lib/PreviewPanel/PreviewPanel.d.ts +0 -5
  88. package/lib/PreviewPanel/PreviewPanel.js +0 -8
  89. package/lib/PreviewPanel/details/MatrixResource.d.ts +0 -7
  90. package/lib/PreviewPanel/details/MatrixResource.js +0 -35
  91. package/lib/ResourceBreadcrumb/ResourceBreadcrumb.d.ts +0 -9
  92. package/lib/ResourceBreadcrumb/ResourceBreadcrumb.js +0 -54
  93. package/lib/ResourceList/ResourceList.d.ts +0 -18
  94. package/lib/ResourceList/ResourceList.js +0 -49
  95. package/lib/ResourcePickerContainer/ResourcePickerContainer.d.ts +0 -17
  96. package/lib/ResourcePickerContainer/ResourcePickerContainer.js +0 -166
  97. package/lib/StatusIndicator/StatusIndicator.d.ts +0 -8
  98. package/lib/StatusIndicator/StatusIndicator.js +0 -27
  99. package/lib/utils/findBestMatchLineage.d.ts +0 -2
  100. package/lib/utils/findBestMatchLineage.js +0 -28
  101. package/lib/utils/uuid.d.ts +0 -1
  102. package/lib/utils/uuid.js +0 -6
  103. package/src/Hooks/useCategorisedSources.spec.ts +0 -39
  104. package/src/Hooks/useCategorisedSources.ts +0 -46
  105. package/src/Hooks/useChildResources.spec.ts +0 -29
  106. package/src/Hooks/useChildResources.ts +0 -21
  107. package/src/Hooks/usePreselectedResourcePath.ts +0 -54
  108. package/src/Hooks/useRecentLocations.spec.ts +0 -81
  109. package/src/Hooks/useRecentLocations.ts +0 -44
  110. package/src/Hooks/useRecentResourcesPaths.ts +0 -54
  111. package/src/Hooks/useResource.spec.ts +0 -61
  112. package/src/Hooks/useResource.ts +0 -40
  113. package/src/Hooks/useResourcePath.spec.ts +0 -120
  114. package/src/Hooks/useResourcePath.ts +0 -76
  115. package/src/Icons/HistoryIcon.tsx +0 -17
  116. package/src/PreviewPanel/PreviewPanel.spec.tsx +0 -198
  117. package/src/PreviewPanel/PreviewPanel.stories.tsx +0 -76
  118. package/src/PreviewPanel/PreviewPanel.tsx +0 -6
  119. package/src/PreviewPanel/details/MatrixResource.tsx +0 -54
  120. package/src/PreviewPanel/details/matrix-resource.scss +0 -16
  121. package/src/ResourceBreadcrumb/ResourceBreadcrumb.spec.tsx +0 -133
  122. package/src/ResourceBreadcrumb/ResourceBreadcrumb.stories.tsx +0 -24
  123. package/src/ResourceBreadcrumb/ResourceBreadcrumb.tsx +0 -79
  124. package/src/ResourceBreadcrumb/resource-breadcrumb.scss +0 -28
  125. package/src/ResourceBreadcrumb/sample-hierarchy.json +0 -27
  126. package/src/ResourceList/ResourceList.spec.tsx +0 -202
  127. package/src/ResourceList/ResourceList.stories.tsx +0 -40
  128. package/src/ResourceList/ResourceList.tsx +0 -83
  129. package/src/ResourceList/sample-resources.json +0 -851
  130. package/src/ResourcePickerContainer/ResourcePickerContainer.spec.tsx +0 -780
  131. package/src/ResourcePickerContainer/ResourcePickerContainer.stories.tsx +0 -45
  132. package/src/ResourcePickerContainer/ResourcePickerContainer.tsx +0 -290
  133. package/src/SourceList/sample-sources.json +0 -251
  134. package/src/StatusIndicator/StatusIndicator.stories.tsx +0 -83
  135. package/src/StatusIndicator/StatusIndicator.tsx +0 -38
  136. package/src/__mocks__/JestHelpers.ts +0 -65
  137. package/src/__mocks__/StorybookHelpers.ts +0 -128
  138. package/src/__mocks__/jestHelpers.spec.ts +0 -38
  139. package/src/utils/findBestMatchLineage.spec.ts +0 -81
  140. package/src/utils/findBestMatchLineage.ts +0 -30
  141. package/src/utils/uuid.ts +0 -5
@@ -1,780 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-empty-function */
2
- import React from 'react';
3
- import { screen, render, waitFor, within, act } from '@testing-library/react';
4
- import userEvent from '@testing-library/user-event';
5
- import { mockResource, mockSource } from '../__mocks__/MockModels';
6
- import { Resource, Source, Hierarchy, ResourceReference } from '../types';
7
- import { Context as ResponsiveContext } from 'react-responsive';
8
- import { OverlayTriggerState } from 'react-stately';
9
-
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: jest.fn(),
41
- onRequestSources: jest.fn().mockResolvedValue([
42
- mockSource({
43
- id: '1',
44
- name: 'Test system',
45
- nodes: [
46
- {
47
- id: '1',
48
- type: {
49
- code: 'site',
50
- name: 'Site',
51
- },
52
- name: 'Test Website',
53
- childCount: 21,
54
- },
55
- ],
56
- }),
57
- ]),
58
- onRequestResource: jest.fn().mockRejectedValue(new Error('onRequestResource has not been mocked.')),
59
- onRequestChildren: jest.fn().mockResolvedValue([
60
- mockResource({
61
- id: '123',
62
- type: {
63
- code: 'page',
64
- name: 'Mocked Page',
65
- },
66
- name: 'Test Page',
67
- childCount: 0,
68
- }),
69
- ]),
70
- onChange: jest.fn(),
71
- };
72
-
73
- describe('ResourcePickerContainer', () => {
74
- beforeEach(() => {
75
- localStorage.setItem(
76
- 'rb_recent_locations',
77
- JSON.stringify([
78
- { resource: '32', source: '1' },
79
- { resource: '20', source: '1' },
80
- ]),
81
- );
82
- });
83
-
84
- it('Queries onRequestSources for source list on startup', async () => {
85
- const onRequestSources = jest.fn(() => {
86
- return Promise.resolve([]);
87
- });
88
-
89
- const { getByLabelText } = render(<ResourcePickerContainer {...baseProps} onRequestSources={onRequestSources} />);
90
- await waitFor(() => {
91
- expect(getByLabelText('Source list')).toBeInTheDocument();
92
- });
93
-
94
- expect(onRequestSources).toHaveBeenCalled();
95
- });
96
-
97
- it('Renders provided sources from onRequestSources', async () => {
98
- const onRequestSources = jest.fn().mockResolvedValue([
99
- mockSource({
100
- id: '1',
101
- name: 'Test system 1',
102
- nodes: [
103
- {
104
- id: '1',
105
- type: {
106
- code: 'site',
107
- name: 'Site',
108
- },
109
- name: 'Test Website 1',
110
- childCount: 21,
111
- },
112
- ],
113
- }),
114
- mockSource({
115
- id: '2',
116
- name: 'Test system 2',
117
- nodes: [
118
- {
119
- id: '1',
120
- type: {
121
- code: 'site',
122
- name: 'Site',
123
- },
124
- name: 'Test Website 2',
125
- childCount: 21,
126
- },
127
- {
128
- id: '2',
129
- type: {
130
- code: 'site',
131
- name: 'Site',
132
- },
133
- name: 'Test Website 3',
134
- childCount: 21,
135
- },
136
- ],
137
- }),
138
- ]);
139
-
140
- const { getAllByText } = render(<ResourcePickerContainer {...baseProps} onRequestSources={onRequestSources} />);
141
- await waitFor(() => {
142
- expect(getAllByText('Test system 1')[0]).toBeInTheDocument();
143
- });
144
-
145
- await waitFor(() => {
146
- const sourceList = screen.getByLabelText('Source list');
147
- expect(within(sourceList).getByText('Test system 1')).toBeInTheDocument();
148
- expect(within(sourceList).getByText('Test Website 1')).toBeInTheDocument();
149
-
150
- expect(within(sourceList).getByText('Test system 2')).toBeInTheDocument();
151
- expect(within(sourceList).getByText('Test Website 2')).toBeInTheDocument();
152
- expect(within(sourceList).getByText('Test Website 3')).toBeInTheDocument();
153
- });
154
- });
155
-
156
- it('The preselected resource is selected', async () => {
157
- const resources: Record<string, Resource> = {
158
- 1: mockResource({ id: '1', name: 'Root Folder' }),
159
- 100: mockResource({ id: '100', name: 'Source root node #1' }),
160
- 200: mockResource({ id: '200', name: 'Source root node #2' }),
161
- 201: mockResource({ id: '201', name: 'Child #1' }),
162
- 202: mockResource({ id: '202', name: 'Child #2' }),
163
- 203: mockResource({
164
- id: '203',
165
- name: 'Leaf',
166
- lineages: [
167
- {
168
- resourceIds: ['1', '200', '201', '202', '203'],
169
- },
170
- ],
171
- }),
172
- 204: mockResource({ id: '204', name: 'Another leaf' }),
173
- 300: mockResource({ id: '300', name: 'Source root node #3' }),
174
- };
175
- const sources: Record<string, Source> = {
176
- 1: mockSource({
177
- id: '1',
178
- name: 'Test system 1',
179
- nodes: [resources[100]],
180
- }),
181
- 2: mockSource({
182
- id: '2',
183
- name: 'Test system 2',
184
- nodes: [resources[200], resources[300]],
185
- }),
186
- };
187
-
188
- const onRequestSources = jest.fn().mockResolvedValue(Object.values(sources));
189
- const onRequestResource = jest.fn((reference: ResourceReference) => Promise.resolve(resources[reference.resource]));
190
- const onRequestChildren = jest.fn().mockResolvedValue([resources[203], resources[204]]);
191
- const preselectedSourceId = '2';
192
- const preselectedResource = resources[203];
193
-
194
- render(
195
- <ResourcePickerContainer
196
- {...baseProps}
197
- onRequestSources={onRequestSources}
198
- onRequestResource={onRequestResource}
199
- onRequestChildren={onRequestChildren}
200
- preselectedSourceId={preselectedSourceId}
201
- preselectedResource={preselectedResource}
202
- />,
203
- );
204
-
205
- await waitFor(() => expect(screen.getByRole('button', { name: 'folder Leaf selected' })).toBeInTheDocument());
206
-
207
- const breadcrumbs = within(screen.getByLabelText('Resource breadcrumb')).getAllByRole('listitem');
208
-
209
- // Breadcrumbs should be populated, source should be populated.
210
- // "Leaf" resource should be selected, "Another leaf" resource should not be selected.
211
- expect(breadcrumbs.map((item) => item.textContent)).toEqual(['', 'Source root node #2', 'Child #1', 'Child #2']);
212
- expect(screen.getByLabelText('Source quick select')).toHaveTextContent(/Source root node #2/);
213
- expect(screen.getByRole('button', { name: 'folder Leaf selected' })).toBeInTheDocument();
214
- expect(screen.getByRole('button', { name: 'folder Another leaf' })).toBeInTheDocument();
215
- });
216
-
217
- it.each([
218
- ['the preselected resource is a root node', 10],
219
- ['the preselected resource lineage does not exist under a root node', 100],
220
- ['the preselected resource lineage does not appear under a root node', 200],
221
- ])('The source list is displayed if %s', async (description: string, preselectedResourceId: number) => {
222
- localStorage.clear();
223
-
224
- const resources: Record<string, Resource> = {
225
- 10: mockResource({
226
- id: '100',
227
- name: 'Source root node #1',
228
- lineages: [{ resourceIds: ['1', '10'] }],
229
- }),
230
- 100: mockResource({
231
- id: '100',
232
- name: 'Resource without lineages',
233
- lineages: undefined,
234
- }),
235
- 200: mockResource({
236
- id: '200',
237
- name: 'Resource not available under a root node',
238
- lineages: [{ resourceIds: ['1', '20', '200'] }],
239
- }),
240
- };
241
- const sources: Record<string, Source> = {
242
- 1: mockSource({
243
- id: '1',
244
- name: 'Test system 1',
245
- nodes: [resources[10]],
246
- }),
247
- };
248
-
249
- const onRequestSources = jest.fn().mockResolvedValue(Object.values(sources));
250
- const onRequestResource = jest.fn((reference: ResourceReference) => Promise.resolve(resources[reference.resource]));
251
- const onRequestChildren = jest.fn().mockResolvedValue([]);
252
- const preselectedSourceId = '1';
253
- const preselectedResource = resources[preselectedResourceId];
254
-
255
- render(
256
- <ResourcePickerContainer
257
- {...baseProps}
258
- onRequestSources={onRequestSources}
259
- onRequestResource={onRequestResource}
260
- onRequestChildren={onRequestChildren}
261
- preselectedSourceId={preselectedSourceId}
262
- preselectedResource={preselectedResource}
263
- />,
264
- );
265
-
266
- await waitFor(() => expect(screen.queryByRole('list', { name: 'Source list' })).toBeInTheDocument());
267
-
268
- // Breadcrumbs should not be displayed.
269
- // Source list should be displayed.
270
- // "Leaf" resource should be selected, "Another leaf" resource should not be selected.
271
- await waitFor(() => expect(screen.queryByLabelText('Resource breadcrumb')).not.toBeInTheDocument());
272
- await waitFor(() =>
273
- expect(screen.getByRole('button', { name: 'Drill down to Source root node #1 children' })).toBeInTheDocument(),
274
- );
275
- });
276
-
277
- it('Selecting a child count drills down', async () => {
278
- const onRequestChildren = jest.fn(() => {
279
- return Promise.resolve([]);
280
- });
281
-
282
- // Component has finished initial render
283
- const { getAllByText } = render(<ResourcePickerContainer {...baseProps} onRequestChildren={onRequestChildren} />);
284
- await waitFor(() => {
285
- expect(getAllByText('Test system')[0]).toBeInTheDocument();
286
- });
287
-
288
- // Click the drill down counter
289
- const user = userEvent.setup();
290
- user.click(screen.getByRole('button', { name: 'Drill down to Test Website children' }));
291
-
292
- await waitFor(() => {
293
- expect(onRequestChildren).toHaveBeenCalled();
294
- });
295
- });
296
-
297
- it('Drill down renders provided resources from onRequestChildren', async () => {
298
- const onRequestChildren = jest
299
- .fn()
300
- .mockResolvedValue([
301
- mockResource({ id: '1', name: 'Test Page 1' }),
302
- mockResource({ id: '2', name: 'Test Page 2' }),
303
- mockResource({ id: '3', name: 'Test Page 3' }),
304
- ]);
305
-
306
- const { getAllByText } = await act(async () => {
307
- // Component has finished initial render
308
- return render(<ResourcePickerContainer {...baseProps} onRequestChildren={onRequestChildren} />);
309
- });
310
- await waitFor(() => {
311
- expect(getAllByText('Test system')[0]).toBeInTheDocument();
312
- });
313
-
314
- // Click the drill down counter
315
- const user = userEvent.setup();
316
- user.click(screen.getByRole('button', { name: 'Drill down to Test Website children' }));
317
-
318
- await waitFor(() => {
319
- expect(screen.getByText('Test Page 1')).toBeInTheDocument();
320
- expect(screen.getByText('Test Page 2')).toBeInTheDocument();
321
- expect(screen.getByText('Test Page 3')).toBeInTheDocument();
322
- });
323
- });
324
-
325
- it.each([
326
- ['mobile', 360, 1],
327
- ['desktop', 1920, 0],
328
- ])(
329
- 'Selecting a node on %s shows its properties in the details panel',
330
- async (description: string, width: number, expectedPreviewPanelCloseButtons: number) => {
331
- await act(async () =>
332
- render(
333
- <ResponsiveContext.Provider value={{ width }}>
334
- <ResourcePickerContainer {...baseProps} />
335
- </ResponsiveContext.Provider>,
336
- ),
337
- );
338
- await waitFor(() => {
339
- expect(screen.getByRole('button', { name: 'Drill down to Test Website children' })).toBeInTheDocument();
340
- });
341
-
342
- // Is on base page
343
- expect(screen.getByLabelText('Source list')).toBeInTheDocument();
344
-
345
- // Drill into the source
346
- const user = userEvent.setup();
347
- user.click(screen.getByRole('button', { name: 'Drill down to Test Website children' }));
348
- await waitFor(() => expect(screen.getByRole('button', { name: 'page Test Page' })).toBeInTheDocument());
349
-
350
- // Select the resource
351
- user.click(screen.getByRole('button', { name: 'page Test Page' }));
352
-
353
- // Asset that the preview panel containing the asset type/ID/etc. is visible
354
- await waitFor(() => expect(screen.getByText('Asset ID')).toBeInTheDocument());
355
- expect(screen.getByText('Mocked Page')).toBeInTheDocument();
356
- expect(screen.getByText('#123')).toBeInTheDocument();
357
- expect(screen.queryAllByRole('button', { name: 'Close details' })).toHaveLength(expectedPreviewPanelCloseButtons);
358
- },
359
- );
360
-
361
- it('Closing the details panel deselects the node', async () => {
362
- render(
363
- <ResponsiveContext.Provider value={{ width: 360 }}>
364
- <ResourcePickerContainer {...baseProps} />
365
- </ResponsiveContext.Provider>,
366
- );
367
- await waitFor(() => {
368
- expect(screen.getByRole('button', { name: 'Drill down to Test Website children' })).toBeInTheDocument();
369
- });
370
-
371
- // Drill into the source
372
- const user = userEvent.setup();
373
- user.click(screen.getByRole('button', { name: 'Drill down to Test Website children' }));
374
- await waitFor(() => expect(screen.getByRole('button', { name: 'page Test Page' })).toBeInTheDocument());
375
-
376
- // Select the resource
377
- user.click(screen.getByRole('button', { name: 'page Test Page' }));
378
-
379
- // Wait for the preview panel to open
380
- await waitFor(() => expect(screen.getByText('Mocked Page')).toBeInTheDocument());
381
- await waitFor(() => expect(screen.getByText('#123')).toBeInTheDocument());
382
-
383
- // Close the preview panel
384
- user.click(screen.getByRole('button', { name: 'Close details' }));
385
-
386
- await waitFor(() => {
387
- // Resource should be unselected and preview modal should disappear
388
- expect(screen.getByText('Mocked Page')).toBeInTheDocument();
389
- expect(screen.getByText('#123')).toBeInTheDocument();
390
- });
391
- });
392
-
393
- it('Passes modal state down to child components: SourceList', async () => {
394
- let mockModalState: OverlayTriggerState | null = null;
395
-
396
- MockSourceList.mockImplementation((args: SourceListProps) => {
397
- mockModalState = args.previewModalState;
398
- return ActualSourceList(args);
399
- });
400
-
401
- render(<ResourcePickerContainer {...baseProps} />);
402
-
403
- expect(mockModalState).toEqual(
404
- expect.objectContaining({
405
- close: expect.any(Function),
406
- isOpen: expect.any(Boolean),
407
- open: expect.any(Function),
408
- setOpen: expect.any(Function),
409
- toggle: expect.any(Function),
410
- }),
411
- );
412
- });
413
-
414
- it('Passes modal state down to child components: ResourceList', async () => {
415
- let mockModalState: OverlayTriggerState | null = null;
416
-
417
- MockResourceList.mockImplementation((args: ResourceListProps) => {
418
- mockModalState = args.previewModalState;
419
- return ActualResourceList(args);
420
- });
421
-
422
- const { getByText, getAllByText } = render(<ResourcePickerContainer {...baseProps} />);
423
- await waitFor(() => {
424
- expect(getAllByText('Test system')[0]).toBeInTheDocument();
425
- });
426
-
427
- // Click the drill down counter
428
- const user = userEvent.setup();
429
- user.click(screen.getByRole('button', { name: 'Drill down to Test Website children' }));
430
- await waitFor(() => {
431
- expect(getByText('Test Page')).toBeInTheDocument();
432
- });
433
-
434
- expect(mockModalState).toEqual(
435
- expect.objectContaining({
436
- close: expect.any(Function),
437
- isOpen: expect.any(Boolean),
438
- open: expect.any(Function),
439
- setOpen: expect.any(Function),
440
- toggle: expect.any(Function),
441
- }),
442
- );
443
- });
444
-
445
- it('Passes modal state down to child components: Details Panel', async () => {
446
- let mockModalState: OverlayTriggerState | null = null;
447
-
448
- MockPreviewPanel.mockImplementation((args: PreviewPanelProps) => {
449
- mockModalState = args.modalState;
450
- return ActualPreviewPanel(args);
451
- });
452
-
453
- const { getAllByText } = render(<ResourcePickerContainer {...baseProps} />);
454
- await waitFor(() => {
455
- expect(getAllByText('Test system')[0]).toBeInTheDocument();
456
- });
457
-
458
- const user = userEvent.setup();
459
- user.click(screen.getByRole('button', { name: 'Drill down to Test Website children' }));
460
-
461
- expect(mockModalState).toEqual(
462
- expect.objectContaining({
463
- close: expect.any(Function),
464
- isOpen: expect.any(Boolean),
465
- open: expect.any(Function),
466
- setOpen: expect.any(Function),
467
- toggle: expect.any(Function),
468
- }),
469
- );
470
- });
471
-
472
- it('Breadcrumb hierarchy is added to on drill down', async () => {
473
- let mockHierarchy: Hierarchy<Source | Resource> = [];
474
-
475
- MockResourceBreadcrumb.mockImplementation((args: ResourceBreadcrumbProps<unknown>) => {
476
- mockHierarchy = args.hierarchy as Hierarchy<Source | Resource>;
477
- return ActualResourceBreadcrumb(args);
478
- });
479
-
480
- const { getByLabelText, getAllByText } = render(<ResourcePickerContainer {...baseProps} />);
481
- await waitFor(() => {
482
- expect(getAllByText('Test system')[0]).toBeInTheDocument();
483
- });
484
-
485
- const user = userEvent.setup();
486
- user.click(screen.getByRole('button', { name: 'Drill down to Test Website children' }));
487
-
488
- // Breadcrumb is showing
489
- await waitFor(() => {
490
- expect(getByLabelText('Resource breadcrumb')).toBeInTheDocument();
491
- });
492
-
493
- // Expect the clicked child was added to breadcrumb
494
- expect(mockHierarchy).toEqual([
495
- {
496
- key: 'source:1-resource:1',
497
- label: 'Test Website',
498
- node: expect.any(Object),
499
- },
500
- ]);
501
- });
502
-
503
- it('Multiple hierarchy is tracked on on drill down', async () => {
504
- let mockHierarchy: Hierarchy<Source | Resource> = [];
505
-
506
- MockResourceBreadcrumb.mockImplementation((args: ResourceBreadcrumbProps<unknown>) => {
507
- mockHierarchy = args.hierarchy as Hierarchy<Source | Resource>;
508
- return ActualResourceBreadcrumb(args);
509
- });
510
-
511
- const onRequestChildren = jest
512
- .fn()
513
- .mockResolvedValueOnce([
514
- mockResource({
515
- id: '11',
516
- name: 'Test Page',
517
- childCount: 4,
518
- }),
519
- ])
520
- .mockResolvedValueOnce([
521
- mockResource({
522
- id: '111',
523
- name: 'Second Page',
524
- childCount: 0,
525
- }),
526
- ]);
527
-
528
- const { getByLabelText, getByText, getAllByText, queryAllByText } = render(
529
- <ResourcePickerContainer {...baseProps} onRequestChildren={onRequestChildren} />,
530
- );
531
- await waitFor(() => {
532
- expect(getAllByText('Test system')[0]).toBeInTheDocument();
533
- });
534
-
535
- const user = userEvent.setup();
536
- user.click(screen.getByRole('button', { name: 'Drill down to Test Website children' }));
537
-
538
- // Breadcrumb is showing
539
- await waitFor(() => {
540
- expect(getByLabelText('Resource breadcrumb')).toBeInTheDocument();
541
- });
542
-
543
- // Loading is done
544
- await waitFor(() => {
545
- expect(queryAllByText('loading Resource list')).toHaveLength(0);
546
- });
547
-
548
- // Drill down again
549
- user.click(screen.getByRole('button', { name: 'Drill down to Test Page children' }));
550
-
551
- await waitFor(() => {
552
- expect(getByText('Second Page')).toBeInTheDocument();
553
- });
554
-
555
- // Expect the clicked child was added to breadcrumb
556
- expect(mockHierarchy).toEqual([
557
- {
558
- key: 'source:1-resource:1',
559
- label: 'Test Website',
560
- node: expect.any(Object),
561
- },
562
- {
563
- key: '11',
564
- label: 'Test Page',
565
- node: expect.any(Object),
566
- },
567
- ]);
568
- });
569
-
570
- it('Can track multiple hierarchy jumping back', async () => {
571
- let mockHierarchy: Hierarchy<Source | Resource> = [];
572
-
573
- MockResourceBreadcrumb.mockImplementation((args: ResourceBreadcrumbProps<unknown>) => {
574
- mockHierarchy = args.hierarchy as Hierarchy<Source | Resource>;
575
- return ActualResourceBreadcrumb(args);
576
- });
577
-
578
- const onRequestChildren = jest
579
- .fn()
580
- .mockResolvedValueOnce([
581
- mockResource({
582
- id: '11',
583
- name: 'First Level',
584
- childCount: 4,
585
- }),
586
- ])
587
- .mockResolvedValueOnce([
588
- mockResource({
589
- id: '111',
590
- name: 'Second Level',
591
- childCount: 0,
592
- }),
593
- ])
594
- .mockResolvedValueOnce([
595
- mockResource({
596
- id: '11',
597
- name: 'First Level',
598
- childCount: 4,
599
- }),
600
- ]);
601
-
602
- const { getByText, getAllByText, queryByText } = render(
603
- <ResourcePickerContainer {...baseProps} onRequestChildren={onRequestChildren} />,
604
- );
605
- await waitFor(() => {
606
- expect(getAllByText('Test system')[0]).toBeInTheDocument();
607
- });
608
-
609
- const user = userEvent.setup();
610
- user.click(screen.getByRole('button', { name: 'Drill down to Test Website children' }));
611
-
612
- // Breadcrumb is showing
613
- await waitFor(() => {
614
- expect(getByText('First Level')).toBeInTheDocument();
615
- });
616
-
617
- // Drill down again
618
- user.click(screen.getByRole('button', { name: 'Drill down to First Level children' }));
619
-
620
- await waitFor(() => {
621
- expect(getByText('Second Level')).toBeInTheDocument();
622
- });
623
-
624
- // Click on previous breadcrumb item
625
- const breadcrumb = screen.getByLabelText('Resource breadcrumb');
626
- user.click(within(breadcrumb).getByRole('button', { name: 'Test Website' }));
627
-
628
- await waitFor(() => {
629
- expect(queryByText('Second Level')).not.toBeInTheDocument();
630
- });
631
-
632
- // Expect the clicked child was added to breadcrumb
633
- expect(mockHierarchy).toEqual([
634
- {
635
- key: 'source:1-resource:1',
636
- label: 'Test Website',
637
- node: expect.any(Object),
638
- },
639
- ]);
640
- });
641
-
642
- it('Multiple hierarchy can handle reset to new source', async () => {
643
- let mockHierarchy: Hierarchy<Source | Resource> = [];
644
-
645
- MockResourceBreadcrumb.mockImplementation((args: ResourceBreadcrumbProps<unknown>) => {
646
- mockHierarchy = args.hierarchy as Hierarchy<Source | Resource>;
647
- return ActualResourceBreadcrumb(args);
648
- });
649
-
650
- const onRequestChildren = jest
651
- .fn()
652
- .mockResolvedValueOnce([mockResource({ name: 'Test Page' })])
653
- .mockResolvedValueOnce([mockResource({ name: 'Second Page' })]);
654
-
655
- const onRequestSources = jest.fn().mockImplementation(() => {
656
- return Promise.resolve([
657
- mockSource({
658
- id: '1',
659
- name: 'Test system',
660
- nodes: [
661
- {
662
- id: '1',
663
- type: {
664
- code: 'site',
665
- name: 'Site',
666
- },
667
- name: 'Test Website',
668
- childCount: 21,
669
- },
670
- {
671
- id: '2',
672
- type: {
673
- code: 'site',
674
- name: 'Site',
675
- },
676
- name: 'Second Website',
677
- childCount: 21,
678
- },
679
- ],
680
- }),
681
- ]);
682
- });
683
-
684
- const { getByLabelText, getAllByText, getByText, getByRole } = render(
685
- <ResourcePickerContainer
686
- {...baseProps}
687
- onRequestSources={onRequestSources}
688
- onRequestChildren={onRequestChildren}
689
- />,
690
- );
691
- await waitFor(() => {
692
- expect(getAllByText('Test system')[0]).toBeInTheDocument();
693
- });
694
-
695
- const user = userEvent.setup();
696
- user.click(screen.getByRole('button', { name: 'Drill down to Test Website children' }));
697
-
698
- // Breadcrumb is showing
699
- await waitFor(() => {
700
- expect(getByLabelText('Resource breadcrumb')).toBeInTheDocument();
701
- });
702
-
703
- user.click(screen.getByRole('button', { name: 'Source quick select' }));
704
-
705
- await waitFor(() => {
706
- expect(getByRole('button', { name: 'Site Second Website' })).toBeInTheDocument();
707
- });
708
-
709
- user.click(getByRole('button', { name: 'Site Second Website' }));
710
-
711
- await waitFor(() => {
712
- expect(getByText('Second Page')).toBeInTheDocument();
713
- });
714
-
715
- // Expect the clicked child was added to breadcrumb
716
- expect(mockHierarchy).toEqual([
717
- {
718
- key: 'source:1-resource:2',
719
- label: 'Second Website',
720
- node: expect.any(Object),
721
- },
722
- ]);
723
- });
724
-
725
- describe('handleDetailSelect() tests', () => {
726
- it('Source select works', async () => {
727
- const onChangeMock = jest.fn();
728
- const onCloseMock = jest.fn();
729
- const { getAllByText } = render(
730
- <ResourcePickerContainer {...baseProps} onChange={onChangeMock} onClose={onCloseMock} />,
731
- );
732
-
733
- await waitFor(() => {
734
- expect(getAllByText('Test system')[0]).toBeInTheDocument();
735
- });
736
-
737
- const user = userEvent.setup();
738
-
739
- // Select the resource
740
- user.click(screen.getByRole('button', { name: 'site Test Website' }));
741
-
742
- // Wait for the preview panel to open
743
- await waitFor(() => expect(screen.getByText('Site')).toBeInTheDocument());
744
- await waitFor(() => expect(screen.getByText('#1')).toBeInTheDocument());
745
-
746
- user.click(screen.getByRole('button', { name: 'Select' }));
747
-
748
- await waitFor(() => expect(onChangeMock).toHaveBeenCalled());
749
- await waitFor(() => expect(onCloseMock).toHaveBeenCalled());
750
- });
751
-
752
- it('Resource select works', async () => {
753
- const onChangeMock = jest.fn();
754
- const onCloseMock = jest.fn();
755
- const { getAllByText } = render(
756
- <ResourcePickerContainer {...baseProps} onChange={onChangeMock} onClose={onCloseMock} />,
757
- );
758
-
759
- await waitFor(() => {
760
- expect(getAllByText('Test system')[0]).toBeInTheDocument();
761
- });
762
-
763
- const user = userEvent.setup();
764
- user.click(screen.getByRole('button', { name: 'Drill down to Test Website children' }));
765
- await waitFor(() => expect(screen.getByRole('button', { name: 'page Test Page' })).toBeInTheDocument());
766
-
767
- // Select the resource
768
- user.click(screen.getByRole('button', { name: 'page Test Page' }));
769
-
770
- // Wait for the preview panel to open
771
- await waitFor(() => expect(screen.getByText('Mocked Page')).toBeInTheDocument());
772
- await waitFor(() => expect(screen.getByText('#123')).toBeInTheDocument());
773
-
774
- user.click(screen.getByRole('button', { name: 'Select' }));
775
-
776
- await waitFor(() => expect(onChangeMock).toHaveBeenCalled());
777
- await waitFor(() => expect(onCloseMock).toHaveBeenCalled());
778
- });
779
- });
780
- });