@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.
@@ -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.getByLabelText(/bucket name/i);
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.getByLabelText(/bucket name/i)).toBeInTheDocument();
105
- expect(screen.getByText('Versioning')).toBeInTheDocument();
106
- expect(screen.getByText('Object-lock')).toBeInTheDocument();
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.getByLabelText(/bucket name/i), 'INVALID');
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.getByLabelText(/bucket name/i), '-invalid');
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.getByLabelText(/bucket name/i), 'invalid-');
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.getByLabelText(/bucket name/i), 'test..bucket');
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.getByLabelText(/bucket name/i), '192.168.1.1');
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.getByLabelText(/bucket name/i), 'xn--test');
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.getByLabelText(/bucket name/i), 'test-s3alias');
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.getByLabelText(/bucket name/i), 'existing-bucket');
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.getByLabelText(/bucket name/i);
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.getByLabelText(/encryption/i);
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.getByLabelText(/versioning/i));
520
- await user_event.click(screen.getByLabelText(/encryption/i));
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.getByLabelText(/object-lock/i));
540
- await user_event.click(screen.getByLabelText(/default retention/i));
541
- await user_event.click(screen.getByLabelText(/encryption/i));
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.getByLabelText(/encryption/i));
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.getByText('Bucket Name')).toBeInTheDocument();
49
- expect(screen.getByText('Storage Location')).toBeInTheDocument();
50
- expect(screen.getByText('Created on')).toBeInTheDocument();
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', 'Example: Search');
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.getByText('Bucket Name')).toBeInTheDocument();
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.getByText('Created on'));
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.getByText('Storage Location'));
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.getByLabelText(/rule name/i)).toBeInTheDocument();
70
- expect(screen.getByText('Events')).toBeInTheDocument();
71
- expect(screen.getByLabelText(/destination queue/i)).toBeInTheDocument();
72
- expect(screen.getByLabelText(/prefix/i)).toBeInTheDocument();
73
- expect(screen.getByLabelText(/suffix/i)).toBeInTheDocument();
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.getByLabelText(/rule name/i);
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.getByLabelText(/rule name/i);
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.getByLabelText(/destination queue/i);
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.getByLabelText(/rule name/i), 'test-rule');
124
- await user_event.type(screen.getByLabelText(/destination queue/i), 'arn:aws:sqs:us-east-1:123:queue');
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.getByLabelText(/rule name/i), 'test-rule');
138
- await user_event.type(screen.getByLabelText(/destination queue/i), 'arn:aws:sqs:us-east-1:123:my-queue');
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.getByLabelText(/prefix/i), 'uploads/');
143
- await user_event.type(screen.getByLabelText(/suffix/i), '.jpg');
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.getByLabelText(/destination queue/i)).toHaveValue('arn:aws:sqs:us-east-1:123:existing-queue');
267
- expect(screen.getByLabelText(/prefix/i)).toHaveValue('data/');
268
- expect(screen.getByLabelText(/suffix/i)).toHaveValue('.json');
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.getByLabelText(/destination queue/i)).toHaveValue('arn:aws:sqs:us-east-1:123:existing-queue');
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.getByLabelText(/role arn/i), roleArn);
59
- await user_event.type(screen.getByLabelText(/rule id/i), ruleId);
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.getByLabelText(/role arn/i)).toBeInTheDocument();
151
- expect(screen.getByLabelText(/rule id/i)).toBeInTheDocument();
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.getByLabelText(/role arn/i)).toBeInTheDocument();
161
- expect(screen.getByLabelText(/rule id/i)).toBeInTheDocument();
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.getByLabelText(/role arn/i);
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.getByLabelText(/rule id/i);
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.getByLabelText(/rule id/i)).toBeInTheDocument();
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.getByLabelText(/rule priority/i);
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.getByLabelText(/role arn/i);
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.getByLabelText(/rule id/i), 'new-rule');
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.getByLabelText(/rule id/i);
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.getByLabelText(/rule id/i);
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.getByLabelText(/rule id/i), 'test-rule');
364
- const priorityInput = screen.getByLabelText(/rule priority/i);
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.getByLabelText(/role arn/i), 'arn:aws:iam::123456789012:role/role');
375
- await user_event.type(screen.getByLabelText(/rule id/i), 'test-rule');
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.getByLabelText(/target account id/i)).toBeInTheDocument();
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.getByLabelText(/replica kms key id/i)).toBeInTheDocument();
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.getByLabelText(/prefix/i)).toBeInTheDocument();
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.getByLabelText(/target account id/i)).toBeInTheDocument();
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.getByLabelText(/replica kms key id/i)).toBeInTheDocument();
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.getByLabelText(/role arn/i), 'arn:aws:iam::123456789012:role/replication-role');
560
- await user_event.type(screen.getByLabelText(/rule id/i), 'complete-rule');
561
- await user_event.type(screen.getByLabelText(/rule priority/i), '5');
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.getByLabelText(/prefix/i)).toBeInTheDocument();
627
+ expect(screen.getByRole('textbox', {
628
+ name: /prefix/i
629
+ })).toBeInTheDocument();
574
630
  });
575
- await user_event.type(screen.getByLabelText(/prefix/i), 'logs/');
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.getByLabelText(/target account id/i)).toBeInTheDocument();
637
+ expect(screen.getByRole('textbox', {
638
+ name: /target account id/i
639
+ })).toBeInTheDocument();
580
640
  });
581
- await user_event.type(screen.getByLabelText(/target account id/i), '987654321098');
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.getByLabelText(/replica kms key id/i)).toBeInTheDocument();
660
+ expect(screen.getByRole('textbox', {
661
+ name: /replica kms key id/i
662
+ })).toBeInTheDocument();
599
663
  });
600
- await user_event.type(screen.getByLabelText(/replica kms key id/i), 'arn:aws:kms:us-east-1:987654321098:key/12345678-1234-1234-1234-123456789012');
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.getByLabelText(/rule id/i), 'new-rule');
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.getByLabelText(/rule id/i), 'test');
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.getByLabelText(/prefix/i);
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.getByLabelText(/prefix/i);
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.getByLabelText(/replica kms key id/i);
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.getByLabelText(/target account id/i);
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.getByLabelText(/rule priority/i);
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.getByLabelText(/rule priority/i);
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.getByLabelText(/rule priority/i);
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.getByLabelText(/rule priority/i);
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.getByLabelText(/role arn/i), 'arn:aws:iam::123456789012:role/role');
1439
- await user_event.type(screen.getByLabelText(/rule id/i), 'no-priority-rule');
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.getByLabelText(/role arn/i), 'arn:aws:iam::123456789012:role/role');
1524
- await user_event.type(screen.getByLabelText(/rule id/i), 'test-rule');
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.getByLabelText(/rule priority/i);
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.getByLabelText(/rule id/i);
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.getByLabelText(/rule id/i);
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.getByLabelText(/rule id/i), 'boundary-test');
1676
- const priorityInput = screen.getByLabelText(/rule priority/i);
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.getByLabelText(/prefix/i)).toBeInTheDocument();
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.getByLabelText(/prefix/i);
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.getByLabelText(/role arn/i);
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.getByLabelText(/target account id/i)).toBeInTheDocument();
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.getByLabelText(/replica kms key id/i)).toBeInTheDocument();
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
- const input = getInput();
53
- const searchIcon = input.parentElement?.querySelector('[aria-label*="Search"]');
54
- expect(searchIcon).toBeInTheDocument();
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
- const input = getInput();
61
- const checkIcon = input.parentElement?.querySelector('[aria-label*="Check"]');
62
- expect(checkIcon).toBeInTheDocument();
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
- const input = getInput();
69
- const errorIcon = input.parentElement?.querySelector('[aria-label*="Close"]');
70
- expect(errorIcon).toBeInTheDocument();
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.getByLabelText(/object-lock/i)).toBeInTheDocument();
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.getByText('Default Retention')).toBeInTheDocument();
85
- expect(screen.getByText('Retention mode')).toBeInTheDocument();
86
- expect(screen.getByText('Retention period')).toBeInTheDocument();
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.getByText('Default Retention')).toBeInTheDocument();
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.getByLabelText(/governance/i);
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.getByLabelText(/default retention/i);
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.getByLabelText(/governance/i);
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.getByText('Default Retention')).toBeInTheDocument();
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.getByLabelText(/retention period/i);
196
+ const retentionPeriodInput = screen.getByRole('spinbutton', {
197
+ name: /retention period/i
198
+ });
175
199
  expect(retentionPeriodInput).toBeInTheDocument();
176
200
  });
177
- const retentionPeriodInput = screen.getByLabelText(/retention period/i);
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.getByText('Default Retention')).toBeInTheDocument();
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.getByLabelText(/retention period/i);
235
+ const retentionPeriodInput = screen.getByRole('spinbutton', {
236
+ name: /retention period/i
237
+ });
206
238
  expect(retentionPeriodInput).toBeInTheDocument();
207
239
  });
208
- const retentionPeriodInput = screen.getByLabelText(/retention period/i);
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.getByLabelText(/governance/i);
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.getByText('Default Retention')).toBeInTheDocument();
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.getByLabelText(/retention period/i);
289
+ const retentionPeriodInput = screen.getByRole('spinbutton', {
290
+ name: /retention period/i
291
+ });
250
292
  expect(retentionPeriodInput).toBeInTheDocument();
251
293
  });
252
- const retentionPeriodInput = screen.getByLabelText(/retention period/i);
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.getByLabelText(/object-lock/i);
408
+ const objectLockCheckbox = screen.getByRole('checkbox', {
409
+ name: /object-lock/i
410
+ });
365
411
  expect(objectLockCheckbox).toBeChecked();
366
412
  });
367
- const defaultRetentionCheckbox = screen.getByLabelText(/default retention/i);
413
+ const defaultRetentionCheckbox = screen.getByRole('checkbox', {
414
+ name: /default retention/i
415
+ });
368
416
  expect(defaultRetentionCheckbox).toBeChecked();
369
- const complianceRadio = screen.getByLabelText(/compliance/i);
417
+ const complianceRadio = screen.getByRole('radio', {
418
+ name: /compliance/i
419
+ });
370
420
  expect(complianceRadio).toBeChecked();
371
- const retentionPeriodInput = screen.getByLabelText(/retention period/i);
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', ()=>{
@@ -5,6 +5,8 @@ export type S3RuntimeConfig = {
5
5
  region: string;
6
6
  forcePathStyle?: boolean;
7
7
  };
8
+ allowCustomEndpoint?: boolean;
9
+ allowSessionToken?: boolean;
8
10
  };
9
11
  export declare const s3RuntimeConfigSchema: Joi.ObjectSchema<any>;
10
12
  /**
@@ -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.5",
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.194.0",
43
+ "@scality/core-ui": ">=0.197.0",
44
44
  "react": ">=18.0.0",
45
45
  "react-dom": ">=18.0.0",
46
- "react-router-dom": ">=6.0.0",
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": {