diff --git a/e2e/toolbox.spec.js b/e2e/toolbox.spec.js index 4626b60..811ecf9 100644 --- a/e2e/toolbox.spec.js +++ b/e2e/toolbox.spec.js @@ -1,5 +1,5 @@ // Rôle : couvre les parcours navigateur principaux des toolboxes locales. -/* global window, navigator, CustomEvent, FileSystemDirectoryHandle */ +/* global window, document, navigator, CustomEvent, FileSystemDirectoryHandle */ import { expect, test } from "@playwright/test"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -247,9 +247,11 @@ test("opens the local automatic backup settings modal", async ({ page }) => { const dialog = page.getByRole("dialog", { name: "Sauvegarde automatique" }); await expect(dialog).toBeVisible(); + expect((await dialog.boundingBox()).width).toBeGreaterThan(700); + expect(await dialog.evaluate((element) => window.getComputedStyle(element).scrollbarColor)).toContain("88, 55, 174"); await expect(dialog.getByText("Cette fonctionnalité n’est pas disponible dans tous les navigateurs.")).toBeVisible(); await expect(dialog.getByText("Opera GX", { exact: true })).toBeVisible(); - await expect(dialog.getByText("Fonctionnement de l’autorisation", { exact: true })).toBeVisible(); + await expect(dialog.getByText("Comment fonctionne la sauvegarde", { exact: true })).toBeVisible(); await expect(dialog.getByText("la popup système ne peut apparaître qu’après un clic de votre part.")).toBeVisible(); await expect(dialog.getByText("10 versions sont conservées.", { exact: true })).toBeVisible(); await expect(dialog.getByText("Dossier configuré :", { exact: true })).toBeVisible(); @@ -275,6 +277,7 @@ test("offers an actionable backup alert without requesting permission at startup await page.addInitScript(() => { window.backupPermissionRequests = 0; window.backupPermissionGranted = false; + window.backupReminderVisibleAtPermissionRequest = true; Object.defineProperty(FileSystemDirectoryHandle.prototype, "queryPermission", { configurable: true, value: async () => window.backupPermissionGranted ? "granted" : "prompt" @@ -283,6 +286,7 @@ test("offers an actionable backup alert without requesting permission at startup configurable: true, value: async () => { window.backupPermissionRequests += 1; + window.backupReminderVisibleAtPermissionRequest = document.body.textContent.includes("Des modifications ont été détectées dans vos toolboxes."); window.backupPermissionGranted = true; return "granted"; } @@ -302,6 +306,7 @@ test("offers an actionable backup alert without requesting permission at startup await reminder.getByRole("button", { name: "Sauvegarder", exact: true }).click(); await expect(reminder).toHaveCount(0); expect(await page.evaluate(() => window.backupPermissionRequests)).toBe(1); + expect(await page.evaluate(() => window.backupReminderVisibleAtPermissionRequest)).toBe(false); await expect.poll(async () => page.evaluate(async () => { const db = await import("/src/utils/indexedDbStorage.js"); return (await db.getSetting("autoBackup", {})).hasUnsavedChanges; diff --git a/website/public/data/site.json b/website/public/data/site.json index 9b44624..a933a3c 100644 --- a/website/public/data/site.json +++ b/website/public/data/site.json @@ -1816,19 +1816,20 @@ "autoBackup": { "eyebrow": "Sauvegarde locale", "title": "Sauvegarde automatique", - "description": "Cette fonctionnalité n’est pas disponible dans tous les navigateurs. Les sauvegardes restent locales à ce navigateur et ne sont envoyées à aucun serveur.", + "description": "La sauvegarde automatique suit les modifications confirmées de vos toolboxes et crée des fichiers JSON dans le dossier choisi. Ces fichiers restent sur votre appareil et ne sont envoyés à aucun serveur. Cette fonctionnalité n’est pas disponible dans tous les navigateurs.", "compatibleLabel": "Navigateurs compatibles", "browsers": [ "Opera GX", "Google Chrome", "Microsoft Edge" ], - "permissionTitle": "Fonctionnement de l’autorisation", - "permissionDescription": "Opera GX peut redemander l’accès après la fermeture du navigateur. Pour des raisons de sécurité, la popup système ne peut apparaître qu’après un clic de votre part.", + "permissionTitle": "Comment fonctionne la sauvegarde", + "permissionDescription": "L’application mémorise si vos toolboxes ont changé depuis la dernière sauvegarde. Opera GX peut redemander l’accès au dossier après la fermeture du navigateur ; la popup système ne peut apparaître qu’après un clic de votre part.", "permissionSteps": [ - "Si l’accès est toujours accordé, la vérification et la sauvegarde éventuelle restent silencieuses.", - "L’application mémorise localement les modifications confirmées. Si l’accès a expiré, une alerte vous propose alors de les sauvegarder.", - "Cliquez sur « Sauvegarder » dans cette alerte : le navigateur redemande l’accès, puis l’application vérifie le dossier avant toute écriture." + "Après chaque modification confirmée d’une toolbox, une sauvegarde est marquée comme nécessaire.", + "Au prochain démarrage, si le dossier est encore accessible, sa vérification et la sauvegarde éventuelle restent silencieuses.", + "Si l’accès a expiré, une alerte apparaît lorsqu’une sauvegarde est nécessaire. Son bouton « Sauvegarder » ferme immédiatement l’alerte et ouvre la demande d’autorisation d’Opera GX.", + "Après autorisation, l’application vérifie le fichier du jour et le contenu de la dernière version. Elle écrit un nouveau fichier uniquement si nécessaire." ], "reminderMessage": "Des modifications ont été détectées dans vos toolboxes. Voulez-vous créer une sauvegarde locale ?", "reminderAction": "Sauvegarder", diff --git a/website/src/components/ToolboxModals.jsx b/website/src/components/ToolboxModals.jsx index f14f19e..8b76c9a 100644 --- a/website/src/components/ToolboxModals.jsx +++ b/website/src/components/ToolboxModals.jsx @@ -1,5 +1,6 @@ // Rôle : fournit les modales globales liées aux toolboxes et confirmations. import { useEffect, useState } from "react"; +import { flushSync } from "react-dom"; import { Icon } from "./Icon.jsx"; import { lockBodyScroll } from "../utils/bodyScrollLock.js"; @@ -35,8 +36,8 @@ export function NotificationToast({ id, message, durationMs = 120000, actionLabe
{message} {actionLabel && onAction && }