@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.
- package/CHANGELOG.md +89 -38
- package/LICENSE.md +15 -0
- package/README.md +9 -0
- package/jest.config.ts +22 -21
- package/lib/Hooks/useSelectedState.d.ts +15 -0
- package/lib/Hooks/useSelectedState.js +16 -0
- package/lib/Hooks/useSources.d.ts +6 -6
- package/lib/Hooks/useSources.js +26 -1
- package/lib/MainContainer/MainContainer.d.ts +17 -0
- package/lib/MainContainer/MainContainer.js +61 -0
- package/lib/Plugin/Plugin.d.ts +13 -0
- package/lib/Plugin/Plugin.js +17 -0
- package/lib/ResourceBrowserContext/ResourceBrowserContext.d.ts +2 -3
- package/lib/ResourceBrowserContext/ResourceBrowserContext.js +4 -17
- package/lib/ResourceBrowserInput/ResourceBrowserInput.d.ts +24 -0
- package/lib/ResourceBrowserInput/ResourceBrowserInput.js +16 -0
- package/lib/ResourcePicker/ResourcePicker.d.ts +6 -4
- package/lib/ResourcePicker/ResourcePicker.js +14 -8
- package/lib/ResourcePicker/States/Selected.d.ts +10 -4
- package/lib/ResourcePicker/States/Selected.js +11 -32
- package/lib/SourceDropdown/SourceDropdown.d.ts +5 -11
- package/lib/SourceDropdown/SourceDropdown.js +20 -99
- package/lib/SourceList/SourceList.d.ts +5 -16
- package/lib/SourceList/SourceList.js +14 -75
- package/lib/index.css +42 -202
- package/lib/index.d.ts +7 -7
- package/lib/index.js +69 -13
- package/lib/types.d.ts +41 -59
- package/package.json +82 -80
- package/src/Hooks/useSelectedState.spec.ts +46 -0
- package/src/Hooks/useSelectedState.ts +22 -0
- package/src/Hooks/useSources.spec.ts +60 -13
- package/src/Hooks/useSources.ts +35 -5
- package/src/Icons/CircledLoopIcon.tsx +8 -8
- package/src/MainContainer/MainContainer.spec.tsx +203 -0
- package/src/MainContainer/MainContainer.stories.tsx +62 -0
- package/src/MainContainer/MainContainer.tsx +101 -0
- package/src/Plugin/Plugin.spec.tsx +46 -0
- package/src/Plugin/Plugin.tsx +20 -0
- package/src/ResourceBrowserContext/ResourceBrowserContext.spec.tsx +65 -106
- package/src/ResourceBrowserContext/ResourceBrowserContext.tsx +24 -39
- package/src/ResourceBrowserInput/ResourceBrowserInput.spec.tsx +192 -0
- package/src/ResourceBrowserInput/ResourceBrowserInput.tsx +81 -0
- package/src/ResourcePicker/ResourcePicker.spec.tsx +159 -116
- package/src/ResourcePicker/ResourcePicker.stories.tsx +28 -24
- package/src/ResourcePicker/ResourcePicker.tsx +79 -59
- package/src/ResourcePicker/States/Error.tsx +8 -8
- package/src/ResourcePicker/States/Loading.tsx +3 -3
- package/src/ResourcePicker/States/Selected.tsx +66 -73
- package/src/ResourcePicker/mock-image-resource.json +25 -47
- package/src/ResourcePicker/mock-resource.json +11 -13
- package/src/ResourcePicker/resource-picker.scss +13 -13
- package/src/SourceDropdown/SourceDropdown.spec.tsx +65 -391
- package/src/SourceDropdown/SourceDropdown.stories.tsx +21 -24
- package/src/SourceDropdown/SourceDropdown.tsx +80 -258
- package/src/SourceList/SourceList.spec.tsx +37 -430
- package/src/SourceList/SourceList.stories.tsx +17 -37
- package/src/SourceList/SourceList.tsx +28 -155
- package/src/__mocks__/MockModels.ts +56 -25
- package/src/__mocks__/PluginExample.tsx +98 -0
- package/src/__mocks__/StorybookHelpers.tsx +141 -0
- package/src/__mocks__/renderWithContext.tsx +14 -18
- package/src/__mocks__/sample-sources.json +32 -0
- package/src/index.scss +18 -8
- package/src/index.spec.tsx +277 -99
- package/src/index.stories.tsx +65 -39
- package/src/index.tsx +119 -57
- package/src/types.ts +54 -63
- package/tailwind.config.cjs +92 -92
- package/vite.config.js +12 -12
- package/lib/Hooks/useCategorisedSources.d.ts +0 -14
- package/lib/Hooks/useCategorisedSources.js +0 -38
- package/lib/Hooks/useChildResources.d.ts +0 -16
- package/lib/Hooks/useChildResources.js +0 -13
- package/lib/Hooks/usePreselectedResourcePath.d.ts +0 -20
- package/lib/Hooks/usePreselectedResourcePath.js +0 -31
- package/lib/Hooks/useRecentLocations.d.ts +0 -5
- package/lib/Hooks/useRecentLocations.js +0 -38
- package/lib/Hooks/useRecentResourcesPaths.d.ts +0 -20
- package/lib/Hooks/useRecentResourcesPaths.js +0 -30
- package/lib/Hooks/useResource.d.ts +0 -28
- package/lib/Hooks/useResource.js +0 -25
- package/lib/Hooks/useResourcePath.d.ts +0 -16
- package/lib/Hooks/useResourcePath.js +0 -64
- package/lib/Icons/HistoryIcon.d.ts +0 -4
- package/lib/Icons/HistoryIcon.js +0 -13
- package/lib/PreviewPanel/PreviewPanel.d.ts +0 -5
- package/lib/PreviewPanel/PreviewPanel.js +0 -8
- package/lib/PreviewPanel/details/MatrixResource.d.ts +0 -7
- package/lib/PreviewPanel/details/MatrixResource.js +0 -35
- package/lib/ResourceBreadcrumb/ResourceBreadcrumb.d.ts +0 -9
- package/lib/ResourceBreadcrumb/ResourceBreadcrumb.js +0 -54
- package/lib/ResourceList/ResourceList.d.ts +0 -18
- package/lib/ResourceList/ResourceList.js +0 -49
- package/lib/ResourcePickerContainer/ResourcePickerContainer.d.ts +0 -17
- package/lib/ResourcePickerContainer/ResourcePickerContainer.js +0 -166
- package/lib/StatusIndicator/StatusIndicator.d.ts +0 -8
- package/lib/StatusIndicator/StatusIndicator.js +0 -27
- package/lib/utils/findBestMatchLineage.d.ts +0 -2
- package/lib/utils/findBestMatchLineage.js +0 -28
- package/lib/utils/uuid.d.ts +0 -1
- package/lib/utils/uuid.js +0 -6
- package/src/Hooks/useCategorisedSources.spec.ts +0 -39
- package/src/Hooks/useCategorisedSources.ts +0 -46
- package/src/Hooks/useChildResources.spec.ts +0 -29
- package/src/Hooks/useChildResources.ts +0 -21
- package/src/Hooks/usePreselectedResourcePath.ts +0 -54
- package/src/Hooks/useRecentLocations.spec.ts +0 -81
- package/src/Hooks/useRecentLocations.ts +0 -44
- package/src/Hooks/useRecentResourcesPaths.ts +0 -54
- package/src/Hooks/useResource.spec.ts +0 -61
- package/src/Hooks/useResource.ts +0 -40
- package/src/Hooks/useResourcePath.spec.ts +0 -120
- package/src/Hooks/useResourcePath.ts +0 -76
- package/src/Icons/HistoryIcon.tsx +0 -17
- package/src/PreviewPanel/PreviewPanel.spec.tsx +0 -198
- package/src/PreviewPanel/PreviewPanel.stories.tsx +0 -76
- package/src/PreviewPanel/PreviewPanel.tsx +0 -6
- package/src/PreviewPanel/details/MatrixResource.tsx +0 -54
- package/src/PreviewPanel/details/matrix-resource.scss +0 -16
- package/src/ResourceBreadcrumb/ResourceBreadcrumb.spec.tsx +0 -133
- package/src/ResourceBreadcrumb/ResourceBreadcrumb.stories.tsx +0 -24
- package/src/ResourceBreadcrumb/ResourceBreadcrumb.tsx +0 -79
- package/src/ResourceBreadcrumb/resource-breadcrumb.scss +0 -28
- package/src/ResourceBreadcrumb/sample-hierarchy.json +0 -27
- package/src/ResourceList/ResourceList.spec.tsx +0 -202
- package/src/ResourceList/ResourceList.stories.tsx +0 -40
- package/src/ResourceList/ResourceList.tsx +0 -83
- package/src/ResourceList/sample-resources.json +0 -851
- package/src/ResourcePickerContainer/ResourcePickerContainer.spec.tsx +0 -780
- package/src/ResourcePickerContainer/ResourcePickerContainer.stories.tsx +0 -45
- package/src/ResourcePickerContainer/ResourcePickerContainer.tsx +0 -290
- package/src/SourceList/sample-sources.json +0 -251
- package/src/StatusIndicator/StatusIndicator.stories.tsx +0 -83
- package/src/StatusIndicator/StatusIndicator.tsx +0 -38
- package/src/__mocks__/JestHelpers.ts +0 -65
- package/src/__mocks__/StorybookHelpers.ts +0 -128
- package/src/__mocks__/jestHelpers.spec.ts +0 -38
- package/src/utils/findBestMatchLineage.spec.ts +0 -81
- package/src/utils/findBestMatchLineage.ts +0 -30
- package/src/utils/uuid.ts +0 -5
@@ -1,12 +1,12 @@
|
|
1
1
|
import React, { PropsWithChildren, useMemo } from 'react';
|
2
|
-
import {
|
2
|
+
import { OnRequestSources, ResourceBrowserPlugin } from '../types';
|
3
|
+
|
3
4
|
import pMemoize from 'p-memoize';
|
4
5
|
import ExpiryMap from 'expiry-map';
|
5
6
|
|
6
7
|
export type ResourceBrowserContextProps = {
|
7
|
-
|
8
|
-
|
9
|
-
onRequestResource: OnRequestResource;
|
8
|
+
onRequestSources: OnRequestSources;
|
9
|
+
plugins: Array<ResourceBrowserPlugin>;
|
10
10
|
};
|
11
11
|
|
12
12
|
/**
|
@@ -14,43 +14,28 @@ export type ResourceBrowserContextProps = {
|
|
14
14
|
* Please use ResourceBrowserContextProvider instead.
|
15
15
|
*/
|
16
16
|
export const ResourceBrowserContext = React.createContext<ResourceBrowserContextProps>({
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
throw new Error('onRequestChildren has not been configured.');
|
22
|
-
},
|
23
|
-
onRequestResource: () => {
|
24
|
-
throw new Error('onRequestResource has not been configured.');
|
25
|
-
},
|
17
|
+
onRequestSources: () => {
|
18
|
+
throw new Error('onRequestSources has not been configured.');
|
19
|
+
},
|
20
|
+
plugins: [],
|
26
21
|
});
|
27
22
|
|
28
23
|
export const ResourceBrowserContextProvider = (props: PropsWithChildren<{ value: ResourceBrowserContextProps }>) => {
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
}),
|
45
|
-
onRequestResource: pMemoize(onRequestResource, {
|
46
|
-
cache,
|
47
|
-
cacheKey: ([reference]) => `onRequestResource.${reference.source}.${reference.resource}`,
|
48
|
-
}),
|
49
|
-
}),
|
50
|
-
[onRequestSources, onRequestChildren, onRequestResource],
|
51
|
-
);
|
24
|
+
const CACHE_DURATION = 30000; // 30 seconds
|
25
|
+
const {
|
26
|
+
value: { onRequestSources, ...other },
|
27
|
+
children,
|
28
|
+
} = props;
|
29
|
+
const cache = new ExpiryMap(CACHE_DURATION);
|
30
|
+
const memoized = useMemo(
|
31
|
+
() => ({
|
32
|
+
onRequestSources: pMemoize(onRequestSources, {
|
33
|
+
cache,
|
34
|
+
cacheKey: () => 'onRequestSources',
|
35
|
+
}),
|
36
|
+
}),
|
37
|
+
[onRequestSources],
|
38
|
+
);
|
52
39
|
|
53
|
-
|
54
|
-
<ResourceBrowserContext.Provider value={{ ...memoized, ...other }}>{children}</ResourceBrowserContext.Provider>
|
55
|
-
);
|
40
|
+
return <ResourceBrowserContext.Provider value={{ ...memoized, ...other }}>{children}</ResourceBrowserContext.Provider>;
|
56
41
|
};
|
@@ -0,0 +1,192 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { render, act, fireEvent, screen, waitFor } from '@testing-library/react';
|
3
|
+
import { ResourceBrowserInput, ResourceBrowserInputProps } from './ResourceBrowserInput';
|
4
|
+
import { mockSource, mockResource } from '../__mocks__/MockModels';
|
5
|
+
import { ResourceBrowserPlugin, ResourceBrowserUnresolvedResource } from '../types';
|
6
|
+
|
7
|
+
import * as RP from '../ResourcePicker/ResourcePicker';
|
8
|
+
jest.spyOn(RP, 'ResourcePicker');
|
9
|
+
|
10
|
+
describe('Resource browser input', () => {
|
11
|
+
const mockUseResource = jest.fn().mockReturnValue({
|
12
|
+
data: null,
|
13
|
+
error: null,
|
14
|
+
isLoading: false,
|
15
|
+
reload: () => {},
|
16
|
+
});
|
17
|
+
const mockChange = jest.fn();
|
18
|
+
const mockSetSource = jest.fn();
|
19
|
+
const mockOnModalStateChange = jest.fn();
|
20
|
+
const renderComponent = (props: Partial<ResourceBrowserInputProps> = {}) => {
|
21
|
+
render(
|
22
|
+
<ResourceBrowserInput
|
23
|
+
modalTitle="Asset picker"
|
24
|
+
value={null}
|
25
|
+
useResource={mockUseResource}
|
26
|
+
onChange={mockChange}
|
27
|
+
plugin={null}
|
28
|
+
source={null}
|
29
|
+
sources={[]}
|
30
|
+
isLoading={false}
|
31
|
+
error={null}
|
32
|
+
setSource={mockSetSource}
|
33
|
+
isModalOpen={false}
|
34
|
+
onModalStateChange={mockOnModalStateChange}
|
35
|
+
{...props}
|
36
|
+
/>,
|
37
|
+
);
|
38
|
+
};
|
39
|
+
|
40
|
+
it('If onClear function is provided passes through to ResourcePicker', async () => {
|
41
|
+
const myOnClear = jest.fn();
|
42
|
+
renderComponent({ onClear: myOnClear });
|
43
|
+
|
44
|
+
await waitFor(() => {
|
45
|
+
expect(RP.ResourcePicker).toHaveBeenCalledWith(
|
46
|
+
expect.objectContaining({
|
47
|
+
onClear: myOnClear,
|
48
|
+
}),
|
49
|
+
{},
|
50
|
+
);
|
51
|
+
});
|
52
|
+
});
|
53
|
+
|
54
|
+
it('If onClear function is not provided creates one which nulls change', async () => {
|
55
|
+
renderComponent();
|
56
|
+
|
57
|
+
await waitFor(() => {
|
58
|
+
expect(RP.ResourcePicker).toHaveBeenCalled();
|
59
|
+
});
|
60
|
+
expect(mockChange).not.toHaveBeenCalled();
|
61
|
+
|
62
|
+
// Invoke modal close callback
|
63
|
+
const { onClear } = (RP.ResourcePicker as unknown as jest.SpyInstance).mock.calls[0][0];
|
64
|
+
onClear();
|
65
|
+
expect(mockChange).toHaveBeenCalledWith(null);
|
66
|
+
});
|
67
|
+
|
68
|
+
/*
|
69
|
+
These tests dont actually belong in this component as they do not control these items directly
|
70
|
+
and never have but were tested here when this was originally written.
|
71
|
+
*/
|
72
|
+
describe('Legacy tests', () => {
|
73
|
+
it('should render the related asset picker with the default label', async () => {
|
74
|
+
renderComponent();
|
75
|
+
|
76
|
+
await waitFor(() => {
|
77
|
+
expect(screen.getByText('Choose asset')).toBeInTheDocument();
|
78
|
+
});
|
79
|
+
});
|
80
|
+
|
81
|
+
it('should display the generic asset picking icon', async () => {
|
82
|
+
renderComponent();
|
83
|
+
|
84
|
+
await waitFor(() => {
|
85
|
+
expect(screen.getByText('Choose asset')).toBeInTheDocument();
|
86
|
+
expect(screen.getByTestId('AdsClickRoundedIcon')).toBeInTheDocument();
|
87
|
+
});
|
88
|
+
});
|
89
|
+
|
90
|
+
it('should display the image icon when only images are allowed', async () => {
|
91
|
+
renderComponent({ allowedTypes: ['image'] });
|
92
|
+
|
93
|
+
await waitFor(() => {
|
94
|
+
expect(screen.getByText('Choose image')).toBeInTheDocument();
|
95
|
+
expect(screen.getByTestId('PhotoLibraryRoundedIcon')).toBeInTheDocument();
|
96
|
+
});
|
97
|
+
});
|
98
|
+
|
99
|
+
it('should disallow removing and replacing selection if the input is disabled', async () => {
|
100
|
+
const source = mockSource();
|
101
|
+
const resource = mockResource({ source: source });
|
102
|
+
mockUseResource.mockReturnValueOnce({
|
103
|
+
data: resource,
|
104
|
+
error: null,
|
105
|
+
isLoading: false,
|
106
|
+
reload: () => {},
|
107
|
+
});
|
108
|
+
const plugin: Partial<ResourceBrowserPlugin> = {
|
109
|
+
renderSelectedResource: () => {
|
110
|
+
return Promise.resolve({
|
111
|
+
icon: <div>IconGoesHere</div>,
|
112
|
+
label: 'Products',
|
113
|
+
description: [],
|
114
|
+
});
|
115
|
+
},
|
116
|
+
};
|
117
|
+
|
118
|
+
renderComponent({
|
119
|
+
value: { sourceId: source.id, resourceId: '100' },
|
120
|
+
isDisabled: true,
|
121
|
+
plugin: plugin as ResourceBrowserPlugin,
|
122
|
+
});
|
123
|
+
|
124
|
+
await waitFor(() => expect(screen.queryByLabelText('Loading selection')).not.toBeInTheDocument());
|
125
|
+
|
126
|
+
expect(screen.getByRole('button', { name: 'Remove selection' })).toBeDisabled();
|
127
|
+
expect(screen.getByRole('button', { name: 'Replace selection' })).toBeDisabled();
|
128
|
+
});
|
129
|
+
|
130
|
+
it('clicking the replace selection button should open the resource browser', async () => {
|
131
|
+
const source = mockSource();
|
132
|
+
const resource = mockResource({ source: source });
|
133
|
+
mockUseResource.mockReturnValueOnce({
|
134
|
+
data: resource,
|
135
|
+
error: null,
|
136
|
+
isLoading: false,
|
137
|
+
reload: () => {},
|
138
|
+
});
|
139
|
+
const plugin: Partial<ResourceBrowserPlugin> = {
|
140
|
+
renderSelectedResource: () => {
|
141
|
+
return Promise.resolve({
|
142
|
+
icon: <div>IconGoesHere</div>,
|
143
|
+
label: 'Products',
|
144
|
+
description: [],
|
145
|
+
});
|
146
|
+
},
|
147
|
+
sourceBrowserComponent: () => {
|
148
|
+
return () => {
|
149
|
+
return <></>;
|
150
|
+
};
|
151
|
+
},
|
152
|
+
};
|
153
|
+
|
154
|
+
renderComponent({ value: { sourceId: source.id, resourceId: '100' }, plugin: plugin as ResourceBrowserPlugin });
|
155
|
+
|
156
|
+
await waitFor(() => expect(screen.queryByLabelText('Loading selection')).not.toBeInTheDocument());
|
157
|
+
await act(() => fireEvent.click(screen.getByRole('button', { name: 'Replace selection' })));
|
158
|
+
expect(screen.getByRole('button', { name: 'Close Asset picker dialog' })).toBeInTheDocument();
|
159
|
+
});
|
160
|
+
|
161
|
+
it.each([
|
162
|
+
['resource selected', { sourceId: mockSource().id, resourceId: '100' }, 1],
|
163
|
+
['resource not selected', null, 0],
|
164
|
+
])(
|
165
|
+
'should only display the "replace" button if a resource has already been selected - %s',
|
166
|
+
async (description: string, value: ResourceBrowserUnresolvedResource | null, expectedReplaceSelectionButtons: number) => {
|
167
|
+
const source = mockSource();
|
168
|
+
const resource = mockResource({ source: source });
|
169
|
+
mockUseResource.mockReturnValueOnce({
|
170
|
+
data: value,
|
171
|
+
error: null,
|
172
|
+
isLoading: false,
|
173
|
+
reload: () => {},
|
174
|
+
});
|
175
|
+
const plugin: Partial<ResourceBrowserPlugin> = {
|
176
|
+
renderSelectedResource: () => {
|
177
|
+
return Promise.resolve({
|
178
|
+
icon: <div>IconGoesHere</div>,
|
179
|
+
label: 'Products',
|
180
|
+
description: [],
|
181
|
+
});
|
182
|
+
},
|
183
|
+
};
|
184
|
+
renderComponent({ value, plugin: plugin as ResourceBrowserPlugin });
|
185
|
+
|
186
|
+
await waitFor(() => expect(screen.queryByLabelText('Loading selection')).not.toBeInTheDocument());
|
187
|
+
|
188
|
+
expect(screen.queryAllByRole('button', { name: 'Replace selection' })).toHaveLength(expectedReplaceSelectionButtons);
|
189
|
+
},
|
190
|
+
);
|
191
|
+
});
|
192
|
+
});
|
@@ -0,0 +1,81 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import MainContainer from '../MainContainer/MainContainer';
|
3
|
+
import { ResourcePicker } from '../ResourcePicker/ResourcePicker';
|
4
|
+
import { ResourceBrowserPlugin, ResourceBrowserSource, ResourceBrowserUnresolvedResource, ResourceBrowserResource } from '../types';
|
5
|
+
|
6
|
+
export type ResourceBrowserInputProps = {
|
7
|
+
modalTitle: string;
|
8
|
+
allowedTypes?: string[];
|
9
|
+
isDisabled?: boolean;
|
10
|
+
value: ResourceBrowserUnresolvedResource | null;
|
11
|
+
useResource(
|
12
|
+
referenceId: string | null,
|
13
|
+
source: ResourceBrowserSource | null,
|
14
|
+
): {
|
15
|
+
data: ResourceBrowserResource | null;
|
16
|
+
error: Error | null;
|
17
|
+
isLoading: boolean;
|
18
|
+
};
|
19
|
+
onChange(resource: ResourceBrowserResource | null): void;
|
20
|
+
onClear?(): void;
|
21
|
+
plugin: ResourceBrowserPlugin | null;
|
22
|
+
source: ResourceBrowserSource | null;
|
23
|
+
sources: ResourceBrowserSource[];
|
24
|
+
isLoading: boolean;
|
25
|
+
error: Error | null;
|
26
|
+
setSource(source: ResourceBrowserSource | null): void;
|
27
|
+
isModalOpen: boolean;
|
28
|
+
onModalStateChange(isOpen: boolean): void;
|
29
|
+
};
|
30
|
+
|
31
|
+
export const ResourceBrowserInput = ({
|
32
|
+
modalTitle,
|
33
|
+
allowedTypes,
|
34
|
+
onChange,
|
35
|
+
value,
|
36
|
+
useResource,
|
37
|
+
isDisabled,
|
38
|
+
onClear,
|
39
|
+
plugin,
|
40
|
+
source,
|
41
|
+
sources,
|
42
|
+
isLoading,
|
43
|
+
error,
|
44
|
+
setSource,
|
45
|
+
isModalOpen,
|
46
|
+
onModalStateChange,
|
47
|
+
}: ResourceBrowserInputProps) => {
|
48
|
+
const { data: resource, error: resourceError, isLoading: isResourceLoading } = useResource(value?.resourceId || null, source);
|
49
|
+
|
50
|
+
const defaultOnClear = () => onChange(null);
|
51
|
+
const onClearFunction = onClear ?? defaultOnClear;
|
52
|
+
|
53
|
+
return (
|
54
|
+
<ResourcePicker
|
55
|
+
resource={resource}
|
56
|
+
plugin={plugin}
|
57
|
+
allowedTypes={allowedTypes}
|
58
|
+
error={resourceError || error}
|
59
|
+
isLoading={isResourceLoading || isLoading}
|
60
|
+
isDisabled={isDisabled}
|
61
|
+
onClear={onClearFunction}
|
62
|
+
isModalOpen={isModalOpen}
|
63
|
+
onModalStateChange={onModalStateChange}
|
64
|
+
>
|
65
|
+
{(onClose, titleProps) => (
|
66
|
+
<MainContainer
|
67
|
+
selectedSource={source}
|
68
|
+
sources={sources}
|
69
|
+
preselectedResource={resource}
|
70
|
+
plugin={plugin}
|
71
|
+
title={modalTitle}
|
72
|
+
titleAriaProps={titleProps}
|
73
|
+
allowedTypes={allowedTypes}
|
74
|
+
onSourceSelect={setSource}
|
75
|
+
onClose={onClose}
|
76
|
+
onChange={onChange}
|
77
|
+
/>
|
78
|
+
)}
|
79
|
+
</ResourcePicker>
|
80
|
+
);
|
81
|
+
};
|
@@ -1,123 +1,166 @@
|
|
1
1
|
import React from 'react';
|
2
|
-
import { render, screen } from '@testing-library/react';
|
3
|
-
import ResourcePicker from './ResourcePicker';
|
4
|
-
|
5
|
-
import
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
3
|
+
import { ResourcePicker } from './ResourcePicker';
|
4
|
+
|
5
|
+
import { ResourceBrowserPlugin } from '../types';
|
6
|
+
import { mockResource } from '../__mocks__/MockModels';
|
6
7
|
|
7
8
|
const defaultProps: any = {
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
resource: null,
|
10
|
+
allowedTypes: undefined,
|
11
|
+
isLoading: false,
|
12
|
+
isError: false,
|
12
13
|
};
|
13
14
|
|
14
15
|
describe('Resource picker', () => {
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
16
|
+
it('should render the initial state with the default label', () => {
|
17
|
+
render(<ResourcePicker {...defaultProps} />);
|
18
|
+
const pickerLabel = screen.getByText('Choose asset');
|
19
|
+
|
20
|
+
expect(pickerLabel).toBeInTheDocument();
|
21
|
+
});
|
22
|
+
|
23
|
+
it("should render the initial state with the image label if it's an image picker", () => {
|
24
|
+
render(<ResourcePicker {...defaultProps} allowedTypes={['image']} />);
|
25
|
+
const pickerLabel = screen.getByText('Choose image');
|
26
|
+
|
27
|
+
expect(pickerLabel).toBeInTheDocument();
|
28
|
+
});
|
29
|
+
|
30
|
+
it('should render the loading state if set to true', () => {
|
31
|
+
render(<ResourcePicker {...defaultProps} isLoading={true} />);
|
32
|
+
const pickerLabel = screen.queryByText('Choose image');
|
33
|
+
const loadingLabel = screen.queryByLabelText('Loading selection');
|
34
|
+
|
35
|
+
expect(pickerLabel).not.toBeInTheDocument();
|
36
|
+
expect(loadingLabel).toBeInTheDocument();
|
37
|
+
});
|
38
|
+
|
39
|
+
it('should render the error state if set to true', () => {
|
40
|
+
const errorMessage = 'Failed to retrieve asset info due to a Component Service API key problem.';
|
41
|
+
|
42
|
+
render(<ResourcePicker {...defaultProps} error={new Error(errorMessage)} />);
|
43
|
+
const pickerLabel = screen.queryByText('Choose image');
|
44
|
+
const errorLabel = screen.queryByText(errorMessage);
|
45
|
+
|
46
|
+
expect(pickerLabel).not.toBeInTheDocument();
|
47
|
+
expect(errorLabel).toBeInTheDocument();
|
48
|
+
});
|
49
|
+
|
50
|
+
it('should render the selected state if there is a resource returned (icon)', async () => {
|
51
|
+
const resource = mockResource();
|
52
|
+
const plugin: Partial<ResourceBrowserPlugin> = {
|
53
|
+
type: 'dam',
|
54
|
+
renderSelectedResource: jest.fn().mockResolvedValue({
|
55
|
+
showThumbnail: false,
|
56
|
+
icon: <div>IconGoesHere</div>,
|
57
|
+
label: 'Products',
|
58
|
+
description: [<div key="1">DescriptionFirstLine</div>, <div key="2">DescriptionSecondLine</div>],
|
59
|
+
}),
|
60
|
+
};
|
61
|
+
|
62
|
+
render(<ResourcePicker {...defaultProps} plugin={plugin as ResourceBrowserPlugin} resource={resource} />);
|
63
|
+
const pickerLabel = screen.queryByText('Choose image');
|
64
|
+
|
65
|
+
expect(pickerLabel).not.toBeInTheDocument();
|
66
|
+
|
67
|
+
// Includes the plugin response in the document
|
68
|
+
await waitFor(() => {
|
69
|
+
expect(screen.queryByText('Products')).toBeInTheDocument();
|
70
|
+
expect(screen.queryByText('IconGoesHere')).toBeInTheDocument();
|
71
|
+
expect(screen.queryByText('DescriptionFirstLine')).toBeInTheDocument();
|
72
|
+
expect(screen.queryByText('DescriptionSecondLine')).toBeInTheDocument();
|
73
|
+
});
|
74
|
+
});
|
75
|
+
|
76
|
+
it('should render the selected state if there is a resource returned (thumbnail)', async () => {
|
77
|
+
const resource = mockResource();
|
78
|
+
const plugin: Partial<ResourceBrowserPlugin> = {
|
79
|
+
type: 'dam',
|
80
|
+
renderSelectedResource: jest.fn().mockResolvedValue({
|
81
|
+
showThumbnail: true,
|
82
|
+
icon: <div>IconGoesHere</div>,
|
83
|
+
label: 'Products',
|
84
|
+
description: [<div key="1">DescriptionFirstLine</div>, <div key="2">DescriptionSecondLine</div>],
|
85
|
+
}),
|
86
|
+
};
|
87
|
+
|
88
|
+
render(<ResourcePicker {...defaultProps} plugin={plugin as ResourceBrowserPlugin} resource={resource} />);
|
89
|
+
const pickerLabel = screen.queryByText('Choose image');
|
90
|
+
|
91
|
+
expect(pickerLabel).not.toBeInTheDocument();
|
92
|
+
|
93
|
+
// Includes the plugin response in the document
|
94
|
+
await waitFor(() => {
|
95
|
+
expect(screen.queryByText('Products')).toBeInTheDocument();
|
96
|
+
expect(screen.queryByAltText('an-image-name')).toBeInTheDocument();
|
97
|
+
expect(screen.queryByText('DescriptionFirstLine')).toBeInTheDocument();
|
98
|
+
expect(screen.queryByText('DescriptionSecondLine')).toBeInTheDocument();
|
99
|
+
});
|
100
|
+
});
|
101
|
+
|
102
|
+
it('should display the reset button in selected state', async () => {
|
103
|
+
const resource = mockResource();
|
104
|
+
const plugin: Partial<ResourceBrowserPlugin> = {
|
105
|
+
type: 'dam',
|
106
|
+
renderSelectedResource: jest.fn().mockResolvedValue({
|
107
|
+
icon: <></>,
|
108
|
+
label: 'Products',
|
109
|
+
description: [],
|
110
|
+
}),
|
111
|
+
};
|
112
|
+
|
113
|
+
render(<ResourcePicker {...defaultProps} plugin={plugin as ResourceBrowserPlugin} resource={resource} />);
|
114
|
+
|
115
|
+
await waitFor(() => {
|
116
|
+
const resourceName = screen.queryByText('Products');
|
117
|
+
const removeButton = screen.queryByLabelText('Remove selection');
|
118
|
+
|
119
|
+
expect(resourceName).toBeInTheDocument();
|
120
|
+
expect(removeButton).toBeInTheDocument();
|
121
|
+
});
|
122
|
+
});
|
123
|
+
|
124
|
+
it('should display disabled reset button in selected + disabled state', async () => {
|
125
|
+
const plugin: Partial<ResourceBrowserPlugin> = {
|
126
|
+
type: 'dam',
|
127
|
+
renderSelectedResource: jest.fn().mockResolvedValue({
|
128
|
+
icon: <></>,
|
129
|
+
label: 'Products',
|
130
|
+
description: [],
|
131
|
+
}),
|
132
|
+
};
|
133
|
+
const resource = mockResource();
|
134
|
+
render(<ResourcePicker {...defaultProps} isDisabled={true} plugin={plugin as ResourceBrowserPlugin} resource={resource} />);
|
135
|
+
|
136
|
+
await waitFor(() => {
|
137
|
+
expect(screen.queryByLabelText('Remove selection')).toBeDisabled();
|
138
|
+
});
|
139
|
+
});
|
140
|
+
|
141
|
+
it('should display the reset button in error state', () => {
|
142
|
+
const errorMessage = 'Failed to fetch resource.';
|
143
|
+
|
144
|
+
render(<ResourcePicker {...defaultProps} error={new Error(errorMessage)} />);
|
145
|
+
const errorLabel = screen.queryByText(errorMessage);
|
146
|
+
const removeButton = screen.queryByLabelText('Remove selection');
|
147
|
+
|
148
|
+
expect(errorLabel).toBeInTheDocument();
|
149
|
+
expect(removeButton).toBeInTheDocument();
|
150
|
+
});
|
151
|
+
|
152
|
+
it('should display disabled reset button in error + disabled state', () => {
|
153
|
+
render(<ResourcePicker {...defaultProps} isDisabled={true} error={new Error('Failed to fetch resource.')} />);
|
154
|
+
|
155
|
+
expect(screen.queryByLabelText('Remove selection')).toBeDisabled();
|
156
|
+
});
|
157
|
+
|
158
|
+
it('should not display the reset button in the empty state', () => {
|
159
|
+
render(<ResourcePicker {...defaultProps} />);
|
160
|
+
const pickerLabel = screen.getByText('Choose asset');
|
161
|
+
const removeButton = screen.queryByLabelText('Remove selection');
|
162
|
+
|
163
|
+
expect(pickerLabel).toBeInTheDocument();
|
164
|
+
expect(removeButton).not.toBeInTheDocument();
|
165
|
+
});
|
123
166
|
});
|