Skip to content

Commit a6ec9e4

Browse files
committed
fix(FR-2652): add Form.Item name to BAIDeleteConfirmModal for label-input association
1 parent ae0ab6f commit a6ec9e4

3 files changed

Lines changed: 93 additions & 67 deletions

File tree

e2e/E2E_COVERAGE_REPORT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# E2E Test Coverage Report
22

3-
> **Last Updated:** 2026-04-22
3+
> **Last Updated:** 2026-04-24
44
> **Router Source:** [`react/src/routes.tsx`](../react/src/routes.tsx)
55
> **E2E Root:** [`e2e/`](.)
66
>

e2e/admin-model-card/admin-model-card-delete.spec.ts

Lines changed: 90 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,49 @@ test.describe(
1515
() => {
1616
test.setTimeout(60000);
1717

18+
const createdResources: Array<{ cardName?: string; folderName?: string }> =
19+
[];
20+
1821
test.beforeEach(async ({ page, request }) => {
1922
await loginAsAdmin(page, request);
2023
});
2124

25+
test.afterEach(async ({ page }) => {
26+
const adminModelCardPage = new AdminModelCardPage(page);
27+
for (const r of [...createdResources].reverse()) {
28+
if (r.cardName) {
29+
try {
30+
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
31+
await adminModelCardPage.waitForTableLoad();
32+
await adminModelCardPage.applyNameFilter(r.cardName);
33+
const row = adminModelCardPage.getRowByName(r.cardName);
34+
if (await row.isVisible()) {
35+
await adminModelCardPage.deleteModelCardByName(r.cardName);
36+
}
37+
} catch {
38+
// ignore — card may have already been deleted by the test
39+
}
40+
}
41+
if (r.folderName) {
42+
try {
43+
await moveToTrashAndVerify(page, r.folderName, 'admin-data');
44+
} catch {
45+
// ignore — folder may already be in trash or deleted
46+
}
47+
try {
48+
await deleteForeverAndVerifyFromTrash(
49+
page,
50+
r.folderName,
51+
'admin-data',
52+
);
53+
} catch {
54+
// ignore — folder may already be permanently deleted
55+
}
56+
}
57+
}
58+
createdResources.length = 0;
59+
});
60+
2261
// 5.1 Superadmin can delete a model card via the trash icon with confirmation
2362
test('Superadmin can delete a model card via the trash icon with confirmation', async ({
2463
page,
@@ -29,6 +68,9 @@ test.describe(
2968
const folderName = `e2e-test-delete-single-folder-${timestamp}`;
3069
const cardName = `e2e-test-delete-single-${timestamp}`;
3170

71+
// Track folder for cleanup (card will be deleted by test, folder needs cleanup)
72+
createdResources.push({ folderName });
73+
3274
// Setup: create a dedicated folder and model card
3375
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
3476
await adminModelCardPage.waitForTableLoad();
@@ -47,13 +89,12 @@ test.describe(
4789

4890
const confirmDialog = adminModelCardPage.getDeleteConfirmDialog();
4991

50-
// Verify the confirmation dialog shows description and item name
92+
// Verify the confirmation dialog shows description text
93+
// (For single item + requireConfirmInput, the item list box is removed;
94+
// the card name appears in the description text instead)
5195
await expect(
5296
confirmDialog.getByText(/Are you sure you want to delete/),
5397
).toBeVisible();
54-
await expect(
55-
confirmDialog.getByText(cardName, { exact: true }).first(),
56-
).toBeVisible();
5798
await expect(
5899
confirmDialog.getByText('This action cannot be undone.'),
59100
).toBeVisible();
@@ -83,9 +124,7 @@ test.describe(
83124
'0 items',
84125
);
85126

86-
// Cleanup: move folder to trash then permanently delete
87-
await moveToTrashAndVerify(page, folderName, 'admin-data');
88-
await deleteForeverAndVerifyFromTrash(page, folderName, 'admin-data');
127+
// Card was successfully deleted — afterEach only needs to clean up the folder
89128
});
90129

91130
// 5.2 Superadmin can cancel a single-delete confirmation without deleting
@@ -98,6 +137,9 @@ test.describe(
98137
const folderName = `e2e-test-no-delete-folder-${timestamp}`;
99138
const cardName = `e2e-test-no-delete-${timestamp}`;
100139

140+
// Track both for cleanup (test does not delete them)
141+
createdResources.push({ cardName, folderName });
142+
101143
// Setup: create a dedicated folder and model card
102144
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
103145
await adminModelCardPage.waitForTableLoad();
@@ -122,11 +164,6 @@ test.describe(
122164

123165
// Verify the model card is still in the table
124166
await expect(adminModelCardPage.getRowByName(cardName)).toBeVisible();
125-
126-
// Cleanup: delete card only, then move folder to trash and permanently delete
127-
await adminModelCardPage.deleteModelCardByName(cardName);
128-
await moveToTrashAndVerify(page, folderName, 'admin-data');
129-
await deleteForeverAndVerifyFromTrash(page, folderName, 'admin-data');
130167
});
131168

132169
// 5.3 Superadmin can select multiple model cards and delete them in bulk
@@ -144,6 +181,9 @@ test.describe(
144181
`${filterPrefix}-3`,
145182
];
146183

184+
// Track folder for cleanup (cards will be deleted by test)
185+
createdResources.push({ folderName });
186+
147187
// Setup: create a shared folder via the "+" button for the first card,
148188
// then reuse it for the remaining cards
149189
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
@@ -171,7 +211,9 @@ test.describe(
171211
});
172212

173213
// Check the header checkbox to select all visible rows
174-
await adminModelCardPage.getHeaderCheckbox().check();
214+
// Use .click() instead of .check() — Ant Design's "select all" checkbox can be in
215+
// indeterminate state when some rows are pre-selected, and .check() fails in that case.
216+
await adminModelCardPage.getHeaderCheckbox().click();
175217

176218
// Verify the BAISelectionLabel appears showing selected count
177219
await expect(adminModelCardPage.getSelectionLabel()).toBeVisible();
@@ -194,7 +236,7 @@ test.describe(
194236
}
195237

196238
// Type "Delete" in the confirmation input (required for bulk delete)
197-
await bulkDialog.getByRole('textbox').fill('Delete');
239+
await bulkDialog.getByLabel(/Type.*to confirm/i).fill('Delete');
198240

199241
// Click Delete to confirm
200242
await bulkDialog.getByRole('button', { name: 'Delete' }).click();
@@ -205,10 +247,7 @@ test.describe(
205247
// Verify the selection label disappears
206248
await expect(adminModelCardPage.getSelectionLabel()).toBeHidden();
207249

208-
// Cleanup: model cards were deleted but the shared folder remains;
209-
// move it to trash and permanently delete
210-
await moveToTrashAndVerify(page, folderName, 'admin-data');
211-
await deleteForeverAndVerifyFromTrash(page, folderName, 'admin-data');
250+
// Cards were deleted by test — afterEach only needs to clean up the shared folder
212251
});
213252

214253
// 5.4 Superadmin can cancel bulk deletion
@@ -220,6 +259,10 @@ test.describe(
220259
const filterPrefix = `e2e-test-bulk-cancel-${timestamp}`;
221260
const cardNames = [`${filterPrefix}-1`, `${filterPrefix}-2`];
222261

262+
// Track both cards and folder for cleanup (test does not delete them)
263+
createdResources.push({ cardName: cardNames[0], folderName });
264+
createdResources.push({ cardName: cardNames[1] });
265+
223266
// Setup: create a shared folder via the "+" button for the first card,
224267
// then reuse it for the second card
225268
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
@@ -267,13 +310,6 @@ test.describe(
267310
for (const name of cardNames) {
268311
await expect(adminModelCardPage.getRowByName(name)).toBeVisible();
269312
}
270-
271-
// Cleanup: delete each model card (card only), then clean up the shared folder
272-
for (const name of cardNames) {
273-
await adminModelCardPage.deleteModelCardByName(name);
274-
}
275-
await moveToTrashAndVerify(page, folderName, 'admin-data');
276-
await deleteForeverAndVerifyFromTrash(page, folderName, 'admin-data');
277313
});
278314

279315
// 5.5 Superadmin can clear selection using the BAISelectionLabel clear button
@@ -286,6 +322,9 @@ test.describe(
286322
const folderName = `e2e-test-clear-sel-folder-${timestamp}`;
287323
const cardName = `e2e-test-clear-sel-${timestamp}`;
288324

325+
// Track both for cleanup (test does not delete them)
326+
createdResources.push({ cardName, folderName });
327+
289328
// Setup: create a model card so the table has at least one row with a checkbox
290329
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
291330
await adminModelCardPage.waitForTableLoad();
@@ -320,11 +359,6 @@ test.describe(
320359

321360
// Verify the BAISelectionLabel disappears
322361
await expect(adminModelCardPage.getSelectionLabel()).toBeHidden();
323-
324-
// Cleanup: delete the model card then clean up the folder
325-
await adminModelCardPage.deleteModelCardByName(cardName);
326-
await moveToTrashAndVerify(page, folderName, 'admin-data');
327-
await deleteForeverAndVerifyFromTrash(page, folderName, 'admin-data');
328362
});
329363

330364
// 5.6 Superadmin can select all model cards using the header checkbox
@@ -337,6 +371,9 @@ test.describe(
337371
const folderName = `e2e-test-select-all-folder-${timestamp}`;
338372
const cardName = `e2e-test-select-all-${timestamp}`;
339373

374+
// Track both for cleanup
375+
createdResources.push({ cardName, folderName });
376+
340377
// Setup: create a model card so the table has at least one row
341378
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
342379
await adminModelCardPage.waitForTableLoad();
@@ -370,17 +407,6 @@ test.describe(
370407
selectionText?.match(/(\d+) selected/)?.[1] ?? '0',
371408
);
372409
expect(selectedCount).toBeGreaterThan(0);
373-
374-
// Cleanup: navigate fresh to reset selection state, then delete the model card
375-
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
376-
await adminModelCardPage.waitForTableLoad();
377-
await adminModelCardPage.applyNameFilter(cardName);
378-
await expect(adminModelCardPage.getRowByName(cardName)).toBeVisible({
379-
timeout: 15000,
380-
});
381-
await adminModelCardPage.deleteModelCardByName(cardName);
382-
await moveToTrashAndVerify(page, folderName, 'admin-data');
383-
await deleteForeverAndVerifyFromTrash(page, folderName, 'admin-data');
384410
});
385411

386412
// 5.7 Superadmin can delete a model card and its associated folder together
@@ -393,6 +419,11 @@ test.describe(
393419
const folderName = `e2e-test-delete-folder-${timestamp}`;
394420
const cardName = `e2e-test-delete-with-folder-${timestamp}`;
395421

422+
// No createdResources.push here — this test fully handles its own cleanup:
423+
// the modal deletes the card, and deleteForeverAndVerifyFromTrash permanently
424+
// removes the folder at the end of the test body. afterEach must not attempt
425+
// to re-clean a folder that no longer exists (it would hang until timeout).
426+
396427
// Create a model card with a new dedicated folder via the "+" button
397428
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
398429
await adminModelCardPage.waitForTableLoad();
@@ -445,20 +476,27 @@ test.describe(
445476
await expect(goToTrashLink).toBeVisible();
446477

447478
// Click "Go to Data > Trash" and verify URL includes folder filter
479+
// The link navigates to /admin-data (not /data) with statusCategory=deleted and folder filter.
480+
// The filter param value may appear URL-encoded in various ways depending on the library
481+
// (e.g. `name+%3D%3D+%22folderName%22` or `name%20%3D%3D%20%22folderName%22`).
482+
// Use decodeURIComponent after re-encoding to normalize before comparison.
448483
await goToTrashLink.click();
449484
await page.waitForURL(
450-
(url) =>
451-
url.pathname === '/data' &&
452-
url.searchParams.get('statusCategory') === 'deleted' &&
453-
url.searchParams.get('filter') === `name == "${folderName}"`,
485+
(url) => {
486+
if (url.pathname !== '/admin-data') return false;
487+
if (url.searchParams.get('statusCategory') !== 'deleted')
488+
return false;
489+
const rawFilter = url.searchParams.get('filter') ?? '';
490+
return rawFilter === `name == "${folderName}"`;
491+
},
454492
{ timeout: 10000 },
455493
);
456494

457495
// Verify the folder row is visible in the trash list and permanently delete it
458496
await deleteForeverAndVerifyFromTrash(page, folderName, 'admin-data');
459497
});
460498

461-
// 5.8 Superadmin deletes card only: notification shows correct message and Go to Trash navigates correctly
499+
// 5.8 Superadmin deletes card only: notification shows correct message (no folder link)
462500
test('Superadmin can delete a model card only and navigate to trash without folder filter', async ({
463501
page,
464502
}) => {
@@ -468,6 +506,9 @@ test.describe(
468506
const folderName = `e2e-test-keep-folder-${timestamp}`;
469507
const cardName = `e2e-test-delete-card-only-${timestamp}`;
470508

509+
// Track folder for cleanup (card is deleted by test, folder is kept)
510+
createdResources.push({ folderName });
511+
471512
// Create a model card with a new dedicated folder via the "+" button
472513
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
473514
await adminModelCardPage.waitForTableLoad();
@@ -495,30 +536,15 @@ test.describe(
495536
// Confirm deletion
496537
await adminModelCardPage.getDeleteConfirmButton().click();
497538

498-
// Verify the notification message for card-only deletion
539+
// When folder is NOT deleted, a message.success() toast is shown (no "Go to Data > Trash" link)
540+
// Verify the success toast for card-only deletion
499541
await expect(
500542
page.getByText(
501543
'Model card has been deleted. The model folder was not deleted.',
502544
),
503545
).toBeVisible({ timeout: 15000 });
504546

505-
// Verify "Go to Data > Trash" link is visible
506-
const goToTrashLink = page.getByText('Go to Data > Trash');
507-
await expect(goToTrashLink).toBeVisible();
508-
509-
// Click "Go to Data > Trash" and verify URL (no folder filter)
510-
await goToTrashLink.click();
511-
await page.waitForURL(
512-
(url) =>
513-
url.pathname === '/data' &&
514-
url.searchParams.get('statusCategory') === 'deleted' &&
515-
!url.searchParams.has('filter'),
516-
{ timeout: 10000 },
517-
);
518-
519-
// Cleanup: move the kept test folder to trash then permanently delete it
520-
await moveToTrashAndVerify(page, folderName, 'admin-data');
521-
await deleteForeverAndVerifyFromTrash(page, folderName, 'admin-data');
547+
// Card was deleted; afterEach will clean up the folder
522548
});
523549
},
524550
);

packages/backend.ai-ui/src/components/BAIDeleteConfirmModal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,11 @@ const BAIDeleteConfirmModal: React.FC<BAIDeleteConfirmModalProps> = ({
160160
<BAIFlex direction="column" align="stretch" gap="xs">
161161
<Text>{resolvedDescription}</Text>
162162
{items.length > 1 && itemListContent}
163-
<Form layout="vertical" preserve={false}>
163+
<Form layout="vertical" requiredMark={false} preserve={false}>
164164
<Form.Item
165+
name="confirmText"
165166
label={resolvedInputLabel}
166167
style={{ marginBottom: 0 }}
167-
required
168168
>
169169
<Input
170170
autoFocus

0 commit comments

Comments
 (0)