@imposium-hub/components 2.15.0-3 → 2.16.0-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/dist/cjs/components/app-wrapper/AppWrapper.d.ts +0 -1
- package/dist/cjs/components/app-wrapper/AppWrapper.js +1 -1
- package/dist/cjs/components/app-wrapper/AppWrapper.js.map +1 -1
- package/dist/cjs/components/assets/AssetsTypeIcon.js +2 -0
- package/dist/cjs/components/assets/AssetsTypeIcon.js.map +1 -1
- package/dist/cjs/components/change-report/ChangeReportTree.js +65 -10
- package/dist/cjs/components/change-report/ChangeReportTree.js.map +1 -1
- package/dist/cjs/components/number-field/NumberField.d.ts +2 -2
- package/dist/cjs/components/number-field/NumberField.js +7 -4
- package/dist/cjs/components/number-field/NumberField.js.map +1 -1
- package/dist/cjs/components/publish-wizard/PublishWizard.js +36 -38
- package/dist/cjs/components/publish-wizard/PublishWizard.js.map +1 -1
- package/dist/cjs/constants/copy.d.ts +1 -0
- package/dist/cjs/constants/copy.js +3 -2
- package/dist/cjs/constants/copy.js.map +1 -1
- package/dist/cjs/constants/icons.d.ts +5 -0
- package/dist/cjs/constants/icons.js +12 -2
- package/dist/cjs/constants/icons.js.map +1 -1
- package/dist/cjs/constants/snippets.d.ts +2 -0
- package/dist/cjs/redux/actions/publish.d.ts +1 -1
- package/dist/cjs/redux/actions/publish.js +2 -2
- package/dist/cjs/redux/actions/publish.js.map +1 -1
- package/dist/esm/components/app-wrapper/AppWrapper.d.ts +0 -1
- package/dist/esm/components/app-wrapper/AppWrapper.js +1 -1
- package/dist/esm/components/app-wrapper/AppWrapper.js.map +1 -1
- package/dist/esm/components/assets/AssetsTypeIcon.js +2 -0
- package/dist/esm/components/assets/AssetsTypeIcon.js.map +1 -1
- package/dist/esm/components/change-report/ChangeReportTree.js +64 -10
- package/dist/esm/components/change-report/ChangeReportTree.js.map +1 -1
- package/dist/esm/components/number-field/NumberField.d.ts +2 -2
- package/dist/esm/components/number-field/NumberField.js +5 -4
- package/dist/esm/components/number-field/NumberField.js.map +1 -1
- package/dist/esm/components/publish-wizard/PublishWizard.js +36 -38
- package/dist/esm/components/publish-wizard/PublishWizard.js.map +1 -1
- package/dist/esm/constants/copy.d.ts +1 -0
- package/dist/esm/constants/copy.js +3 -2
- package/dist/esm/constants/copy.js.map +1 -1
- package/dist/esm/constants/icons.d.ts +5 -0
- package/dist/esm/constants/icons.js +10 -0
- package/dist/esm/constants/icons.js.map +1 -1
- package/dist/esm/constants/snippets.d.ts +2 -0
- package/dist/esm/redux/actions/publish.d.ts +1 -1
- package/dist/esm/redux/actions/publish.js +2 -2
- package/dist/esm/redux/actions/publish.js.map +1 -1
- package/dist/styles.css +54 -10
- package/dist/styles.less +65 -13
- package/less/components/change-report.less +50 -9
- package/less/components/publish-wizard.less +15 -4
- package/package.json +5 -1
- package/src/components/advanced-number-field/AdvancedNumberField.test.tsx +704 -0
- package/src/components/anchor-field/AnchorField.test.tsx +130 -0
- package/src/components/app-wrapper/AppWrapper.tsx +1 -6
- package/src/components/asset-details/AssetDetails.test.tsx +494 -0
- package/src/components/assets/AssetField.test.tsx +439 -0
- package/src/components/assets/AssetsTableAssetIdCell.test.tsx +134 -0
- package/src/components/assets/AssetsTableAssetIdFilter.test.tsx +95 -0
- package/src/components/assets/AssetsTableComplexTagCell.test.tsx +159 -0
- package/src/components/assets/AssetsTableDateCell.test.tsx +106 -0
- package/src/components/assets/AssetsTypeIcon.tsx +2 -0
- package/src/components/change-report/ChangeReportTree.tsx +104 -16
- package/src/components/number-field/NumberField.test.tsx +383 -0
- package/src/components/number-field/NumberField.tsx +15 -9
- package/src/components/publish-wizard/PublishWizard.tsx +52 -53
- package/src/components/text-field/TextField.test.tsx +988 -0
- package/src/constants/copy.ts +3 -2
- package/src/constants/icons.tsx +10 -0
- package/src/constants/snippets.ts +2 -0
- package/src/redux/actions/publish.ts +7 -2
- package/src/test/setup.ts +91 -0
- package/src/test/utils.tsx +44 -0
- package/tsconfig.eslint.json +8 -0
- package/tsconfig.json +1 -1
- package/vitest.config.ts +31 -0
- package/src/components/service-icon/ServiceIcon.test.tsx +0 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { render, act } from '@testing-library/react';
|
|
4
|
+
import { Provider } from 'react-redux';
|
|
5
|
+
import { legacy_createStore as createStore } from 'redux';
|
|
6
|
+
|
|
7
|
+
import { ASSET_TYPES } from '../../constants/assets';
|
|
8
|
+
|
|
9
|
+
vi.mock('../../Util', async (importOriginal) => {
|
|
10
|
+
const actual = (await importOriginal()) as any;
|
|
11
|
+
return {
|
|
12
|
+
...actual,
|
|
13
|
+
mimetypeConformsToOverlay: vi.fn(() => true)
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
vi.mock('../text-field/TextField', () => ({
|
|
18
|
+
default: ({ value, label, readOnly, placeholder, loading, buttons, ...props }: any) => (
|
|
19
|
+
<div data-testid={`textfield-${label || 'unlabeled'}`}>
|
|
20
|
+
<label>{label}</label>
|
|
21
|
+
<input
|
|
22
|
+
value={value || ''}
|
|
23
|
+
readOnly={readOnly}
|
|
24
|
+
placeholder={placeholder || ''}
|
|
25
|
+
/>
|
|
26
|
+
{loading && <span data-testid='loading-indicator'>Loading...</span>}
|
|
27
|
+
{buttons && <div data-testid='buttons-container'>{buttons}</div>}
|
|
28
|
+
</div>
|
|
29
|
+
)
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
vi.mock('../button/Button', () => ({
|
|
33
|
+
default: ({ children, onClick, tooltip, ...props }: any) => (
|
|
34
|
+
<button
|
|
35
|
+
data-testid={`btn-${tooltip || 'unknown'}`}
|
|
36
|
+
onClick={onClick}>
|
|
37
|
+
{children}
|
|
38
|
+
</button>
|
|
39
|
+
)
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
vi.mock('./AssetsTableDropzone', () => ({
|
|
43
|
+
default: ({ children, onDrop, disable }: any) => (
|
|
44
|
+
<div
|
|
45
|
+
data-testid='dropzone'
|
|
46
|
+
data-disabled={disable}>
|
|
47
|
+
{children}
|
|
48
|
+
</div>
|
|
49
|
+
)
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
vi.mock('./DroppableAssetRenderer', () => ({
|
|
53
|
+
default: ({ children, onDrop }: any) => <div data-testid='droppable-renderer'>{children}</div>
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
vi.mock('../../constants/icons', () => ({
|
|
57
|
+
ICON_FILTER: '<svg data-testid="icon-filter" />',
|
|
58
|
+
ICON_TIMES: '<svg data-testid="icon-times" />'
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
vi.mock('../../redux/actions/asset-filters', () => ({
|
|
62
|
+
updateFilters: vi.fn((filters) => ({ type: 'UPDATE_FILTERS', payload: filters }))
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
vi.mock('../../redux/actions/asset-uploads', () => ({
|
|
66
|
+
uploadAssets: vi.fn(() => ({ type: 'UPLOAD_ASSETS' }))
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
const createMockStore = (overrides = {}) => {
|
|
70
|
+
const defaultState = {
|
|
71
|
+
assetList: {
|
|
72
|
+
loading: false,
|
|
73
|
+
page: 1,
|
|
74
|
+
total_pages: 1,
|
|
75
|
+
asset_count: 0,
|
|
76
|
+
assets: []
|
|
77
|
+
},
|
|
78
|
+
...overrides
|
|
79
|
+
};
|
|
80
|
+
return createStore((state = defaultState) => state);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const createMockApi = (assetResponse = { name: 'test-asset.png' }, shouldReject = false) => ({
|
|
84
|
+
getAssetItem: vi.fn(() =>
|
|
85
|
+
shouldReject ? Promise.reject(new Error('Not found')) : Promise.resolve(assetResponse)
|
|
86
|
+
)
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('AssetField', () => {
|
|
90
|
+
let AssetFieldConnected: any;
|
|
91
|
+
|
|
92
|
+
beforeEach(async () => {
|
|
93
|
+
vi.clearAllMocks();
|
|
94
|
+
vi.useFakeTimers();
|
|
95
|
+
const module = await import('./AssetField');
|
|
96
|
+
AssetFieldConnected = module.default;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
afterEach(() => {
|
|
100
|
+
vi.useRealTimers();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const defaultProps = {
|
|
104
|
+
api: createMockApi(),
|
|
105
|
+
storyId: 'story-123',
|
|
106
|
+
assetId: 'asset-456',
|
|
107
|
+
label: 'Background',
|
|
108
|
+
width: 300,
|
|
109
|
+
tooltip: 'Select an asset',
|
|
110
|
+
onChange: vi.fn(),
|
|
111
|
+
accepts: ASSET_TYPES.IMAGE
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const renderWithStore = (props = {}, storeOverrides = {}) => {
|
|
115
|
+
const store = createMockStore(storeOverrides);
|
|
116
|
+
return render(
|
|
117
|
+
<Provider store={store}>
|
|
118
|
+
<AssetFieldConnected
|
|
119
|
+
{...defaultProps}
|
|
120
|
+
{...props}
|
|
121
|
+
/>
|
|
122
|
+
</Provider>
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
it('renders with all wrapper components', () => {
|
|
127
|
+
const { container } = renderWithStore();
|
|
128
|
+
|
|
129
|
+
expect(container.querySelector('[data-testid="dropzone"]')).toBeTruthy();
|
|
130
|
+
expect(container.querySelector('[data-testid="droppable-renderer"]')).toBeTruthy();
|
|
131
|
+
expect(container.querySelector('[data-testid="textfield-Background"]')).toBeTruthy();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('applies width style and error class', () => {
|
|
135
|
+
const { container } = renderWithStore({ width: 500 });
|
|
136
|
+
const assetFieldDiv: HTMLElement = container.querySelector('.asset-field');
|
|
137
|
+
expect(assetFieldDiv.style.width).toBe('500px');
|
|
138
|
+
expect(assetFieldDiv.classList.contains('error')).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('defaults to 100% width when no width prop is provided', () => {
|
|
142
|
+
const { container } = renderWithStore({ width: undefined });
|
|
143
|
+
const assetFieldDiv: HTMLElement = container.querySelector('.asset-field');
|
|
144
|
+
expect(assetFieldDiv.style.width).toBe('100%');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('shows drag prompt placeholder when no assetId', () => {
|
|
148
|
+
const { container } = renderWithStore({ assetId: null });
|
|
149
|
+
const input = container.querySelector('input');
|
|
150
|
+
expect(input.placeholder).toBe('Drag Asset Here');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('calls api.getAssetItem on mount when assetId is present', () => {
|
|
154
|
+
const mockApi = createMockApi();
|
|
155
|
+
renderWithStore({ api: mockApi });
|
|
156
|
+
expect(mockApi.getAssetItem).toHaveBeenCalledWith('asset-456');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('does not call api.getAssetItem on mount when assetId is absent', () => {
|
|
160
|
+
const mockApi = createMockApi();
|
|
161
|
+
renderWithStore({ api: mockApi, assetId: null });
|
|
162
|
+
expect(mockApi.getAssetItem).not.toHaveBeenCalled();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('displays asset name after successful API load', async () => {
|
|
166
|
+
const mockApi = createMockApi({ name: 'my-video.mp4' });
|
|
167
|
+
const { container } = renderWithStore({ api: mockApi });
|
|
168
|
+
|
|
169
|
+
await vi.waitFor(() => {
|
|
170
|
+
const input = container.querySelector('input');
|
|
171
|
+
expect(input.value).toBe('my-video.mp4');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('displays error message after failed API load', async () => {
|
|
176
|
+
const mockApi = createMockApi(null, true);
|
|
177
|
+
const { container } = renderWithStore({ api: mockApi });
|
|
178
|
+
|
|
179
|
+
await vi.waitFor(() => {
|
|
180
|
+
const input = container.querySelector('input');
|
|
181
|
+
expect(input.value).toContain('Could not find asset');
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('shows filter buttons when accepts is a string and assetName is set', async () => {
|
|
186
|
+
const mockApi = createMockApi({ name: 'loaded-asset.png' });
|
|
187
|
+
const { container } = renderWithStore({ api: mockApi, accepts: 'image' });
|
|
188
|
+
|
|
189
|
+
await vi.waitFor(() => {
|
|
190
|
+
const buttonsContainer = container.querySelector('[data-testid="buttons-container"]');
|
|
191
|
+
expect(buttonsContainer).toBeTruthy();
|
|
192
|
+
|
|
193
|
+
const filterTypeBtn = container.querySelector(
|
|
194
|
+
'[data-testid="btn-Filter assets to match Asset type"]'
|
|
195
|
+
);
|
|
196
|
+
expect(filterTypeBtn).toBeTruthy();
|
|
197
|
+
|
|
198
|
+
const filterNameBtn = container.querySelector(
|
|
199
|
+
'[data-testid="btn-Filter assets to the selected Asset"]'
|
|
200
|
+
);
|
|
201
|
+
expect(filterNameBtn).toBeTruthy();
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('shows clear button when assetId is present', () => {
|
|
206
|
+
const { container } = renderWithStore();
|
|
207
|
+
|
|
208
|
+
const clearBtn = container.querySelector('[data-testid="btn-Clear the selected Asset"]');
|
|
209
|
+
expect(clearBtn).toBeTruthy();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('does not show filter buttons when accepts is an array', async () => {
|
|
213
|
+
const mockApi = createMockApi({ name: 'loaded.png' });
|
|
214
|
+
const { container } = renderWithStore({ api: mockApi, accepts: ['image', 'video'] });
|
|
215
|
+
|
|
216
|
+
await vi.waitFor(() => {
|
|
217
|
+
const filterTypeBtn = container.querySelector(
|
|
218
|
+
'[data-testid="btn-Filter assets to match Asset type"]'
|
|
219
|
+
);
|
|
220
|
+
expect(filterTypeBtn).toBeFalsy();
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('reloads asset when assetList changes via dispatch', () => {
|
|
225
|
+
const mockApi = createMockApi({ name: 'initial.png' });
|
|
226
|
+
const reducer = (
|
|
227
|
+
state: any = {
|
|
228
|
+
assetList: { loading: false, page: 1, total_pages: 1, asset_count: 0, assets: [] }
|
|
229
|
+
},
|
|
230
|
+
action: any
|
|
231
|
+
) => {
|
|
232
|
+
if (action.type === 'UPDATE_ASSET_LIST') {
|
|
233
|
+
return { ...state, assetList: action.payload };
|
|
234
|
+
}
|
|
235
|
+
return state;
|
|
236
|
+
};
|
|
237
|
+
const store = createStore(reducer);
|
|
238
|
+
|
|
239
|
+
render(
|
|
240
|
+
<Provider store={store}>
|
|
241
|
+
<AssetFieldConnected
|
|
242
|
+
{...defaultProps}
|
|
243
|
+
api={mockApi}
|
|
244
|
+
/>
|
|
245
|
+
</Provider>
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
mockApi.getAssetItem.mockClear();
|
|
249
|
+
|
|
250
|
+
act(() => {
|
|
251
|
+
store.dispatch({
|
|
252
|
+
type: 'UPDATE_ASSET_LIST',
|
|
253
|
+
payload: {
|
|
254
|
+
loading: false,
|
|
255
|
+
page: 2,
|
|
256
|
+
total_pages: 2,
|
|
257
|
+
asset_count: 10,
|
|
258
|
+
assets: [{ id: '1' }]
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
act(() => {
|
|
264
|
+
vi.advanceTimersByTime(350);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
expect(mockApi.getAssetItem).toHaveBeenCalled();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('clears timeout on unmount', () => {
|
|
271
|
+
const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
|
|
272
|
+
const { unmount } = renderWithStore();
|
|
273
|
+
unmount();
|
|
274
|
+
expect(clearTimeoutSpy).toHaveBeenCalled();
|
|
275
|
+
clearTimeoutSpy.mockRestore();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('exercises setFilter with string accepts', () => {
|
|
279
|
+
const { container } = renderWithStore({ accepts: 'image' });
|
|
280
|
+
const filterBtn: HTMLElement = container.querySelector(
|
|
281
|
+
'[data-testid="btn-Filter assets to match Asset type"]'
|
|
282
|
+
);
|
|
283
|
+
expect(filterBtn).toBeTruthy();
|
|
284
|
+
filterBtn?.click();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('exercises setFilter with no accepts', () => {
|
|
288
|
+
const { container } = renderWithStore({ accepts: undefined });
|
|
289
|
+
const filterBtn = container.querySelector(
|
|
290
|
+
'[data-testid="btn-Filter assets to match Asset type"]'
|
|
291
|
+
);
|
|
292
|
+
expect(filterBtn).toBeFalsy();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('exercises clearAsset button', async () => {
|
|
296
|
+
const onChangeMock = vi.fn();
|
|
297
|
+
const { container } = renderWithStore({ onChange: onChangeMock });
|
|
298
|
+
const clearBtn: HTMLElement = container.querySelector(
|
|
299
|
+
'[data-testid="btn-Clear the selected Asset"]'
|
|
300
|
+
);
|
|
301
|
+
expect(clearBtn).toBeTruthy();
|
|
302
|
+
clearBtn?.click();
|
|
303
|
+
|
|
304
|
+
await vi.waitFor(() => {
|
|
305
|
+
expect(onChangeMock).toHaveBeenCalledWith(null);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('exercises setFilterToAsset button', async () => {
|
|
310
|
+
const mockApi = createMockApi({ name: 'my-asset.png' });
|
|
311
|
+
const { container } = renderWithStore({ api: mockApi, accepts: 'image' });
|
|
312
|
+
|
|
313
|
+
await vi.waitFor(() => {
|
|
314
|
+
const filterNameBtn: HTMLElement = container.querySelector(
|
|
315
|
+
'[data-testid="btn-Filter assets to the selected Asset"]'
|
|
316
|
+
);
|
|
317
|
+
expect(filterNameBtn).toBeTruthy();
|
|
318
|
+
filterNameBtn?.click();
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('exercises onAssetDrop with matching type', () => {
|
|
323
|
+
const onChangeMock = vi.fn();
|
|
324
|
+
const WrappedComponent = AssetFieldConnected.WrappedComponent;
|
|
325
|
+
|
|
326
|
+
const instance = new WrappedComponent({
|
|
327
|
+
...defaultProps,
|
|
328
|
+
onChange: onChangeMock,
|
|
329
|
+
accepts: 'image',
|
|
330
|
+
updateFilters: vi.fn(),
|
|
331
|
+
uploadAssets: vi.fn(),
|
|
332
|
+
assetList: { loading: false, page: 1, total_pages: 1, asset_count: 0, assets: [] }
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
instance.setState = vi.fn((stateOrFn, callback) => {
|
|
336
|
+
if (typeof stateOrFn === 'object') {
|
|
337
|
+
instance.state = { ...instance.state, ...stateOrFn };
|
|
338
|
+
}
|
|
339
|
+
if (callback) callback();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
instance.onAssetDrop(
|
|
343
|
+
{ rowData: { name: 'dropped.png', type: 'image', id: 'asset-789' } },
|
|
344
|
+
null
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
expect(instance.setState).toHaveBeenCalled();
|
|
348
|
+
expect(onChangeMock).toHaveBeenCalledWith({
|
|
349
|
+
name: 'dropped.png',
|
|
350
|
+
type: 'image',
|
|
351
|
+
id: 'asset-789'
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('exercises onAssetDrop with mismatched type and onError', () => {
|
|
356
|
+
const onErrorMock = vi.fn();
|
|
357
|
+
const WrappedComponent = AssetFieldConnected.WrappedComponent;
|
|
358
|
+
|
|
359
|
+
const instance = new WrappedComponent({
|
|
360
|
+
...defaultProps,
|
|
361
|
+
onError: onErrorMock,
|
|
362
|
+
accepts: 'image',
|
|
363
|
+
updateFilters: vi.fn(),
|
|
364
|
+
uploadAssets: vi.fn(),
|
|
365
|
+
assetList: { loading: false, page: 1, total_pages: 1, asset_count: 0, assets: [] }
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
instance.setState = vi.fn();
|
|
369
|
+
|
|
370
|
+
instance.onAssetDrop(
|
|
371
|
+
{ rowData: { name: 'dropped.mp4', type: 'video', id: 'asset-999' } },
|
|
372
|
+
null
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
expect(onErrorMock).toHaveBeenCalled();
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('exercises onAssetDrop with array accepts and mismatched type', () => {
|
|
379
|
+
const onErrorMock = vi.fn();
|
|
380
|
+
const WrappedComponent = AssetFieldConnected.WrappedComponent;
|
|
381
|
+
|
|
382
|
+
const instance = new WrappedComponent({
|
|
383
|
+
...defaultProps,
|
|
384
|
+
onError: onErrorMock,
|
|
385
|
+
accepts: ['image', 'audio'],
|
|
386
|
+
updateFilters: vi.fn(),
|
|
387
|
+
uploadAssets: vi.fn(),
|
|
388
|
+
assetList: { loading: false, page: 1, total_pages: 1, asset_count: 0, assets: [] }
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
instance.setState = vi.fn();
|
|
392
|
+
|
|
393
|
+
instance.onAssetDrop(
|
|
394
|
+
{ rowData: { name: 'dropped.mp4', type: 'video', id: 'asset-999' } },
|
|
395
|
+
null
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
expect(onErrorMock).toHaveBeenCalledWith(expect.stringContaining('image or audio'));
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('exercises onFileDrop with monitor', () => {
|
|
402
|
+
const WrappedComponent = AssetFieldConnected.WrappedComponent;
|
|
403
|
+
const uploadAssetsMock = vi.fn();
|
|
404
|
+
|
|
405
|
+
const instance = new WrappedComponent({
|
|
406
|
+
...defaultProps,
|
|
407
|
+
accepts: 'image',
|
|
408
|
+
updateFilters: vi.fn(),
|
|
409
|
+
uploadAssets: uploadAssetsMock,
|
|
410
|
+
assetList: { loading: false, page: 1, total_pages: 1, asset_count: 0, assets: [] }
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
instance.setState = vi.fn();
|
|
414
|
+
|
|
415
|
+
const mockMonitor = {
|
|
416
|
+
getItem: () => ({
|
|
417
|
+
files: [new File(['content'], 'test.png', { type: 'image/png' })]
|
|
418
|
+
})
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
instance.onFileDrop(null, mockMonitor);
|
|
422
|
+
expect(uploadAssetsMock).toHaveBeenCalled();
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('exercises onFileDrop without monitor', () => {
|
|
426
|
+
const WrappedComponent = AssetFieldConnected.WrappedComponent;
|
|
427
|
+
|
|
428
|
+
const instance = new WrappedComponent({
|
|
429
|
+
...defaultProps,
|
|
430
|
+
updateFilters: vi.fn(),
|
|
431
|
+
uploadAssets: vi.fn(),
|
|
432
|
+
assetList: { loading: false, page: 1, total_pages: 1, asset_count: 0, assets: [] }
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
instance.setState = vi.fn();
|
|
436
|
+
instance.onFileDrop(null, null);
|
|
437
|
+
expect(instance.setState).not.toHaveBeenCalled();
|
|
438
|
+
});
|
|
439
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { render, fireEvent } from '@testing-library/react';
|
|
4
|
+
import { Provider } from 'react-redux';
|
|
5
|
+
import { legacy_createStore as createStore } from 'redux';
|
|
6
|
+
import { copying } from '../../constants/copy';
|
|
7
|
+
|
|
8
|
+
vi.mock('../../redux/actions/asset-filters', () => ({
|
|
9
|
+
updateFilters: vi.fn(() => ({ type: 'MOCK_UPDATE_FILTERS' })),
|
|
10
|
+
default: { UPDATE: 'assetFilters/UPDATE', RESET: 'footageFilters/RESET' }
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
import AssetsTableAssetIdCellMemoized from './AssetsTableAssetIdCell';
|
|
14
|
+
|
|
15
|
+
const MOCK_ASSET_ID = 'asset-abc-123';
|
|
16
|
+
|
|
17
|
+
const createMockStore = (overrides = {}) => {
|
|
18
|
+
const defaultState = {
|
|
19
|
+
assetFilters: {},
|
|
20
|
+
...overrides
|
|
21
|
+
};
|
|
22
|
+
return createStore((state = defaultState) => state);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const buildCellProp = (assetId: string) => ({
|
|
26
|
+
row: {
|
|
27
|
+
original: { id: assetId }
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const renderComponent = (propOverrides = {}) => {
|
|
32
|
+
const defaultProps = {
|
|
33
|
+
cell: buildCellProp(MOCK_ASSET_ID),
|
|
34
|
+
onNotification: vi.fn(),
|
|
35
|
+
onError: vi.fn(),
|
|
36
|
+
...propOverrides
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const store = createMockStore();
|
|
40
|
+
|
|
41
|
+
const result = render(
|
|
42
|
+
<Provider store={store}>
|
|
43
|
+
<AssetsTableAssetIdCellMemoized {...defaultProps} />
|
|
44
|
+
</Provider>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return { ...result, props: defaultProps };
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
describe('AssetsTableAssetIdCell', () => {
|
|
51
|
+
let clipboardWriteTextMock: ReturnType<typeof vi.fn>;
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
clipboardWriteTextMock = vi.fn();
|
|
55
|
+
Object.assign(navigator, {
|
|
56
|
+
clipboard: {
|
|
57
|
+
writeText: clipboardWriteTextMock
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('renders the asset id in a div with the correct class', () => {
|
|
63
|
+
const { container } = renderComponent();
|
|
64
|
+
const idCell = container.querySelector('.imposium-asset-id');
|
|
65
|
+
|
|
66
|
+
expect(idCell).toBeTruthy();
|
|
67
|
+
expect(idCell.textContent).toBe(MOCK_ASSET_ID);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('calls onNotification after successful clipboard copy', async () => {
|
|
71
|
+
clipboardWriteTextMock.mockResolvedValueOnce(undefined);
|
|
72
|
+
const { container, props } = renderComponent();
|
|
73
|
+
|
|
74
|
+
const idCell: HTMLElement = container.querySelector('.imposium-asset-id');
|
|
75
|
+
fireEvent.click(idCell);
|
|
76
|
+
|
|
77
|
+
await vi.waitFor(() => {
|
|
78
|
+
expect(clipboardWriteTextMock).toHaveBeenCalledWith(MOCK_ASSET_ID);
|
|
79
|
+
expect(props.onNotification).toHaveBeenCalledWith(copying.copied);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('calls onError after failed clipboard copy', async () => {
|
|
84
|
+
clipboardWriteTextMock.mockRejectedValueOnce(new Error('clipboard denied'));
|
|
85
|
+
const { container, props } = renderComponent();
|
|
86
|
+
|
|
87
|
+
const idCell: HTMLElement = container.querySelector('.imposium-asset-id');
|
|
88
|
+
fireEvent.click(idCell);
|
|
89
|
+
|
|
90
|
+
await vi.waitFor(() => {
|
|
91
|
+
expect(props.onError).toHaveBeenCalledWith(copying.error);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('does not throw when onNotification is not provided', async () => {
|
|
96
|
+
clipboardWriteTextMock.mockResolvedValueOnce(undefined);
|
|
97
|
+
const { container } = renderComponent({
|
|
98
|
+
onNotification: undefined,
|
|
99
|
+
onError: undefined
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const idCell: HTMLElement = container.querySelector('.imposium-asset-id');
|
|
103
|
+
fireEvent.click(idCell);
|
|
104
|
+
|
|
105
|
+
await vi.waitFor(() => {
|
|
106
|
+
expect(clipboardWriteTextMock).toHaveBeenCalledWith(MOCK_ASSET_ID);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('does not throw when onError is not provided', async () => {
|
|
111
|
+
clipboardWriteTextMock.mockRejectedValueOnce(new Error('denied'));
|
|
112
|
+
const { container } = renderComponent({
|
|
113
|
+
onNotification: undefined,
|
|
114
|
+
onError: undefined
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const idCell: HTMLElement = container.querySelector('.imposium-asset-id');
|
|
118
|
+
fireEvent.click(idCell);
|
|
119
|
+
|
|
120
|
+
await vi.waitFor(() => {
|
|
121
|
+
expect(clipboardWriteTextMock).toHaveBeenCalled();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('renders with a different asset id', () => {
|
|
126
|
+
const differentId = 'other-asset-456';
|
|
127
|
+
const { container } = renderComponent({
|
|
128
|
+
cell: buildCellProp(differentId)
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const idCell = container.querySelector('.imposium-asset-id');
|
|
132
|
+
expect(idCell.textContent).toBe(differentId);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { render } from '@testing-library/react';
|
|
4
|
+
import { Provider } from 'react-redux';
|
|
5
|
+
import { legacy_createStore as createStore } from 'redux';
|
|
6
|
+
|
|
7
|
+
vi.mock('../text-field/TextField', () => ({
|
|
8
|
+
default: ({ className, submittable, submittableType, value, doSubmit, header }: any) => (
|
|
9
|
+
<div
|
|
10
|
+
data-testid='mock-text-field'
|
|
11
|
+
data-classname={className}
|
|
12
|
+
data-submittable={submittable}
|
|
13
|
+
data-submittable-type={submittableType}
|
|
14
|
+
data-value={value || ''}
|
|
15
|
+
data-header={header}>
|
|
16
|
+
<button
|
|
17
|
+
data-testid='submit-trigger'
|
|
18
|
+
onClick={() => doSubmit('new-filter-value')}>
|
|
19
|
+
Submit
|
|
20
|
+
</button>
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock('../../redux/actions/asset-filters', () => ({
|
|
26
|
+
updateFilters: vi.fn((filters) => ({ type: 'assetFilters/UPDATE', newFilters: filters })),
|
|
27
|
+
default: { UPDATE: 'assetFilters/UPDATE', RESET: 'footageFilters/RESET' }
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
import AssetsTableAssetIdFilterMemoized from './AssetsTableAssetIdFilter';
|
|
31
|
+
|
|
32
|
+
const MOCK_ASSET_ID_FILTER = 'existing-filter-123';
|
|
33
|
+
|
|
34
|
+
const createMockStore = (assetIdFilter = '') => {
|
|
35
|
+
const initialState = { assetFilters: { id: assetIdFilter } };
|
|
36
|
+
return createStore((state = initialState, action: any) => {
|
|
37
|
+
if (action.type === 'assetFilters/UPDATE') {
|
|
38
|
+
return {
|
|
39
|
+
...state,
|
|
40
|
+
assetFilters: { ...state.assetFilters, ...action.newFilters }
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return state;
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const renderComponent = (assetIdFilter = '') => {
|
|
48
|
+
const store = createMockStore(assetIdFilter);
|
|
49
|
+
|
|
50
|
+
const result = render(
|
|
51
|
+
<Provider store={store}>
|
|
52
|
+
<AssetsTableAssetIdFilterMemoized />
|
|
53
|
+
</Provider>
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return { ...result, store };
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
describe('AssetsTableAssetIdFilter', () => {
|
|
60
|
+
it('renders the TextField with correct props', () => {
|
|
61
|
+
const { getByTestId } = renderComponent(MOCK_ASSET_ID_FILTER);
|
|
62
|
+
const textField = getByTestId('mock-text-field');
|
|
63
|
+
|
|
64
|
+
expect(textField.getAttribute('data-classname')).toBe('asset-id');
|
|
65
|
+
expect(textField.getAttribute('data-submittable')).toBe('true');
|
|
66
|
+
expect(textField.getAttribute('data-submittable-type')).toBe('search');
|
|
67
|
+
expect(textField.getAttribute('data-value')).toBe(MOCK_ASSET_ID_FILTER);
|
|
68
|
+
expect(textField.getAttribute('data-header')).toBe('true');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('dispatches updateFilters with the submitted value', () => {
|
|
72
|
+
const { getByTestId, store } = renderComponent();
|
|
73
|
+
const submitButton = getByTestId('submit-trigger');
|
|
74
|
+
|
|
75
|
+
submitButton.click();
|
|
76
|
+
|
|
77
|
+
const storeState = store.getState() as any;
|
|
78
|
+
expect(storeState.assetFilters.id).toBe('new-filter-value');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('renders with an empty filter value when no id is set', () => {
|
|
82
|
+
const { getByTestId } = renderComponent();
|
|
83
|
+
const textField = getByTestId('mock-text-field');
|
|
84
|
+
|
|
85
|
+
expect(textField.getAttribute('data-value')).toBe('');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('passes the current filter value from the redux store', () => {
|
|
89
|
+
const filterValue = 'search-term-abc';
|
|
90
|
+
const { getByTestId } = renderComponent(filterValue);
|
|
91
|
+
const textField = getByTestId('mock-text-field');
|
|
92
|
+
|
|
93
|
+
expect(textField.getAttribute('data-value')).toBe(filterValue);
|
|
94
|
+
});
|
|
95
|
+
});
|