box-ui-elements 24.0.0-beta.4 → 24.0.0-beta.6
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/explorer.css +1 -1
- package/dist/explorer.js +1 -1
- package/dist/openwith.js +1 -1
- package/dist/picker.js +1 -1
- package/dist/preview.js +1 -1
- package/dist/sharing.js +1 -1
- package/dist/sidebar.js +1 -1
- package/dist/uploader.js +1 -1
- package/es/api/Metadata.js +98 -13
- package/es/api/Metadata.js.flow +110 -12
- package/es/api/Metadata.js.map +1 -1
- package/es/elements/common/messages.js +16 -0
- package/es/elements/common/messages.js.flow +25 -0
- package/es/elements/common/messages.js.map +1 -1
- package/es/elements/content-explorer/Content.js +5 -2
- package/es/elements/content-explorer/Content.js.map +1 -1
- package/es/elements/content-explorer/ContentExplorer.js +31 -6
- package/es/elements/content-explorer/ContentExplorer.js.map +1 -1
- package/es/elements/content-explorer/MetadataQueryAPIHelper.js +164 -10
- package/es/elements/content-explorer/MetadataQueryAPIHelper.js.map +1 -1
- package/es/elements/content-explorer/MetadataQueryBuilder.js +115 -0
- package/es/elements/content-explorer/MetadataQueryBuilder.js.map +1 -0
- package/es/elements/content-explorer/MetadataSidePanel.js +40 -14
- package/es/elements/content-explorer/MetadataSidePanel.js.map +1 -1
- package/es/elements/content-explorer/MetadataViewContainer.js +133 -36
- package/es/elements/content-explorer/MetadataViewContainer.js.map +1 -1
- package/es/elements/content-explorer/stories/MetadataView.stories.js +3 -25
- package/es/elements/content-explorer/stories/MetadataView.stories.js.map +1 -1
- package/es/elements/content-explorer/stories/tests/MetadataView-visual.stories.js +65 -29
- package/es/elements/content-explorer/stories/tests/MetadataView-visual.stories.js.map +1 -1
- package/es/elements/content-explorer/utils.js +140 -12
- package/es/elements/content-explorer/utils.js.map +1 -1
- package/es/src/elements/common/__mocks__/mockMetadata.d.ts +8 -24
- package/es/src/elements/content-explorer/Content.d.ts +4 -3
- package/es/src/elements/content-explorer/ContentExplorer.d.ts +19 -6
- package/es/src/elements/content-explorer/MetadataQueryAPIHelper.d.ts +22 -3
- package/es/src/elements/content-explorer/MetadataQueryBuilder.d.ts +27 -0
- package/es/src/elements/content-explorer/MetadataSidePanel.d.ts +6 -3
- package/es/src/elements/content-explorer/MetadataViewContainer.d.ts +10 -4
- package/es/src/elements/content-explorer/__tests__/MetadataQueryBuilder.test.d.ts +1 -0
- package/es/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.d.ts +1 -0
- package/es/src/elements/content-explorer/utils.d.ts +9 -3
- package/i18n/bn-IN.js +4 -0
- package/i18n/bn-IN.properties +12 -0
- package/i18n/da-DK.js +4 -0
- package/i18n/da-DK.properties +12 -0
- package/i18n/de-DE.js +5 -1
- package/i18n/de-DE.properties +12 -0
- package/i18n/en-AU.js +4 -0
- package/i18n/en-AU.properties +12 -0
- package/i18n/en-CA.js +4 -0
- package/i18n/en-CA.properties +12 -0
- package/i18n/en-GB.js +4 -0
- package/i18n/en-GB.properties +12 -0
- package/i18n/en-US.js +4 -0
- package/i18n/en-US.properties +8 -0
- package/i18n/en-x-pseudo.js +4 -0
- package/i18n/es-419.js +5 -1
- package/i18n/es-419.properties +12 -0
- package/i18n/es-ES.js +5 -1
- package/i18n/es-ES.properties +12 -0
- package/i18n/fi-FI.js +4 -0
- package/i18n/fi-FI.properties +12 -0
- package/i18n/fr-CA.js +4 -0
- package/i18n/fr-CA.properties +12 -0
- package/i18n/fr-FR.js +4 -0
- package/i18n/fr-FR.properties +12 -0
- package/i18n/hi-IN.js +4 -0
- package/i18n/hi-IN.properties +12 -0
- package/i18n/it-IT.js +4 -0
- package/i18n/it-IT.properties +12 -0
- package/i18n/ja-JP.js +6 -2
- package/i18n/ja-JP.properties +14 -2
- package/i18n/ko-KR.js +4 -0
- package/i18n/ko-KR.properties +12 -0
- package/i18n/nb-NO.js +4 -0
- package/i18n/nb-NO.properties +12 -0
- package/i18n/nl-NL.js +4 -0
- package/i18n/nl-NL.properties +12 -0
- package/i18n/pl-PL.js +4 -0
- package/i18n/pl-PL.properties +12 -0
- package/i18n/pt-BR.js +4 -0
- package/i18n/pt-BR.properties +12 -0
- package/i18n/ru-RU.js +5 -1
- package/i18n/ru-RU.properties +12 -0
- package/i18n/sv-SE.js +4 -0
- package/i18n/sv-SE.properties +12 -0
- package/i18n/tr-TR.js +5 -1
- package/i18n/tr-TR.properties +12 -0
- package/i18n/zh-CN.js +4 -0
- package/i18n/zh-CN.properties +12 -0
- package/i18n/zh-TW.js +4 -0
- package/i18n/zh-TW.properties +12 -0
- package/package.json +3 -3
- package/src/api/Metadata.js +110 -12
- package/src/api/__tests__/Metadata.test.js +120 -0
- package/src/elements/common/__mocks__/mockMetadata.ts +7 -11
- package/src/elements/common/messages.js +25 -0
- package/src/elements/content-explorer/Content.tsx +9 -2
- package/src/elements/content-explorer/ContentExplorer.tsx +71 -17
- package/src/elements/content-explorer/MetadataQueryAPIHelper.ts +199 -8
- package/src/elements/content-explorer/MetadataQueryBuilder.ts +159 -0
- package/src/elements/content-explorer/MetadataSidePanel.tsx +55 -14
- package/src/elements/content-explorer/MetadataViewContainer.tsx +164 -29
- package/src/elements/content-explorer/__tests__/Content.test.tsx +1 -0
- package/src/elements/content-explorer/__tests__/ContentExplorer.test.tsx +38 -7
- package/src/elements/content-explorer/__tests__/MetadataQueryAPIHelper.test.ts +428 -12
- package/src/elements/content-explorer/__tests__/MetadataQueryBuilder.test.ts +419 -0
- package/src/elements/content-explorer/__tests__/MetadataSidePanel.test.tsx +145 -3
- package/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx +413 -9
- package/src/elements/content-explorer/stories/MetadataView.stories.tsx +3 -21
- package/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx +56 -21
- package/src/elements/content-explorer/utils.ts +150 -13
|
@@ -3,18 +3,34 @@ import * as React from 'react';
|
|
|
3
3
|
import type { Collection } from '../../../common/types/core';
|
|
4
4
|
import type { MetadataTemplate, MetadataTemplateField } from '../../../common/types/metadata';
|
|
5
5
|
import { render, screen, userEvent, waitFor, within } from '../../../test-utils/testing-library';
|
|
6
|
-
import MetadataViewContainer, {
|
|
6
|
+
import MetadataViewContainer, {
|
|
7
|
+
type MetadataViewContainerProps,
|
|
8
|
+
convertFilterValuesToExternal,
|
|
9
|
+
type ExternalFilterValues,
|
|
10
|
+
} from '../MetadataViewContainer';
|
|
7
11
|
|
|
8
12
|
describe('elements/content-explorer/MetadataViewContainer', () => {
|
|
9
13
|
const mockItems = [
|
|
10
|
-
{
|
|
11
|
-
|
|
14
|
+
{
|
|
15
|
+
id: '1',
|
|
16
|
+
name: 'File 1.txt',
|
|
17
|
+
type: 'file',
|
|
18
|
+
'item.name': 'File 1.txt',
|
|
19
|
+
industry: 'tech',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: '2',
|
|
23
|
+
name: 'File 2.pdf',
|
|
24
|
+
type: 'file',
|
|
25
|
+
'item.name': 'File 2.pdf',
|
|
26
|
+
industry: 'finance',
|
|
27
|
+
},
|
|
12
28
|
];
|
|
13
29
|
|
|
14
30
|
const mockMetadataTemplateFields: MetadataTemplateField[] = [
|
|
15
31
|
{
|
|
16
32
|
id: 'field1',
|
|
17
|
-
key: '
|
|
33
|
+
key: 'item.name',
|
|
18
34
|
displayName: 'Name',
|
|
19
35
|
type: 'string',
|
|
20
36
|
},
|
|
@@ -28,6 +44,22 @@ describe('elements/content-explorer/MetadataViewContainer', () => {
|
|
|
28
44
|
{ key: 'finance', id: 'finance1' },
|
|
29
45
|
],
|
|
30
46
|
},
|
|
47
|
+
{
|
|
48
|
+
id: 'field3',
|
|
49
|
+
key: 'price',
|
|
50
|
+
displayName: 'Price',
|
|
51
|
+
type: 'float',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 'field4',
|
|
55
|
+
key: 'category',
|
|
56
|
+
displayName: 'Category',
|
|
57
|
+
type: 'multiSelect',
|
|
58
|
+
options: [
|
|
59
|
+
{ key: 'category1', id: 'cat1' },
|
|
60
|
+
{ key: 'category2', id: 'cat2' },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
31
63
|
];
|
|
32
64
|
|
|
33
65
|
const mockMetadataTemplate: MetadataTemplate = {
|
|
@@ -49,7 +81,7 @@ describe('elements/content-explorer/MetadataViewContainer', () => {
|
|
|
49
81
|
columns: [
|
|
50
82
|
{
|
|
51
83
|
textValue: 'Name',
|
|
52
|
-
id: 'name',
|
|
84
|
+
id: 'item.name',
|
|
53
85
|
type: 'string',
|
|
54
86
|
allowsSorting: true,
|
|
55
87
|
minWidth: 250,
|
|
@@ -66,17 +98,22 @@ describe('elements/content-explorer/MetadataViewContainer', () => {
|
|
|
66
98
|
},
|
|
67
99
|
],
|
|
68
100
|
metadataTemplate: mockMetadataTemplate,
|
|
101
|
+
onMetadataFilter: jest.fn(),
|
|
69
102
|
};
|
|
70
103
|
|
|
71
104
|
const renderComponent = (props: Partial<MetadataViewContainerProps> = {}) => {
|
|
72
105
|
return render(<MetadataViewContainer {...defaultProps} {...props} />);
|
|
73
106
|
};
|
|
74
107
|
|
|
108
|
+
beforeEach(() => {
|
|
109
|
+
jest.clearAllMocks();
|
|
110
|
+
});
|
|
111
|
+
|
|
75
112
|
test('should render MetadataView component', () => {
|
|
76
113
|
renderComponent();
|
|
77
114
|
|
|
78
115
|
expect(screen.getByRole('button', { name: 'All Filters' })).toBeInTheDocument();
|
|
79
|
-
expect(screen.
|
|
116
|
+
expect(screen.getAllByRole('button', { name: 'Name' })).toHaveLength(2); // One in filter bar, one in table header
|
|
80
117
|
expect(screen.getByRole('button', { name: 'Industry' })).toBeInTheDocument();
|
|
81
118
|
expect(screen.getByText('File 1.txt')).toBeInTheDocument();
|
|
82
119
|
expect(screen.getByText('File 2.pdf')).toBeInTheDocument();
|
|
@@ -101,7 +138,11 @@ describe('elements/content-explorer/MetadataViewContainer', () => {
|
|
|
101
138
|
],
|
|
102
139
|
};
|
|
103
140
|
|
|
104
|
-
renderComponent({
|
|
141
|
+
renderComponent({
|
|
142
|
+
metadataTemplate: template,
|
|
143
|
+
actionBarProps: { onFilterSubmit },
|
|
144
|
+
onMetadataFilter: jest.fn(),
|
|
145
|
+
});
|
|
105
146
|
|
|
106
147
|
await userEvent().click(screen.getByRole('button', { name: /Contact Role/ }));
|
|
107
148
|
await userEvent().click(within(screen.getByRole('menu')).getByRole('menuitemcheckbox', { name: 'Developer' }));
|
|
@@ -112,7 +153,370 @@ describe('elements/content-explorer/MetadataViewContainer', () => {
|
|
|
112
153
|
await waitFor(() => expect(onFilterSubmit).toHaveBeenCalledTimes(2));
|
|
113
154
|
const firstCall = onFilterSubmit.mock.calls[0][0];
|
|
114
155
|
const secondCall = onFilterSubmit.mock.calls[1][0];
|
|
115
|
-
|
|
116
|
-
expect(
|
|
156
|
+
|
|
157
|
+
expect(firstCall.role.value).toEqual(['Developer']);
|
|
158
|
+
expect(secondCall.role.value).toEqual(['Developer', 'Marketing']);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('should call onMetadataFilter and onFilterSubmit when filter is submitted', async () => {
|
|
162
|
+
const onFilterSubmit = jest.fn();
|
|
163
|
+
const onMetadataFilter = jest.fn();
|
|
164
|
+
const template: MetadataTemplate = {
|
|
165
|
+
...mockMetadataTemplate,
|
|
166
|
+
fields: [
|
|
167
|
+
{
|
|
168
|
+
id: 'field1',
|
|
169
|
+
key: 'status',
|
|
170
|
+
displayName: 'Status',
|
|
171
|
+
type: 'enum',
|
|
172
|
+
options: [
|
|
173
|
+
{ id: 's1', key: 'Active' },
|
|
174
|
+
{ id: 's2', key: 'Inactive' },
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
renderComponent({
|
|
181
|
+
metadataTemplate: template,
|
|
182
|
+
actionBarProps: { onFilterSubmit },
|
|
183
|
+
onMetadataFilter,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
await userEvent().click(screen.getByRole('button', { name: /Status/ }));
|
|
187
|
+
await userEvent().click(within(screen.getByRole('menu')).getByRole('menuitemcheckbox', { name: 'Active' }));
|
|
188
|
+
|
|
189
|
+
await waitFor(() => {
|
|
190
|
+
expect(onMetadataFilter).toHaveBeenCalledTimes(1);
|
|
191
|
+
expect(onFilterSubmit).toHaveBeenCalledTimes(1);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const filterCall = onMetadataFilter.mock.calls[0][0];
|
|
195
|
+
const submitCall = onFilterSubmit.mock.calls[0][0];
|
|
196
|
+
|
|
197
|
+
expect(filterCall.status.value).toEqual(['Active']);
|
|
198
|
+
expect(submitCall.status.value).toEqual(['Active']);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('should only call onMetadataFilter when onFilterSubmit is not provided', async () => {
|
|
202
|
+
const onMetadataFilter = jest.fn();
|
|
203
|
+
const template: MetadataTemplate = {
|
|
204
|
+
...mockMetadataTemplate,
|
|
205
|
+
fields: [
|
|
206
|
+
{
|
|
207
|
+
id: 'field1',
|
|
208
|
+
key: 'status',
|
|
209
|
+
displayName: 'Status',
|
|
210
|
+
type: 'enum',
|
|
211
|
+
options: [
|
|
212
|
+
{ id: 's1', key: 'Active' },
|
|
213
|
+
{ id: 's2', key: 'Inactive' },
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
renderComponent({
|
|
220
|
+
metadataTemplate: template,
|
|
221
|
+
onMetadataFilter,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
await userEvent().click(screen.getByRole('button', { name: /Status/ }));
|
|
225
|
+
await userEvent().click(within(screen.getByRole('menu')).getByRole('menuitemcheckbox', { name: 'Active' }));
|
|
226
|
+
|
|
227
|
+
await waitFor(() => {
|
|
228
|
+
expect(onMetadataFilter).toHaveBeenCalledTimes(1);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const filterCall = onMetadataFilter.mock.calls[0][0];
|
|
232
|
+
expect(filterCall.status.value).toEqual(['Active']);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('should handle initial filter values transformation', () => {
|
|
236
|
+
const initialFilterValues = {
|
|
237
|
+
industry: {
|
|
238
|
+
fieldType: 'enum' as const,
|
|
239
|
+
value: ['tech'],
|
|
240
|
+
},
|
|
241
|
+
price: {
|
|
242
|
+
fieldType: 'float' as const,
|
|
243
|
+
value: { range: { gt: 10, lt: 100 } },
|
|
244
|
+
},
|
|
245
|
+
name: {
|
|
246
|
+
fieldType: 'string' as const,
|
|
247
|
+
value: ['search term'],
|
|
248
|
+
},
|
|
249
|
+
category: {
|
|
250
|
+
fieldType: 'multiSelect' as const,
|
|
251
|
+
value: ['category1', 'category2'],
|
|
252
|
+
},
|
|
253
|
+
} as unknown as ExternalFilterValues;
|
|
254
|
+
|
|
255
|
+
renderComponent({
|
|
256
|
+
actionBarProps: { initialFilterValues },
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
expect(screen.getByRole('button', { name: 'All Filters 3' })).toBeInTheDocument();
|
|
260
|
+
expect(screen.getByRole('button', { name: /Industry/i })).toHaveTextContent(/\(1\)/);
|
|
261
|
+
expect(screen.getByRole('button', { name: /Category/i })).toHaveTextContent(/\(2\)/);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test('should handle empty metadata template fields', () => {
|
|
265
|
+
const emptyTemplate: MetadataTemplate = {
|
|
266
|
+
...mockMetadataTemplate,
|
|
267
|
+
fields: [],
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
renderComponent({ metadataTemplate: emptyTemplate });
|
|
271
|
+
|
|
272
|
+
expect(screen.getByRole('button', { name: 'All Filters' })).toBeInTheDocument();
|
|
273
|
+
expect(screen.getByText('File 1.txt')).toBeInTheDocument();
|
|
274
|
+
expect(screen.getByText('File 2.pdf')).toBeInTheDocument();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test('should handle undefined metadata template', () => {
|
|
278
|
+
renderComponent({ metadataTemplate: undefined as unknown as MetadataTemplate });
|
|
279
|
+
|
|
280
|
+
expect(screen.getByRole('button', { name: 'All Filters' })).toBeInTheDocument();
|
|
281
|
+
expect(screen.getByText('File 1.txt')).toBeInTheDocument();
|
|
282
|
+
expect(screen.getByText('File 2.pdf')).toBeInTheDocument();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test('should handle empty collection items', () => {
|
|
286
|
+
const emptyCollection: Collection = {
|
|
287
|
+
id: '0',
|
|
288
|
+
items: [],
|
|
289
|
+
percentLoaded: 100,
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
renderComponent({ currentCollection: emptyCollection });
|
|
293
|
+
|
|
294
|
+
expect(screen.getByRole('button', { name: 'All Filters' })).toBeInTheDocument();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('should handle undefined collection items', () => {
|
|
298
|
+
const collectionWithoutItems: Collection = {
|
|
299
|
+
id: '0',
|
|
300
|
+
percentLoaded: 100,
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
renderComponent({ currentCollection: collectionWithoutItems });
|
|
304
|
+
|
|
305
|
+
expect(screen.getByRole('button', { name: 'All Filters' })).toBeInTheDocument();
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('should memoize filterGroups when metadataTemplate changes', () => {
|
|
309
|
+
const { rerender } = renderComponent();
|
|
310
|
+
|
|
311
|
+
// Re-render with same template
|
|
312
|
+
rerender(<MetadataViewContainer {...defaultProps} />);
|
|
313
|
+
|
|
314
|
+
// Re-render with different template
|
|
315
|
+
const newTemplate: MetadataTemplate = {
|
|
316
|
+
...mockMetadataTemplate,
|
|
317
|
+
id: 'template2',
|
|
318
|
+
displayName: 'New Template',
|
|
319
|
+
};
|
|
320
|
+
rerender(<MetadataViewContainer {...defaultProps} metadataTemplate={newTemplate} />);
|
|
321
|
+
|
|
322
|
+
expect(screen.getByRole('button', { name: 'All Filters' })).toBeInTheDocument();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test('should handle fields with no options', () => {
|
|
326
|
+
const templateWithoutOptions: MetadataTemplate = {
|
|
327
|
+
...mockMetadataTemplate,
|
|
328
|
+
fields: [
|
|
329
|
+
{
|
|
330
|
+
id: 'field1',
|
|
331
|
+
key: 'name',
|
|
332
|
+
displayName: 'File Name',
|
|
333
|
+
type: 'string',
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
id: 'field2',
|
|
337
|
+
key: 'industry',
|
|
338
|
+
displayName: 'Industry',
|
|
339
|
+
type: 'enum',
|
|
340
|
+
// No options defined
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
renderComponent({ metadataTemplate: templateWithoutOptions });
|
|
346
|
+
|
|
347
|
+
expect(screen.getByRole('button', { name: 'All Filters' })).toBeInTheDocument();
|
|
348
|
+
expect(screen.getAllByRole('button', { name: 'Name' })).toHaveLength(1); // Only the one added by component
|
|
349
|
+
expect(screen.getByRole('button', { name: 'File Name' })).toBeInTheDocument();
|
|
350
|
+
expect(screen.getByRole('button', { name: 'Industry' })).toBeInTheDocument();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test('should handle multiple field types in filter submission', async () => {
|
|
354
|
+
const onFilterSubmit = jest.fn();
|
|
355
|
+
const onMetadataFilter = jest.fn();
|
|
356
|
+
const template: MetadataTemplate = {
|
|
357
|
+
...mockMetadataTemplate,
|
|
358
|
+
fields: [
|
|
359
|
+
{
|
|
360
|
+
id: 'field1',
|
|
361
|
+
key: 'status',
|
|
362
|
+
displayName: 'Status',
|
|
363
|
+
type: 'enum',
|
|
364
|
+
options: [
|
|
365
|
+
{ id: 's1', key: 'Active' },
|
|
366
|
+
{ id: 's2', key: 'Inactive' },
|
|
367
|
+
],
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
id: 'field2',
|
|
371
|
+
key: 'price',
|
|
372
|
+
displayName: 'Price',
|
|
373
|
+
type: 'float',
|
|
374
|
+
},
|
|
375
|
+
],
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
renderComponent({
|
|
379
|
+
metadataTemplate: template,
|
|
380
|
+
actionBarProps: { onFilterSubmit },
|
|
381
|
+
onMetadataFilter,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Test enum filter
|
|
385
|
+
await userEvent().click(screen.getByRole('button', { name: /Status/ }));
|
|
386
|
+
await userEvent().click(within(screen.getByRole('menu')).getByRole('menuitemcheckbox', { name: 'Active' }));
|
|
387
|
+
|
|
388
|
+
await waitFor(() => {
|
|
389
|
+
expect(onMetadataFilter).toHaveBeenCalledTimes(1);
|
|
390
|
+
expect(onFilterSubmit).toHaveBeenCalledTimes(1);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
const filterCall = onMetadataFilter.mock.calls[0][0];
|
|
394
|
+
expect(filterCall.status.value).toEqual(['Active']);
|
|
395
|
+
expect(filterCall.status.fieldType).toBe('enum');
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
describe('convertFilterValuesToExternal', () => {
|
|
399
|
+
test('should convert enum values to string arrays', () => {
|
|
400
|
+
const internalFilters = {
|
|
401
|
+
'status-filter': {
|
|
402
|
+
fieldType: 'enum' as const,
|
|
403
|
+
options: [
|
|
404
|
+
{ key: 'active', id: 'active1' },
|
|
405
|
+
{ key: 'inactive', id: 'inactive1' },
|
|
406
|
+
],
|
|
407
|
+
value: { enum: ['active', 'inactive'] },
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
const result = convertFilterValuesToExternal(internalFilters);
|
|
412
|
+
|
|
413
|
+
expect(result['status-filter'].value).toEqual(['active', 'inactive']);
|
|
414
|
+
expect(result['status-filter'].fieldType).toBe('enum');
|
|
415
|
+
expect(result['status-filter'].options).toEqual([
|
|
416
|
+
{ key: 'active', id: 'active1' },
|
|
417
|
+
{ key: 'inactive', id: 'inactive1' },
|
|
418
|
+
]);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
test('should keep range values unchanged', () => {
|
|
422
|
+
const internalFilters = {
|
|
423
|
+
'price-filter': {
|
|
424
|
+
fieldType: 'float' as const,
|
|
425
|
+
value: { range: { gt: 10, lt: 100 }, advancedFilterOption: 'range' },
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const result = convertFilterValuesToExternal(internalFilters);
|
|
430
|
+
|
|
431
|
+
expect(result['price-filter'].value).toEqual({ range: { gt: 10, lt: 100 }, advancedFilterOption: 'range' });
|
|
432
|
+
expect(result['price-filter'].fieldType).toBe('float');
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test('should keep float values unchanged', () => {
|
|
436
|
+
const internalFilters = {
|
|
437
|
+
'rating-filter': {
|
|
438
|
+
fieldType: 'float' as const,
|
|
439
|
+
value: { range: { gt: 4.5, lt: 5.0 }, advancedFilterOption: 'range' },
|
|
440
|
+
},
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
const result = convertFilterValuesToExternal(internalFilters);
|
|
444
|
+
|
|
445
|
+
expect(result['rating-filter'].value).toEqual({
|
|
446
|
+
range: { gt: 4.5, lt: 5.0 },
|
|
447
|
+
advancedFilterOption: 'range',
|
|
448
|
+
});
|
|
449
|
+
expect(result['rating-filter'].fieldType).toBe('float');
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test('should handle mixed field types', () => {
|
|
453
|
+
const internalFilters = {
|
|
454
|
+
'status-filter': {
|
|
455
|
+
fieldType: 'enum' as const,
|
|
456
|
+
options: [
|
|
457
|
+
{ key: 'active', id: 'active1' },
|
|
458
|
+
{ key: 'inactive', id: 'inactive1' },
|
|
459
|
+
],
|
|
460
|
+
value: { enum: ['active'] },
|
|
461
|
+
},
|
|
462
|
+
'price-filter': {
|
|
463
|
+
fieldType: 'float' as const,
|
|
464
|
+
value: { range: { gt: 0, lt: 50 }, advancedFilterOption: 'range' },
|
|
465
|
+
},
|
|
466
|
+
'category-filter': {
|
|
467
|
+
fieldType: 'multiSelect' as const,
|
|
468
|
+
options: [
|
|
469
|
+
{ key: 'tech', id: 'tech1' },
|
|
470
|
+
{ key: 'finance', id: 'finance1' },
|
|
471
|
+
{ key: 'healthcare', id: 'healthcare1' },
|
|
472
|
+
],
|
|
473
|
+
value: { enum: ['tech', 'finance'] },
|
|
474
|
+
},
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const result = convertFilterValuesToExternal(internalFilters);
|
|
478
|
+
|
|
479
|
+
expect(result['status-filter'].value).toEqual(['active']);
|
|
480
|
+
expect(result['price-filter'].value).toEqual({ range: { gt: 0, lt: 50 }, advancedFilterOption: 'range' });
|
|
481
|
+
expect(result['category-filter'].value).toEqual(['tech', 'finance']);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
test('should handle empty filter object', () => {
|
|
485
|
+
const result = convertFilterValuesToExternal({});
|
|
486
|
+
expect(result).toEqual({});
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
test('should handle enum values with empty array', () => {
|
|
490
|
+
const internalFilters = {
|
|
491
|
+
'status-filter': {
|
|
492
|
+
fieldType: 'enum' as const,
|
|
493
|
+
options: [{ key: 'active', id: 'active1' }],
|
|
494
|
+
value: { enum: [] },
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
const result = convertFilterValuesToExternal(internalFilters);
|
|
499
|
+
|
|
500
|
+
expect(result['status-filter'].value).toEqual([]);
|
|
501
|
+
expect(result['status-filter'].fieldType).toBe('enum');
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
test('should handle multiSelect values', () => {
|
|
505
|
+
const internalFilters = {
|
|
506
|
+
'category-filter': {
|
|
507
|
+
fieldType: 'multiSelect' as const,
|
|
508
|
+
options: [
|
|
509
|
+
{ key: 'tech', id: 'tech1' },
|
|
510
|
+
{ key: 'finance', id: 'finance1' },
|
|
511
|
+
],
|
|
512
|
+
value: { enum: ['tech', 'finance'] },
|
|
513
|
+
},
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
const result = convertFilterValuesToExternal(internalFilters);
|
|
517
|
+
|
|
518
|
+
expect(result['category-filter'].value).toEqual(['tech', 'finance']);
|
|
519
|
+
expect(result['category-filter'].fieldType).toBe('multiSelect');
|
|
520
|
+
});
|
|
117
521
|
});
|
|
118
522
|
});
|
|
@@ -23,37 +23,19 @@ const metadataQuery = {
|
|
|
23
23
|
|
|
24
24
|
ancestor_folder_id: '0',
|
|
25
25
|
fields: [
|
|
26
|
-
`name`,
|
|
27
26
|
`${metadataSourceFieldName}.industry`,
|
|
28
27
|
`${metadataSourceFieldName}.last_contacted_at`,
|
|
29
28
|
`${metadataSourceFieldName}.role`,
|
|
29
|
+
`${metadataSourceFieldName}.number`,
|
|
30
30
|
],
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
const fieldsToShow = [
|
|
34
|
-
{ key: `name` },
|
|
35
|
-
{ key: `${metadataSourceFieldName}.industry`, canEdit: true },
|
|
36
|
-
{ key: `${metadataSourceFieldName}.last_contacted_at`, canEdit: true },
|
|
37
|
-
{ key: `${metadataSourceFieldName}.role`, canEdit: true },
|
|
38
|
-
];
|
|
39
|
-
|
|
40
33
|
const columns = mockSchema.fields.map(field => {
|
|
41
|
-
if (field.key === 'name') {
|
|
42
|
-
return {
|
|
43
|
-
textValue: field.displayName,
|
|
44
|
-
id: 'name',
|
|
45
|
-
type: 'string',
|
|
46
|
-
allowsSorting: true,
|
|
47
|
-
minWidth: 250,
|
|
48
|
-
maxWidth: 250,
|
|
49
|
-
isRowHeader: true,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
34
|
if (field.type === 'date') {
|
|
54
35
|
return {
|
|
55
36
|
textValue: field.displayName,
|
|
56
37
|
id: `${metadataSourceFieldName}.${field.key}`,
|
|
38
|
+
key: `${metadataSourceFieldName}.${field.key}`,
|
|
57
39
|
type: field.type,
|
|
58
40
|
allowsSorting: true,
|
|
59
41
|
minWidth: 200,
|
|
@@ -68,6 +50,7 @@ const columns = mockSchema.fields.map(field => {
|
|
|
68
50
|
return {
|
|
69
51
|
textValue: field.displayName,
|
|
70
52
|
id: `${metadataSourceFieldName}.${field.key}`,
|
|
53
|
+
key: `${metadataSourceFieldName}.${field.key}`,
|
|
71
54
|
type: field.type,
|
|
72
55
|
allowsSorting: true,
|
|
73
56
|
minWidth: 200,
|
|
@@ -87,7 +70,6 @@ export const metadataView: Story = {
|
|
|
87
70
|
},
|
|
88
71
|
},
|
|
89
72
|
metadataQuery,
|
|
90
|
-
fieldsToShow,
|
|
91
73
|
defaultView,
|
|
92
74
|
features: {
|
|
93
75
|
contentExplorer: {
|
|
@@ -3,7 +3,9 @@ import { http, HttpResponse } from 'msw';
|
|
|
3
3
|
import { Download, SignMeOthers } from '@box/blueprint-web-assets/icons/Fill/index';
|
|
4
4
|
import { Sign } from '@box/blueprint-web-assets/icons/Line';
|
|
5
5
|
import { expect, fn, userEvent, waitFor, within, screen } from 'storybook/test';
|
|
6
|
+
|
|
6
7
|
import noop from 'lodash/noop';
|
|
8
|
+
import orderBy from 'lodash/orderBy';
|
|
7
9
|
|
|
8
10
|
import ContentExplorer from '../../ContentExplorer';
|
|
9
11
|
import { DEFAULT_HOSTNAME_API } from '../../../../constants';
|
|
@@ -30,13 +32,11 @@ const metadataQuery = {
|
|
|
30
32
|
fields: [
|
|
31
33
|
// Default to returning all fields in the metadata template schema, and name as a standalone (non-metadata) field
|
|
32
34
|
...mockSchema.fields.map(field => `${metadataFieldNamePrefix}.${field.key}`),
|
|
33
|
-
'name',
|
|
34
35
|
],
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
// Used for metadata view v1
|
|
38
39
|
const fieldsToShow = [
|
|
39
|
-
{ key: `${metadataFieldNamePrefix}.name`, canEdit: false, displayName: 'Alias' },
|
|
40
40
|
{ key: `${metadataFieldNamePrefix}.industry`, canEdit: true },
|
|
41
41
|
{ key: `${metadataFieldNamePrefix}.last_contacted_at`, canEdit: true },
|
|
42
42
|
{ key: `${metadataFieldNamePrefix}.role`, canEdit: true },
|
|
@@ -44,15 +44,6 @@ const fieldsToShow = [
|
|
|
44
44
|
|
|
45
45
|
// Used for metadata view v2
|
|
46
46
|
const columns = [
|
|
47
|
-
{
|
|
48
|
-
// Always include the name column
|
|
49
|
-
textValue: 'Name',
|
|
50
|
-
id: 'name',
|
|
51
|
-
type: 'string',
|
|
52
|
-
allowsSorting: true,
|
|
53
|
-
minWidth: 150,
|
|
54
|
-
maxWidth: 150,
|
|
55
|
-
},
|
|
56
47
|
...mockSchema.fields.map(field => ({
|
|
57
48
|
textValue: field.displayName,
|
|
58
49
|
id: `${metadataFieldNamePrefix}.${field.key}`,
|
|
@@ -138,17 +129,16 @@ export const metadataViewV2: Story = {
|
|
|
138
129
|
args: metadataViewV2ElementProps,
|
|
139
130
|
};
|
|
140
131
|
|
|
141
|
-
// @TODO Assert that rows are actually sorted in a different order, once handleSortChange is implemented
|
|
142
132
|
export const metadataViewV2SortsFromHeader: Story = {
|
|
143
133
|
args: metadataViewV2ElementProps,
|
|
144
134
|
play: async ({ canvas }) => {
|
|
145
|
-
await
|
|
146
|
-
|
|
147
|
-
|
|
135
|
+
const industryHeader = await canvas.findByRole('columnheader', { name: 'Industry' });
|
|
136
|
+
expect(industryHeader).toBeInTheDocument();
|
|
137
|
+
|
|
138
|
+
const firstRow = await canvas.findByRole('row', { name: /Child 2/i });
|
|
139
|
+
expect(firstRow).toBeInTheDocument();
|
|
148
140
|
|
|
149
|
-
|
|
150
|
-
const industryHeader = within(firstRow).getByRole('columnheader', { name: 'Industry' });
|
|
151
|
-
userEvent.click(industryHeader);
|
|
141
|
+
await userEvent.click(industryHeader);
|
|
152
142
|
},
|
|
153
143
|
};
|
|
154
144
|
|
|
@@ -166,9 +156,9 @@ export const metadataViewV2WithCustomActions: Story = {
|
|
|
166
156
|
|
|
167
157
|
const initialFilterActionBarProps = {
|
|
168
158
|
initialFilterValues: {
|
|
169
|
-
|
|
159
|
+
industry: { value: ['Legal'] },
|
|
170
160
|
'mimetype-filter': { value: ['boxnoteType', 'documentType', 'threedType'] },
|
|
171
|
-
|
|
161
|
+
role: { value: ['Developer', 'Business Owner', 'Marketing'] },
|
|
172
162
|
},
|
|
173
163
|
};
|
|
174
164
|
|
|
@@ -237,6 +227,37 @@ export const metadataViewV2WithBulkItemActionMenuShowsItemActionMenu: Story = {
|
|
|
237
227
|
},
|
|
238
228
|
};
|
|
239
229
|
|
|
230
|
+
export const sidePanelOpenWithMultipleItemsSelected: Story = {
|
|
231
|
+
args: {
|
|
232
|
+
...metadataViewV2ElementProps,
|
|
233
|
+
metadataViewProps: {
|
|
234
|
+
columns,
|
|
235
|
+
tableProps: {
|
|
236
|
+
isSelectAllEnabled: true,
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
play: async ({ canvas }) => {
|
|
242
|
+
await waitFor(() => {
|
|
243
|
+
expect(canvas.getByRole('row', { name: /Child 2/i })).toBeInTheDocument();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Select the first row by clicking its checkbox
|
|
247
|
+
const firstItem = canvas.getByRole('row', { name: /Child 2/i });
|
|
248
|
+
const checkbox = within(firstItem).getByRole('checkbox');
|
|
249
|
+
await userEvent.click(checkbox);
|
|
250
|
+
|
|
251
|
+
// Select the second row by clicking its checkbox
|
|
252
|
+
const secondItem = canvas.getAllByRole('row', { name: /Child 1/i })[0];
|
|
253
|
+
const secondCheckbox = within(secondItem).getByRole('checkbox');
|
|
254
|
+
await userEvent.click(secondCheckbox);
|
|
255
|
+
|
|
256
|
+
const metadataButton = canvas.getByRole('button', { name: 'Metadata' });
|
|
257
|
+
await userEvent.click(metadataButton);
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
|
|
240
261
|
const meta: Meta<typeof ContentExplorer> = {
|
|
241
262
|
title: 'Elements/ContentExplorer/tests/MetadataView/visual',
|
|
242
263
|
component: ContentExplorer,
|
|
@@ -248,7 +269,21 @@ const meta: Meta<typeof ContentExplorer> = {
|
|
|
248
269
|
parameters: {
|
|
249
270
|
msw: {
|
|
250
271
|
handlers: [
|
|
251
|
-
|
|
272
|
+
// Note that the Metadata API backend normally handles the sorting. The mocks below simulate the sorting for specific cases, but may not 100% accurately reflect the backend behavior.
|
|
273
|
+
http.post(`${DEFAULT_HOSTNAME_API}/2.0/metadata_queries/execute_read`, async ({ request }) => {
|
|
274
|
+
const body = await request.clone().json();
|
|
275
|
+
const orderByDirection = body.order_by[0].direction;
|
|
276
|
+
const orderByFieldKey = body.order_by[0].field_key;
|
|
277
|
+
|
|
278
|
+
// Hardcoded case for sorting by industry
|
|
279
|
+
if (orderByFieldKey === `industry` && orderByDirection === 'ASC') {
|
|
280
|
+
const sortedMetadata = orderBy(
|
|
281
|
+
mockMetadata.entries,
|
|
282
|
+
'metadata.enterprise_0.templateName.industry',
|
|
283
|
+
'asc',
|
|
284
|
+
);
|
|
285
|
+
return HttpResponse.json({ ...mockMetadata, entries: sortedMetadata });
|
|
286
|
+
}
|
|
252
287
|
return HttpResponse.json(mockMetadata);
|
|
253
288
|
}),
|
|
254
289
|
http.get(`${DEFAULT_HOSTNAME_API}/2.0/metadata_templates/enterprise/templateName/schema`, () => {
|