feat: confirmations destructives de /trash en fenêtre thématisée (v7.45.6)
Les 3 confirmations destructives de la poubelle (suppression définitive unitaire, suppression groupée, Empty Trash) remplacent le confirm() du navigateur par une fenêtre FlowDeck : - réutilise les classes flowdeck-modal-overlay / .flowdeck-modal déjà présentes dans base.html (variables --bg-secondary / --border / --accent, rayon 12 px, boutons Cancel + confirmation coloré) → aucun nouveau CSS ; - overlay cliquable pour annuler, Échap pour annuler, × pour annuler ; - bouton de confirmation porteur du libellé exact (Delete / Delete all / Empty Trash) et message reprenant le nom de la page ou le nombre de pages ; - role="alertdialog" aria-modal + aria-labelledby ; - composant dans trashData() : askConfirm / cancelConfirm / acceptConfirm, mutations de propriétés individuelles (pitfall réactivité Alpine) ; - id #fd-trash-confirm (la classe flowdeck-modal-overlay sert déjà à #fd-global-folder-modal → locator unique côté test) ; - aucun confirm() natif ne subsiste dans trash.html. Gates : e2e/trash_ui.spec.js étendu — ouverture + titre + message de la fenêtre, ANNULATION vérifiée (page toujours présente), confirmation (suppression réelle : plus dans la poubelle + contenu effacé), fenêtre Empty Trash ouverte/annulée quel que soit le contenu, purge réelle seulement si la poubelle ne contient que des pages E2E (instance partagée) ; le handler dialog du test échoue si un dialog natif revient (NATIVE_DIALOG). Suite e2e 11 passed · pytest 1099 passed · ruff OK · OpenAPI 510 chemins / 7.45.6.
This commit is contained in:
+50
-4
@@ -51,14 +51,16 @@ test('trash : rendu, dates, sélection groupée, restauration, purge', async ({
|
||||
const errs = [];
|
||||
page.on('pageerror', (e) => errs.push('PAGEERROR ' + String(e).slice(0, 160)));
|
||||
page.on('console', (m) => { if (m.type() === 'error') errs.push('CONSOLE ' + m.text().slice(0, 160)); });
|
||||
page.on('dialog', (d) => d.accept()); // confirm() des actions destructives
|
||||
// Aucun dialog système acceptable : on échoue si le navigateur ouvre un
|
||||
// confirm()/alert() natif (c'est tout l'objet du changement).
|
||||
page.on('dialog', (d) => { errs.push('NATIVE_DIALOG ' + d.message()); d.dismiss().catch(() => {}); });
|
||||
|
||||
await login(page);
|
||||
|
||||
// DB persistante : nommage unique par exécution, on ne dépend jamais des
|
||||
// restes des runs précédents.
|
||||
const TAG = 'E2E Trash ' + Date.now().toString(36).toUpperCase();
|
||||
const nameA = TAG + ' A', nameB = TAG + ' B', nameC = TAG + ' C', nameD = TAG + ' D';
|
||||
const nameA = TAG + ' A', nameB = TAG + ' B', nameC = TAG + ' C', nameD = TAG + ' D', nameE = TAG + ' E';
|
||||
|
||||
const idA = await mkTrashed(page, nameA);
|
||||
const idB = await mkTrashed(page, nameB);
|
||||
@@ -109,6 +111,37 @@ test('trash : rendu, dates, sélection groupée, restauration, purge', async ({
|
||||
ids = await trashIds(page);
|
||||
expect(ids).not.toContain(idC);
|
||||
|
||||
// Suppression définitive unitaire → fenêtre de confirmation THÉMATIQUE
|
||||
// (plus de confirm() du navigateur)
|
||||
const idE = await mkTrashed(page, nameE);
|
||||
await page.reload({ waitUntil: 'load' });
|
||||
await page.waitForTimeout(900);
|
||||
const rowE = page.locator('.trash-item', { hasText: nameE });
|
||||
await expect(rowE).toHaveCount(1);
|
||||
await rowE.first().locator('button[title="Delete permanently"]').click();
|
||||
|
||||
const modal = page.locator('#fd-trash-confirm'); // id sinon 2e match #fd-global-folder-modal
|
||||
await expect(modal).toBeVisible();
|
||||
await expect(modal.locator('h3')).toHaveText('Delete permanently');
|
||||
await expect(modal.locator('.flowdeck-modal-body p')).toContainText(nameE);
|
||||
|
||||
// Annulation → la page est toujours là
|
||||
await modal.locator('button.cancel').click();
|
||||
await expect(modal).toBeHidden();
|
||||
await expect(rowE).toHaveCount(1);
|
||||
|
||||
// Confirmation → suppression réelle (plus dans la poubelle, contenu effacé)
|
||||
await rowE.first().locator('button[title="Delete permanently"]').click();
|
||||
await expect(modal).toBeVisible();
|
||||
await modal.locator('button.confirm').click();
|
||||
await page.waitForTimeout(900);
|
||||
await expect(rowE).toHaveCount(0);
|
||||
expect(await trashIds(page)).not.toContain(idE);
|
||||
// Le SW transforme les réponses API non-ok (404 ici) en erreur réseau →
|
||||
// `api()` peut rejeter ; dans les deux cas la page n'est plus servable.
|
||||
const content = await api(page, 'GET', '/api/pages/' + idE + '/content').catch(() => ({ ok: false, status: 0 }));
|
||||
expect(content.ok).toBeFalsy();
|
||||
|
||||
// Purge totale — seulement si la poubelle ne contient QUE des pages E2E
|
||||
// (l'instance de test est partagée : on ne vide jamais la poubelle d'autrui).
|
||||
await mkTrashed(page, nameD);
|
||||
@@ -116,8 +149,20 @@ test('trash : rendu, dates, sélection groupée, restauration, purge', async ({
|
||||
await page.waitForTimeout(900);
|
||||
await expect(page.locator('.trash-item', { hasText: nameD })).toHaveCount(1);
|
||||
const all = (await api(page, 'GET', '/board/api/trash')).body || [];
|
||||
// Fenêtre Empty Trash : ouverture + titre + annulation — testée quel que soit
|
||||
// le contenu (l'instance est partagée, on n'a pas le droit de tout vider).
|
||||
await page.locator('button.btn-sm', { hasText: 'Empty Trash' }).click();
|
||||
await expect(modal).toBeVisible();
|
||||
await expect(modal.locator('h3')).toHaveText('Empty Trash?');
|
||||
await expect(modal.locator('.flowdeck-modal-body p')).toContainText(String(all.length));
|
||||
await modal.locator('button.cancel').click();
|
||||
await expect(modal).toBeHidden();
|
||||
await expect(page.locator('.trash-item', { hasText: nameD })).toHaveCount(1);
|
||||
|
||||
if (all.every((i) => i.name.startsWith('E2E Trash'))) {
|
||||
await page.getByRole('button', { name: 'Empty Trash' }).click();
|
||||
await page.locator('button.btn-sm', { hasText: 'Empty Trash' }).click();
|
||||
await expect(modal).toBeVisible();
|
||||
await modal.locator('button.confirm').click();
|
||||
await page.waitForTimeout(900);
|
||||
expect(await trashIds(page)).toHaveLength(0);
|
||||
await expect(page.locator('.lib-empty', { hasText: 'Trash is empty' })).toBeVisible();
|
||||
@@ -126,6 +171,7 @@ test('trash : rendu, dates, sélection groupée, restauration, purge', async ({
|
||||
JSON.stringify(all.filter((i) => !i.name.startsWith('E2E Trash')).map((i) => i.name)));
|
||||
}
|
||||
|
||||
const fatal = errs.filter((e) => e.startsWith('PAGEERROR') || e.includes('Alpine Expression Error'));
|
||||
const fatal = errs.filter((e) =>
|
||||
e.startsWith('PAGEERROR') || e.includes('Alpine Expression Error') || e.startsWith('NATIVE_DIALOG'));
|
||||
expect(fatal, fatal.join('\n')).toHaveLength(0);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user