@squiz/resource-browser 1.32.1-alpha.14 → 1.32.1-alpha.16

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 (90) hide show
  1. package/jest.config.ts +12 -1
  2. package/lib/Hooks/useCategorisedSources.d.ts +14 -0
  3. package/lib/Hooks/useCategorisedSources.js +38 -0
  4. package/lib/Hooks/useChildResources.d.ts +19 -0
  5. package/lib/Hooks/useChildResources.js +35 -0
  6. package/lib/Hooks/useResourcePath.d.ts +16 -0
  7. package/lib/Hooks/useResourcePath.js +64 -0
  8. package/lib/Hooks/useSources.d.ts +16 -0
  9. package/lib/Hooks/useSources.js +29 -0
  10. package/lib/Icons/Icon.d.ts +7 -7
  11. package/lib/Icons/Icon.js +7 -9
  12. package/lib/Icons/MatrixResources/Audio.js +1 -1
  13. package/lib/Icons/MatrixResources/Excel.js +1 -1
  14. package/lib/Icons/MatrixResources/MatrixResourceMap.d.ts +6 -6
  15. package/lib/Icons/MatrixResources/MatrixResourceMap.js +6 -6
  16. package/lib/Icons/MatrixResources/Pdf.js +1 -1
  17. package/lib/Icons/MatrixResources/Powerpoint.js +1 -1
  18. package/lib/Icons/MatrixResources/Video.js +1 -1
  19. package/lib/Icons/MatrixResources/Word.js +1 -1
  20. package/lib/Modal/Modal.js +1 -1
  21. package/lib/PreviewPanel/PreviewModal.js +1 -1
  22. package/lib/PreviewPanel/PreviewPanel.d.ts +4 -6
  23. package/lib/PreviewPanel/PreviewPanel.js +11 -39
  24. package/lib/PreviewPanel/details/MatrixResource.d.ts +4 -9
  25. package/lib/PreviewPanel/details/MatrixResource.js +20 -16
  26. package/lib/ResourceBreadcrumb/ResourceBreadcrumb.d.ts +5 -5
  27. package/lib/ResourceBreadcrumb/ResourceBreadcrumb.js +3 -3
  28. package/lib/ResourceItem/ResourceItem.d.ts +6 -8
  29. package/lib/ResourceItem/ResourceItem.js +3 -3
  30. package/lib/ResourceList/ResourceList.d.ts +5 -4
  31. package/lib/ResourceList/ResourceList.js +3 -3
  32. package/lib/ResourcePickerContainer/ResourcePickerContainer.d.ts +4 -5
  33. package/lib/ResourcePickerContainer/ResourcePickerContainer.js +34 -89
  34. package/lib/SourceDropdown/SourceDropdown.d.ts +5 -5
  35. package/lib/SourceDropdown/SourceDropdown.js +19 -27
  36. package/lib/SourceList/SourceList.d.ts +4 -4
  37. package/lib/SourceList/SourceList.js +7 -5
  38. package/lib/index.css +6 -0
  39. package/lib/index.d.ts +6 -29
  40. package/lib/index.js +2 -3
  41. package/lib/uuid.js +1 -3
  42. package/package.json +3 -2
  43. package/src/Hooks/useCategorisedSources.spec.ts +39 -0
  44. package/src/Hooks/useCategorisedSources.ts +46 -0
  45. package/src/Hooks/useChildResources.spec.ts +49 -0
  46. package/src/Hooks/useChildResources.ts +43 -0
  47. package/src/Hooks/useResourcePath.spec.ts +124 -0
  48. package/src/Hooks/useResourcePath.ts +76 -0
  49. package/src/Hooks/useSources.spec.ts +33 -0
  50. package/src/Hooks/useSources.ts +33 -0
  51. package/src/Icons/Icon.stories.tsx +7 -7
  52. package/src/Icons/Icon.tsx +9 -14
  53. package/src/Icons/MatrixResources/Audio.tsx +1 -1
  54. package/src/Icons/MatrixResources/Excel.tsx +1 -1
  55. package/src/Icons/MatrixResources/MatrixResourceMap.ts +7 -7
  56. package/src/Icons/MatrixResources/Pdf.tsx +1 -1
  57. package/src/Icons/MatrixResources/Powerpoint.tsx +1 -1
  58. package/src/Icons/MatrixResources/Video.tsx +1 -1
  59. package/src/Icons/MatrixResources/Word.tsx +1 -1
  60. package/src/Modal/Modal.tsx +1 -1
  61. package/src/PreviewPanel/PreviewModal.tsx +1 -1
  62. package/src/PreviewPanel/PreviewPanel.spec.tsx +20 -62
  63. package/src/PreviewPanel/PreviewPanel.stories.tsx +16 -24
  64. package/src/PreviewPanel/PreviewPanel.tsx +15 -51
  65. package/src/PreviewPanel/details/MatrixResource.tsx +23 -19
  66. package/src/ResourceBreadcrumb/ResourceBreadcrumb.spec.tsx +13 -23
  67. package/src/ResourceBreadcrumb/ResourceBreadcrumb.stories.tsx +1 -1
  68. package/src/ResourceBreadcrumb/ResourceBreadcrumb.tsx +8 -9
  69. package/src/ResourceBreadcrumb/sample-hierarchy.json +15 -25
  70. package/src/ResourceItem/ResourceItem.tsx +10 -12
  71. package/src/ResourceList/ResourceList.spec.tsx +8 -53
  72. package/src/ResourceList/ResourceList.stories.tsx +2 -2
  73. package/src/ResourceList/ResourceList.tsx +12 -10
  74. package/src/ResourceList/sample-resources.json +551 -49
  75. package/src/ResourcePickerContainer/ResourcePickerContainer.spec.tsx +196 -315
  76. package/src/ResourcePickerContainer/ResourcePickerContainer.stories.tsx +7 -29
  77. package/src/ResourcePickerContainer/ResourcePickerContainer.tsx +63 -127
  78. package/src/SourceDropdown/SourceDropdown.spec.tsx +63 -60
  79. package/src/SourceDropdown/SourceDropdown.stories.tsx +4 -7
  80. package/src/SourceDropdown/SourceDropdown.tsx +34 -41
  81. package/src/SourceList/SourceList.spec.tsx +38 -32
  82. package/src/SourceList/SourceList.tsx +17 -19
  83. package/src/SourceList/sample-sources.json +186 -77
  84. package/src/__mocks__/MockModels.ts +30 -0
  85. package/src/__mocks__/StorybookHelpers.ts +46 -0
  86. package/src/index.stories.tsx +13 -38
  87. package/src/index.tsx +5 -29
  88. package/src/types.d.ts +71 -0
  89. package/src/uuid.ts +2 -4
  90. package/src/SourceDropdown/sample-sources.json +0 -110
@@ -1,27 +1,28 @@
1
1
  import React, { useState, useRef } from 'react';
2
2
  import { useFocusWithin, useKeyboard } from '@react-aria/interactions';
3
3
 
4
- import type { Source, Resource, NodeIdentifier } from '../index';
4
+ import type { Source, ScopedSource } from '../types';
5
5
  import Spinner from '../Spinner/Spinner';
6
6
  import Icon, { IconOptions } from '../Icons/Icon';
7
7
 
8
8
  import uuid from '../uuid';
9
+ import { useCategorisedSources } from '../Hooks/useCategorisedSources';
9
10
 
10
11
  export default function SourceDropdown({
11
12
  sources,
12
- currentSource,
13
+ selectedSource,
13
14
  isLoading,
14
15
  onRootSelect,
15
16
  onSourceSelect,
16
17
  }: {
17
- sources: Array<Source>;
18
- currentSource: NodeIdentifier | null;
18
+ sources: Source[];
19
+ selectedSource: ScopedSource | null;
19
20
  isLoading: boolean;
20
21
  onRootSelect: () => void;
21
- onSourceSelect: (node: NodeIdentifier, resetHierarchy: boolean) => void;
22
+ onSourceSelect: (source: ScopedSource) => void;
22
23
  }) {
24
+ const categorisedSources = useCategorisedSources(sources);
23
25
  const [uniqueId] = useState(uuid());
24
-
25
26
  const buttonRef = useRef<HTMLButtonElement>(null);
26
27
  const [isOpen, setIsOpen] = useState(false);
27
28
 
@@ -42,10 +43,10 @@ export default function SourceDropdown({
42
43
  },
43
44
  });
44
45
 
45
- const handleSourceClick = (id: NodeIdentifier) => {
46
+ const handleSourceClick = (source: ScopedSource) => {
46
47
  setIsOpen(false);
47
48
  buttonRef.current?.focus();
48
- onSourceSelect(id, true);
49
+ onSourceSelect(source);
49
50
  };
50
51
 
51
52
  const handleRootSelect = () => {
@@ -54,19 +55,6 @@ export default function SourceDropdown({
54
55
  onRootSelect();
55
56
  };
56
57
 
57
- let currentResource: Resource | undefined = undefined;
58
-
59
- for (let i = 0; i < sources.length; i++) {
60
- const source = sources[i];
61
- if (currentSource?.source === source.id) {
62
- currentResource = source.nodes.find((node) => {
63
- if (node.id.id === currentSource?.id) {
64
- return node;
65
- }
66
- });
67
- }
68
- }
69
-
70
58
  return (
71
59
  <div {...focusWithinProps} {...keyboardProps} className="relative w-72 border border-2 rounded border-gray-300">
72
60
  <button
@@ -78,15 +66,20 @@ export default function SourceDropdown({
78
66
  onClick={() => setIsOpen(!isOpen)}
79
67
  className="relative flex items-center text-sm font-semibold p-2 w-full"
80
68
  >
81
- {currentResource && (
69
+ {selectedSource && (
82
70
  <>
83
71
  <span className="sr-only">current source </span>
84
- <Icon icon={currentResource.type as IconOptions} resourceSource="matrix" aria-hidden className="mr-2.5" />
85
- <div className="truncate max-w-[200px]">{currentResource.label}</div>
72
+ <Icon
73
+ icon={selectedSource.resource?.type.code as IconOptions}
74
+ resourceSource="matrix"
75
+ aria-hidden
76
+ className="mr-2.5"
77
+ />
78
+ <div className="truncate max-w-[200px]">{selectedSource.resource?.name || selectedSource.source.name}</div>
86
79
  </>
87
80
  )}
88
81
 
89
- {!currentResource && (
82
+ {!selectedSource && (
90
83
  <>
91
84
  <span className="sr-only">view </span>
92
85
  <Icon icon={'root' as IconOptions} aria-hidden className="mr-2.5" />
@@ -122,38 +115,38 @@ export default function SourceDropdown({
122
115
  </li>
123
116
  )}
124
117
  {!isLoading &&
125
- sources.map(({ id: sourceId, name, nodes }, index) => {
118
+ categorisedSources.map(({ key, label, sources }, index) => {
126
119
  return (
127
- <li
128
- key={sourceId}
129
- className={`flex flex-col text-sm font-semibold text-grey-800 ${index > 0 ? 'mt-3' : ''}`}
130
- >
120
+ <li key={key} className={`flex flex-col text-sm font-semibold text-grey-800 ${index > 0 ? 'mt-3' : ''}`}>
131
121
  <div className="relative flex justify-center before:w-full before:h-px before:bg-gray-300 before:absolute before:top-2/4 before:z-0">
132
- <span className="z-10 bg-gray-100 px-2.5">{name}</span>
122
+ <span className="z-10 bg-gray-100 px-2.5">{label}</span>
133
123
  </div>
134
- {nodes?.length > 0 && (
135
- <ul aria-label={`${name} nodes`} className="flex flex-col mt-2">
136
- {nodes.map(({ type, id: nodeId, selected, label }) => {
124
+ {sources?.length > 0 && (
125
+ <ul aria-label={`${label} nodes`} className="flex flex-col mt-2">
126
+ {sources.map(({ source, resource }) => {
127
+ const isSelectedSource =
128
+ source.id === selectedSource?.source.id && resource?.id === selectedSource?.resource?.id;
129
+
137
130
  return (
138
131
  <li
139
- key={`${sourceId}-${nodeId.id}`}
132
+ key={`${source.id}-${resource?.id}`}
140
133
  className="flex items-center bg-white border border-b-0 last:border-b border-grey-200 first:rounded-t last:rounded-b"
141
134
  >
142
135
  <button
143
136
  type="button"
144
- onClick={() => handleSourceClick(nodeId)}
137
+ onClick={() => handleSourceClick({ source, resource })}
145
138
  className={`relative grow flex items-center p-2.5 hover:bg-gray-100 focus:bg-gray-100 ${
146
- selected ? 'bg-blue-100 text-blue-400' : ''
139
+ isSelectedSource ? 'bg-blue-100 text-blue-400' : ''
147
140
  }`}
148
141
  >
149
142
  <Icon
150
- icon={type as IconOptions}
143
+ icon={resource?.type.code as IconOptions}
151
144
  resourceSource="matrix"
152
- aria-label={type}
145
+ aria-label={resource?.type.name}
153
146
  className="shrink-0 mr-2.5"
154
147
  />
155
- <span className="text-left mr-7">{label}</span>
156
- {nodeId === currentResource?.id && (
148
+ <span className="text-left mr-7">{resource?.name || source.name}</span>
149
+ {isSelectedSource && (
157
150
  <Icon
158
151
  icon={'selected' as IconOptions}
159
152
  aria-label="selected"
@@ -2,63 +2,60 @@
2
2
  import React from 'react';
3
3
  import { screen, render, waitFor, within } from '@testing-library/react';
4
4
  import userEvent from '@testing-library/user-event';
5
-
5
+ import { mockSource } from '../__mocks__/MockModels';
6
6
  import { useOverlayTriggerState, OverlayTriggerState } from 'react-stately';
7
7
  import SourceList from './SourceList';
8
+ import { Source } from '../types';
8
9
 
9
- const sources = [
10
- {
10
+ const sources: Source[] = [
11
+ mockSource({
11
12
  id: '1',
12
13
  name: 'Source 1',
13
14
  nodes: [
14
15
  {
15
- id: {
16
- id: '1',
17
- source: '1',
16
+ id: '1',
17
+ type: {
18
+ code: 'site',
19
+ name: 'Site',
18
20
  },
19
- type: 'site',
20
- selected: false,
21
- label: 'Node 1',
21
+ name: 'Node 1',
22
22
  childCount: 21,
23
23
  },
24
24
  {
25
- id: {
26
- id: '2',
27
- source: '1',
25
+ id: '2',
26
+ type: {
27
+ code: 'site',
28
+ name: 'Site',
28
29
  },
29
- type: 'site',
30
- selected: false,
31
- label: 'Node 2',
30
+ name: 'Node 2',
32
31
  childCount: 13,
33
32
  },
34
33
  ],
35
- },
36
- {
34
+ }),
35
+ mockSource({
37
36
  id: '2',
38
37
  name: 'Source 2',
39
38
  nodes: [
40
39
  {
41
- id: {
42
- id: '1',
43
- source: '2',
40
+ id: '3',
41
+ type: {
42
+ code: 'site',
43
+ name: 'Site',
44
44
  },
45
- type: 'site',
46
- selected: false,
47
- label: 'Node 3',
45
+ name: 'Node 3',
48
46
  childCount: 15,
49
47
  },
50
48
  {
51
- id: {
52
- id: '2',
53
- source: '2',
49
+ id: '4',
50
+ type: {
51
+ code: 'site',
52
+ name: 'Site',
54
53
  },
55
- type: 'site',
56
- selected: false,
57
- label: 'Node 4',
54
+ name: 'Node 4',
58
55
  childCount: 10,
59
56
  },
60
57
  ],
61
- },
58
+ }),
62
59
  ];
63
60
 
64
61
  function SourceListTestWrapper({
@@ -191,7 +188,13 @@ describe('SourceList', () => {
191
188
 
192
189
  await waitFor(() => {
193
190
  // Provides the item that was clicked and an id reference to the button that was clicked
194
- expect(onSourceSelect).toHaveBeenCalledWith({ source: '1', id: '1' }, { id: expect.any(String) });
191
+ expect(onSourceSelect).toHaveBeenCalledWith(
192
+ {
193
+ source: sources[0],
194
+ resource: sources[0].nodes[0],
195
+ },
196
+ { id: expect.any(String) },
197
+ );
195
198
  });
196
199
  });
197
200
 
@@ -218,7 +221,10 @@ describe('SourceList', () => {
218
221
  user.click(screen.getByRole('button', { name: 'Drill down to Node 1 children' }));
219
222
 
220
223
  await waitFor(() => {
221
- expect(onSourceDrillDown).toHaveBeenCalledWith({ source: '1', id: '1' });
224
+ expect(onSourceDrillDown).toHaveBeenCalledWith({
225
+ source: sources[0],
226
+ resource: sources[0].nodes[0],
227
+ });
222
228
  });
223
229
  });
224
230
  });
@@ -3,16 +3,17 @@ import { OverlayTriggerState } from 'react-stately';
3
3
  import { DOMAttributes, FocusableElement } from '@react-types/shared';
4
4
 
5
5
  import ResourceItem from '../ResourceItem/ResourceItem';
6
- import { NodeIdentifier, Source } from '../index';
6
+ import { Source, ScopedSource } from '../types';
7
7
  import { SkeletonList } from '../Skeleton/List/SkeletonList';
8
8
  import clsx from 'clsx';
9
+ import { useCategorisedSources } from '../Hooks/useCategorisedSources';
9
10
 
10
11
  export interface SourceListProps {
11
- sources: Array<Source>;
12
+ sources: Source[];
12
13
  previewModalState: OverlayTriggerState;
13
14
  isLoading: boolean;
14
- onSourceSelect: (node: NodeIdentifier, overlayProps: DOMAttributes<FocusableElement>) => void;
15
- onSourceDrillDown: (node: NodeIdentifier) => void;
15
+ onSourceSelect: (node: ScopedSource, overlayProps: DOMAttributes<FocusableElement>) => void;
16
+ onSourceDrillDown: (node: ScopedSource) => void;
16
17
  allowedTypes?: string[] | undefined;
17
18
  }
18
19
 
@@ -24,6 +25,7 @@ const SourceList = function ({
24
25
  onSourceDrillDown,
25
26
  allowedTypes,
26
27
  }: SourceListProps) {
28
+ const categorisedSources = useCategorisedSources(sources);
27
29
  const listRef = useRef<HTMLUListElement>(null);
28
30
 
29
31
  useEffect(() => {
@@ -53,26 +55,22 @@ const SourceList = function ({
53
55
  )}
54
56
 
55
57
  {!isLoading &&
56
- sources.map(({ id: sourceId, name, nodes }, index) => {
58
+ categorisedSources.map(({ key, label, sources }, index) => {
57
59
  return (
58
- <li
59
- key={sourceId}
60
- className={`flex flex-col text-sm font-semibold text-grey-800 ${index > 0 ? 'mt-3' : ''}`}
61
- >
60
+ <li key={key} className={`flex flex-col text-sm font-semibold text-grey-800 ${index > 0 ? 'mt-3' : ''}`}>
62
61
  <div className="relative flex justify-center before:w-full before:h-px before:bg-gray-300 before:absolute before:top-2/4 before:z-0">
63
- <span className="z-10 bg-gray-100 px-2.5">{name}</span>
62
+ <span className="z-10 bg-gray-100 px-2.5">{label}</span>
64
63
  </div>
65
- {nodes?.length > 0 && (
66
- <ul aria-label={`${name} nodes`} className="flex flex-col">
67
- {nodes.map(({ type, id: nodeId, selected, label, childCount }) => {
64
+ {sources.length > 0 && (
65
+ <ul aria-label={`${label} nodes`} className="flex flex-col">
66
+ {sources.map(({ source, resource }) => {
68
67
  return (
69
68
  <ResourceItem
70
- key={`${sourceId}-${nodeId.id}`}
71
- id={nodeId}
72
- selected={selected}
73
- label={label}
74
- type={type}
75
- childCount={childCount}
69
+ key={`${source.id}-${resource?.id}`}
70
+ item={{ source, resource }}
71
+ label={resource?.name || source.name}
72
+ type={resource?.type.code || 'folder'}
73
+ childCount={resource?.childCount || 0}
76
74
  previewModalState={previewModalState}
77
75
  onSelect={onSourceSelect}
78
76
  onDrillDown={onSourceDrillDown}
@@ -4,24 +4,43 @@
4
4
  "name": "Acme corporate system",
5
5
  "nodes": [
6
6
  {
7
- "id": {
8
- "id": "1",
9
- "source": "1"
7
+ "id": "1",
8
+ "type": {
9
+ "code": "site",
10
+ "name": "Site"
10
11
  },
11
- "type": "site",
12
- "selected": true,
13
- "label": "HandyHomes Website",
12
+ "status": {
13
+ "code": "live",
14
+ "name": "Live"
15
+ },
16
+ "name": "HandyHomes Website",
14
17
  "childCount": 21
15
18
  },
16
19
  {
17
- "id": {
18
- "id": "2",
19
- "source": "1"
20
+ "id": "2",
21
+ "type": {
22
+ "code": "site",
23
+ "name": "Site"
24
+ },
25
+ "status": {
26
+ "code": "live",
27
+ "name": "Live"
20
28
  },
21
- "type": "site",
22
- "selected": false,
23
- "label": "Another Website very long title to make it wrap to multiple lines",
29
+ "name": "Another Website very long title to make it wrap to multiple lines",
24
30
  "childCount": 135877
31
+ },
32
+ {
33
+ "id": "3",
34
+ "type": {
35
+ "code": "folder",
36
+ "name": "Folder"
37
+ },
38
+ "status": {
39
+ "code": "live",
40
+ "name": "Live"
41
+ },
42
+ "name": "Images",
43
+ "childCount": 100
25
44
  }
26
45
  ]
27
46
  },
@@ -30,81 +49,171 @@
30
49
  "name": "Acme internal system",
31
50
  "nodes": [
32
51
  {
33
- "id": {
34
- "id": "1",
35
- "source": "2"
52
+ "id": "4",
53
+ "type": {
54
+ "code": "site",
55
+ "name": "Site"
56
+ },
57
+ "status": {
58
+ "code": "live",
59
+ "name": "Live"
36
60
  },
37
- "type": "site",
38
- "selected": false,
39
- "label": "Intranet Website",
61
+ "name": "Intranet Website",
40
62
  "childCount": 15
41
63
  },
42
64
  {
43
- "id": {
44
- "id": "2",
45
- "source": "2"
65
+ "id": "5",
66
+ "type": {
67
+ "code": "site",
68
+ "name": "Site"
69
+ },
70
+ "status": {
71
+ "code": "live",
72
+ "name": "Live"
46
73
  },
47
- "type": "site",
48
- "selected": false,
49
- "label": "Social Website",
74
+ "name": "Social Website",
50
75
  "childCount": 10
51
76
  }
52
77
  ]
53
78
  },
54
79
  {
55
80
  "id": "3",
56
- "name": "Other system",
57
- "nodes": [
58
- {
59
- "id": {
60
- "id": "1",
61
- "source": "3"
62
- },
63
- "type": "folder",
64
- "selected": false,
65
- "label": "Digital asset manager",
66
- "childCount": 0
67
- },
68
- {
69
- "id": {
70
- "id": "2",
71
- "source": "3"
72
- },
73
- "type": "image",
74
- "selected": false,
75
- "label": "Unsplash image library",
76
- "childCount": 0
77
- },
78
- {
79
- "id": {
80
- "id": "3",
81
- "source": "3"
82
- },
83
- "type": "image",
84
- "selected": false,
85
- "label": "Unsplash image library",
86
- "childCount": 0
87
- },
88
- {
89
- "id": {
90
- "id": "4",
91
- "source": "3"
92
- },
93
- "type": "image",
94
- "selected": false,
95
- "label": "Unsplash image library",
96
- "childCount": 0
97
- },
98
- {
99
- "id": {
100
- "id": "5",
101
- "source": "3"
102
- },
103
- "type": "image",
104
- "selected": false,
105
- "label": "Unsplash image library",
106
- "childCount": 0
107
- }
108
- ]
81
+ "name": "Digital asset manager",
82
+ "nodes": []
83
+ },
84
+ {
85
+ "id": "4",
86
+ "name": "Unsplash image library",
87
+ "nodes": []
88
+ },
89
+ {
90
+ "id": "5",
91
+ "name": "Unsplash image library 2",
92
+ "nodes": []
93
+ },
94
+ {
95
+ "id": "6",
96
+ "name": "Unsplash image library 3",
97
+ "nodes": []
98
+ },
99
+ {
100
+ "id": "7",
101
+ "name": "Unsplash image library 4",
102
+ "nodes": []
103
+ },
104
+ {
105
+ "id": "8",
106
+ "name": "Unsplash image library 5",
107
+ "nodes": []
108
+ },
109
+ {
110
+ "id": "9",
111
+ "name": "Unsplash image library 6",
112
+ "nodes": []
113
+ },
114
+ {
115
+ "id": "10",
116
+ "name": "Unsplash image library 7",
117
+ "nodes": []
118
+ },
119
+ {
120
+ "id": "11",
121
+ "name": "Unsplash image library 8",
122
+ "nodes": []
123
+ },
124
+ {
125
+ "id": "12",
126
+ "name": "Unsplash image library 9",
127
+ "nodes": []
128
+ },
129
+ {
130
+ "id": "13",
131
+ "name": "Unsplash image library 10",
132
+ "nodes": []
133
+ },
134
+ {
135
+ "id": "14",
136
+ "name": "Unsplash image library 11",
137
+ "nodes": []
138
+ },
139
+ {
140
+ "id": "15",
141
+ "name": "Unsplash image library 12",
142
+ "nodes": []
143
+ },
144
+ {
145
+ "id": "16",
146
+ "name": "Unsplash image library 13",
147
+ "nodes": []
148
+ },
149
+ {
150
+ "id": "17",
151
+ "name": "Unsplash image library 14",
152
+ "nodes": []
153
+ },
154
+ {
155
+ "id": "18",
156
+ "name": "Unsplash image library 15",
157
+ "nodes": []
158
+ },
159
+ {
160
+ "id": "19",
161
+ "name": "Unsplash image library 16",
162
+ "nodes": []
163
+ },
164
+ {
165
+ "id": "20",
166
+ "name": "Unsplash image library 17",
167
+ "nodes": []
168
+ },
169
+ {
170
+ "id": "21",
171
+ "name": "Unsplash image library 18",
172
+ "nodes": []
173
+ },
174
+ {
175
+ "id": "22",
176
+ "name": "Unsplash image library 19",
177
+ "nodes": []
178
+ },
179
+ {
180
+ "id": "23",
181
+ "name": "Unsplash image library 20",
182
+ "nodes": []
183
+ },
184
+ {
185
+ "id": "24",
186
+ "name": "Unsplash image library 21",
187
+ "nodes": []
188
+ },
189
+ {
190
+ "id": "25",
191
+ "name": "Unsplash image library 22",
192
+ "nodes": []
193
+ },
194
+ {
195
+ "id": "26",
196
+ "name": "Unsplash image library 23",
197
+ "nodes": []
198
+ },
199
+ {
200
+ "id": "27",
201
+ "name": "Unsplash image library 24",
202
+ "nodes": []
203
+ },
204
+ {
205
+ "id": "28",
206
+ "name": "Unsplash image library 25",
207
+ "nodes": []
208
+ },
209
+ {
210
+ "id": "29",
211
+ "name": "Unsplash image library 26",
212
+ "nodes": []
213
+ },
214
+ {
215
+ "id": "30",
216
+ "name": "Unsplash image library 27",
217
+ "nodes": []
109
218
  }
110
219
  ]
@@ -0,0 +1,30 @@
1
+ import { DeepPartial, Resource, ScopedSource, Source } from '../types';
2
+
3
+ export const mockSource = (properties: DeepPartial<Source> = {}): Source => {
4
+ return {
5
+ id: '1',
6
+ name: 'Test source',
7
+ ...properties,
8
+ nodes: properties?.nodes?.map((node) => mockResource(node as Partial<Resource>)) || [],
9
+ };
10
+ };
11
+
12
+ export const mockResource = (properties: Partial<Resource> = {}): Resource => {
13
+ return {
14
+ id: '1',
15
+ name: 'Test resource',
16
+ type: { code: 'folder', name: 'Folder' },
17
+ status: { code: 'live', name: 'Live' },
18
+ url: 'https://no-where.com',
19
+ urls: [],
20
+ childCount: 0,
21
+ ...properties,
22
+ };
23
+ };
24
+
25
+ export const mockScopedSource = (properties: DeepPartial<ScopedSource> = {}): ScopedSource => {
26
+ return {
27
+ source: mockSource(properties?.source),
28
+ resource: properties?.resource ? mockResource(properties?.resource as Partial<Resource>) : null,
29
+ };
30
+ };