@squiz/resource-browser 3.1.0 → 3.1.2
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 +13 -0
- package/lib/Hooks/useAuth.js +2 -1
- package/lib/Plugin/Plugin.d.ts +2 -1
- package/lib/ResourceBrowserInput/ResourceBrowserInput.d.ts +4 -7
- package/lib/ResourceBrowserInput/ResourceBrowserInput.js +2 -2
- package/lib/ResourcePicker/ResourcePicker.d.ts +2 -1
- package/lib/ResourcePicker/ResourcePicker.js +13 -3
- package/lib/index.css +14 -1
- package/lib/index.js +9 -2
- package/package.json +1 -1
- package/src/Hooks/useAuth.spec.tsx +46 -2
- package/src/Hooks/useAuth.ts +2 -1
- package/src/Plugin/Plugin.spec.tsx +4 -0
- package/src/Plugin/Plugin.tsx +2 -1
- package/src/ResourceBrowserInput/ResourceBrowserInput.spec.tsx +1 -0
- package/src/ResourceBrowserInput/ResourceBrowserInput.tsx +5 -8
- package/src/ResourcePicker/ResourcePicker.spec.tsx +10 -0
- package/src/ResourcePicker/ResourcePicker.tsx +26 -2
- package/src/__mocks__/StorybookHelpers.tsx +4 -0
- package/src/index.spec.tsx +231 -8
- package/src/index.tsx +14 -1
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 3.1.2
|
4
|
+
|
5
|
+
### Patch Changes
|
6
|
+
|
7
|
+
- c1ebb09: format scope as expected for oAuth calls (space delimited)
|
8
|
+
- 213ea12: change plugin render comparison to type not object ref
|
9
|
+
|
10
|
+
## 3.1.1
|
11
|
+
|
12
|
+
### Patch Changes
|
13
|
+
|
14
|
+
- 1ce2636: dont render select preview if switched to different source
|
15
|
+
|
3
16
|
## 3.1.0
|
4
17
|
|
5
18
|
### Patch Changes
|
package/lib/Hooks/useAuth.js
CHANGED
@@ -19,7 +19,8 @@ const useAuth = (authConfig) => {
|
|
19
19
|
return;
|
20
20
|
}
|
21
21
|
const encodedRedirectUrl = encodeURIComponent(authConfig.redirectUrl);
|
22
|
-
const
|
22
|
+
const scope = authConfig?.scope.split(';').join(' '); // Saved in scope1;scope2;scope3 format, sent in scope1 scope2 scope3 format
|
23
|
+
const loginUrl = `${authConfig?.authUrl}?client_id=${authConfig?.clientId}&scope=${scope}&redirect_uri=${encodedRedirectUrl}&response_type=code&state=state`;
|
23
24
|
const popup = window.open(loginUrl, 'Login', 'width=600,height=600');
|
24
25
|
if (!popup) {
|
25
26
|
console.error('Popup failed to open');
|
package/lib/Plugin/Plugin.d.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { ResourceBrowserInputProps } from '../ResourceBrowserInput/ResourceBrowserInput';
|
3
|
-
import { InlineType } from '../types';
|
3
|
+
import { ResourceBrowserPluginType, InlineType } from '../types';
|
4
4
|
/**
|
5
5
|
* This plugin component exsits to deal with React rules of Hooks stupidity.
|
6
6
|
*
|
@@ -9,6 +9,7 @@ import { InlineType } from '../types';
|
|
9
9
|
* needs to render its UI etc.
|
10
10
|
*/
|
11
11
|
export type PluginRenderType = ResourceBrowserInputProps & {
|
12
|
+
type: ResourceBrowserPluginType | null;
|
12
13
|
render: boolean;
|
13
14
|
inline: boolean;
|
14
15
|
inlineType?: InlineType;
|
@@ -1,15 +1,11 @@
|
|
1
1
|
import React from 'react';
|
2
|
-
import { ResourceBrowserPlugin, ResourceBrowserSource, ResourceBrowserUnresolvedResource, ResourceBrowserResource, ResourceBrowserSourceWithPlugin, PluginLaunchMode } from '../types';
|
2
|
+
import { ResourceBrowserPlugin, ResourceBrowserSource, ResourceBrowserUnresolvedResource, ResourceBrowserResource, ResourceBrowserSourceWithPlugin, PluginLaunchMode, useResolveResourceResponse } from '../types';
|
3
3
|
export type ResourceBrowserInputProps = {
|
4
4
|
modalTitle: string;
|
5
5
|
allowedTypes?: string[];
|
6
6
|
isDisabled?: boolean;
|
7
7
|
value: ResourceBrowserUnresolvedResource | null;
|
8
|
-
useResource(referenceId: string | null, source: ResourceBrowserSource | null):
|
9
|
-
data: ResourceBrowserResource | null;
|
10
|
-
error: Error | null;
|
11
|
-
isLoading: boolean;
|
12
|
-
};
|
8
|
+
useResource(referenceId: string | null, source: ResourceBrowserSource | null): useResolveResourceResponse;
|
13
9
|
onChange(resource: ResourceBrowserResource | null): void;
|
14
10
|
onClear?(): void;
|
15
11
|
plugin: ResourceBrowserPlugin | null;
|
@@ -18,9 +14,10 @@ export type ResourceBrowserInputProps = {
|
|
18
14
|
source: ResourceBrowserSource | null;
|
19
15
|
sources: ResourceBrowserSourceWithPlugin[];
|
20
16
|
isLoading: boolean;
|
17
|
+
isOtherSourceValue: boolean;
|
21
18
|
error: Error | null;
|
22
19
|
setSource(source: ResourceBrowserSource, mode?: PluginLaunchMode): void;
|
23
20
|
isModalOpen: boolean;
|
24
21
|
onModalStateChange(isOpen: boolean): void;
|
25
22
|
};
|
26
|
-
export declare const ResourceBrowserInput: ({ modalTitle, allowedTypes, onChange, value, useResource, isDisabled, onClear, plugin, pluginMode, searchEnabled, source, sources, isLoading, error, setSource, isModalOpen, onModalStateChange, }: ResourceBrowserInputProps) => React.JSX.Element;
|
23
|
+
export declare const ResourceBrowserInput: ({ modalTitle, allowedTypes, onChange, value, useResource, isDisabled, onClear, plugin, pluginMode, searchEnabled, source, sources, isLoading, isOtherSourceValue, error, setSource, isModalOpen, onModalStateChange, }: ResourceBrowserInputProps) => React.JSX.Element;
|
@@ -7,10 +7,10 @@ exports.ResourceBrowserInput = void 0;
|
|
7
7
|
const react_1 = __importDefault(require("react"));
|
8
8
|
const MainContainer_1 = __importDefault(require("../MainContainer/MainContainer"));
|
9
9
|
const ResourcePicker_1 = require("../ResourcePicker/ResourcePicker");
|
10
|
-
const ResourceBrowserInput = ({ modalTitle, allowedTypes, onChange, value, useResource, isDisabled, onClear, plugin, pluginMode, searchEnabled, source, sources, isLoading, error, setSource, isModalOpen, onModalStateChange, }) => {
|
10
|
+
const ResourceBrowserInput = ({ modalTitle, allowedTypes, onChange, value, useResource, isDisabled, onClear, plugin, pluginMode, searchEnabled, source, sources, isLoading, isOtherSourceValue, error, setSource, isModalOpen, onModalStateChange, }) => {
|
11
11
|
const { data: resource, error: resourceError, isLoading: isResourceLoading } = useResource(value?.resourceId || null, source);
|
12
12
|
const defaultOnClear = () => onChange(null);
|
13
13
|
const onClearFunction = onClear ?? defaultOnClear;
|
14
|
-
return (react_1.default.createElement(ResourcePicker_1.ResourcePicker, { resource: resource, plugin: plugin, allowedTypes: allowedTypes, error: resourceError || error, isLoading: isResourceLoading || isLoading, isDisabled: isDisabled, onClear: onClearFunction, isModalOpen: isModalOpen, onModalStateChange: onModalStateChange }, (onClose, titleProps) => (react_1.default.createElement(MainContainer_1.default, { selectedSource: source, sources: sources, preselectedResource: resource, plugin: plugin, pluginMode: pluginMode, searchEnabled: searchEnabled, title: modalTitle, titleAriaProps: titleProps, allowedTypes: allowedTypes, onSourceSelect: setSource, onClose: onClose, onChange: onChange }))));
|
14
|
+
return (react_1.default.createElement(ResourcePicker_1.ResourcePicker, { resource: resource, plugin: plugin, allowedTypes: allowedTypes, error: resourceError || error, isLoading: isResourceLoading || isLoading, isOtherSourceValue: isOtherSourceValue, isDisabled: isDisabled, onClear: onClearFunction, isModalOpen: isModalOpen, onModalStateChange: onModalStateChange }, (onClose, titleProps) => (react_1.default.createElement(MainContainer_1.default, { selectedSource: source, sources: sources, preselectedResource: resource, plugin: plugin, pluginMode: pluginMode, searchEnabled: searchEnabled, title: modalTitle, titleAriaProps: titleProps, allowedTypes: allowedTypes, onSourceSelect: setSource, onClose: onClose, onChange: onChange }))));
|
15
15
|
};
|
16
16
|
exports.ResourceBrowserInput = ResourceBrowserInput;
|
@@ -7,10 +7,11 @@ export type ResourcePickerProps = {
|
|
7
7
|
allowedTypes: string[] | undefined;
|
8
8
|
error: Error | null;
|
9
9
|
isLoading: boolean;
|
10
|
+
isOtherSourceValue: boolean;
|
10
11
|
isDisabled?: boolean;
|
11
12
|
children: (onClose: () => void, titleProps: DOMAttributes) => React.ReactElement;
|
12
13
|
onClear: () => void;
|
13
14
|
isModalOpen: boolean;
|
14
15
|
onModalStateChange(isOpen: boolean): void;
|
15
16
|
};
|
16
|
-
export declare const ResourcePicker: ({ resource, plugin, allowedTypes, error: externalError, isLoading: isExternalLoading, isDisabled, children, onClear, isModalOpen, onModalStateChange, }: ResourcePickerProps) => React.JSX.Element;
|
17
|
+
export declare const ResourcePicker: ({ resource, plugin, allowedTypes, error: externalError, isLoading: isExternalLoading, isOtherSourceValue, isDisabled, children, onClear, isModalOpen, onModalStateChange, }: ResourcePickerProps) => React.JSX.Element;
|
@@ -14,16 +14,26 @@ const Loading_1 = require("./States/Loading");
|
|
14
14
|
const Selected_1 = require("./States/Selected");
|
15
15
|
const useSelectedState_1 = require("../Hooks/useSelectedState");
|
16
16
|
const clsx_1 = __importDefault(require("clsx"));
|
17
|
-
const ResourcePicker = ({ resource, plugin, allowedTypes, error: externalError, isLoading: isExternalLoading, isDisabled, children, onClear, isModalOpen, onModalStateChange, }) => {
|
17
|
+
const ResourcePicker = ({ resource, plugin, allowedTypes, error: externalError, isLoading: isExternalLoading, isOtherSourceValue, isDisabled, children, onClear, isModalOpen, onModalStateChange, }) => {
|
18
18
|
const { data: selectedState, error, isLoading } = (0, useSelectedState_1.useSelectedState)({ resource, plugin });
|
19
19
|
const isImagePicker = allowedTypes && allowedTypes.length === 1 && allowedTypes.includes('image');
|
20
20
|
const isEmpty = resource === null && !isExternalLoading && !externalError;
|
21
21
|
return (react_1.default.createElement("div", { className: (0, clsx_1.default)('resource-picker', isDisabled && 'resource-picker--disabled') },
|
22
22
|
isImagePicker ? (react_1.default.createElement(PhotoLibraryRounded_1.default, { "aria-hidden": true, className: "w-6 h-6" })) : (react_1.default.createElement(AdsClickRounded_1.default, { "aria-hidden": true, className: "w-6 h-6" })),
|
23
|
-
|
23
|
+
isOtherSourceValue && (
|
24
|
+
// User is selecting a new image from a different source, render a loading spinner as we can't resolve the value
|
25
|
+
react_1.default.createElement("div", { className: "resource-picker-info" },
|
26
|
+
react_1.default.createElement("div", { className: "resource-picker-info__layout" },
|
27
|
+
react_1.default.createElement(Loading_1.LoadingState, null),
|
28
|
+
react_1.default.createElement(resource_browser_ui_lib_1.ModalTrigger, { overlayTriggerState: {
|
29
|
+
isOpen: isModalOpen,
|
30
|
+
onOpenChange: onModalStateChange,
|
31
|
+
}, showLabel: false, label: "", isDisabled: true, scope: "squiz-rb-scope" }, children)))),
|
32
|
+
isEmpty && !isOtherSourceValue && (react_1.default.createElement(resource_browser_ui_lib_1.ModalTrigger, { overlayTriggerState: {
|
24
33
|
isOpen: isModalOpen,
|
25
34
|
onOpenChange: onModalStateChange,
|
26
|
-
}, showLabel: true, label: isImagePicker ? `Choose image` : `Choose asset`, icon: react_1.default.createElement(AddCircleOutlineRounded_1.default, { "aria-hidden": true, className: `!w-4 !h-4 text-blue-300 ${isDisabled ? 'text-gray-600' : ''}` }), isDisabled: isDisabled, scope: "squiz-rb-scope" }, children))
|
35
|
+
}, showLabel: true, label: isImagePicker ? `Choose image` : `Choose asset`, icon: react_1.default.createElement(AddCircleOutlineRounded_1.default, { "aria-hidden": true, className: `!w-4 !h-4 text-blue-300 ${isDisabled ? 'text-gray-600' : ''}` }), isDisabled: isDisabled, scope: "squiz-rb-scope" }, children)),
|
36
|
+
!isEmpty && !isOtherSourceValue && (react_1.default.createElement("div", { className: "resource-picker-info" },
|
27
37
|
react_1.default.createElement("div", { className: "resource-picker-info__layout" },
|
28
38
|
(isExternalLoading || isLoading) && react_1.default.createElement(Loading_1.LoadingState, null),
|
29
39
|
(externalError || error) && (react_1.default.createElement(Error_1.ErrorState, { error: externalError || error, isDisabled: isDisabled, onClear: onClear })),
|
package/lib/index.css
CHANGED
@@ -5727,6 +5727,9 @@
|
|
5727
5727
|
margin: 0.25rem 0.5rem;
|
5728
5728
|
border-bottom: 1px solid #e0e0e0;
|
5729
5729
|
}
|
5730
|
+
.squiz-rb-scope .selection-list__item:first-child:not(.squiz-rb-plugin *) {
|
5731
|
+
margin-top: 8px;
|
5732
|
+
}
|
5730
5733
|
.squiz-rb-scope .selection-list__item:last-child:not(.squiz-rb-plugin *) {
|
5731
5734
|
border-bottom: none;
|
5732
5735
|
}
|
@@ -6164,6 +6167,7 @@
|
|
6164
6167
|
margin: auto;
|
6165
6168
|
}
|
6166
6169
|
.squiz-rb-scope .image-info__title:not(.squiz-rb-plugin *) {
|
6170
|
+
padding: 0 2.2rem;
|
6167
6171
|
margin: 1rem 0;
|
6168
6172
|
color: #3d3d3d;
|
6169
6173
|
}
|
@@ -6172,6 +6176,7 @@
|
|
6172
6176
|
color: #707070;
|
6173
6177
|
}
|
6174
6178
|
.squiz-rb-scope .image-info__details-list:not(.squiz-rb-plugin *) {
|
6179
|
+
width: 270px;
|
6175
6180
|
display: flex;
|
6176
6181
|
flex-direction: column;
|
6177
6182
|
margin: 1rem 0;
|
@@ -6186,10 +6191,11 @@
|
|
6186
6191
|
display: flex;
|
6187
6192
|
}
|
6188
6193
|
.squiz-rb-scope .image-info__details-value:not(.squiz-rb-plugin *) {
|
6194
|
+
margin-left: 20px;
|
6189
6195
|
color: #3d3d3d;
|
6190
6196
|
}
|
6191
6197
|
.squiz-rb-scope .image-variant__list:not(.squiz-rb-plugin *) {
|
6192
|
-
width:
|
6198
|
+
width: 280px;
|
6193
6199
|
margin-bottom: 1rem;
|
6194
6200
|
}
|
6195
6201
|
.squiz-rb-scope .image-variant__item:not(.squiz-rb-plugin *) {
|
@@ -6199,6 +6205,9 @@
|
|
6199
6205
|
width: 100%;
|
6200
6206
|
justify-content: space-between;
|
6201
6207
|
}
|
6208
|
+
.squiz-rb-scope .selection-list__item:focus-visible:not(.squiz-rb-plugin *) {
|
6209
|
+
outline-width: 0px;
|
6210
|
+
}
|
6202
6211
|
.squiz-rb-scope .selection-list__item--selected .image-variant__checkmark:not(.squiz-rb-plugin *) {
|
6203
6212
|
visibility: visible;
|
6204
6213
|
}
|
@@ -6253,6 +6262,10 @@
|
|
6253
6262
|
overflow-y: auto;
|
6254
6263
|
}
|
6255
6264
|
}
|
6265
|
+
.squiz-rb-scope .divider-container:not(.squiz-rb-plugin *) {
|
6266
|
+
padding: 0 1.25rem;
|
6267
|
+
width: 100%;
|
6268
|
+
}
|
6256
6269
|
.squiz-rb-scope .resource-picker:not(.squiz-rb-plugin *) {
|
6257
6270
|
display: grid;
|
6258
6271
|
grid-template-columns: 24px 1fr;
|
package/lib/index.js
CHANGED
@@ -98,12 +98,19 @@ const ResourceBrowser = (props) => {
|
|
98
98
|
}, [setIsModalOpen]);
|
99
99
|
// If the modal closes and we dont have a value clear the source state so it goes back to the launcher on re-open
|
100
100
|
(0, react_1.useEffect)(() => {
|
101
|
+
// If modal is closed and we dont have a value
|
101
102
|
if (!isModalOpen && !value && (sources?.length > 1 || searchEnabled)) {
|
102
103
|
setSource(null);
|
103
104
|
setMode(null);
|
104
105
|
}
|
105
106
|
else if (!isModalOpen) {
|
107
|
+
// Otherwise reset the RB mode
|
106
108
|
setMode(null);
|
109
|
+
// If there is a value passed in, reset the source so the preselected asset preview renders correctly
|
110
|
+
if (value && value.sourceId !== source?.id) {
|
111
|
+
const source = sources.find((source) => source.id === value?.sourceId) || null;
|
112
|
+
setSource(source);
|
113
|
+
}
|
107
114
|
}
|
108
115
|
}, [sources, isModalOpen]);
|
109
116
|
// Reset function to allow user manual reload if sources fail in inline usage
|
@@ -112,7 +119,7 @@ const ResourceBrowser = (props) => {
|
|
112
119
|
}, [reloadSources]);
|
113
120
|
// Render a default "plugin" and one for each item in the plugins array. They are conditionally rendered based on what is selected
|
114
121
|
return (react_1.default.createElement("div", { className: "squiz-rb-scope" },
|
115
|
-
react_1.default.createElement(Plugin_1.PluginRender, { key: "default", render: plugin === null, inline: !!inline, inlineType: inlineType, ...props, source: source, sources: sources, setSource: handleSourceSelect, isLoading: isLoading, error: sourcesError || error, plugin: plugin, pluginMode: mode, searchEnabled: searchEnabled, useResource: () => {
|
122
|
+
react_1.default.createElement(Plugin_1.PluginRender, { key: "default", type: null, render: plugin === null, inline: !!inline, inlineType: inlineType, ...props, source: source, sources: sources, setSource: handleSourceSelect, isLoading: isLoading, isOtherSourceValue: false, error: sourcesError || error, plugin: plugin, pluginMode: mode, searchEnabled: searchEnabled, useResource: () => {
|
116
123
|
return {
|
117
124
|
data: null,
|
118
125
|
error: null,
|
@@ -120,7 +127,7 @@ const ResourceBrowser = (props) => {
|
|
120
127
|
};
|
121
128
|
}, isModalOpen: isModalOpen, onModalStateChange: handleModalStateChange, onRetry: handleReset }),
|
122
129
|
plugins.map((thisPlugin) => {
|
123
|
-
return (react_1.default.createElement(Plugin_1.PluginRender, { key: thisPlugin.type, render: thisPlugin === plugin, inline: !!inline, inlineType: inlineType, ...props, source: source, sources: sources, setSource: handleSourceSelect, isLoading: isLoading, error: sourcesError || error, plugin: plugin, pluginMode: mode, searchEnabled: searchEnabled, useResource: thisPlugin.useResolveResource, isModalOpen: isModalOpen, onModalStateChange: handleModalStateChange, onRetry: handleReset }));
|
130
|
+
return (react_1.default.createElement(Plugin_1.PluginRender, { key: thisPlugin.type, type: thisPlugin.type, render: thisPlugin.type === plugin?.type, inline: !!inline, inlineType: inlineType, ...props, value: value && source ? (value.sourceId === source.id ? value : null) : null, isOtherSourceValue: value && source ? (value.sourceId !== source.id ? true : false) : false, source: source, sources: sources, setSource: handleSourceSelect, isLoading: isLoading, error: sourcesError || error, plugin: plugin, pluginMode: mode, searchEnabled: searchEnabled, useResource: thisPlugin.useResolveResource, isModalOpen: isModalOpen, onModalStateChange: handleModalStateChange, onRetry: handleReset }));
|
124
131
|
})));
|
125
132
|
};
|
126
133
|
exports.ResourceBrowser = ResourceBrowser;
|
package/package.json
CHANGED
@@ -14,7 +14,7 @@ describe('useAuth', () => {
|
|
14
14
|
authUrl: 'https://auth.example.com',
|
15
15
|
clientId: 'example-client-id',
|
16
16
|
redirectUrl: 'https://example.com/callback',
|
17
|
-
scope: '
|
17
|
+
scope: 'offline',
|
18
18
|
};
|
19
19
|
|
20
20
|
beforeEach(() => {
|
@@ -107,7 +107,7 @@ describe('useAuth', () => {
|
|
107
107
|
result.current.login();
|
108
108
|
|
109
109
|
expect(window.open).toHaveBeenCalledWith(
|
110
|
-
`${authConfig.authUrl}?client_id=${authConfig.clientId}&scope=
|
110
|
+
`${authConfig.authUrl}?client_id=${authConfig.clientId}&scope=offline&redirect_uri=${encodeURIComponent(authConfig.redirectUrl)}&response_type=code&state=state`,
|
111
111
|
'Login',
|
112
112
|
'width=600,height=600',
|
113
113
|
);
|
@@ -126,6 +126,50 @@ describe('useAuth', () => {
|
|
126
126
|
expect(popupMock.close).toHaveBeenCalled();
|
127
127
|
});
|
128
128
|
|
129
|
+
it('splits the scope from dxp-console and sends in expected format (space delimited)', async () => {
|
130
|
+
jest.useFakeTimers();
|
131
|
+
|
132
|
+
const popupMock = {
|
133
|
+
closed: false,
|
134
|
+
close: jest.fn(),
|
135
|
+
} as unknown as Window;
|
136
|
+
|
137
|
+
jest.spyOn(window, 'open').mockImplementation(() => popupMock);
|
138
|
+
mockGetCookieValue.mockReturnValueOnce(null).mockReturnValueOnce('newAuthToken').mockReturnValueOnce('newRefreshToken');
|
139
|
+
|
140
|
+
const { result } = renderHook(() => useAuth({ ...authConfig, scope: 'offline;asset:read' }));
|
141
|
+
|
142
|
+
result.current.login();
|
143
|
+
|
144
|
+
expect(window.open).toHaveBeenCalledWith(
|
145
|
+
`${authConfig.authUrl}?client_id=${authConfig.clientId}&scope=offline asset:read&redirect_uri=${encodeURIComponent(authConfig.redirectUrl)}&response_type=code&state=state`,
|
146
|
+
'Login',
|
147
|
+
'width=600,height=600',
|
148
|
+
);
|
149
|
+
});
|
150
|
+
|
151
|
+
it('Works with an empty scope', async () => {
|
152
|
+
jest.useFakeTimers();
|
153
|
+
|
154
|
+
const popupMock = {
|
155
|
+
closed: false,
|
156
|
+
close: jest.fn(),
|
157
|
+
} as unknown as Window;
|
158
|
+
|
159
|
+
jest.spyOn(window, 'open').mockImplementation(() => popupMock);
|
160
|
+
mockGetCookieValue.mockReturnValueOnce(null).mockReturnValueOnce('newAuthToken').mockReturnValueOnce('newRefreshToken');
|
161
|
+
|
162
|
+
const { result } = renderHook(() => useAuth({ ...authConfig, scope: '' }));
|
163
|
+
|
164
|
+
result.current.login();
|
165
|
+
|
166
|
+
expect(window.open).toHaveBeenCalledWith(
|
167
|
+
`${authConfig.authUrl}?client_id=${authConfig.clientId}&scope=&redirect_uri=${encodeURIComponent(authConfig.redirectUrl)}&response_type=code&state=state`,
|
168
|
+
'Login',
|
169
|
+
'width=600,height=600',
|
170
|
+
);
|
171
|
+
});
|
172
|
+
|
129
173
|
it('should refresh access token and update state', async () => {
|
130
174
|
mockGetCookieValue.mockReturnValue('initialRefreshToken');
|
131
175
|
mockRefreshAccessToken.mockResolvedValue('newAuthToken');
|
package/src/Hooks/useAuth.ts
CHANGED
@@ -20,7 +20,8 @@ export const useAuth = (authConfig: AuthenticationConfiguration | undefined) =>
|
|
20
20
|
return;
|
21
21
|
}
|
22
22
|
const encodedRedirectUrl = encodeURIComponent(authConfig.redirectUrl);
|
23
|
-
const
|
23
|
+
const scope = authConfig?.scope.split(';').join(' '); // Saved in scope1;scope2;scope3 format, sent in scope1 scope2 scope3 format
|
24
|
+
const loginUrl = `${authConfig?.authUrl}?client_id=${authConfig?.clientId}&scope=${scope}&redirect_uri=${encodedRedirectUrl}&response_type=code&state=state`;
|
24
25
|
const popup = window.open(loginUrl, 'Login', 'width=600,height=600');
|
25
26
|
|
26
27
|
if (!popup) {
|
@@ -19,8 +19,10 @@ describe('Plugin', () => {
|
|
19
19
|
|
20
20
|
it('Does render ResourceBrowserInput if render is true', async () => {
|
21
21
|
const props = {
|
22
|
+
type: null,
|
22
23
|
modalTitle: 'Asset picker',
|
23
24
|
value: null,
|
25
|
+
isOtherSourceValue: false,
|
24
26
|
onChange: jest.fn(),
|
25
27
|
onClear: jest.fn(),
|
26
28
|
useResource: () => {
|
@@ -51,8 +53,10 @@ describe('Plugin', () => {
|
|
51
53
|
|
52
54
|
it('Does render ResourceBrowserInlineButton if inline is true', async () => {
|
53
55
|
const props = {
|
56
|
+
type: null,
|
54
57
|
modalTitle: 'Asset picker',
|
55
58
|
value: null,
|
59
|
+
isOtherSourceValue: false,
|
56
60
|
onChange: jest.fn(),
|
57
61
|
onClear: jest.fn(),
|
58
62
|
useResource: () => {
|
package/src/Plugin/Plugin.tsx
CHANGED
@@ -2,7 +2,7 @@ import React from 'react';
|
|
2
2
|
import { ResourceBrowserInput, ResourceBrowserInputProps } from '../ResourceBrowserInput/ResourceBrowserInput';
|
3
3
|
import { ResourceBrowserInlineButton } from '../ResourceBrowserInlineButton/ResourceBrowserInlineButton';
|
4
4
|
import { AuthProvider } from '../ResourceBrowserContext/AuthProvider';
|
5
|
-
import { InlineType } from '../types';
|
5
|
+
import { ResourceBrowserPluginType, InlineType } from '../types';
|
6
6
|
|
7
7
|
/**
|
8
8
|
* This plugin component exsits to deal with React rules of Hooks stupidity.
|
@@ -12,6 +12,7 @@ import { InlineType } from '../types';
|
|
12
12
|
* needs to render its UI etc.
|
13
13
|
*/
|
14
14
|
export type PluginRenderType = ResourceBrowserInputProps & {
|
15
|
+
type: ResourceBrowserPluginType | null;
|
15
16
|
render: boolean;
|
16
17
|
inline: boolean;
|
17
18
|
inlineType?: InlineType;
|
@@ -8,6 +8,7 @@ import {
|
|
8
8
|
ResourceBrowserResource,
|
9
9
|
ResourceBrowserSourceWithPlugin,
|
10
10
|
PluginLaunchMode,
|
11
|
+
useResolveResourceResponse,
|
11
12
|
} from '../types';
|
12
13
|
|
13
14
|
export type ResourceBrowserInputProps = {
|
@@ -15,14 +16,7 @@ export type ResourceBrowserInputProps = {
|
|
15
16
|
allowedTypes?: string[];
|
16
17
|
isDisabled?: boolean;
|
17
18
|
value: ResourceBrowserUnresolvedResource | null;
|
18
|
-
useResource(
|
19
|
-
referenceId: string | null,
|
20
|
-
source: ResourceBrowserSource | null,
|
21
|
-
): {
|
22
|
-
data: ResourceBrowserResource | null;
|
23
|
-
error: Error | null;
|
24
|
-
isLoading: boolean;
|
25
|
-
};
|
19
|
+
useResource(referenceId: string | null, source: ResourceBrowserSource | null): useResolveResourceResponse;
|
26
20
|
onChange(resource: ResourceBrowserResource | null): void;
|
27
21
|
onClear?(): void;
|
28
22
|
plugin: ResourceBrowserPlugin | null;
|
@@ -31,6 +25,7 @@ export type ResourceBrowserInputProps = {
|
|
31
25
|
source: ResourceBrowserSource | null;
|
32
26
|
sources: ResourceBrowserSourceWithPlugin[];
|
33
27
|
isLoading: boolean;
|
28
|
+
isOtherSourceValue: boolean;
|
34
29
|
error: Error | null;
|
35
30
|
setSource(source: ResourceBrowserSource, mode?: PluginLaunchMode): void;
|
36
31
|
isModalOpen: boolean;
|
@@ -51,6 +46,7 @@ export const ResourceBrowserInput = ({
|
|
51
46
|
source,
|
52
47
|
sources,
|
53
48
|
isLoading,
|
49
|
+
isOtherSourceValue,
|
54
50
|
error,
|
55
51
|
setSource,
|
56
52
|
isModalOpen,
|
@@ -68,6 +64,7 @@ export const ResourceBrowserInput = ({
|
|
68
64
|
allowedTypes={allowedTypes}
|
69
65
|
error={resourceError || error}
|
70
66
|
isLoading={isResourceLoading || isLoading}
|
67
|
+
isOtherSourceValue={isOtherSourceValue}
|
71
68
|
isDisabled={isDisabled}
|
72
69
|
onClear={onClearFunction}
|
73
70
|
isModalOpen={isModalOpen}
|
@@ -10,6 +10,7 @@ const defaultProps: any = {
|
|
10
10
|
allowedTypes: undefined,
|
11
11
|
isLoading: false,
|
12
12
|
isError: false,
|
13
|
+
isOtherSourceValue: false,
|
13
14
|
};
|
14
15
|
|
15
16
|
describe('Resource picker', () => {
|
@@ -36,6 +37,15 @@ describe('Resource picker', () => {
|
|
36
37
|
expect(loadingLabel).toBeInTheDocument();
|
37
38
|
});
|
38
39
|
|
40
|
+
it('should render the loading state if isOtherSourceValue is set to true', () => {
|
41
|
+
render(<ResourcePicker {...defaultProps} isLoading={false} isOtherSourceValue={true} />);
|
42
|
+
const pickerLabel = screen.queryByText('Choose image');
|
43
|
+
const loadingLabel = screen.queryByLabelText('Loading selection');
|
44
|
+
|
45
|
+
expect(pickerLabel).not.toBeInTheDocument();
|
46
|
+
expect(loadingLabel).toBeInTheDocument();
|
47
|
+
});
|
48
|
+
|
39
49
|
it('should render the error state if set to true', () => {
|
40
50
|
const errorMessage = 'Failed to retrieve asset info due to a Component Service API key problem.';
|
41
51
|
|
@@ -18,6 +18,7 @@ export type ResourcePickerProps = {
|
|
18
18
|
allowedTypes: string[] | undefined;
|
19
19
|
error: Error | null;
|
20
20
|
isLoading: boolean;
|
21
|
+
isOtherSourceValue: boolean;
|
21
22
|
isDisabled?: boolean;
|
22
23
|
children: (onClose: () => void, titleProps: DOMAttributes) => React.ReactElement;
|
23
24
|
onClear: () => void;
|
@@ -31,6 +32,7 @@ export const ResourcePicker = ({
|
|
31
32
|
allowedTypes,
|
32
33
|
error: externalError,
|
33
34
|
isLoading: isExternalLoading,
|
35
|
+
isOtherSourceValue,
|
34
36
|
isDisabled,
|
35
37
|
children,
|
36
38
|
onClear,
|
@@ -49,7 +51,28 @@ export const ResourcePicker = ({
|
|
49
51
|
) : (
|
50
52
|
<AdsClickRoundedIcon aria-hidden className="w-6 h-6" />
|
51
53
|
)}
|
52
|
-
{
|
54
|
+
{isOtherSourceValue && (
|
55
|
+
// User is selecting a new image from a different source, render a loading spinner as we can't resolve the value
|
56
|
+
<div className="resource-picker-info">
|
57
|
+
<div className="resource-picker-info__layout">
|
58
|
+
<LoadingState />
|
59
|
+
{/* Need to render the ModalTrigger still as it renders the <Modal/> */}
|
60
|
+
<ModalTrigger
|
61
|
+
overlayTriggerState={{
|
62
|
+
isOpen: isModalOpen,
|
63
|
+
onOpenChange: onModalStateChange,
|
64
|
+
}}
|
65
|
+
showLabel={false}
|
66
|
+
label=""
|
67
|
+
isDisabled={true}
|
68
|
+
scope="squiz-rb-scope"
|
69
|
+
>
|
70
|
+
{children}
|
71
|
+
</ModalTrigger>
|
72
|
+
</div>
|
73
|
+
</div>
|
74
|
+
)}
|
75
|
+
{isEmpty && !isOtherSourceValue && (
|
53
76
|
<ModalTrigger
|
54
77
|
overlayTriggerState={{
|
55
78
|
isOpen: isModalOpen,
|
@@ -68,7 +91,8 @@ export const ResourcePicker = ({
|
|
68
91
|
>
|
69
92
|
{children}
|
70
93
|
</ModalTrigger>
|
71
|
-
)
|
94
|
+
)}
|
95
|
+
{!isEmpty && !isOtherSourceValue && (
|
72
96
|
<div className="resource-picker-info">
|
73
97
|
<div className="resource-picker-info__layout">
|
74
98
|
{(isExternalLoading || isLoading) && <LoadingState />}
|
@@ -93,6 +93,7 @@ export const createPlugins = (callbackWait: number, headerPortal = false, resour
|
|
93
93
|
className="col-start-2 col-end-2 flex flex-row gap-1 justify-self-start items-center font-normal text-sm"
|
94
94
|
>
|
95
95
|
<div>
|
96
|
+
<span>{type} plugin</span>
|
96
97
|
<dt className="hidden">Asset size</dt>
|
97
98
|
<dd className="text-gray-600">
|
98
99
|
{resource.squizImage && (
|
@@ -147,6 +148,9 @@ export const createPlugins = (callbackWait: number, headerPortal = false, resour
|
|
147
148
|
},
|
148
149
|
});
|
149
150
|
}, callbackWait);
|
151
|
+
} else {
|
152
|
+
setResource(null);
|
153
|
+
setIsLoading(false);
|
150
154
|
}
|
151
155
|
}, [unresolvedResource, setResource, setIsLoading]);
|
152
156
|
|
package/src/index.spec.tsx
CHANGED
@@ -8,6 +8,8 @@ import { ResourceBrowserPlugin, ResourceBrowserSource, ResourceBrowserSourceWith
|
|
8
8
|
import * as RBI from './ResourceBrowserInput/ResourceBrowserInput';
|
9
9
|
jest.spyOn(RBI, 'ResourceBrowserInput');
|
10
10
|
|
11
|
+
import * as Plugin from './Plugin/Plugin';
|
12
|
+
|
11
13
|
var useSourceReloadMock = jest.fn();
|
12
14
|
jest.mock('./Hooks/useSources', () => {
|
13
15
|
const actual = jest.requireActual('./Hooks/useSources');
|
@@ -45,12 +47,22 @@ describe('Resource browser input', () => {
|
|
45
47
|
renderResourceLauncher: jest.fn(),
|
46
48
|
} as unknown as ResourceBrowserPlugin;
|
47
49
|
|
50
|
+
const mockMatrixPlugin = {
|
51
|
+
type: 'matrix',
|
52
|
+
resolveResource: mockResolveResource,
|
53
|
+
renderSelectedResource: mockRenderSelectedResource,
|
54
|
+
sourceBrowserComponent: mockSourceBrowserComponent,
|
55
|
+
useResolveResource: mockUseResolveResource,
|
56
|
+
sourceSearchComponent: jest.fn(),
|
57
|
+
renderResourceLauncher: jest.fn(),
|
58
|
+
} as ResourceBrowserPlugin;
|
59
|
+
|
48
60
|
const renderComponent = (props: Partial<ResourceBrowserProps> = {}, searchEnabled?: boolean) => {
|
49
61
|
return renderWithContext(
|
50
62
|
<ResourceBrowser modalTitle="Asset picker" value={null} onChange={mockChange} onClear={mockOnClear} {...props} />,
|
51
63
|
{
|
52
64
|
onRequestSources: mockRequestSources,
|
53
|
-
plugins: [mockDamPlugin],
|
65
|
+
plugins: [mockMatrixPlugin, mockDamPlugin],
|
54
66
|
searchEnabled: !!searchEnabled,
|
55
67
|
},
|
56
68
|
);
|
@@ -154,7 +166,7 @@ describe('Resource browser input', () => {
|
|
154
166
|
});
|
155
167
|
});
|
156
168
|
|
157
|
-
it('onSourceSelect will set mode
|
169
|
+
it('onSourceSelect will set mode if provided', async () => {
|
158
170
|
const sourcesInput = [mockSource({ type: 'dam' }), mockSource()];
|
159
171
|
const calculatedSources = sourcesInput.map((source) => calculateExpectedSource(source));
|
160
172
|
mockRequestSources.mockResolvedValueOnce(sourcesInput);
|
@@ -214,6 +226,49 @@ describe('Resource browser input', () => {
|
|
214
226
|
});
|
215
227
|
});
|
216
228
|
|
229
|
+
it('Value will only be passed to ResourceBrowserInput if value source matches currently selected source', async () => {
|
230
|
+
const sourcesInput = [mockSource({ id: '1' }), mockSource({ id: '2' })];
|
231
|
+
const calculatedSources = sourcesInput.map((source) => calculateExpectedSource(source));
|
232
|
+
mockRequestSources.mockResolvedValueOnce(sourcesInput);
|
233
|
+
|
234
|
+
const value = { sourceId: sourcesInput[0].id, resourceId: '123456' };
|
235
|
+
|
236
|
+
renderComponent({ value });
|
237
|
+
|
238
|
+
await waitFor(() => {
|
239
|
+
expect(RBI.ResourceBrowserInput).toHaveBeenLastCalledWith(
|
240
|
+
expect.objectContaining({
|
241
|
+
value,
|
242
|
+
isOtherSourceValue: false,
|
243
|
+
source: calculatedSources[0],
|
244
|
+
}),
|
245
|
+
{},
|
246
|
+
);
|
247
|
+
});
|
248
|
+
|
249
|
+
const { onModalStateChange, setSource } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
|
250
|
+
// Invoke open the modal
|
251
|
+
act(() => {
|
252
|
+
onModalStateChange(true);
|
253
|
+
});
|
254
|
+
|
255
|
+
// Change the source
|
256
|
+
act(() => {
|
257
|
+
setSource(calculatedSources[1]);
|
258
|
+
});
|
259
|
+
|
260
|
+
await waitFor(() => {
|
261
|
+
expect(RBI.ResourceBrowserInput).toHaveBeenLastCalledWith(
|
262
|
+
expect.objectContaining({
|
263
|
+
value: null,
|
264
|
+
isOtherSourceValue: true,
|
265
|
+
source: calculatedSources[1],
|
266
|
+
}),
|
267
|
+
{},
|
268
|
+
);
|
269
|
+
});
|
270
|
+
});
|
271
|
+
|
217
272
|
it('onModalStateChange called with false will reset the Source and Plugin', async () => {
|
218
273
|
const sourcesInput = [mockSource({ type: 'dam' }), mockSource()];
|
219
274
|
const calculatedSources = sourcesInput.map((source) => calculateExpectedSource(source));
|
@@ -252,7 +307,13 @@ describe('Resource browser input', () => {
|
|
252
307
|
|
253
308
|
// Invoke modal close callback
|
254
309
|
const { onModalStateChange } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
|
255
|
-
|
310
|
+
// Invoke open and close the modal
|
311
|
+
act(() => {
|
312
|
+
onModalStateChange(true);
|
313
|
+
});
|
314
|
+
act(() => {
|
315
|
+
onModalStateChange(false);
|
316
|
+
});
|
256
317
|
|
257
318
|
// Expect no source or plugin
|
258
319
|
await waitFor(() => {
|
@@ -286,6 +347,9 @@ describe('Resource browser input', () => {
|
|
286
347
|
|
287
348
|
// Invoke modal close callback
|
288
349
|
const { onModalStateChange } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
|
350
|
+
act(() => {
|
351
|
+
onModalStateChange(true);
|
352
|
+
});
|
289
353
|
act(() => {
|
290
354
|
onModalStateChange(false);
|
291
355
|
});
|
@@ -339,7 +403,13 @@ describe('Resource browser input', () => {
|
|
339
403
|
|
340
404
|
// Invoke modal close callback
|
341
405
|
const { onModalStateChange } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
|
342
|
-
|
406
|
+
// Invoke open and close the modal
|
407
|
+
act(() => {
|
408
|
+
onModalStateChange(true);
|
409
|
+
});
|
410
|
+
act(() => {
|
411
|
+
onModalStateChange(false);
|
412
|
+
});
|
343
413
|
|
344
414
|
// Expect no source or plugin
|
345
415
|
await waitFor(() => {
|
@@ -355,8 +425,8 @@ describe('Resource browser input', () => {
|
|
355
425
|
});
|
356
426
|
});
|
357
427
|
|
358
|
-
it('onModalStateChange called with false will
|
359
|
-
const sourcesInput = [mockSource({
|
428
|
+
it('onModalStateChange called with false will reset the Source and Plugin to the value defaults if a value exists', async () => {
|
429
|
+
const sourcesInput = [mockSource({ id: '1' }), mockSource({ id: '2' })];
|
360
430
|
const calculatedSources = sourcesInput.map((source) => calculateExpectedSource(source));
|
361
431
|
mockRequestSources.mockResolvedValueOnce(sourcesInput);
|
362
432
|
|
@@ -373,8 +443,27 @@ describe('Resource browser input', () => {
|
|
373
443
|
);
|
374
444
|
});
|
375
445
|
|
376
|
-
|
377
|
-
|
446
|
+
const { onModalStateChange, setSource } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
|
447
|
+
// Invoke open and close the modal
|
448
|
+
act(() => {
|
449
|
+
onModalStateChange(true);
|
450
|
+
});
|
451
|
+
|
452
|
+
// Change the source
|
453
|
+
act(() => {
|
454
|
+
setSource(calculatedSources[1]);
|
455
|
+
});
|
456
|
+
|
457
|
+
await waitFor(() => {
|
458
|
+
expect(RBI.ResourceBrowserInput).toHaveBeenCalledWith(
|
459
|
+
expect.objectContaining({
|
460
|
+
source: calculatedSources[1],
|
461
|
+
plugin: mockDamPlugin,
|
462
|
+
}),
|
463
|
+
{},
|
464
|
+
);
|
465
|
+
});
|
466
|
+
|
378
467
|
act(() => {
|
379
468
|
onModalStateChange(false);
|
380
469
|
});
|
@@ -411,6 +500,9 @@ describe('Resource browser input', () => {
|
|
411
500
|
|
412
501
|
// Invoke modal close callback
|
413
502
|
const { onModalStateChange } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
|
503
|
+
act(() => {
|
504
|
+
onModalStateChange(true);
|
505
|
+
});
|
414
506
|
act(() => {
|
415
507
|
onModalStateChange(false);
|
416
508
|
});
|
@@ -444,4 +536,135 @@ describe('Resource browser input', () => {
|
|
444
536
|
expect(useSourceReloadMock).toHaveBeenCalled();
|
445
537
|
});
|
446
538
|
});
|
539
|
+
|
540
|
+
describe('Resource browser plugin', () => {
|
541
|
+
beforeEach(() => {
|
542
|
+
jest.spyOn(Plugin, 'PluginRender');
|
543
|
+
});
|
544
|
+
afterEach(() => {
|
545
|
+
(Plugin.PluginRender as jest.Mock).mockRestore();
|
546
|
+
});
|
547
|
+
|
548
|
+
it('Will default to a non plugin based render for initial load and selection of first source', async () => {
|
549
|
+
const sourcesInput = [mockSource({ type: 'dam' }), mockSource({ type: 'matrix' })];
|
550
|
+
mockRequestSources.mockResolvedValue(sourcesInput);
|
551
|
+
renderComponent();
|
552
|
+
|
553
|
+
// Will render a default with no selected source
|
554
|
+
await waitFor(() => {
|
555
|
+
expect(Plugin.PluginRender).toHaveBeenCalledWith(
|
556
|
+
expect.objectContaining({
|
557
|
+
render: true,
|
558
|
+
type: null,
|
559
|
+
plugin: null,
|
560
|
+
}),
|
561
|
+
{},
|
562
|
+
);
|
563
|
+
|
564
|
+
expect(Plugin.PluginRender).toHaveBeenCalledWith(
|
565
|
+
expect.objectContaining({
|
566
|
+
render: false,
|
567
|
+
type: 'dam',
|
568
|
+
}),
|
569
|
+
{},
|
570
|
+
);
|
571
|
+
|
572
|
+
expect(Plugin.PluginRender).toHaveBeenCalledWith(
|
573
|
+
expect.objectContaining({
|
574
|
+
render: false,
|
575
|
+
type: 'matrix',
|
576
|
+
}),
|
577
|
+
{},
|
578
|
+
);
|
579
|
+
});
|
580
|
+
});
|
581
|
+
|
582
|
+
it('Will only send render=true to the Plugin for the currently selected source', async () => {
|
583
|
+
const sourcesInput = [mockSource({ type: 'dam' }), mockSource({ type: 'matrix' })];
|
584
|
+
mockRequestSources.mockResolvedValue(sourcesInput);
|
585
|
+
|
586
|
+
// Render with an input so it will default a source
|
587
|
+
renderComponent({ value: { sourceId: sourcesInput[0].id, resourceId: '123456' } });
|
588
|
+
|
589
|
+
// Will render a default with no selected source
|
590
|
+
await waitFor(() => {
|
591
|
+
expect(Plugin.PluginRender).toHaveBeenCalledWith(
|
592
|
+
expect.objectContaining({
|
593
|
+
render: false,
|
594
|
+
type: null,
|
595
|
+
}),
|
596
|
+
{},
|
597
|
+
);
|
598
|
+
|
599
|
+
expect(Plugin.PluginRender).toHaveBeenCalledWith(
|
600
|
+
expect.objectContaining({
|
601
|
+
render: true,
|
602
|
+
type: 'dam',
|
603
|
+
}),
|
604
|
+
{},
|
605
|
+
);
|
606
|
+
|
607
|
+
expect(Plugin.PluginRender).toHaveBeenCalledWith(
|
608
|
+
expect.objectContaining({
|
609
|
+
render: false,
|
610
|
+
type: 'matrix',
|
611
|
+
}),
|
612
|
+
{},
|
613
|
+
);
|
614
|
+
});
|
615
|
+
|
616
|
+
// renderComponent({ value: { sourceId: sourcesInput[0].id, resourceId: '123456' } });
|
617
|
+
});
|
618
|
+
|
619
|
+
it('Will match plugin to render based on type', async () => {
|
620
|
+
const sourcesInput = [mockSource({ type: 'dam' }), mockSource({ type: 'matrix' })];
|
621
|
+
const calculatedSources = sourcesInput.map((source) => calculateExpectedSource(source));
|
622
|
+
mockRequestSources.mockResolvedValue(sourcesInput);
|
623
|
+
renderComponent();
|
624
|
+
|
625
|
+
// Modify the source so the plugin object comparison doesnt match
|
626
|
+
calculatedSources[0] = {
|
627
|
+
...calculatedSources[0],
|
628
|
+
plugin: {
|
629
|
+
// @ts-ignore
|
630
|
+
test: 'test',
|
631
|
+
...calculatedSources[0].plugin,
|
632
|
+
},
|
633
|
+
};
|
634
|
+
|
635
|
+
// Get the provided callback
|
636
|
+
const { setSource } = (RBI.ResourceBrowserInput as unknown as jest.SpyInstance).mock.calls[0][0];
|
637
|
+
// Invoke it
|
638
|
+
act(() => {
|
639
|
+
setSource(calculatedSources[0]);
|
640
|
+
});
|
641
|
+
|
642
|
+
// Will render a default with no selected source
|
643
|
+
await waitFor(() => {
|
644
|
+
expect(Plugin.PluginRender).toHaveBeenCalledWith(
|
645
|
+
expect.objectContaining({
|
646
|
+
render: true,
|
647
|
+
type: null,
|
648
|
+
}),
|
649
|
+
{},
|
650
|
+
);
|
651
|
+
|
652
|
+
expect(Plugin.PluginRender).toHaveBeenCalledWith(
|
653
|
+
expect.objectContaining({
|
654
|
+
render: true,
|
655
|
+
type: 'dam',
|
656
|
+
}),
|
657
|
+
{},
|
658
|
+
);
|
659
|
+
|
660
|
+
expect(Plugin.PluginRender).toHaveBeenCalledWith(
|
661
|
+
expect.objectContaining({
|
662
|
+
render: false,
|
663
|
+
type: 'matrix',
|
664
|
+
}),
|
665
|
+
{},
|
666
|
+
);
|
667
|
+
});
|
668
|
+
});
|
669
|
+
});
|
447
670
|
});
|
package/src/index.tsx
CHANGED
@@ -103,11 +103,19 @@ export const ResourceBrowser = (props: ResourceBrowserProps) => {
|
|
103
103
|
|
104
104
|
// If the modal closes and we dont have a value clear the source state so it goes back to the launcher on re-open
|
105
105
|
useEffect(() => {
|
106
|
+
// If modal is closed and we dont have a value
|
106
107
|
if (!isModalOpen && !value && (sources?.length > 1 || searchEnabled)) {
|
107
108
|
setSource(null);
|
108
109
|
setMode(null);
|
109
110
|
} else if (!isModalOpen) {
|
111
|
+
// Otherwise reset the RB mode
|
110
112
|
setMode(null);
|
113
|
+
|
114
|
+
// If there is a value passed in, reset the source so the preselected asset preview renders correctly
|
115
|
+
if (value && value.sourceId !== source?.id) {
|
116
|
+
const source = sources.find((source) => source.id === value?.sourceId) || null;
|
117
|
+
setSource(source);
|
118
|
+
}
|
111
119
|
}
|
112
120
|
}, [sources, isModalOpen]);
|
113
121
|
|
@@ -121,6 +129,7 @@ export const ResourceBrowser = (props: ResourceBrowserProps) => {
|
|
121
129
|
<div className="squiz-rb-scope">
|
122
130
|
<PluginRender
|
123
131
|
key="default"
|
132
|
+
type={null}
|
124
133
|
render={plugin === null}
|
125
134
|
inline={!!inline}
|
126
135
|
inlineType={inlineType}
|
@@ -129,6 +138,7 @@ export const ResourceBrowser = (props: ResourceBrowserProps) => {
|
|
129
138
|
sources={sources}
|
130
139
|
setSource={handleSourceSelect}
|
131
140
|
isLoading={isLoading}
|
141
|
+
isOtherSourceValue={false}
|
132
142
|
error={sourcesError || error}
|
133
143
|
plugin={plugin}
|
134
144
|
pluginMode={mode}
|
@@ -148,10 +158,13 @@ export const ResourceBrowser = (props: ResourceBrowserProps) => {
|
|
148
158
|
return (
|
149
159
|
<PluginRender
|
150
160
|
key={thisPlugin.type}
|
151
|
-
|
161
|
+
type={thisPlugin.type}
|
162
|
+
render={thisPlugin.type === plugin?.type}
|
152
163
|
inline={!!inline}
|
153
164
|
inlineType={inlineType}
|
154
165
|
{...props}
|
166
|
+
value={value && source ? (value.sourceId === source.id ? value : null) : null} // Don't pass value if value isn't for selected source
|
167
|
+
isOtherSourceValue={value && source ? (value.sourceId !== source.id ? true : false) : false} // Block render of existing asset if not from this source
|
155
168
|
source={source}
|
156
169
|
sources={sources}
|
157
170
|
setSource={handleSourceSelect}
|