@scality/data-browser-library 1.0.3 → 1.0.5
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/components/DataBrowserUI.js +18 -8
- package/dist/components/__tests__/BucketList.test.js +74 -1
- package/dist/components/__tests__/ObjectList.test.js +94 -2
- package/dist/components/buckets/BucketCreate.d.ts +1 -0
- package/dist/components/buckets/BucketCreate.js +57 -7
- package/dist/components/buckets/BucketDetails.js +0 -1
- package/dist/components/buckets/BucketLifecycleFormPage.js +209 -213
- package/dist/components/buckets/BucketList.js +25 -4
- package/dist/components/buckets/BucketReplicationFormPage.js +9 -3
- package/dist/components/buckets/DeleteBucketConfigRuleButton.js +1 -1
- package/dist/components/buckets/notifications/BucketNotificationList.js +1 -1
- package/dist/components/objects/DeleteObjectButton.d.ts +1 -0
- package/dist/components/objects/DeleteObjectButton.js +11 -5
- package/dist/components/objects/ObjectDetails/FormComponents.d.ts +9 -0
- package/dist/components/objects/ObjectDetails/FormComponents.js +37 -0
- package/dist/components/objects/ObjectDetails/ObjectMetadata.js +182 -204
- package/dist/components/objects/ObjectDetails/ObjectSummary.js +22 -5
- package/dist/components/objects/ObjectDetails/ObjectTags.js +109 -154
- package/dist/components/objects/ObjectDetails/__tests__/ObjectMetadata.test.d.ts +1 -0
- package/dist/components/objects/ObjectDetails/__tests__/ObjectMetadata.test.js +230 -0
- package/dist/components/objects/ObjectDetails/__tests__/ObjectTags.test.d.ts +1 -0
- package/dist/components/objects/ObjectDetails/__tests__/ObjectTags.test.js +342 -0
- package/dist/components/objects/ObjectDetails/__tests__/formUtils.test.d.ts +1 -0
- package/dist/components/objects/ObjectDetails/__tests__/formUtils.test.js +202 -0
- package/dist/components/objects/ObjectDetails/index.d.ts +2 -1
- package/dist/components/objects/ObjectDetails/index.js +12 -16
- package/dist/components/objects/ObjectList.d.ts +3 -2
- package/dist/components/objects/ObjectList.js +204 -104
- package/dist/components/objects/ObjectPage.js +22 -5
- package/dist/components/ui/ArrayFieldActions.js +0 -2
- package/dist/components/ui/FilterFormSection.js +17 -36
- package/dist/components/ui/FormGrid.d.ts +7 -0
- package/dist/components/ui/FormGrid.js +37 -0
- package/dist/components/ui/Table.elements.js +1 -0
- package/dist/config/types.d.ts +45 -2
- package/dist/hooks/__tests__/usePresigningS3Client.test.d.ts +1 -0
- package/dist/hooks/__tests__/usePresigningS3Client.test.js +104 -0
- package/dist/hooks/factories/index.d.ts +1 -1
- package/dist/hooks/factories/index.js +2 -2
- package/dist/hooks/factories/useCreateS3MutationHook.d.ts +2 -1
- package/dist/hooks/factories/useCreateS3MutationHook.js +10 -3
- package/dist/hooks/factories/useCreateS3QueryHook.d.ts +1 -0
- package/dist/hooks/factories/useCreateS3QueryHook.js +9 -6
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +2 -1
- package/dist/hooks/presignedOperations.js +4 -4
- package/dist/hooks/useBucketLocations.d.ts +6 -0
- package/dist/hooks/useBucketLocations.js +45 -0
- package/dist/hooks/usePresigningS3Client.d.ts +13 -0
- package/dist/hooks/usePresigningS3Client.js +21 -0
- package/package.json +4 -4
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { Box, Button, Input } from "@scality/core-ui/dist/next";
|
|
2
|
+
import { Text, spacing, useToast } from "@scality/core-ui";
|
|
3
|
+
import { Box, Input } from "@scality/core-ui/dist/next";
|
|
5
4
|
import { useEffect, useState } from "react";
|
|
6
5
|
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
|
7
6
|
import { useDeleteObjectTagging, useObjectTagging, useSetObjectTagging } from "../../../hooks/index.js";
|
|
8
7
|
import { useInvalidateQueries } from "../../providers/DataBrowserProvider.js";
|
|
9
8
|
import { ArrayFieldActions } from "../../ui/ArrayFieldActions.js";
|
|
9
|
+
import { Table, TableContainer } from "../../ui/Table.elements.js";
|
|
10
|
+
import { FormCell, FormColumnHeaders, FormError, FormHeader, FormLoading, FormRow } from "./FormComponents.js";
|
|
10
11
|
import { hasFormDataChanged } from "./formUtils.js";
|
|
11
12
|
const MAX_TAGS_PER_OBJECT = 10;
|
|
12
13
|
const MAX_TAG_KEY_LENGTH = 128;
|
|
@@ -100,6 +101,7 @@ const ObjectTags = ({ bucketName, objectKey, versionId })=>{
|
|
|
100
101
|
}
|
|
101
102
|
]
|
|
102
103
|
});
|
|
104
|
+
setIsInitialized(false);
|
|
103
105
|
showToast({
|
|
104
106
|
open: true,
|
|
105
107
|
message: 'Tags saved successfully',
|
|
@@ -114,164 +116,117 @@ const ObjectTags = ({ bucketName, objectKey, versionId })=>{
|
|
|
114
116
|
});
|
|
115
117
|
}
|
|
116
118
|
};
|
|
117
|
-
if ('pending' === tagsStatus) return /*#__PURE__*/ jsx(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
padding: spacing.r32,
|
|
121
|
-
children: /*#__PURE__*/ jsx(Loader, {})
|
|
119
|
+
if ('pending' === tagsStatus) return /*#__PURE__*/ jsx(FormLoading, {});
|
|
120
|
+
if ('error' === tagsStatus) return /*#__PURE__*/ jsx(FormError, {
|
|
121
|
+
message: "Error loading tags"
|
|
122
122
|
});
|
|
123
|
-
|
|
124
|
-
padding: spacing.r16,
|
|
125
|
-
children: /*#__PURE__*/ jsx(Text, {
|
|
126
|
-
color: "statusCritical",
|
|
127
|
-
children: "Error loading tags"
|
|
128
|
-
})
|
|
129
|
-
});
|
|
130
|
-
return /*#__PURE__*/ jsx(Box, {
|
|
123
|
+
return /*#__PURE__*/ jsx(TableContainer, {
|
|
131
124
|
children: /*#__PURE__*/ jsxs("form", {
|
|
132
125
|
onSubmit: handleSubmit(onSubmit),
|
|
126
|
+
style: {
|
|
127
|
+
display: 'flex',
|
|
128
|
+
flexDirection: 'column',
|
|
129
|
+
minHeight: 0,
|
|
130
|
+
flex: 1
|
|
131
|
+
},
|
|
133
132
|
children: [
|
|
134
|
-
/*#__PURE__*/
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
alignItems: "center",
|
|
138
|
-
padding: spacing.r16,
|
|
139
|
-
borderBottom: "1px solid",
|
|
140
|
-
borderColor: "backgroundLevel3",
|
|
141
|
-
children: [
|
|
142
|
-
/*#__PURE__*/ jsx(Text, {
|
|
143
|
-
isEmphazed: true,
|
|
144
|
-
children: "Edit Tags"
|
|
145
|
-
}),
|
|
146
|
-
/*#__PURE__*/ jsx(Button, {
|
|
147
|
-
variant: "primary",
|
|
148
|
-
label: "Save",
|
|
149
|
-
disabled: !isValid || isSubmitting || !hasChanges(),
|
|
150
|
-
type: "submit",
|
|
151
|
-
icon: /*#__PURE__*/ jsx(Icon, {
|
|
152
|
-
name: "Save"
|
|
153
|
-
})
|
|
154
|
-
})
|
|
155
|
-
]
|
|
133
|
+
/*#__PURE__*/ jsx(FormHeader, {
|
|
134
|
+
disabled: !isValid || !hasChanges(),
|
|
135
|
+
isSubmitting: isSubmitting
|
|
156
136
|
}),
|
|
157
|
-
/*#__PURE__*/
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
/*#__PURE__*/ jsxs(Box, {
|
|
191
|
-
display: "flex",
|
|
192
|
-
gap: spacing.r8,
|
|
193
|
-
alignItems: "center",
|
|
194
|
-
children: [
|
|
195
|
-
/*#__PURE__*/ jsx(Controller, {
|
|
196
|
-
control: control,
|
|
197
|
-
name: `tags.${index}.key`,
|
|
198
|
-
rules: {
|
|
199
|
-
validate: (value, formValues)=>{
|
|
200
|
-
if (1 === formValues.tags.length && !value.trim() && !formValues.tags[0].value.trim()) return true;
|
|
201
|
-
if (!value.trim()) return 'Key cannot be empty or whitespace only';
|
|
202
|
-
if (value.length > MAX_TAG_KEY_LENGTH) return `Key must be ${MAX_TAG_KEY_LENGTH} characters or less`;
|
|
203
|
-
const keys = formValues.tags.map((t)=>t.key.trim());
|
|
204
|
-
const duplicates = keys.filter((k)=>k === value.trim());
|
|
205
|
-
return duplicates.length > 1 ? `Duplicate key "${value.trim()}"` : true;
|
|
206
|
-
}
|
|
207
|
-
},
|
|
208
|
-
render: ({ field: { onChange, value } })=>/*#__PURE__*/ jsx(Input, {
|
|
209
|
-
id: `key-${field.id}`,
|
|
210
|
-
value: value,
|
|
211
|
-
onChange: onChange,
|
|
212
|
-
placeholder: "Key",
|
|
213
|
-
"aria-label": `Tag ${index + 1} key`
|
|
137
|
+
/*#__PURE__*/ jsx(Table, {
|
|
138
|
+
children: /*#__PURE__*/ jsxs(Box, {
|
|
139
|
+
paddingTop: spacing.r16,
|
|
140
|
+
display: "flex",
|
|
141
|
+
flexDirection: "column",
|
|
142
|
+
gap: spacing.r12,
|
|
143
|
+
children: [
|
|
144
|
+
/*#__PURE__*/ jsx(FormColumnHeaders, {}),
|
|
145
|
+
fields.map((field, index)=>/*#__PURE__*/ jsxs(Box, {
|
|
146
|
+
children: [
|
|
147
|
+
/*#__PURE__*/ jsxs(FormRow, {
|
|
148
|
+
children: [
|
|
149
|
+
/*#__PURE__*/ jsx(FormCell, {
|
|
150
|
+
children: /*#__PURE__*/ jsx(Controller, {
|
|
151
|
+
control: control,
|
|
152
|
+
name: `tags.${index}.key`,
|
|
153
|
+
rules: {
|
|
154
|
+
validate: (value, formValues)=>{
|
|
155
|
+
if (!value.trim() && !formValues.tags[index].value.trim()) return true;
|
|
156
|
+
if (!value.trim()) return 'Key cannot be empty or whitespace only';
|
|
157
|
+
if (value.length > MAX_TAG_KEY_LENGTH) return `Key must be ${MAX_TAG_KEY_LENGTH} characters or less`;
|
|
158
|
+
const keys = formValues.tags.map((t)=>t.key.trim()).filter(Boolean);
|
|
159
|
+
const duplicates = keys.filter((k)=>k === value.trim());
|
|
160
|
+
return duplicates.length > 1 ? `Duplicate key "${value.trim()}"` : true;
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
render: ({ field: { onChange, value } })=>/*#__PURE__*/ jsx(Input, {
|
|
164
|
+
id: `key-${field.id}`,
|
|
165
|
+
value: value,
|
|
166
|
+
onChange: onChange,
|
|
167
|
+
placeholder: "Key",
|
|
168
|
+
"aria-label": `Tag ${index + 1} key`
|
|
169
|
+
})
|
|
214
170
|
})
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
placeholder: "Value",
|
|
236
|
-
"aria-label": `Tag ${index + 1} value`
|
|
171
|
+
}),
|
|
172
|
+
/*#__PURE__*/ jsx(FormCell, {
|
|
173
|
+
children: /*#__PURE__*/ jsx(Controller, {
|
|
174
|
+
control: control,
|
|
175
|
+
name: `tags.${index}.value`,
|
|
176
|
+
rules: {
|
|
177
|
+
validate: (value, formValues)=>{
|
|
178
|
+
if (!value.trim() && !formValues.tags[index].key.trim()) return true;
|
|
179
|
+
if (!value.trim()) return 'Value cannot be empty or whitespace only';
|
|
180
|
+
if (value.length > MAX_TAG_VALUE_LENGTH) return `Value must be ${MAX_TAG_VALUE_LENGTH} characters or less`;
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
render: ({ field: { onChange, value } })=>/*#__PURE__*/ jsx(Input, {
|
|
185
|
+
id: `value-${field.id}`,
|
|
186
|
+
value: value,
|
|
187
|
+
onChange: onChange,
|
|
188
|
+
placeholder: "Value",
|
|
189
|
+
"aria-label": `Tag ${index + 1} value`
|
|
190
|
+
})
|
|
237
191
|
})
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
192
|
+
}),
|
|
193
|
+
/*#__PURE__*/ jsx(ArrayFieldActions, {
|
|
194
|
+
showAdd: index === fields.length - 1,
|
|
195
|
+
onRemove: ()=>{
|
|
196
|
+
remove(index);
|
|
197
|
+
if (1 === fields.length) append({
|
|
198
|
+
key: '',
|
|
199
|
+
value: ''
|
|
200
|
+
});
|
|
201
|
+
},
|
|
202
|
+
onAdd: ()=>append({
|
|
203
|
+
key: '',
|
|
204
|
+
value: ''
|
|
205
|
+
}),
|
|
206
|
+
canRemove: !!watch(`tags.${index}.key`) || !!watch(`tags.${index}.value`),
|
|
207
|
+
canAdd: fields.length < MAX_TAGS_PER_OBJECT && !!watch(`tags.${index}.key`) && !!watch(`tags.${index}.value`),
|
|
208
|
+
removeLabel: "Remove tag",
|
|
209
|
+
addLabel: "Add tag"
|
|
210
|
+
})
|
|
211
|
+
]
|
|
212
|
+
}),
|
|
213
|
+
(errors.tags?.[index]?.key || errors.tags?.[index]?.value) && /*#__PURE__*/ jsxs(Box, {
|
|
214
|
+
paddingTop: spacing.r4,
|
|
215
|
+
children: [
|
|
216
|
+
errors.tags?.[index]?.key && /*#__PURE__*/ jsx(Text, {
|
|
217
|
+
color: "statusCritical",
|
|
218
|
+
children: errors.tags[index]?.key?.message
|
|
219
|
+
}),
|
|
220
|
+
errors.tags?.[index]?.value && /*#__PURE__*/ jsx(Text, {
|
|
221
|
+
color: "statusCritical",
|
|
222
|
+
children: errors.tags[index]?.value?.message
|
|
223
|
+
})
|
|
224
|
+
]
|
|
225
|
+
})
|
|
226
|
+
]
|
|
227
|
+
}, field.id))
|
|
228
|
+
]
|
|
229
|
+
})
|
|
275
230
|
})
|
|
276
231
|
]
|
|
277
232
|
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useToast } from "@scality/core-ui";
|
|
3
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
4
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
5
|
+
import user_event from "@testing-library/user-event";
|
|
6
|
+
import { DataBrowserUICustomizationProvider } from "../../../../contexts/DataBrowserUICustomizationContext.js";
|
|
7
|
+
import { useCopyObject, useObjectMetadata } from "../../../../hooks/index.js";
|
|
8
|
+
import { ObjectMetadata } from "../ObjectMetadata.js";
|
|
9
|
+
jest.mock('../../../../hooks');
|
|
10
|
+
jest.mock('@scality/core-ui', ()=>{
|
|
11
|
+
const actual = jest.requireActual('@scality/core-ui');
|
|
12
|
+
return {
|
|
13
|
+
...actual,
|
|
14
|
+
useToast: jest.fn()
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
jest.mock('../../../providers/DataBrowserProvider', ()=>({
|
|
18
|
+
...jest.requireActual('../../../providers/DataBrowserProvider'),
|
|
19
|
+
useInvalidateQueries: jest.fn(()=>jest.fn().mockResolvedValue(void 0))
|
|
20
|
+
}));
|
|
21
|
+
const mockUseObjectMetadata = jest.mocked(useObjectMetadata);
|
|
22
|
+
const mockUseCopyObject = jest.mocked(useCopyObject);
|
|
23
|
+
const mockUseToast = jest.mocked(useToast);
|
|
24
|
+
const mockShowToast = jest.fn();
|
|
25
|
+
const setupMockDefaults = (metadataOverrides = {})=>{
|
|
26
|
+
mockUseObjectMetadata.mockReturnValue({
|
|
27
|
+
data: {
|
|
28
|
+
ContentType: 'text/plain',
|
|
29
|
+
...metadataOverrides
|
|
30
|
+
},
|
|
31
|
+
status: 'success'
|
|
32
|
+
});
|
|
33
|
+
mockUseCopyObject.mockReturnValue({
|
|
34
|
+
mutateAsync: jest.fn().mockResolvedValue({}),
|
|
35
|
+
reset: jest.fn()
|
|
36
|
+
});
|
|
37
|
+
mockUseToast.mockReturnValue({
|
|
38
|
+
showToast: mockShowToast
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
const renderWithProviders = (ui)=>{
|
|
42
|
+
const queryClient = new QueryClient({
|
|
43
|
+
defaultOptions: {
|
|
44
|
+
queries: {
|
|
45
|
+
retry: false
|
|
46
|
+
},
|
|
47
|
+
mutations: {
|
|
48
|
+
retry: false
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return render(/*#__PURE__*/ jsx(QueryClientProvider, {
|
|
53
|
+
client: queryClient,
|
|
54
|
+
children: /*#__PURE__*/ jsx(DataBrowserUICustomizationProvider, {
|
|
55
|
+
config: {},
|
|
56
|
+
children: ui
|
|
57
|
+
})
|
|
58
|
+
}));
|
|
59
|
+
};
|
|
60
|
+
const defaultProps = {
|
|
61
|
+
bucketName: 'test-bucket',
|
|
62
|
+
objectKey: 'test-object.txt'
|
|
63
|
+
};
|
|
64
|
+
const getSaveButton = ()=>screen.getByRole('button', {
|
|
65
|
+
name: /save/i
|
|
66
|
+
});
|
|
67
|
+
describe('ObjectMetadata', ()=>{
|
|
68
|
+
beforeEach(()=>{
|
|
69
|
+
jest.clearAllMocks();
|
|
70
|
+
});
|
|
71
|
+
describe('Loading and Error States', ()=>{
|
|
72
|
+
it('should show loader when metadata is loading', ()=>{
|
|
73
|
+
mockUseObjectMetadata.mockReturnValue({
|
|
74
|
+
data: void 0,
|
|
75
|
+
status: 'pending'
|
|
76
|
+
});
|
|
77
|
+
mockUseCopyObject.mockReturnValue({
|
|
78
|
+
reset: jest.fn()
|
|
79
|
+
});
|
|
80
|
+
mockUseToast.mockReturnValue({
|
|
81
|
+
showToast: mockShowToast
|
|
82
|
+
});
|
|
83
|
+
const { container } = renderWithProviders(/*#__PURE__*/ jsx(ObjectMetadata, {
|
|
84
|
+
...defaultProps
|
|
85
|
+
}));
|
|
86
|
+
expect(container.querySelectorAll('svg').length).toBeGreaterThan(0);
|
|
87
|
+
});
|
|
88
|
+
it('should show error message when metadata fails to load', ()=>{
|
|
89
|
+
mockUseObjectMetadata.mockReturnValue({
|
|
90
|
+
data: void 0,
|
|
91
|
+
status: 'error'
|
|
92
|
+
});
|
|
93
|
+
mockUseCopyObject.mockReturnValue({
|
|
94
|
+
reset: jest.fn()
|
|
95
|
+
});
|
|
96
|
+
mockUseToast.mockReturnValue({
|
|
97
|
+
showToast: mockShowToast
|
|
98
|
+
});
|
|
99
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectMetadata, {
|
|
100
|
+
...defaultProps
|
|
101
|
+
}));
|
|
102
|
+
expect(screen.getByText('Error loading metadata')).toBeInTheDocument();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
describe('Empty Row Validation', ()=>{
|
|
106
|
+
it('should keep Save disabled when no metadata exists and nothing is changed', async ()=>{
|
|
107
|
+
setupMockDefaults({
|
|
108
|
+
ContentType: void 0,
|
|
109
|
+
Metadata: {}
|
|
110
|
+
});
|
|
111
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectMetadata, {
|
|
112
|
+
...defaultProps
|
|
113
|
+
}));
|
|
114
|
+
await waitFor(()=>{
|
|
115
|
+
expect(screen.getByText('Key')).toBeInTheDocument();
|
|
116
|
+
});
|
|
117
|
+
expect(getSaveButton()).toBeDisabled();
|
|
118
|
+
});
|
|
119
|
+
it('should allow empty rows alongside filled rows without blocking Save', async ()=>{
|
|
120
|
+
setupMockDefaults({
|
|
121
|
+
ContentType: 'text/plain',
|
|
122
|
+
Metadata: {
|
|
123
|
+
customKey: 'customValue'
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectMetadata, {
|
|
127
|
+
...defaultProps
|
|
128
|
+
}));
|
|
129
|
+
await waitFor(()=>{
|
|
130
|
+
expect(screen.getByText('Key')).toBeInTheDocument();
|
|
131
|
+
});
|
|
132
|
+
await waitFor(()=>{
|
|
133
|
+
expect(screen.getByDisplayValue('text/plain')).toBeInTheDocument();
|
|
134
|
+
});
|
|
135
|
+
const user = user_event.setup();
|
|
136
|
+
const valueInput = screen.getByDisplayValue('text/plain');
|
|
137
|
+
await user.clear(valueInput);
|
|
138
|
+
await user.type(valueInput, 'application/json');
|
|
139
|
+
await waitFor(()=>{
|
|
140
|
+
expect(getSaveButton()).toBeEnabled();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
describe('Remove Button (canRemove)', ()=>{
|
|
145
|
+
it('should disable remove for a single empty placeholder row', async ()=>{
|
|
146
|
+
setupMockDefaults({
|
|
147
|
+
ContentType: void 0,
|
|
148
|
+
Metadata: {}
|
|
149
|
+
});
|
|
150
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectMetadata, {
|
|
151
|
+
...defaultProps
|
|
152
|
+
}));
|
|
153
|
+
await waitFor(()=>{
|
|
154
|
+
expect(screen.getByText('Key')).toBeInTheDocument();
|
|
155
|
+
});
|
|
156
|
+
expect(screen.getByRole('button', {
|
|
157
|
+
name: /remove metadata/i
|
|
158
|
+
})).toBeDisabled();
|
|
159
|
+
});
|
|
160
|
+
it('should enable remove for any row when there are multiple rows', async ()=>{
|
|
161
|
+
setupMockDefaults({
|
|
162
|
+
CacheControl: 'max-age=300',
|
|
163
|
+
ContentType: 'text/plain'
|
|
164
|
+
});
|
|
165
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectMetadata, {
|
|
166
|
+
...defaultProps
|
|
167
|
+
}));
|
|
168
|
+
await waitFor(()=>{
|
|
169
|
+
expect(screen.getByDisplayValue('max-age=300')).toBeInTheDocument();
|
|
170
|
+
});
|
|
171
|
+
const removeButtons = screen.getAllByRole('button', {
|
|
172
|
+
name: /remove metadata/i
|
|
173
|
+
});
|
|
174
|
+
expect(removeButtons[0]).toBeEnabled();
|
|
175
|
+
expect(removeButtons[1]).toBeEnabled();
|
|
176
|
+
});
|
|
177
|
+
it('should enable Save after removing one of two metadata entries', async ()=>{
|
|
178
|
+
setupMockDefaults({
|
|
179
|
+
CacheControl: 'max-age=300',
|
|
180
|
+
ContentType: 'text/plain'
|
|
181
|
+
});
|
|
182
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectMetadata, {
|
|
183
|
+
...defaultProps
|
|
184
|
+
}));
|
|
185
|
+
const user = user_event.setup();
|
|
186
|
+
await waitFor(()=>{
|
|
187
|
+
expect(screen.getByDisplayValue('max-age=300')).toBeInTheDocument();
|
|
188
|
+
});
|
|
189
|
+
const removeButtons = screen.getAllByRole('button', {
|
|
190
|
+
name: /remove metadata/i
|
|
191
|
+
});
|
|
192
|
+
await user.click(removeButtons[0]);
|
|
193
|
+
await waitFor(()=>{
|
|
194
|
+
expect(getSaveButton()).toBeEnabled();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
describe('Save Button Change Detection', ()=>{
|
|
199
|
+
it('should disable Save when no changes are made to existing metadata', async ()=>{
|
|
200
|
+
setupMockDefaults({
|
|
201
|
+
ContentType: 'text/plain'
|
|
202
|
+
});
|
|
203
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectMetadata, {
|
|
204
|
+
...defaultProps
|
|
205
|
+
}));
|
|
206
|
+
await waitFor(()=>{
|
|
207
|
+
expect(screen.getByDisplayValue('text/plain')).toBeInTheDocument();
|
|
208
|
+
});
|
|
209
|
+
expect(getSaveButton()).toBeDisabled();
|
|
210
|
+
});
|
|
211
|
+
it('should enable Save when a metadata value is modified', async ()=>{
|
|
212
|
+
setupMockDefaults({
|
|
213
|
+
ContentType: 'text/plain'
|
|
214
|
+
});
|
|
215
|
+
renderWithProviders(/*#__PURE__*/ jsx(ObjectMetadata, {
|
|
216
|
+
...defaultProps
|
|
217
|
+
}));
|
|
218
|
+
const user = user_event.setup();
|
|
219
|
+
await waitFor(()=>{
|
|
220
|
+
expect(screen.getByDisplayValue('text/plain')).toBeInTheDocument();
|
|
221
|
+
});
|
|
222
|
+
const valueInput = screen.getByDisplayValue('text/plain');
|
|
223
|
+
await user.clear(valueInput);
|
|
224
|
+
await user.type(valueInput, 'application/json');
|
|
225
|
+
await waitFor(()=>{
|
|
226
|
+
expect(getSaveButton()).toBeEnabled();
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|