@scality/data-browser-library 1.0.5 → 1.0.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/components/__tests__/BucketCreate.test.js +60 -20
- package/dist/components/__tests__/BucketList.test.js +19 -7
- package/dist/components/__tests__/BucketNotificationFormPage.test.js +54 -19
- package/dist/components/__tests__/BucketReplicationFormPage.test.js +183 -61
- package/dist/components/__tests__/MetadataSearch.test.js +18 -12
- package/dist/components/objects/ObjectLock/__tests__/ObjectLockSettings.test.js +78 -26
- package/dist/config/__tests__/factory.test.js +29 -1
- package/dist/config/factory.d.ts +2 -0
- package/dist/config/factory.js +3 -1
- package/package.json +4 -3
|
@@ -76,7 +76,9 @@ const setupMocks = (overrides = {})=>{
|
|
|
76
76
|
mockUseSetBucketEncryption.mockReturnValue(createMockMutationResult(mockMutations.setBucketEncryption));
|
|
77
77
|
};
|
|
78
78
|
const fillBucketName = async (name)=>{
|
|
79
|
-
const nameInput = screen.
|
|
79
|
+
const nameInput = screen.getByRole('textbox', {
|
|
80
|
+
name: /bucket name/i
|
|
81
|
+
});
|
|
80
82
|
await user_event.type(nameInput, name);
|
|
81
83
|
};
|
|
82
84
|
const submitForm = async ()=>{
|
|
@@ -101,13 +103,21 @@ describe('BucketCreate', ()=>{
|
|
|
101
103
|
it('renders form with required fields', ()=>{
|
|
102
104
|
renderBucketCreate();
|
|
103
105
|
expect(screen.getByText('Create a New Bucket')).toBeInTheDocument();
|
|
104
|
-
expect(screen.
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
expect(screen.getByRole('textbox', {
|
|
107
|
+
name: /bucket name/i
|
|
108
|
+
})).toBeInTheDocument();
|
|
109
|
+
expect(screen.getByRole('checkbox', {
|
|
110
|
+
name: /versioning/i
|
|
111
|
+
})).toBeInTheDocument();
|
|
112
|
+
expect(screen.getByRole('checkbox', {
|
|
113
|
+
name: /object-lock/i
|
|
114
|
+
})).toBeInTheDocument();
|
|
107
115
|
});
|
|
108
116
|
it('shows validation error for invalid bucket name with uppercase', async ()=>{
|
|
109
117
|
renderBucketCreate();
|
|
110
|
-
await user_event.type(screen.
|
|
118
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
119
|
+
name: /bucket name/i
|
|
120
|
+
}), 'INVALID');
|
|
111
121
|
await user_event.tab();
|
|
112
122
|
await waitFor(()=>{
|
|
113
123
|
expect(screen.getByText(/bucket names can include only lowercase/i)).toBeInTheDocument();
|
|
@@ -115,7 +125,9 @@ describe('BucketCreate', ()=>{
|
|
|
115
125
|
});
|
|
116
126
|
it('shows validation error for bucket name starting with hyphen', async ()=>{
|
|
117
127
|
renderBucketCreate();
|
|
118
|
-
await user_event.type(screen.
|
|
128
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
129
|
+
name: /bucket name/i
|
|
130
|
+
}), '-invalid');
|
|
119
131
|
await user_event.tab();
|
|
120
132
|
await waitFor(()=>{
|
|
121
133
|
expect(screen.getByText(/bucket names can include only lowercase/i)).toBeInTheDocument();
|
|
@@ -123,7 +135,9 @@ describe('BucketCreate', ()=>{
|
|
|
123
135
|
});
|
|
124
136
|
it('shows validation error for bucket name ending with hyphen', async ()=>{
|
|
125
137
|
renderBucketCreate();
|
|
126
|
-
await user_event.type(screen.
|
|
138
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
139
|
+
name: /bucket name/i
|
|
140
|
+
}), 'invalid-');
|
|
127
141
|
await user_event.tab();
|
|
128
142
|
await waitFor(()=>{
|
|
129
143
|
expect(screen.getByText(/bucket names can include only lowercase/i)).toBeInTheDocument();
|
|
@@ -131,7 +145,9 @@ describe('BucketCreate', ()=>{
|
|
|
131
145
|
});
|
|
132
146
|
it('shows validation error for bucket name with adjacent periods', async ()=>{
|
|
133
147
|
renderBucketCreate();
|
|
134
|
-
await user_event.type(screen.
|
|
148
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
149
|
+
name: /bucket name/i
|
|
150
|
+
}), 'test..bucket');
|
|
135
151
|
await user_event.tab();
|
|
136
152
|
await waitFor(()=>{
|
|
137
153
|
expect(screen.getByText(/bucket names cannot contain two adjacent periods/i)).toBeInTheDocument();
|
|
@@ -139,7 +155,9 @@ describe('BucketCreate', ()=>{
|
|
|
139
155
|
});
|
|
140
156
|
it('shows validation error for bucket name formatted as IP address', async ()=>{
|
|
141
157
|
renderBucketCreate();
|
|
142
|
-
await user_event.type(screen.
|
|
158
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
159
|
+
name: /bucket name/i
|
|
160
|
+
}), '192.168.1.1');
|
|
143
161
|
await user_event.tab();
|
|
144
162
|
await waitFor(()=>{
|
|
145
163
|
expect(screen.getByText(/bucket names must not be formatted as an ip address/i)).toBeInTheDocument();
|
|
@@ -147,7 +165,9 @@ describe('BucketCreate', ()=>{
|
|
|
147
165
|
});
|
|
148
166
|
it('shows validation error for bucket name with forbidden prefix', async ()=>{
|
|
149
167
|
renderBucketCreate();
|
|
150
|
-
await user_event.type(screen.
|
|
168
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
169
|
+
name: /bucket name/i
|
|
170
|
+
}), 'xn--test');
|
|
151
171
|
await user_event.tab();
|
|
152
172
|
await waitFor(()=>{
|
|
153
173
|
expect(screen.getByText(/bucket names must not start with/i)).toBeInTheDocument();
|
|
@@ -155,7 +175,9 @@ describe('BucketCreate', ()=>{
|
|
|
155
175
|
});
|
|
156
176
|
it('shows validation error for bucket name with forbidden suffix', async ()=>{
|
|
157
177
|
renderBucketCreate();
|
|
158
|
-
await user_event.type(screen.
|
|
178
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
179
|
+
name: /bucket name/i
|
|
180
|
+
}), 'test-s3alias');
|
|
159
181
|
await user_event.tab();
|
|
160
182
|
await waitFor(()=>{
|
|
161
183
|
expect(screen.getByText(/bucket names must not end with/i)).toBeInTheDocument();
|
|
@@ -170,7 +192,9 @@ describe('BucketCreate', ()=>{
|
|
|
170
192
|
]
|
|
171
193
|
}
|
|
172
194
|
});
|
|
173
|
-
await user_event.type(screen.
|
|
195
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
196
|
+
name: /bucket name/i
|
|
197
|
+
}), 'existing-bucket');
|
|
174
198
|
await user_event.tab();
|
|
175
199
|
await waitFor(()=>{
|
|
176
200
|
expect(screen.getByText(/a bucket with this name already exists/i)).toBeInTheDocument();
|
|
@@ -185,7 +209,9 @@ describe('BucketCreate', ()=>{
|
|
|
185
209
|
'123-bucket-456'
|
|
186
210
|
];
|
|
187
211
|
for (const name of validNames){
|
|
188
|
-
const input = screen.
|
|
212
|
+
const input = screen.getByRole('textbox', {
|
|
213
|
+
name: /bucket name/i
|
|
214
|
+
});
|
|
189
215
|
await user_event.clear(input);
|
|
190
216
|
await user_event.type(input, name);
|
|
191
217
|
await user_event.tab();
|
|
@@ -469,7 +495,9 @@ describe('BucketCreate', ()=>{
|
|
|
469
495
|
});
|
|
470
496
|
renderBucketCreate();
|
|
471
497
|
await fillBucketName('test-bucket');
|
|
472
|
-
const encryptionCheckbox = screen.
|
|
498
|
+
const encryptionCheckbox = screen.getByRole('checkbox', {
|
|
499
|
+
name: /encryption/i
|
|
500
|
+
});
|
|
473
501
|
await user_event.click(encryptionCheckbox);
|
|
474
502
|
await submitForm();
|
|
475
503
|
const createSuccessCallback = mockMutations.createBucket.mock.calls[0][1].onSuccess;
|
|
@@ -516,8 +544,12 @@ describe('BucketCreate', ()=>{
|
|
|
516
544
|
});
|
|
517
545
|
renderBucketCreate();
|
|
518
546
|
await fillBucketName('test-bucket');
|
|
519
|
-
await user_event.click(screen.
|
|
520
|
-
|
|
547
|
+
await user_event.click(screen.getByRole('checkbox', {
|
|
548
|
+
name: /versioning/i
|
|
549
|
+
}));
|
|
550
|
+
await user_event.click(screen.getByRole('checkbox', {
|
|
551
|
+
name: /encryption/i
|
|
552
|
+
}));
|
|
521
553
|
await submitForm();
|
|
522
554
|
const createSuccessCallback = mockMutations.createBucket.mock.calls[0][1].onSuccess;
|
|
523
555
|
createSuccessCallback();
|
|
@@ -536,9 +568,15 @@ describe('BucketCreate', ()=>{
|
|
|
536
568
|
});
|
|
537
569
|
renderBucketCreate();
|
|
538
570
|
await fillBucketName('test-bucket');
|
|
539
|
-
await user_event.click(screen.
|
|
540
|
-
|
|
541
|
-
|
|
571
|
+
await user_event.click(screen.getByRole('checkbox', {
|
|
572
|
+
name: /object-lock/i
|
|
573
|
+
}));
|
|
574
|
+
await user_event.click(screen.getByRole('checkbox', {
|
|
575
|
+
name: /default retention/i
|
|
576
|
+
}));
|
|
577
|
+
await user_event.click(screen.getByRole('checkbox', {
|
|
578
|
+
name: /encryption/i
|
|
579
|
+
}));
|
|
542
580
|
await submitForm();
|
|
543
581
|
const createSuccessCallback = mockMutations.createBucket.mock.calls[0][1].onSuccess;
|
|
544
582
|
createSuccessCallback();
|
|
@@ -557,7 +595,9 @@ describe('BucketCreate', ()=>{
|
|
|
557
595
|
});
|
|
558
596
|
renderBucketCreate();
|
|
559
597
|
await fillBucketName('test-bucket');
|
|
560
|
-
await user_event.click(screen.
|
|
598
|
+
await user_event.click(screen.getByRole('checkbox', {
|
|
599
|
+
name: /encryption/i
|
|
600
|
+
}));
|
|
561
601
|
await submitForm();
|
|
562
602
|
const createSuccessCallback = mockMutations.createBucket.mock.calls[0][1].onSuccess;
|
|
563
603
|
createSuccessCallback();
|
|
@@ -45,9 +45,15 @@ describe('BucketList', ()=>{
|
|
|
45
45
|
buckets: mockBuckets
|
|
46
46
|
});
|
|
47
47
|
expect(screen.getByRole('grid')).toBeInTheDocument();
|
|
48
|
-
expect(screen.
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
expect(screen.getByRole('columnheader', {
|
|
49
|
+
name: /bucket name/i
|
|
50
|
+
})).toBeInTheDocument();
|
|
51
|
+
expect(screen.getByRole('columnheader', {
|
|
52
|
+
name: /storage location/i
|
|
53
|
+
})).toBeInTheDocument();
|
|
54
|
+
expect(screen.getByRole('columnheader', {
|
|
55
|
+
name: /created on/i
|
|
56
|
+
})).toBeInTheDocument();
|
|
51
57
|
});
|
|
52
58
|
it('displays buckets with their names as clickable links', ()=>{
|
|
53
59
|
const onNavigateToBucket = jest.fn();
|
|
@@ -109,14 +115,16 @@ describe('BucketList', ()=>{
|
|
|
109
115
|
});
|
|
110
116
|
const searchInput = screen.getByRole('searchbox');
|
|
111
117
|
expect(searchInput).toBeInTheDocument();
|
|
112
|
-
expect(searchInput).toHaveAttribute('placeholder', '
|
|
118
|
+
expect(searchInput).toHaveAttribute('placeholder', 'Search');
|
|
113
119
|
});
|
|
114
120
|
it('handles empty buckets list', ()=>{
|
|
115
121
|
renderBucketList({
|
|
116
122
|
buckets: []
|
|
117
123
|
});
|
|
118
124
|
expect(screen.getByRole('grid')).toBeInTheDocument();
|
|
119
|
-
expect(screen.
|
|
125
|
+
expect(screen.getByRole('columnheader', {
|
|
126
|
+
name: /bucket name/i
|
|
127
|
+
})).toBeInTheDocument();
|
|
120
128
|
});
|
|
121
129
|
it('handles buckets without names gracefully', ()=>{
|
|
122
130
|
const bucketsWithMissingNames = [
|
|
@@ -248,7 +256,9 @@ describe('BucketList', ()=>{
|
|
|
248
256
|
renderBucketList({
|
|
249
257
|
buckets: bucketsWithReversedDateOrder
|
|
250
258
|
});
|
|
251
|
-
fireEvent.click(screen.
|
|
259
|
+
fireEvent.click(screen.getByRole('columnheader', {
|
|
260
|
+
name: /created on/i
|
|
261
|
+
}));
|
|
252
262
|
const rows = screen.getAllByRole('row');
|
|
253
263
|
expect(rows[1]).toHaveTextContent('alpha-bucket');
|
|
254
264
|
expect(rows[2]).toHaveTextContent('gamma-bucket');
|
|
@@ -272,7 +282,9 @@ describe('BucketList', ()=>{
|
|
|
272
282
|
renderBucketList({
|
|
273
283
|
buckets: mockBuckets
|
|
274
284
|
});
|
|
275
|
-
fireEvent.click(screen.
|
|
285
|
+
fireEvent.click(screen.getByRole('columnheader', {
|
|
286
|
+
name: /storage location/i
|
|
287
|
+
}));
|
|
276
288
|
const rows = screen.getAllByRole('row');
|
|
277
289
|
expect(rows[1]).toHaveTextContent('test-bucket-3');
|
|
278
290
|
expect(rows[2]).toHaveTextContent('test-bucket-2');
|
|
@@ -66,11 +66,18 @@ describe('BucketNotificationFormPage', ()=>{
|
|
|
66
66
|
it('renders create notification form with all fields', ()=>{
|
|
67
67
|
renderCreatePage();
|
|
68
68
|
expect(screen.getByText('Create Bucket Notification')).toBeInTheDocument();
|
|
69
|
-
expect(screen.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
expect(screen.
|
|
73
|
-
|
|
69
|
+
expect(screen.getByRole('textbox', {
|
|
70
|
+
name: /rule name/i
|
|
71
|
+
})).toBeInTheDocument();
|
|
72
|
+
expect(screen.getByRole('textbox', {
|
|
73
|
+
name: /destination queue/i
|
|
74
|
+
})).toBeInTheDocument();
|
|
75
|
+
expect(screen.getByRole('textbox', {
|
|
76
|
+
name: /^prefix$/i
|
|
77
|
+
})).toBeInTheDocument();
|
|
78
|
+
expect(screen.getByRole('textbox', {
|
|
79
|
+
name: /^suffix$/i
|
|
80
|
+
})).toBeInTheDocument();
|
|
74
81
|
});
|
|
75
82
|
it('disables create button when form is pristine', ()=>{
|
|
76
83
|
renderCreatePage();
|
|
@@ -80,7 +87,9 @@ describe('BucketNotificationFormPage', ()=>{
|
|
|
80
87
|
});
|
|
81
88
|
it('shows validation error when rule name is empty', async ()=>{
|
|
82
89
|
renderCreatePage();
|
|
83
|
-
const ruleNameInput = screen.
|
|
90
|
+
const ruleNameInput = screen.getByRole('textbox', {
|
|
91
|
+
name: /rule name/i
|
|
92
|
+
});
|
|
84
93
|
await user_event.type(ruleNameInput, 'a');
|
|
85
94
|
await user_event.clear(ruleNameInput);
|
|
86
95
|
await waitFor(()=>{
|
|
@@ -104,7 +113,9 @@ describe('BucketNotificationFormPage', ()=>{
|
|
|
104
113
|
status: 'success'
|
|
105
114
|
});
|
|
106
115
|
renderCreatePage();
|
|
107
|
-
const ruleNameInput = screen.
|
|
116
|
+
const ruleNameInput = screen.getByRole('textbox', {
|
|
117
|
+
name: /rule name/i
|
|
118
|
+
});
|
|
108
119
|
await user_event.type(ruleNameInput, 'existing-rule');
|
|
109
120
|
await waitFor(()=>{
|
|
110
121
|
expect(screen.getByText(/a rule with this name already exists/i)).toBeInTheDocument();
|
|
@@ -112,7 +123,9 @@ describe('BucketNotificationFormPage', ()=>{
|
|
|
112
123
|
});
|
|
113
124
|
it('shows validation error when queue ARN is invalid', async ()=>{
|
|
114
125
|
renderCreatePage();
|
|
115
|
-
const queueArnInput = screen.
|
|
126
|
+
const queueArnInput = screen.getByRole('textbox', {
|
|
127
|
+
name: /destination queue/i
|
|
128
|
+
});
|
|
116
129
|
await user_event.type(queueArnInput, 'invalid-arn');
|
|
117
130
|
await waitFor(()=>{
|
|
118
131
|
expect(screen.getByText(/must be a valid arn/i)).toBeInTheDocument();
|
|
@@ -120,8 +133,12 @@ describe('BucketNotificationFormPage', ()=>{
|
|
|
120
133
|
});
|
|
121
134
|
it('enables create button when form is valid', async ()=>{
|
|
122
135
|
renderCreatePage();
|
|
123
|
-
await user_event.type(screen.
|
|
124
|
-
|
|
136
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
137
|
+
name: /rule name/i
|
|
138
|
+
}), 'test-rule');
|
|
139
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
140
|
+
name: /destination queue/i
|
|
141
|
+
}), 'arn:aws:sqs:us-east-1:123:queue');
|
|
125
142
|
const checkbox = screen.getByRole('checkbox', {
|
|
126
143
|
name: /s3:ObjectCreated:\*/i
|
|
127
144
|
});
|
|
@@ -134,13 +151,21 @@ describe('BucketNotificationFormPage', ()=>{
|
|
|
134
151
|
});
|
|
135
152
|
it('creates notification with all fields and navigates on success', async ()=>{
|
|
136
153
|
renderCreatePage();
|
|
137
|
-
await user_event.type(screen.
|
|
138
|
-
|
|
154
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
155
|
+
name: /rule name/i
|
|
156
|
+
}), 'test-rule');
|
|
157
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
158
|
+
name: /destination queue/i
|
|
159
|
+
}), 'arn:aws:sqs:us-east-1:123:my-queue');
|
|
139
160
|
fireEvent.click(screen.getByRole('checkbox', {
|
|
140
161
|
name: /s3:ObjectCreated:Put/i
|
|
141
162
|
}));
|
|
142
|
-
await user_event.type(screen.
|
|
143
|
-
|
|
163
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
164
|
+
name: /^prefix$/i
|
|
165
|
+
}), 'uploads/');
|
|
166
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
167
|
+
name: /^suffix$/i
|
|
168
|
+
}), '.jpg');
|
|
144
169
|
mockMutate.mockImplementation((_, options)=>{
|
|
145
170
|
options?.onSuccess?.();
|
|
146
171
|
});
|
|
@@ -263,9 +288,15 @@ describe('BucketNotificationFormPage', ()=>{
|
|
|
263
288
|
it('pre-fills form with existing rule data', async ()=>{
|
|
264
289
|
renderEditPage();
|
|
265
290
|
await waitFor(()=>{
|
|
266
|
-
expect(screen.
|
|
267
|
-
|
|
268
|
-
|
|
291
|
+
expect(screen.getByRole('textbox', {
|
|
292
|
+
name: /destination queue/i
|
|
293
|
+
})).toHaveValue('arn:aws:sqs:us-east-1:123:existing-queue');
|
|
294
|
+
expect(screen.getByRole('textbox', {
|
|
295
|
+
name: /^prefix$/i
|
|
296
|
+
})).toHaveValue('data/');
|
|
297
|
+
expect(screen.getByRole('textbox', {
|
|
298
|
+
name: /^suffix$/i
|
|
299
|
+
})).toHaveValue('.json');
|
|
269
300
|
});
|
|
270
301
|
});
|
|
271
302
|
it('shows Save button instead of Create in edit mode', ()=>{
|
|
@@ -280,9 +311,13 @@ describe('BucketNotificationFormPage', ()=>{
|
|
|
280
311
|
it('updates notification and navigates on success', async ()=>{
|
|
281
312
|
renderEditPage();
|
|
282
313
|
await waitFor(()=>{
|
|
283
|
-
expect(screen.
|
|
314
|
+
expect(screen.getByRole('textbox', {
|
|
315
|
+
name: /destination queue/i
|
|
316
|
+
})).toHaveValue('arn:aws:sqs:us-east-1:123:existing-queue');
|
|
317
|
+
});
|
|
318
|
+
const queueInput = screen.getByRole('textbox', {
|
|
319
|
+
name: /destination queue/i
|
|
284
320
|
});
|
|
285
|
-
const queueInput = screen.getByLabelText(/destination queue/i);
|
|
286
321
|
await user_event.clear(queueInput);
|
|
287
322
|
await user_event.type(queueInput, 'arn:aws:sqs:us-east-1:123:updated-queue');
|
|
288
323
|
mockMutate.mockImplementation((_, options)=>{
|
|
@@ -55,8 +55,12 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
55
55
|
const mockMutate = jest.fn();
|
|
56
56
|
const fillRequiredFields = async (options)=>{
|
|
57
57
|
const { roleArn = 'arn:aws:iam::123456789012:role/replication-role', ruleId, targetBucket = 'destination-bucket' } = options;
|
|
58
|
-
await user_event.type(screen.
|
|
59
|
-
|
|
58
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
59
|
+
name: /role arn/i
|
|
60
|
+
}), roleArn);
|
|
61
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
62
|
+
name: /rule id/i
|
|
63
|
+
}), ruleId);
|
|
60
64
|
const targetBucketSelect = screen.getByLabelText(/target bucket/i);
|
|
61
65
|
await user_event.click(targetBucketSelect);
|
|
62
66
|
await user_event.click(screen.getByRole('option', {
|
|
@@ -147,8 +151,12 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
147
151
|
it('displays all form sections', async ()=>{
|
|
148
152
|
renderBucketReplicationFormPage();
|
|
149
153
|
await waitFor(()=>{
|
|
150
|
-
expect(screen.
|
|
151
|
-
|
|
154
|
+
expect(screen.getByRole('textbox', {
|
|
155
|
+
name: /role arn/i
|
|
156
|
+
})).toBeInTheDocument();
|
|
157
|
+
expect(screen.getByRole('textbox', {
|
|
158
|
+
name: /rule id/i
|
|
159
|
+
})).toBeInTheDocument();
|
|
152
160
|
expect(screen.getByText('Replicate encrypted objects')).toBeInTheDocument();
|
|
153
161
|
expect(screen.getByText('Delete marker replication')).toBeInTheDocument();
|
|
154
162
|
});
|
|
@@ -157,14 +165,20 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
157
165
|
describe('Form Fields - Initial State', ()=>{
|
|
158
166
|
it('renders required form fields in create mode', ()=>{
|
|
159
167
|
renderBucketReplicationFormPage();
|
|
160
|
-
expect(screen.
|
|
161
|
-
|
|
168
|
+
expect(screen.getByRole('textbox', {
|
|
169
|
+
name: /role arn/i
|
|
170
|
+
})).toBeInTheDocument();
|
|
171
|
+
expect(screen.getByRole('textbox', {
|
|
172
|
+
name: /rule id/i
|
|
173
|
+
})).toBeInTheDocument();
|
|
162
174
|
expect(screen.getByLabelText(/status/i)).toBeInTheDocument();
|
|
163
175
|
expect(screen.getByLabelText(/target bucket/i)).toBeInTheDocument();
|
|
164
176
|
});
|
|
165
177
|
it('shows Role ARN input when no existing rules', ()=>{
|
|
166
178
|
renderBucketReplicationFormPage();
|
|
167
|
-
const roleInput = screen.
|
|
179
|
+
const roleInput = screen.getByRole('textbox', {
|
|
180
|
+
name: /role arn/i
|
|
181
|
+
});
|
|
168
182
|
expect(roleInput).toBeInTheDocument();
|
|
169
183
|
expect(roleInput.tagName).toBe('INPUT');
|
|
170
184
|
});
|
|
@@ -194,7 +208,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
194
208
|
});
|
|
195
209
|
it('shows Rule ID input in create mode', ()=>{
|
|
196
210
|
renderBucketReplicationFormPage();
|
|
197
|
-
const ruleIdInput = screen.
|
|
211
|
+
const ruleIdInput = screen.getByRole('textbox', {
|
|
212
|
+
name: /rule id/i
|
|
213
|
+
});
|
|
198
214
|
expect(ruleIdInput).toBeInTheDocument();
|
|
199
215
|
expect(ruleIdInput.tagName).toBe('INPUT');
|
|
200
216
|
});
|
|
@@ -229,14 +245,18 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
229
245
|
it('renders Status select field', async ()=>{
|
|
230
246
|
renderBucketReplicationFormPage();
|
|
231
247
|
await waitFor(()=>{
|
|
232
|
-
expect(screen.
|
|
248
|
+
expect(screen.getByRole('textbox', {
|
|
249
|
+
name: /rule id/i
|
|
250
|
+
})).toBeInTheDocument();
|
|
233
251
|
});
|
|
234
252
|
const statusElement = document.querySelector('[id="status"]');
|
|
235
253
|
expect(statusElement).toBeInTheDocument();
|
|
236
254
|
});
|
|
237
255
|
it('renders Priority number input with auto-assigned placeholder', ()=>{
|
|
238
256
|
renderBucketReplicationFormPage();
|
|
239
|
-
const priorityInput = screen.
|
|
257
|
+
const priorityInput = screen.getByRole('spinbutton', {
|
|
258
|
+
name: /rule priority/i
|
|
259
|
+
});
|
|
240
260
|
expect(priorityInput).toBeInTheDocument();
|
|
241
261
|
expect(priorityInput).toHaveAttribute('placeholder', 'Example: Auto-assigned: 0');
|
|
242
262
|
});
|
|
@@ -255,7 +275,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
255
275
|
});
|
|
256
276
|
it('renders storage class select with all options', async ()=>{
|
|
257
277
|
renderBucketReplicationFormPage();
|
|
258
|
-
const storageClassSelect = screen.getByLabelText(/storage class/i
|
|
278
|
+
const storageClassSelect = screen.getByLabelText(/storage class/i, {
|
|
279
|
+
selector: 'input'
|
|
280
|
+
});
|
|
259
281
|
await user_event.click(storageClassSelect);
|
|
260
282
|
expect(screen.getByRole('option', {
|
|
261
283
|
name: 'Same as source'
|
|
@@ -283,7 +305,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
283
305
|
describe('Form Validation', ()=>{
|
|
284
306
|
it('validates Role ARN is required for first replication rule', async ()=>{
|
|
285
307
|
renderBucketReplicationFormPage();
|
|
286
|
-
const roleInput = screen.
|
|
308
|
+
const roleInput = screen.getByRole('textbox', {
|
|
309
|
+
name: /role arn/i
|
|
310
|
+
});
|
|
287
311
|
await user_event.type(roleInput, 'test');
|
|
288
312
|
await user_event.clear(roleInput);
|
|
289
313
|
await waitFor(()=>{
|
|
@@ -310,7 +334,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
310
334
|
status: 'success'
|
|
311
335
|
});
|
|
312
336
|
renderBucketReplicationFormPage();
|
|
313
|
-
await user_event.type(screen.
|
|
337
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
338
|
+
name: /rule id/i
|
|
339
|
+
}), 'new-rule');
|
|
314
340
|
const targetBucketSelect = screen.getByLabelText(/target bucket/i);
|
|
315
341
|
await user_event.click(targetBucketSelect);
|
|
316
342
|
await user_event.click(screen.getByRole('option', {
|
|
@@ -325,7 +351,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
325
351
|
});
|
|
326
352
|
it('validates Rule ID is required', async ()=>{
|
|
327
353
|
renderBucketReplicationFormPage();
|
|
328
|
-
const ruleIdInput = screen.
|
|
354
|
+
const ruleIdInput = screen.getByRole('textbox', {
|
|
355
|
+
name: /rule id/i
|
|
356
|
+
});
|
|
329
357
|
await user_event.type(ruleIdInput, 'test');
|
|
330
358
|
await user_event.clear(ruleIdInput);
|
|
331
359
|
await waitFor(()=>{
|
|
@@ -352,7 +380,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
352
380
|
status: 'success'
|
|
353
381
|
});
|
|
354
382
|
renderBucketReplicationFormPage();
|
|
355
|
-
const ruleIdInput = screen.
|
|
383
|
+
const ruleIdInput = screen.getByRole('textbox', {
|
|
384
|
+
name: /rule id/i
|
|
385
|
+
});
|
|
356
386
|
await user_event.type(ruleIdInput, 'existing-rule');
|
|
357
387
|
await waitFor(()=>{
|
|
358
388
|
expect(screen.getByText(/a rule with this id already exists/i)).toBeInTheDocument();
|
|
@@ -360,8 +390,12 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
360
390
|
});
|
|
361
391
|
it('validates Priority must be >= 0', async ()=>{
|
|
362
392
|
renderBucketReplicationFormPage();
|
|
363
|
-
await user_event.type(screen.
|
|
364
|
-
|
|
393
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
394
|
+
name: /rule id/i
|
|
395
|
+
}), 'test-rule');
|
|
396
|
+
const priorityInput = screen.getByRole('spinbutton', {
|
|
397
|
+
name: /rule priority/i
|
|
398
|
+
});
|
|
365
399
|
await user_event.clear(priorityInput);
|
|
366
400
|
await user_event.type(priorityInput, '-1');
|
|
367
401
|
await user_event.tab();
|
|
@@ -371,8 +405,12 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
371
405
|
});
|
|
372
406
|
it('disables submit button when Target Bucket is not selected', async ()=>{
|
|
373
407
|
renderBucketReplicationFormPage();
|
|
374
|
-
await user_event.type(screen.
|
|
375
|
-
|
|
408
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
409
|
+
name: /role arn/i
|
|
410
|
+
}), 'arn:aws:iam::123456789012:role/role');
|
|
411
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
412
|
+
name: /rule id/i
|
|
413
|
+
}), 'test-rule');
|
|
376
414
|
await waitFor(()=>{
|
|
377
415
|
const createButton = screen.getByRole('button', {
|
|
378
416
|
name: /create/i
|
|
@@ -385,7 +423,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
385
423
|
const sameAccountToggle = findToggleByLabel('Same account destination');
|
|
386
424
|
await user_event.click(sameAccountToggle);
|
|
387
425
|
await waitFor(()=>{
|
|
388
|
-
expect(screen.
|
|
426
|
+
expect(screen.getByRole('textbox', {
|
|
427
|
+
name: /target account id/i
|
|
428
|
+
})).toBeInTheDocument();
|
|
389
429
|
});
|
|
390
430
|
});
|
|
391
431
|
it('shows Replica KMS Key ID field when encryptReplicatedObjects is true', async ()=>{
|
|
@@ -393,7 +433,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
393
433
|
const encryptToggle = findToggleByLabel('Encrypt replicated objects');
|
|
394
434
|
await user_event.click(encryptToggle);
|
|
395
435
|
await waitFor(()=>{
|
|
396
|
-
expect(screen.
|
|
436
|
+
expect(screen.getByRole('textbox', {
|
|
437
|
+
name: /replica kms key id/i
|
|
438
|
+
})).toBeInTheDocument();
|
|
397
439
|
});
|
|
398
440
|
});
|
|
399
441
|
it('shows prefix field when filterType is prefix', async ()=>{
|
|
@@ -409,7 +451,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
409
451
|
name: 'Prefix filter'
|
|
410
452
|
}));
|
|
411
453
|
await waitFor(()=>{
|
|
412
|
-
expect(screen.
|
|
454
|
+
expect(screen.getByRole('textbox', {
|
|
455
|
+
name: /prefix/i
|
|
456
|
+
})).toBeInTheDocument();
|
|
413
457
|
});
|
|
414
458
|
});
|
|
415
459
|
});
|
|
@@ -505,7 +549,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
505
549
|
const sameAccountToggle = findToggleByLabel('Same account destination');
|
|
506
550
|
await user_event.click(sameAccountToggle);
|
|
507
551
|
await waitFor(()=>{
|
|
508
|
-
expect(screen.
|
|
552
|
+
expect(screen.getByRole('textbox', {
|
|
553
|
+
name: /target account id/i
|
|
554
|
+
})).toBeInTheDocument();
|
|
509
555
|
});
|
|
510
556
|
});
|
|
511
557
|
it('renders target bucket as Input for cross-account replication', async ()=>{
|
|
@@ -523,7 +569,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
523
569
|
const encryptToggle = findToggleByLabel('Encrypt replicated objects');
|
|
524
570
|
await user_event.click(encryptToggle);
|
|
525
571
|
await waitFor(()=>{
|
|
526
|
-
expect(screen.
|
|
572
|
+
expect(screen.getByRole('textbox', {
|
|
573
|
+
name: /replica kms key id/i
|
|
574
|
+
})).toBeInTheDocument();
|
|
527
575
|
});
|
|
528
576
|
});
|
|
529
577
|
});
|
|
@@ -556,9 +604,15 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
556
604
|
});
|
|
557
605
|
it('submits complete replication rule with all optional fields', async ()=>{
|
|
558
606
|
renderBucketReplicationFormPage();
|
|
559
|
-
await user_event.type(screen.
|
|
560
|
-
|
|
561
|
-
|
|
607
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
608
|
+
name: /role arn/i
|
|
609
|
+
}), 'arn:aws:iam::123456789012:role/replication-role');
|
|
610
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
611
|
+
name: /rule id/i
|
|
612
|
+
}), 'complete-rule');
|
|
613
|
+
await user_event.type(screen.getByRole('spinbutton', {
|
|
614
|
+
name: /rule priority/i
|
|
615
|
+
}), '5');
|
|
562
616
|
const filterSelect = document.querySelector('[id="filterType"]');
|
|
563
617
|
await user_event.click(filterSelect);
|
|
564
618
|
await waitFor(()=>{
|
|
@@ -570,15 +624,23 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
570
624
|
name: 'Prefix filter'
|
|
571
625
|
}));
|
|
572
626
|
await waitFor(()=>{
|
|
573
|
-
expect(screen.
|
|
627
|
+
expect(screen.getByRole('textbox', {
|
|
628
|
+
name: /prefix/i
|
|
629
|
+
})).toBeInTheDocument();
|
|
574
630
|
});
|
|
575
|
-
await user_event.type(screen.
|
|
631
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
632
|
+
name: /prefix/i
|
|
633
|
+
}), 'logs/');
|
|
576
634
|
const sameAccountToggle = findToggleByLabel('Same account destination');
|
|
577
635
|
await user_event.click(sameAccountToggle);
|
|
578
636
|
await waitFor(()=>{
|
|
579
|
-
expect(screen.
|
|
637
|
+
expect(screen.getByRole('textbox', {
|
|
638
|
+
name: /target account id/i
|
|
639
|
+
})).toBeInTheDocument();
|
|
580
640
|
});
|
|
581
|
-
await user_event.type(screen.
|
|
641
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
642
|
+
name: /target account id/i
|
|
643
|
+
}), '987654321098');
|
|
582
644
|
await user_event.type(screen.getByLabelText(/target bucket/i), 'cross-account-bucket');
|
|
583
645
|
const storageClassSelect = document.querySelector('[id="storageClass"]');
|
|
584
646
|
await user_event.click(storageClassSelect);
|
|
@@ -595,9 +657,13 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
595
657
|
const includeEncryptedToggle = findToggleByLabel('Replicate encrypted objects');
|
|
596
658
|
await user_event.click(includeEncryptedToggle);
|
|
597
659
|
await waitFor(()=>{
|
|
598
|
-
expect(screen.
|
|
660
|
+
expect(screen.getByRole('textbox', {
|
|
661
|
+
name: /replica kms key id/i
|
|
662
|
+
})).toBeInTheDocument();
|
|
599
663
|
});
|
|
600
|
-
await user_event.type(screen.
|
|
664
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
665
|
+
name: /replica kms key id/i
|
|
666
|
+
}), 'arn:aws:kms:us-east-1:987654321098:key/12345678-1234-1234-1234-123456789012');
|
|
601
667
|
const enforceRTCToggle = findToggleByLabel('Replication Time Control (RTC)');
|
|
602
668
|
await user_event.click(enforceRTCToggle);
|
|
603
669
|
const replicaModToggle = findToggleByLabel('Replica modification sync');
|
|
@@ -674,7 +740,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
674
740
|
status: 'success'
|
|
675
741
|
});
|
|
676
742
|
renderBucketReplicationFormPage();
|
|
677
|
-
await user_event.type(screen.
|
|
743
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
744
|
+
name: /rule id/i
|
|
745
|
+
}), 'new-rule');
|
|
678
746
|
const targetBucketSelect = screen.getByLabelText(/target bucket/i);
|
|
679
747
|
await user_event.click(targetBucketSelect);
|
|
680
748
|
await user_event.click(screen.getByRole('option', {
|
|
@@ -753,7 +821,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
753
821
|
});
|
|
754
822
|
it('disables submit button when form is invalid', async ()=>{
|
|
755
823
|
renderBucketReplicationFormPage();
|
|
756
|
-
await user_event.type(screen.
|
|
824
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
825
|
+
name: /rule id/i
|
|
826
|
+
}), 'test');
|
|
757
827
|
await waitFor(()=>{
|
|
758
828
|
const createButton = screen.getByRole('button', {
|
|
759
829
|
name: /create/i
|
|
@@ -1033,7 +1103,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
1033
1103
|
});
|
|
1034
1104
|
renderBucketReplicationFormPage('test-bucket', 'prefix-rule');
|
|
1035
1105
|
await waitFor(()=>{
|
|
1036
|
-
const prefixInput = screen.
|
|
1106
|
+
const prefixInput = screen.getByRole('textbox', {
|
|
1107
|
+
name: /prefix/i
|
|
1108
|
+
});
|
|
1037
1109
|
expect(prefixInput).toHaveValue('documents/');
|
|
1038
1110
|
});
|
|
1039
1111
|
});
|
|
@@ -1106,7 +1178,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
1106
1178
|
});
|
|
1107
1179
|
renderBucketReplicationFormPage('test-bucket', 'and-rule');
|
|
1108
1180
|
await waitFor(()=>{
|
|
1109
|
-
const prefixInput = screen.
|
|
1181
|
+
const prefixInput = screen.getByRole('textbox', {
|
|
1182
|
+
name: /prefix/i
|
|
1183
|
+
});
|
|
1110
1184
|
expect(prefixInput).toHaveValue('logs/');
|
|
1111
1185
|
expect(screen.getByDisplayValue('type')).toBeInTheDocument();
|
|
1112
1186
|
expect(screen.getByDisplayValue('audit')).toBeInTheDocument();
|
|
@@ -1172,7 +1246,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
1172
1246
|
await waitFor(()=>{
|
|
1173
1247
|
const encryptReplicatedToggle = findToggleByLabel('Encrypt replicated objects');
|
|
1174
1248
|
expect(encryptReplicatedToggle).toBeChecked();
|
|
1175
|
-
const kmsKeyInput = screen.
|
|
1249
|
+
const kmsKeyInput = screen.getByRole('textbox', {
|
|
1250
|
+
name: /replica kms key id/i
|
|
1251
|
+
});
|
|
1176
1252
|
expect(kmsKeyInput).toHaveValue('arn:aws:kms:us-east-1:123456789012:key/12345');
|
|
1177
1253
|
});
|
|
1178
1254
|
});
|
|
@@ -1300,7 +1376,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
1300
1376
|
await waitFor(()=>{
|
|
1301
1377
|
const sameAccountToggle = screen.getByText(/not same account destination/i);
|
|
1302
1378
|
expect(sameAccountToggle).toBeInTheDocument();
|
|
1303
|
-
const accountIdInput = screen.
|
|
1379
|
+
const accountIdInput = screen.getByRole('textbox', {
|
|
1380
|
+
name: /target account id/i
|
|
1381
|
+
});
|
|
1304
1382
|
expect(accountIdInput).toHaveValue('987654321098');
|
|
1305
1383
|
const targetBucketInput = screen.getByLabelText(/target bucket/i);
|
|
1306
1384
|
expect(targetBucketInput).toHaveValue('cross-account-bucket');
|
|
@@ -1359,7 +1437,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
1359
1437
|
});
|
|
1360
1438
|
renderBucketReplicationFormPage('test-bucket', 'storage-class-rule');
|
|
1361
1439
|
await waitFor(()=>{
|
|
1362
|
-
const storageClassSelect = screen.getByLabelText(/storage class/i
|
|
1440
|
+
const storageClassSelect = screen.getByLabelText(/storage class/i, {
|
|
1441
|
+
selector: 'input'
|
|
1442
|
+
});
|
|
1363
1443
|
expect(storageClassSelect).toBeInTheDocument();
|
|
1364
1444
|
});
|
|
1365
1445
|
});
|
|
@@ -1385,7 +1465,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
1385
1465
|
});
|
|
1386
1466
|
renderBucketReplicationFormPage('test-bucket', 'priority-rule');
|
|
1387
1467
|
await waitFor(()=>{
|
|
1388
|
-
const priorityInput = screen.
|
|
1468
|
+
const priorityInput = screen.getByRole('spinbutton', {
|
|
1469
|
+
name: /rule priority/i
|
|
1470
|
+
});
|
|
1389
1471
|
expect(priorityInput).toHaveValue(99);
|
|
1390
1472
|
});
|
|
1391
1473
|
});
|
|
@@ -1393,7 +1475,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
1393
1475
|
describe('Priority Auto-assignment', ()=>{
|
|
1394
1476
|
it('auto-assigns priority 0 when no existing rules', ()=>{
|
|
1395
1477
|
renderBucketReplicationFormPage();
|
|
1396
|
-
const priorityInput = screen.
|
|
1478
|
+
const priorityInput = screen.getByRole('spinbutton', {
|
|
1479
|
+
name: /rule priority/i
|
|
1480
|
+
});
|
|
1397
1481
|
expect(priorityInput).toHaveAttribute('placeholder', 'Example: Auto-assigned: 0');
|
|
1398
1482
|
});
|
|
1399
1483
|
it('auto-assigns next available priority when rules exist', ()=>{
|
|
@@ -1424,19 +1508,27 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
1424
1508
|
status: 'success'
|
|
1425
1509
|
});
|
|
1426
1510
|
renderBucketReplicationFormPage();
|
|
1427
|
-
const priorityInput = screen.
|
|
1511
|
+
const priorityInput = screen.getByRole('spinbutton', {
|
|
1512
|
+
name: /rule priority/i
|
|
1513
|
+
});
|
|
1428
1514
|
expect(priorityInput).toHaveAttribute('placeholder', 'Example: Auto-assigned: 11');
|
|
1429
1515
|
});
|
|
1430
1516
|
it('allows manual priority override', async ()=>{
|
|
1431
1517
|
renderBucketReplicationFormPage();
|
|
1432
|
-
const priorityInput = screen.
|
|
1518
|
+
const priorityInput = screen.getByRole('spinbutton', {
|
|
1519
|
+
name: /rule priority/i
|
|
1520
|
+
});
|
|
1433
1521
|
await user_event.type(priorityInput, '25');
|
|
1434
1522
|
expect(priorityInput).toHaveValue(25);
|
|
1435
1523
|
});
|
|
1436
1524
|
it('accepts empty priority and defaults to 0 on submit', async ()=>{
|
|
1437
1525
|
renderBucketReplicationFormPage();
|
|
1438
|
-
await user_event.type(screen.
|
|
1439
|
-
|
|
1526
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
1527
|
+
name: /role arn/i
|
|
1528
|
+
}), 'arn:aws:iam::123456789012:role/role');
|
|
1529
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
1530
|
+
name: /rule id/i
|
|
1531
|
+
}), 'no-priority-rule');
|
|
1440
1532
|
const targetBucketSelect = screen.getByLabelText(/target bucket/i);
|
|
1441
1533
|
await user_event.click(targetBucketSelect);
|
|
1442
1534
|
await user_event.click(screen.getByRole('option', {
|
|
@@ -1520,8 +1612,12 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
1520
1612
|
isPending: true
|
|
1521
1613
|
});
|
|
1522
1614
|
renderBucketReplicationFormPage();
|
|
1523
|
-
await user_event.type(screen.
|
|
1524
|
-
|
|
1615
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
1616
|
+
name: /role arn/i
|
|
1617
|
+
}), 'arn:aws:iam::123456789012:role/role');
|
|
1618
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
1619
|
+
name: /rule id/i
|
|
1620
|
+
}), 'test-rule');
|
|
1525
1621
|
const targetBucketSelect = screen.getByLabelText(/target bucket/i);
|
|
1526
1622
|
await user_event.click(targetBucketSelect);
|
|
1527
1623
|
await user_event.click(screen.getByRole('option', {
|
|
@@ -1619,7 +1715,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
1619
1715
|
});
|
|
1620
1716
|
renderBucketReplicationFormPage('test-bucket', 'no-priority');
|
|
1621
1717
|
await waitFor(()=>{
|
|
1622
|
-
const priorityInput = screen.
|
|
1718
|
+
const priorityInput = screen.getByRole('spinbutton', {
|
|
1719
|
+
name: /rule priority/i
|
|
1720
|
+
});
|
|
1623
1721
|
expect(priorityInput).toHaveValue(null);
|
|
1624
1722
|
});
|
|
1625
1723
|
});
|
|
@@ -1645,7 +1743,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
1645
1743
|
});
|
|
1646
1744
|
renderBucketReplicationFormPage('test-bucket', 'no-storage-class');
|
|
1647
1745
|
await waitFor(()=>{
|
|
1648
|
-
const storageClassSelect = screen.getByLabelText(/storage class/i
|
|
1746
|
+
const storageClassSelect = screen.getByLabelText(/storage class/i, {
|
|
1747
|
+
selector: 'input'
|
|
1748
|
+
});
|
|
1649
1749
|
expect(storageClassSelect).toBeInTheDocument();
|
|
1650
1750
|
});
|
|
1651
1751
|
});
|
|
@@ -1654,7 +1754,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
1654
1754
|
it('accepts Rule ID with reasonable length', async ()=>{
|
|
1655
1755
|
renderBucketReplicationFormPage();
|
|
1656
1756
|
const longRuleId = 'rule-name-with-many-segments-' + 'segment-'.repeat(10);
|
|
1657
|
-
const ruleIdInput = screen.
|
|
1757
|
+
const ruleIdInput = screen.getByRole('textbox', {
|
|
1758
|
+
name: /rule id/i
|
|
1759
|
+
});
|
|
1658
1760
|
await user_event.type(ruleIdInput, longRuleId);
|
|
1659
1761
|
await waitFor(()=>{
|
|
1660
1762
|
expect(ruleIdInput).toHaveValue(longRuleId);
|
|
@@ -1663,7 +1765,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
1663
1765
|
it('handles special characters in Rule ID', async ()=>{
|
|
1664
1766
|
renderBucketReplicationFormPage();
|
|
1665
1767
|
const validSpecialChars = 'rule-name_with.special-chars_123';
|
|
1666
|
-
const ruleIdInput = screen.
|
|
1768
|
+
const ruleIdInput = screen.getByRole('textbox', {
|
|
1769
|
+
name: /rule id/i
|
|
1770
|
+
});
|
|
1667
1771
|
await user_event.type(ruleIdInput, validSpecialChars);
|
|
1668
1772
|
await user_event.tab();
|
|
1669
1773
|
await waitFor(()=>{
|
|
@@ -1672,8 +1776,12 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
1672
1776
|
});
|
|
1673
1777
|
it('validates Priority with boundary values', async ()=>{
|
|
1674
1778
|
renderBucketReplicationFormPage();
|
|
1675
|
-
await user_event.type(screen.
|
|
1676
|
-
|
|
1779
|
+
await user_event.type(screen.getByRole('textbox', {
|
|
1780
|
+
name: /rule id/i
|
|
1781
|
+
}), 'boundary-test');
|
|
1782
|
+
const priorityInput = screen.getByRole('spinbutton', {
|
|
1783
|
+
name: /rule priority/i
|
|
1784
|
+
});
|
|
1677
1785
|
await user_event.clear(priorityInput);
|
|
1678
1786
|
await user_event.type(priorityInput, '0');
|
|
1679
1787
|
await user_event.tab();
|
|
@@ -1700,10 +1808,14 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
1700
1808
|
name: 'Prefix filter'
|
|
1701
1809
|
}));
|
|
1702
1810
|
await waitFor(()=>{
|
|
1703
|
-
expect(screen.
|
|
1811
|
+
expect(screen.getByRole('textbox', {
|
|
1812
|
+
name: /prefix/i
|
|
1813
|
+
})).toBeInTheDocument();
|
|
1704
1814
|
});
|
|
1705
1815
|
const longPrefix = 'logs/' + 'a'.repeat(100);
|
|
1706
|
-
const prefixInput = screen.
|
|
1816
|
+
const prefixInput = screen.getByRole('textbox', {
|
|
1817
|
+
name: /prefix/i
|
|
1818
|
+
});
|
|
1707
1819
|
await user_event.type(prefixInput, longPrefix);
|
|
1708
1820
|
await waitFor(()=>{
|
|
1709
1821
|
expect(prefixInput).toHaveValue(longPrefix);
|
|
@@ -1711,7 +1823,9 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
1711
1823
|
});
|
|
1712
1824
|
it('accepts valid Role ARN format', async ()=>{
|
|
1713
1825
|
renderBucketReplicationFormPage();
|
|
1714
|
-
const roleArnInput = screen.
|
|
1826
|
+
const roleArnInput = screen.getByRole('textbox', {
|
|
1827
|
+
name: /role arn/i
|
|
1828
|
+
});
|
|
1715
1829
|
await user_event.type(roleArnInput, 'arn:aws:iam::123456789012:role/replication-role');
|
|
1716
1830
|
await user_event.tab();
|
|
1717
1831
|
await waitFor(()=>{
|
|
@@ -1723,9 +1837,13 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
1723
1837
|
const sameAccountToggle = findToggleByLabel('Same account destination');
|
|
1724
1838
|
await user_event.click(sameAccountToggle);
|
|
1725
1839
|
await waitFor(()=>{
|
|
1726
|
-
expect(screen.
|
|
1840
|
+
expect(screen.getByRole('textbox', {
|
|
1841
|
+
name: /target account id/i
|
|
1842
|
+
})).toBeInTheDocument();
|
|
1843
|
+
});
|
|
1844
|
+
const accountIdInput = screen.getByRole('textbox', {
|
|
1845
|
+
name: /target account id/i
|
|
1727
1846
|
});
|
|
1728
|
-
const accountIdInput = screen.getByLabelText(/target account id/i);
|
|
1729
1847
|
await user_event.type(accountIdInput, '123456789012');
|
|
1730
1848
|
await user_event.tab();
|
|
1731
1849
|
await waitFor(()=>{
|
|
@@ -1744,9 +1862,13 @@ describe('BucketReplicationFormPage', ()=>{
|
|
|
1744
1862
|
const encryptToggle = findToggleByLabel('Encrypt replicated objects');
|
|
1745
1863
|
await user_event.click(encryptToggle);
|
|
1746
1864
|
await waitFor(()=>{
|
|
1747
|
-
expect(screen.
|
|
1865
|
+
expect(screen.getByRole('textbox', {
|
|
1866
|
+
name: /replica kms key id/i
|
|
1867
|
+
})).toBeInTheDocument();
|
|
1868
|
+
});
|
|
1869
|
+
const kmsKeyInput = screen.getByRole('textbox', {
|
|
1870
|
+
name: /replica kms key id/i
|
|
1748
1871
|
});
|
|
1749
|
-
const kmsKeyInput = screen.getByLabelText(/replica kms key id/i);
|
|
1750
1872
|
const validKmsArn = 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012';
|
|
1751
1873
|
await user_event.type(kmsKeyInput, validKmsArn);
|
|
1752
1874
|
await waitFor(()=>{
|
|
@@ -47,27 +47,33 @@ describe('MetadataSearch', ()=>{
|
|
|
47
47
|
await user_event.type(input, 'test query');
|
|
48
48
|
expect(searchButton).toBeEnabled();
|
|
49
49
|
});
|
|
50
|
-
it('shows search icon by default', ()=>{
|
|
50
|
+
it('shows search icon by default', async ()=>{
|
|
51
51
|
renderMetadataSearch();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
await waitFor(()=>{
|
|
53
|
+
expect(screen.getByRole('img', {
|
|
54
|
+
hidden: true
|
|
55
|
+
})).toBeInTheDocument();
|
|
56
|
+
});
|
|
55
57
|
});
|
|
56
|
-
it('shows check icon when metadata search is active', ()=>{
|
|
58
|
+
it('shows check icon when metadata search is active', async ()=>{
|
|
57
59
|
renderMetadataSearch({}, [
|
|
58
60
|
'/bucket/test-bucket?metadatasearch=test'
|
|
59
61
|
]);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
await waitFor(()=>{
|
|
63
|
+
expect(screen.getByRole('img', {
|
|
64
|
+
hidden: true
|
|
65
|
+
})).toBeInTheDocument();
|
|
66
|
+
});
|
|
63
67
|
});
|
|
64
|
-
it('shows error icon when error prop is true', ()=>{
|
|
68
|
+
it('shows error icon when error prop is true', async ()=>{
|
|
65
69
|
renderMetadataSearch({
|
|
66
70
|
isError: true
|
|
67
71
|
});
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
await waitFor(()=>{
|
|
73
|
+
expect(screen.getByRole('img', {
|
|
74
|
+
hidden: true
|
|
75
|
+
})).toBeInTheDocument();
|
|
76
|
+
});
|
|
71
77
|
});
|
|
72
78
|
it('populates input with existing search query from URL', ()=>{
|
|
73
79
|
renderMetadataSearch({}, [
|
|
@@ -59,7 +59,9 @@ describe('ObjectLockSettings', ()=>{
|
|
|
59
59
|
await waitFor(()=>{
|
|
60
60
|
expect(screen.getByText('Object-lock settings')).toBeInTheDocument();
|
|
61
61
|
});
|
|
62
|
-
expect(screen.
|
|
62
|
+
expect(screen.getByRole('checkbox', {
|
|
63
|
+
name: /object-lock/i
|
|
64
|
+
})).toBeInTheDocument();
|
|
63
65
|
});
|
|
64
66
|
it('disables save button when Object Lock is not enabled', async ()=>{
|
|
65
67
|
renderObjectLockSettings();
|
|
@@ -81,9 +83,15 @@ describe('ObjectLockSettings', ()=>{
|
|
|
81
83
|
});
|
|
82
84
|
renderObjectLockSettings();
|
|
83
85
|
await waitFor(()=>{
|
|
84
|
-
expect(screen.
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
expect(screen.getByRole('checkbox', {
|
|
87
|
+
name: /default retention/i
|
|
88
|
+
})).toBeInTheDocument();
|
|
89
|
+
expect(screen.getByRole('radio', {
|
|
90
|
+
name: /governance/i
|
|
91
|
+
})).toBeInTheDocument();
|
|
92
|
+
expect(screen.getByRole('spinbutton', {
|
|
93
|
+
name: /retention period/i
|
|
94
|
+
})).toBeInTheDocument();
|
|
87
95
|
}, {
|
|
88
96
|
timeout: 3000
|
|
89
97
|
});
|
|
@@ -118,13 +126,19 @@ describe('ObjectLockSettings', ()=>{
|
|
|
118
126
|
});
|
|
119
127
|
renderObjectLockSettings();
|
|
120
128
|
await waitFor(()=>{
|
|
121
|
-
expect(screen.
|
|
129
|
+
expect(screen.getByRole('checkbox', {
|
|
130
|
+
name: /default retention/i
|
|
131
|
+
})).toBeInTheDocument();
|
|
132
|
+
});
|
|
133
|
+
const defaultRetentionCheckbox = screen.getByRole('checkbox', {
|
|
134
|
+
name: /default retention/i
|
|
122
135
|
});
|
|
123
|
-
const defaultRetentionCheckbox = screen.getByLabelText(/default retention/i);
|
|
124
136
|
expect(defaultRetentionCheckbox).not.toBeChecked();
|
|
125
137
|
fireEvent.click(defaultRetentionCheckbox);
|
|
126
138
|
await waitFor(()=>{
|
|
127
|
-
const governanceRadio = screen.
|
|
139
|
+
const governanceRadio = screen.getByRole('radio', {
|
|
140
|
+
name: /governance/i
|
|
141
|
+
});
|
|
128
142
|
expect(governanceRadio).not.toBeDisabled();
|
|
129
143
|
}, {
|
|
130
144
|
timeout: 3000
|
|
@@ -147,11 +161,15 @@ describe('ObjectLockSettings', ()=>{
|
|
|
147
161
|
});
|
|
148
162
|
renderObjectLockSettings();
|
|
149
163
|
await waitFor(()=>{
|
|
150
|
-
const defaultRetentionCheckbox = screen.
|
|
164
|
+
const defaultRetentionCheckbox = screen.getByRole('checkbox', {
|
|
165
|
+
name: /default retention/i
|
|
166
|
+
});
|
|
151
167
|
fireEvent.click(defaultRetentionCheckbox);
|
|
152
168
|
});
|
|
153
169
|
await waitFor(()=>{
|
|
154
|
-
const governanceRadio = screen.
|
|
170
|
+
const governanceRadio = screen.getByRole('radio', {
|
|
171
|
+
name: /governance/i
|
|
172
|
+
});
|
|
155
173
|
expect(governanceRadio).toBeDisabled();
|
|
156
174
|
});
|
|
157
175
|
});
|
|
@@ -166,15 +184,23 @@ describe('ObjectLockSettings', ()=>{
|
|
|
166
184
|
});
|
|
167
185
|
renderObjectLockSettings();
|
|
168
186
|
await waitFor(()=>{
|
|
169
|
-
expect(screen.
|
|
187
|
+
expect(screen.getByRole('checkbox', {
|
|
188
|
+
name: /default retention/i
|
|
189
|
+
})).toBeInTheDocument();
|
|
190
|
+
});
|
|
191
|
+
const defaultRetentionCheckbox = screen.getByRole('checkbox', {
|
|
192
|
+
name: /default retention/i
|
|
170
193
|
});
|
|
171
|
-
const defaultRetentionCheckbox = screen.getByLabelText(/default retention/i);
|
|
172
194
|
fireEvent.click(defaultRetentionCheckbox);
|
|
173
195
|
await waitFor(()=>{
|
|
174
|
-
const retentionPeriodInput = screen.
|
|
196
|
+
const retentionPeriodInput = screen.getByRole('spinbutton', {
|
|
197
|
+
name: /retention period/i
|
|
198
|
+
});
|
|
175
199
|
expect(retentionPeriodInput).toBeInTheDocument();
|
|
176
200
|
});
|
|
177
|
-
const retentionPeriodInput = screen.
|
|
201
|
+
const retentionPeriodInput = screen.getByRole('spinbutton', {
|
|
202
|
+
name: /retention period/i
|
|
203
|
+
});
|
|
178
204
|
await user_event.clear(retentionPeriodInput);
|
|
179
205
|
await user_event.type(retentionPeriodInput, '0');
|
|
180
206
|
await waitFor(()=>{
|
|
@@ -197,18 +223,28 @@ describe('ObjectLockSettings', ()=>{
|
|
|
197
223
|
});
|
|
198
224
|
renderObjectLockSettings();
|
|
199
225
|
await waitFor(()=>{
|
|
200
|
-
expect(screen.
|
|
226
|
+
expect(screen.getByRole('checkbox', {
|
|
227
|
+
name: /default retention/i
|
|
228
|
+
})).toBeInTheDocument();
|
|
229
|
+
});
|
|
230
|
+
const defaultRetentionCheckbox = screen.getByRole('checkbox', {
|
|
231
|
+
name: /default retention/i
|
|
201
232
|
});
|
|
202
|
-
const defaultRetentionCheckbox = screen.getByLabelText(/default retention/i);
|
|
203
233
|
fireEvent.click(defaultRetentionCheckbox);
|
|
204
234
|
await waitFor(()=>{
|
|
205
|
-
const retentionPeriodInput = screen.
|
|
235
|
+
const retentionPeriodInput = screen.getByRole('spinbutton', {
|
|
236
|
+
name: /retention period/i
|
|
237
|
+
});
|
|
206
238
|
expect(retentionPeriodInput).toBeInTheDocument();
|
|
207
239
|
});
|
|
208
|
-
const retentionPeriodInput = screen.
|
|
240
|
+
const retentionPeriodInput = screen.getByRole('spinbutton', {
|
|
241
|
+
name: /retention period/i
|
|
242
|
+
});
|
|
209
243
|
await user_event.clear(retentionPeriodInput);
|
|
210
244
|
await user_event.type(retentionPeriodInput, '30');
|
|
211
|
-
const governanceRadio = screen.
|
|
245
|
+
const governanceRadio = screen.getByRole('radio', {
|
|
246
|
+
name: /governance/i
|
|
247
|
+
});
|
|
212
248
|
fireEvent.click(governanceRadio);
|
|
213
249
|
mockMutate.mockImplementation((_, options)=>{
|
|
214
250
|
options?.onSuccess?.();
|
|
@@ -241,15 +277,23 @@ describe('ObjectLockSettings', ()=>{
|
|
|
241
277
|
});
|
|
242
278
|
renderObjectLockSettings();
|
|
243
279
|
await waitFor(()=>{
|
|
244
|
-
expect(screen.
|
|
280
|
+
expect(screen.getByRole('checkbox', {
|
|
281
|
+
name: /default retention/i
|
|
282
|
+
})).toBeInTheDocument();
|
|
283
|
+
});
|
|
284
|
+
const defaultRetentionCheckbox = screen.getByRole('checkbox', {
|
|
285
|
+
name: /default retention/i
|
|
245
286
|
});
|
|
246
|
-
const defaultRetentionCheckbox = screen.getByLabelText(/default retention/i);
|
|
247
287
|
fireEvent.click(defaultRetentionCheckbox);
|
|
248
288
|
await waitFor(()=>{
|
|
249
|
-
const retentionPeriodInput = screen.
|
|
289
|
+
const retentionPeriodInput = screen.getByRole('spinbutton', {
|
|
290
|
+
name: /retention period/i
|
|
291
|
+
});
|
|
250
292
|
expect(retentionPeriodInput).toBeInTheDocument();
|
|
251
293
|
});
|
|
252
|
-
const retentionPeriodInput = screen.
|
|
294
|
+
const retentionPeriodInput = screen.getByRole('spinbutton', {
|
|
295
|
+
name: /retention period/i
|
|
296
|
+
});
|
|
253
297
|
await user_event.clear(retentionPeriodInput);
|
|
254
298
|
await user_event.type(retentionPeriodInput, '2');
|
|
255
299
|
mockMutate.mockImplementation((_, options)=>{
|
|
@@ -361,14 +405,22 @@ describe('ObjectLockSettings', ()=>{
|
|
|
361
405
|
});
|
|
362
406
|
renderObjectLockSettings();
|
|
363
407
|
await waitFor(()=>{
|
|
364
|
-
const objectLockCheckbox = screen.
|
|
408
|
+
const objectLockCheckbox = screen.getByRole('checkbox', {
|
|
409
|
+
name: /object-lock/i
|
|
410
|
+
});
|
|
365
411
|
expect(objectLockCheckbox).toBeChecked();
|
|
366
412
|
});
|
|
367
|
-
const defaultRetentionCheckbox = screen.
|
|
413
|
+
const defaultRetentionCheckbox = screen.getByRole('checkbox', {
|
|
414
|
+
name: /default retention/i
|
|
415
|
+
});
|
|
368
416
|
expect(defaultRetentionCheckbox).toBeChecked();
|
|
369
|
-
const complianceRadio = screen.
|
|
417
|
+
const complianceRadio = screen.getByRole('radio', {
|
|
418
|
+
name: /compliance/i
|
|
419
|
+
});
|
|
370
420
|
expect(complianceRadio).toBeChecked();
|
|
371
|
-
const retentionPeriodInput = screen.
|
|
421
|
+
const retentionPeriodInput = screen.getByRole('spinbutton', {
|
|
422
|
+
name: /retention period/i
|
|
423
|
+
});
|
|
372
424
|
expect(retentionPeriodInput).toHaveValue(60);
|
|
373
425
|
});
|
|
374
426
|
});
|
|
@@ -212,7 +212,9 @@ describe('s3RuntimeConfigSchema', ()=>{
|
|
|
212
212
|
endpoint: 'https://s3.amazonaws.com',
|
|
213
213
|
region: 'us-east-1',
|
|
214
214
|
forcePathStyle: true
|
|
215
|
-
}
|
|
215
|
+
},
|
|
216
|
+
allowCustomEndpoint: true,
|
|
217
|
+
allowSessionToken: true
|
|
216
218
|
};
|
|
217
219
|
const { error } = s3RuntimeConfigSchema.validate(config);
|
|
218
220
|
expect(error).toBeUndefined();
|
|
@@ -282,6 +284,32 @@ describe('s3RuntimeConfigSchema', ()=>{
|
|
|
282
284
|
expect(error).toBeDefined();
|
|
283
285
|
expect(error?.message).toContain('boolean');
|
|
284
286
|
});
|
|
287
|
+
it('should reject config with wrong type for allowCustomEndpoint', ()=>{
|
|
288
|
+
const config = {
|
|
289
|
+
s3: {
|
|
290
|
+
region: 'us-east-1'
|
|
291
|
+
},
|
|
292
|
+
allowCustomEndpoint: 'yes'
|
|
293
|
+
};
|
|
294
|
+
const { error } = s3RuntimeConfigSchema.validate(config, {
|
|
295
|
+
convert: false
|
|
296
|
+
});
|
|
297
|
+
expect(error).toBeDefined();
|
|
298
|
+
expect(error?.message).toContain('boolean');
|
|
299
|
+
});
|
|
300
|
+
it('should reject config with wrong type for allowSessionToken', ()=>{
|
|
301
|
+
const config = {
|
|
302
|
+
s3: {
|
|
303
|
+
region: 'us-east-1'
|
|
304
|
+
},
|
|
305
|
+
allowSessionToken: 'yes'
|
|
306
|
+
};
|
|
307
|
+
const { error } = s3RuntimeConfigSchema.validate(config, {
|
|
308
|
+
convert: false
|
|
309
|
+
});
|
|
310
|
+
expect(error).toBeDefined();
|
|
311
|
+
expect(error?.message).toContain('boolean');
|
|
312
|
+
});
|
|
285
313
|
});
|
|
286
314
|
describe('Schema behavior', ()=>{
|
|
287
315
|
it('should return all validation errors when abortEarly is false', ()=>{
|
package/dist/config/factory.d.ts
CHANGED
package/dist/config/factory.js
CHANGED
|
@@ -4,7 +4,9 @@ const s3RuntimeConfigSchema = joi.object({
|
|
|
4
4
|
endpoint: joi.string().optional().allow('origin'),
|
|
5
5
|
region: joi.string().min(1).required(),
|
|
6
6
|
forcePathStyle: joi.boolean().optional()
|
|
7
|
-
}).required()
|
|
7
|
+
}).required(),
|
|
8
|
+
allowCustomEndpoint: joi.boolean().optional(),
|
|
9
|
+
allowSessionToken: joi.boolean().optional()
|
|
8
10
|
});
|
|
9
11
|
async function loadRuntimeConfig(configUrl) {
|
|
10
12
|
const response = await fetch(configUrl);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scality/data-browser-library",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "A modular React component library for browsing S3 buckets and objects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -40,10 +40,11 @@
|
|
|
40
40
|
"react-hook-form": "^7.48.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@scality/core-ui": "0.
|
|
43
|
+
"@scality/core-ui": ">=0.197.0",
|
|
44
44
|
"react": ">=18.0.0",
|
|
45
45
|
"react-dom": ">=18.0.0",
|
|
46
|
-
"react-router
|
|
46
|
+
"react-router": ">=7.1.3",
|
|
47
|
+
"react-router-dom": ">=7.1.3",
|
|
47
48
|
"styled-components": "^5.0.0"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|