@squiz/resource-browser 3.1.1 → 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 CHANGED
@@ -1,5 +1,12 @@
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
+
3
10
  ## 3.1.1
4
11
 
5
12
  ### Patch Changes
@@ -19,7 +19,8 @@ const useAuth = (authConfig) => {
19
19
  return;
20
20
  }
21
21
  const encodedRedirectUrl = encodeURIComponent(authConfig.redirectUrl);
22
- const loginUrl = `${authConfig?.authUrl}?client_id=${authConfig?.clientId}&scope=${authConfig?.scope}&redirect_uri=${encodedRedirectUrl}&response_type=code&state=state`;
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');
@@ -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;
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: 250px;
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
@@ -119,7 +119,7 @@ const ResourceBrowser = (props) => {
119
119
  }, [reloadSources]);
120
120
  // Render a default "plugin" and one for each item in the plugins array. They are conditionally rendered based on what is selected
121
121
  return (react_1.default.createElement("div", { className: "squiz-rb-scope" },
122
- 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, isOtherSourceValue: false, 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: () => {
123
123
  return {
124
124
  data: null,
125
125
  error: null,
@@ -127,7 +127,7 @@ const ResourceBrowser = (props) => {
127
127
  };
128
128
  }, isModalOpen: isModalOpen, onModalStateChange: handleModalStateChange, onRetry: handleReset }),
129
129
  plugins.map((thisPlugin) => {
130
- return (react_1.default.createElement(Plugin_1.PluginRender, { key: thisPlugin.type, render: thisPlugin === plugin, 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 }));
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 }));
131
131
  })));
132
132
  };
133
133
  exports.ResourceBrowser = ResourceBrowser;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squiz/resource-browser",
3
- "version": "3.1.1",
3
+ "version": "3.1.2",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "private": false,
@@ -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: 'offline_access',
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=offline_access&redirect_uri=${encodeURIComponent(authConfig.redirectUrl)}&response_type=code&state=state`,
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');
@@ -20,7 +20,8 @@ export const useAuth = (authConfig: AuthenticationConfiguration | undefined) =>
20
20
  return;
21
21
  }
22
22
  const encodedRedirectUrl = encodeURIComponent(authConfig.redirectUrl);
23
- const loginUrl = `${authConfig?.authUrl}?client_id=${authConfig?.clientId}&scope=${authConfig?.scope}&redirect_uri=${encodedRedirectUrl}&response_type=code&state=state`;
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,6 +19,7 @@ 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,
24
25
  isOtherSourceValue: false,
@@ -52,6 +53,7 @@ describe('Plugin', () => {
52
53
 
53
54
  it('Does render ResourceBrowserInlineButton if inline is true', async () => {
54
55
  const props = {
56
+ type: null,
55
57
  modalTitle: 'Asset picker',
56
58
  value: null,
57
59
  isOtherSourceValue: false,
@@ -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,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
  );
@@ -524,4 +536,135 @@ describe('Resource browser input', () => {
524
536
  expect(useSourceReloadMock).toHaveBeenCalled();
525
537
  });
526
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
+ });
527
670
  });
package/src/index.tsx CHANGED
@@ -129,6 +129,7 @@ export const ResourceBrowser = (props: ResourceBrowserProps) => {
129
129
  <div className="squiz-rb-scope">
130
130
  <PluginRender
131
131
  key="default"
132
+ type={null}
132
133
  render={plugin === null}
133
134
  inline={!!inline}
134
135
  inlineType={inlineType}
@@ -157,7 +158,8 @@ export const ResourceBrowser = (props: ResourceBrowserProps) => {
157
158
  return (
158
159
  <PluginRender
159
160
  key={thisPlugin.type}
160
- render={thisPlugin === plugin}
161
+ type={thisPlugin.type}
162
+ render={thisPlugin.type === plugin?.type}
161
163
  inline={!!inline}
162
164
  inlineType={inlineType}
163
165
  {...props}