Minor UI changes
This commit is contained in:
@@ -1,8 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { db } from './lib/db.js';
|
import { db } from './lib/db.js';
|
||||||
import { dayState, reconcile, refresh } from './lib/day.svelte.js';
|
import { dayState, reconcile, refresh } from './lib/day.svelte.js';
|
||||||
import { backupState, loadBackupStatus } from './lib/backup-state.svelte.js';
|
import { loadBackupStatus } from './lib/backup-state.svelte.js';
|
||||||
import { backupReminderDue } from './lib/backup-safety.js';
|
|
||||||
import { go, router } from './lib/router.svelte.js';
|
import { go, router } from './lib/router.svelte.js';
|
||||||
import { loadSettings, settings } from './lib/settings.svelte.js';
|
import { loadSettings, settings } from './lib/settings.svelte.js';
|
||||||
import { needsRefresh, applyUpdate } from './lib/updates.svelte.js';
|
import { needsRefresh, applyUpdate } from './lib/updates.svelte.js';
|
||||||
@@ -19,7 +18,15 @@
|
|||||||
import { loadSyncStatus, synchronize, takeAutomationRole } from './lib/sync/client.js';
|
import { loadSyncStatus, synchronize, takeAutomationRole } from './lib/sync/client.js';
|
||||||
import { syncState } from './lib/sync/state.svelte.js';
|
import { syncState } from './lib/sync/state.svelte.js';
|
||||||
|
|
||||||
let roleDismissed = $state(false);
|
const roleNoticeDismissedKey = 'pardis:automation-role-notice-dismissed';
|
||||||
|
function wasRoleNoticeDismissed() {
|
||||||
|
try { return localStorage.getItem(roleNoticeDismissedKey) === '1'; } catch { return false; }
|
||||||
|
}
|
||||||
|
let roleDismissed = $state(wasRoleNoticeDismissed());
|
||||||
|
function dismissRoleNotice() {
|
||||||
|
roleDismissed = true;
|
||||||
|
try { localStorage.setItem(roleNoticeDismissedKey, '1'); } catch { /* Keep the dismissal for this app session. */ }
|
||||||
|
}
|
||||||
let roleError = $state('');
|
let roleError = $state('');
|
||||||
async function takeRole() {
|
async function takeRole() {
|
||||||
try { await takeAutomationRole(); roleError = ''; await tick(); }
|
try { await takeAutomationRole(); roleError = ''; await tick(); }
|
||||||
@@ -29,8 +36,7 @@
|
|||||||
let ready = $state(false);
|
let ready = $state(false);
|
||||||
let bootError = $state('');
|
let bootError = $state('');
|
||||||
let retry = $state(0);
|
let retry = $state(0);
|
||||||
let clockNow = $state(Date.now());
|
async function tick() { if (!ready) return; try { await db.open(); if (router.route === 'sync' && !syncState.linked) await refresh(); else await reconcile(); await loadBackupStatus(); bootError = ''; } catch (error) { bootError = `Actualisation impossible : ${error.message}`; } }
|
||||||
async function tick() { if (!ready) return; try { await db.open(); if (router.route === 'sync' && !syncState.linked) await refresh(); else await reconcile(); await loadBackupStatus(); clockNow = Date.now(); bootError = ''; } catch (error) { bootError = `Actualisation impossible : ${error.message}`; } }
|
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
retry;
|
retry;
|
||||||
@@ -91,12 +97,9 @@
|
|||||||
<div class="boot">{#if bootError}<div class="stack"><p role="alert">{bootError}</p><button onclick={() => (retry += 1)}>Réessayer</button></div>{:else}<p>Chargement…</p>{/if}</div>
|
<div class="boot">{#if bootError}<div class="stack"><p role="alert">{bootError}</p><button onclick={() => (retry += 1)}>Réessayer</button></div>{:else}<p>Chargement…</p>{/if}</div>
|
||||||
{:else}
|
{:else}
|
||||||
{#if syncState.enabled && syncState.owner && syncState.owner !== syncState.deviceId && !roleDismissed && view !== 'sync'}
|
{#if syncState.enabled && syncState.owner && syncState.owner !== syncState.deviceId && !roleDismissed && view !== 'sync'}
|
||||||
<div class="update"><span>Les automatismes sont exécutés sur un autre appareil.</span><button disabled={syncState.busy} onclick={takeRole}>Utiliser cet appareil pour les automatismes</button><button onclick={() => { roleDismissed = true; }}>Plus tard</button></div>
|
<div class="update"><span>Les automatismes sont exécutés sur un autre appareil.</span><button disabled={syncState.busy} onclick={takeRole}>Utiliser cet appareil pour les automatismes</button><button onclick={dismissRoleNotice}>Plus tard</button></div>
|
||||||
{#if roleError}<p role="status">{roleError}</p>{/if}
|
{#if roleError}<p role="status">{roleError}</p>{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{#if backupState.loaded && backupReminderDue(backupState.status, clockNow) && !['settings', 'data'].includes(view)}
|
|
||||||
<div class="update"><span>Sauvegarde à vérifier</span><button disabled={dayState.importing} onclick={() => go('data')}>Sauvegarder</button></div>
|
|
||||||
{/if}
|
|
||||||
{#if needsRefresh.value && !dayState.day}
|
{#if needsRefresh.value && !dayState.day}
|
||||||
<!-- Only offered while no day is running, so an update can never interrupt
|
<!-- Only offered while no day is running, so an update can never interrupt
|
||||||
a day in progress. -->
|
a day in progress. -->
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
parseBackup,
|
parseBackup,
|
||||||
} from '../lib/backup.js';
|
} from '../lib/backup.js';
|
||||||
import { applyImport, dayState } from '../lib/day.svelte.js';
|
import { applyImport, dayState } from '../lib/day.svelte.js';
|
||||||
import { prepareVerifiedExport, verifyExportFile, backupReminderDue } from '../lib/backup-safety.js';
|
import { prepareVerifiedExport, verifyExportFile } from '../lib/backup-safety.js';
|
||||||
import { backupState, loadBackupStatus } from '../lib/backup-state.svelte.js';
|
import { backupState, loadBackupStatus } from '../lib/backup-state.svelte.js';
|
||||||
import { db } from '../lib/db.js';
|
import { db } from '../lib/db.js';
|
||||||
import { syncState } from '../lib/sync/state.svelte.js';
|
import { syncState } from '../lib/sync/state.svelte.js';
|
||||||
@@ -133,7 +133,6 @@
|
|||||||
|
|
||||||
<p class="dim small">Dernier export préparé : {backupState.status.exportedAt ? new Date(backupState.status.exportedAt).toLocaleString('fr-FR') : 'aucun'}.</p>
|
<p class="dim small">Dernier export préparé : {backupState.status.exportedAt ? new Date(backupState.status.exportedAt).toLocaleString('fr-FR') : 'aucun'}.</p>
|
||||||
<p class="dim small">Dernière sauvegarde vérifiée : {backupState.status.verifiedExportedAt ? new Date(backupState.status.verifiedExportedAt).toLocaleString('fr-FR') : 'aucune'}.</p>
|
<p class="dim small">Dernière sauvegarde vérifiée : {backupState.status.verifiedExportedAt ? new Date(backupState.status.verifiedExportedAt).toLocaleString('fr-FR') : 'aucune'}.</p>
|
||||||
{#if backupState.loaded && backupReminderDue(backupState.status)}<p role="status">Pensez à sauvegarder : exportez puis vérifiez le fichier enregistré. Un rappel apparaît après sept jours sans sauvegarde vérifiée.</p>{/if}
|
|
||||||
<button disabled={busy || !backupState.status.checksum} onclick={() => { verifying = true; picker.click(); }}>Vérifier le fichier exporté…</button>
|
<button disabled={busy || !backupState.status.checksum} onclick={() => { verifying = true; picker.click(); }}>Vérifier le fichier exporté…</button>
|
||||||
<p class="dim small">Choisissez le fichier téléchargé : son contenu complet sera comparé à l’export. Aucune journée ne sera modifiée.</p>
|
<p class="dim small">Choisissez le fichier téléchargé : son contenu complet sera comparé à l’export. Aucune journée ne sera modifiée.</p>
|
||||||
<button disabled={busy} onclick={recoverPurge}>Récupérer la sauvegarde avant purge</button>
|
<button disabled={busy} onclick={recoverPurge}>Récupérer la sauvegarde avant purge</button>
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import { db, exportBackup, readSettings } from './db.js';
|
|||||||
import { parseBackup } from './backup.js';
|
import { parseBackup } from './backup.js';
|
||||||
import { DAY, localDateKey } from './time.js';
|
import { DAY, localDateKey } from './time.js';
|
||||||
|
|
||||||
export const BACKUP_REMINDER_DAYS = 7;
|
|
||||||
|
|
||||||
export async function readBackupStatus() {
|
export async function readBackupStatus() {
|
||||||
return (await db.settings.get('backup-status'))?.value ?? {};
|
return (await db.settings.get('backup-status'))?.value ?? {};
|
||||||
}
|
}
|
||||||
@@ -44,11 +42,6 @@ export async function verifyExportFile(text, now = Date.now()) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function backupReminderDue(status, now = Date.now()) {
|
|
||||||
const verified = Date.parse(status.verifiedExportedAt ?? '');
|
|
||||||
return !Number.isFinite(verified) || now - verified >= BACKUP_REMINDER_DAYS * DAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function purgeCandidates(days, retentionDays, now = Date.now()) {
|
export function purgeCandidates(days, retentionDays, now = Date.now()) {
|
||||||
const closed = days.filter(day => day.status === 'closed').sort((a, b) => b.date.localeCompare(a.date));
|
const closed = days.filter(day => day.status === 'closed').sort((a, b) => b.date.localeCompare(a.date));
|
||||||
const cutoff = localDateKey(now - retentionDays * DAY);
|
const cutoff = localDateKey(now - retentionDays * DAY);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'fake-indexeddb/auto';
|
import 'fake-indexeddb/auto';
|
||||||
import { beforeEach, expect, it, vi } from 'vitest';
|
import { beforeEach, expect, it, vi } from 'vitest';
|
||||||
import { db, getDay, getEntries, importBackup } from './db.js';
|
import { db, getDay, getEntries, importBackup } from './db.js';
|
||||||
import { backupReminderDue, prepareVerifiedExport, purgeCandidates, purgeReviewedDays, readBackupStatus, verifyExportFile } from './backup-safety.js';
|
import { prepareVerifiedExport, purgeCandidates, purgeReviewedDays, readBackupStatus, verifyExportFile } from './backup-safety.js';
|
||||||
import { parseBackup } from './backup.js';
|
import { parseBackup } from './backup.js';
|
||||||
import { atLocalTime, DAY } from './time.js';
|
import { atLocalTime } from './time.js';
|
||||||
|
|
||||||
const now = atLocalTime('2026-10-08', '18:00');
|
const now = atLocalTime('2026-10-08', '18:00');
|
||||||
const record = date => ({ date, status: 'closed', startedTs: atLocalTime(date,'08:00'), endedTs: atLocalTime(date,'17:00'), plannedStartTs: atLocalTime(date,'08:00'), plannedEndTs: atLocalTime(date,'17:00'), lunchType: 'panier' });
|
const record = date => ({ date, status: 'closed', startedTs: atLocalTime(date,'08:00'), endedTs: atLocalTime(date,'17:00'), plannedStartTs: atLocalTime(date,'08:00'), plannedEndTs: atLocalTime(date,'17:00'), lunchType: 'panier' });
|
||||||
@@ -24,11 +24,6 @@ it('only verifies the complete exported file, without altering the history', asy
|
|||||||
const corrupted = JSON.parse(prepared.text); corrupted.entries[0].task = 'Changed';
|
const corrupted = JSON.parse(prepared.text); corrupted.entries[0].task = 'Changed';
|
||||||
await expect(verifyExportFile(JSON.stringify(corrupted))).rejects.toThrow(/ne correspond pas/);
|
await expect(verifyExportFile(JSON.stringify(corrupted))).rejects.toThrow(/ne correspond pas/);
|
||||||
});
|
});
|
||||||
it('reminds after seven days from the verified export, rather than the day it was checked', () => {
|
|
||||||
expect(backupReminderDue({},now)).toBe(true);
|
|
||||||
expect(backupReminderDue({verifiedExportedAt:new Date(now-6*DAY).toISOString()},now)).toBe(false);
|
|
||||||
expect(backupReminderDue({verifiedExportedAt:new Date(now-7*DAY).toISOString(),verifiedAt:now},now)).toBe(true);
|
|
||||||
});
|
|
||||||
it('preserves an importable snapshot and purges only the dates reviewed', async () => {
|
it('preserves an importable snapshot and purges only the dates reviewed', async () => {
|
||||||
await install('2025-01-01'); await install('2026-10-08');
|
await install('2025-01-01'); await install('2026-10-08');
|
||||||
const old = await reviewed('2025-01-01');
|
const old = await reviewed('2025-01-01');
|
||||||
|
|||||||
@@ -24,16 +24,26 @@
|
|||||||
<fieldset class="screen" disabled={dayState.importing}>
|
<fieldset class="screen" disabled={dayState.importing}>
|
||||||
<h1>Réglages</h1>
|
<h1>Réglages</h1>
|
||||||
|
|
||||||
<section class="card stack">
|
|
||||||
<h2>Statistiques</h2>
|
|
||||||
<p class="dim small">Heures par client et tâche, repas et évolution de l’activité.</p>
|
|
||||||
<button type="button" onclick={() => go('stats')}>Consulter les statistiques</button>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{#if settingsError.message}
|
{#if settingsError.message}
|
||||||
<p class="error card">{settingsError.message}</p>
|
<p class="error card">{settingsError.message}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<section class="card stack">
|
||||||
|
<h2>Affichage</h2>
|
||||||
|
<div class="btn-row">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class:sel={settings.theme === 'jour'}
|
||||||
|
onclick={() => updateSettings({ theme: 'jour' })}>Jour</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class:sel={settings.theme === 'nuit'}
|
||||||
|
onclick={() => updateSettings({ theme: 'nuit' })}>Nuit</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="card stack">
|
<section class="card stack">
|
||||||
<h2>Jours travaillés</h2>
|
<h2>Jours travaillés</h2>
|
||||||
<div class="days">
|
<div class="days">
|
||||||
@@ -108,11 +118,9 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<p class="dim small">Type par défaut ; modifiable chaque jour.</p>
|
<p class="dim small">Type par défaut ; modifiable chaque jour.</p>
|
||||||
<p class="dim small">Utiliser ces horaires pour les rappels et, si activée ci-dessous, la pause automatique.</p>
|
<p class="dim small">Utiliser ces horaires pour les rappels et, si activée, la pause automatique.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card stack"><h2>Automatismes</h2><button onclick={() => go('automations')}>Gérer les automatismes</button></section>
|
|
||||||
|
|
||||||
<section class="card stack">
|
<section class="card stack">
|
||||||
<h2>Arrondi</h2>
|
<h2>Arrondi</h2>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
@@ -132,29 +140,22 @@
|
|||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card stack">
|
<section class="card stack"><h2>Automatismes</h2><button onclick={() => go('automations')}>Gérer les automatismes</button></section>
|
||||||
<h2>Affichage</h2>
|
|
||||||
<div class="btn-row">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class:sel={settings.theme === 'jour'}
|
|
||||||
onclick={() => updateSettings({ theme: 'jour' })}>Jour</button
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class:sel={settings.theme === 'nuit'}
|
|
||||||
onclick={() => updateSettings({ theme: 'nuit' })}>Nuit</button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="card stack">
|
<section class="card stack">
|
||||||
<h2>Données et sauvegardes</h2>
|
<h2>Données et sauvegardes</h2>
|
||||||
<p class="dim small">Sauvegarder, exporter ou importer les données et régler leur durée de conservation.</p>
|
<p class="dim small">Sauvegarder, exporter ou importer les données et régler leur durée de conservation.</p>
|
||||||
|
<p class="dim small">Pensez à exporter et vérifier régulièrement une sauvegarde pour pouvoir restaurer vos données en cas de perte de l’appareil.</p>
|
||||||
<button type="button" onclick={() => go('data')}>Gérer les données et sauvegardes</button>
|
<button type="button" onclick={() => go('data')}>Gérer les données et sauvegardes</button>
|
||||||
</section>
|
</section>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
<section class="card stack">
|
||||||
|
<h2>Statistiques</h2>
|
||||||
|
<p class="dim small">Heures par client et tâche, repas et évolution de l’activité.</p>
|
||||||
|
<button type="button" onclick={() => go('stats')}>Consulter les statistiques</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
fieldset.screen {
|
fieldset.screen {
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ async function importFixture(page, payload = fixture()) {
|
|||||||
await page.clock.install({ time:now });
|
await page.clock.install({ time:now });
|
||||||
await page.goto('/#/settings');
|
await page.goto('/#/settings');
|
||||||
await expect(page.getByRole('heading',{name:'Réglages',exact:true})).toBeVisible();
|
await expect(page.getByRole('heading',{name:'Réglages',exact:true})).toBeVisible();
|
||||||
|
await expect(page.getByText('Pensez à exporter et vérifier régulièrement une sauvegarde pour pouvoir restaurer vos données en cas de perte de l’appareil.',{exact:true})).toBeVisible();
|
||||||
await page.getByRole('button',{name:'Gérer les données et sauvegardes'}).click();
|
await page.getByRole('button',{name:'Gérer les données et sauvegardes'}).click();
|
||||||
await expect(page.getByRole('heading',{name:'Données et sauvegardes',exact:true})).toBeVisible();
|
await expect(page.getByRole('heading',{name:'Données et sauvegardes',exact:true})).toBeVisible();
|
||||||
await page.locator('input[type=file]').setInputFiles({ name:'fixture-synthetique.json',mimeType:'application/json',buffer:Buffer.from(JSON.stringify(payload)) });
|
await page.locator('input[type=file]').setInputFiles({ name:'fixture-synthetique.json',mimeType:'application/json',buffer:Buffer.from(JSON.stringify(payload)) });
|
||||||
@@ -21,6 +22,11 @@ async function importFixture(page, payload = fixture()) {
|
|||||||
await expect(page.getByRole('status').filter({hasText:'Données remplacées'})).toBeVisible();
|
await expect(page.getByRole('status').filter({hasText:'Données remplacées'})).toBeVisible();
|
||||||
await page.getByRole('button',{name:'Récap',exact:true}).click();
|
await page.getByRole('button',{name:'Récap',exact:true}).click();
|
||||||
}
|
}
|
||||||
|
async function openDataSettings(page) {
|
||||||
|
await page.getByRole('button',{name:'Réglages',exact:true}).click();
|
||||||
|
await page.getByRole('button',{name:'Gérer les données et sauvegardes'}).click();
|
||||||
|
await expect(page.getByRole('heading',{name:'Données et sauvegardes',exact:true})).toBeVisible();
|
||||||
|
}
|
||||||
async function entries(page) {
|
async function entries(page) {
|
||||||
return page.evaluate(() => new Promise((resolve,reject) => {
|
return page.evaluate(() => new Promise((resolve,reject) => {
|
||||||
const request = indexedDB.open('TimeLogDB');
|
const request = indexedDB.open('TimeLogDB');
|
||||||
@@ -88,8 +94,7 @@ test('consultation, correction, recalcul, copie et statistiques à 320 px', asyn
|
|||||||
await expect(page.getByText('Heures de présence',{exact:true})).toBeVisible();
|
await expect(page.getByText('Heures de présence',{exact:true})).toBeVisible();
|
||||||
await expect(page.getByTestId('stats-worked')).toHaveText('9h00');
|
await expect(page.getByTestId('stats-worked')).toHaveText('9h00');
|
||||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
|
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
|
||||||
// Wider or larger fonts must also fit the backup reminder on CI and mobile.
|
await expect(page.getByText('Sauvegarde à vérifier',{exact:true})).toHaveCount(0);
|
||||||
await expect(page.locator('.update')).toContainText('Sauvegarde à vérifier');
|
|
||||||
await page.addStyleTag({content:'.update { font-family: monospace; font-size: 24px; }'});
|
await page.addStyleTag({content:'.update { font-family: monospace; font-size: 24px; }'});
|
||||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
|
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
|
||||||
});
|
});
|
||||||
@@ -113,7 +118,7 @@ test('date conservée, rechargement et Retour depuis les trois nouvelles pages',
|
|||||||
await expect(page.getByRole('heading',{name:'Réglages',exact:true})).toBeVisible();
|
await expect(page.getByRole('heading',{name:'Réglages',exact:true})).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('écran données dédié, conservation persistée et accès depuis le rappel', async ({page}) => {
|
test('écran données dédié, conservation persistée et accès depuis les réglages', async ({page}) => {
|
||||||
await importFixture(page,fixture({dates:['2025-01-01',date]}));
|
await importFixture(page,fixture({dates:['2025-01-01',date]}));
|
||||||
await page.getByRole('button',{name:'Réglages',exact:true}).click();
|
await page.getByRole('button',{name:'Réglages',exact:true}).click();
|
||||||
await expect(page.getByRole('button',{name:'Exporter les données',exact:true})).toHaveCount(0);
|
await expect(page.getByRole('button',{name:'Exporter les données',exact:true})).toHaveCount(0);
|
||||||
@@ -144,14 +149,13 @@ test('écran données dédié, conservation persistée et accès depuis le rappe
|
|||||||
await page.getByRole('button',{name:'Gérer les données et sauvegardes'}).click();
|
await page.getByRole('button',{name:'Gérer les données et sauvegardes'}).click();
|
||||||
await page.goBack();
|
await page.goBack();
|
||||||
await expect(page.getByRole('heading',{name:'Réglages',exact:true})).toBeVisible();
|
await expect(page.getByRole('heading',{name:'Réglages',exact:true})).toBeVisible();
|
||||||
await page.getByRole('button',{name:'Récap',exact:true}).click();
|
await page.getByRole('button',{name:'Gérer les données et sauvegardes'}).click();
|
||||||
await page.getByRole('button',{name:'Sauvegarder',exact:true}).click();
|
|
||||||
await expect(page.getByRole('heading',{name:'Données et sauvegardes',exact:true})).toBeVisible();
|
await expect(page.getByRole('heading',{name:'Données et sauvegardes',exact:true})).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('échec de conservation restaure le champ et les données, puis permet de réessayer', async ({page}) => {
|
test('échec de conservation restaure le champ et les données, puis permet de réessayer', async ({page}) => {
|
||||||
await importFixture(page,fixture({dates:['2025-01-01',date]}));
|
await importFixture(page,fixture({dates:['2025-01-01',date]}));
|
||||||
await page.getByRole('button',{name:'Sauvegarder',exact:true}).click();
|
await openDataSettings(page);
|
||||||
const input = page.getByLabel('Conserver (jours)');
|
const input = page.getByLabel('Conserver (jours)');
|
||||||
await expect(input).toHaveValue('30');
|
await expect(input).toHaveValue('30');
|
||||||
const before = await storedData(page);
|
const before = await storedData(page);
|
||||||
@@ -250,7 +254,7 @@ test('export relu et vérifié, PWA relancée hors ligne', async ({page,context}
|
|||||||
|
|
||||||
test('export modifié refusé sans écriture, puis vérification du fichier original', async ({page}) => {
|
test('export modifié refusé sans écriture, puis vérification du fichier original', async ({page}) => {
|
||||||
await importFixture(page);
|
await importFixture(page);
|
||||||
await page.getByRole('button',{name:'Sauvegarder',exact:true}).click();
|
await openDataSettings(page);
|
||||||
const downloading = page.waitForEvent('download');
|
const downloading = page.waitForEvent('download');
|
||||||
await page.getByRole('button',{name:'Exporter les données',exact:true}).click();
|
await page.getByRole('button',{name:'Exporter les données',exact:true}).click();
|
||||||
const download = await downloading;
|
const download = await downloading;
|
||||||
@@ -311,7 +315,7 @@ test('purge confirmée et restauration de sa sauvegarde récupérable', async ({
|
|||||||
|
|
||||||
test('échec de purge conserve les journées et la sauvegarde précédente, puis permet de réessayer', async ({page}) => {
|
test('échec de purge conserve les journées et la sauvegarde précédente, puis permet de réessayer', async ({page}) => {
|
||||||
await importFixture(page,fixture({dates:['2025-01-01',date]}));
|
await importFixture(page,fixture({dates:['2025-01-01',date]}));
|
||||||
await page.getByRole('button',{name:'Sauvegarder',exact:true}).click();
|
await openDataSettings(page);
|
||||||
const dialog = page.getByRole('dialog',{name:'Confirmer la purge'});
|
const dialog = page.getByRole('dialog',{name:'Confirmer la purge'});
|
||||||
await page.getByRole('button',{name:'Purger maintenant'}).click();
|
await page.getByRole('button',{name:'Purger maintenant'}).click();
|
||||||
await dialog.getByRole('button',{name:'Confirmer la purge'}).click();
|
await dialog.getByRole('button',{name:'Confirmer la purge'}).click();
|
||||||
|
|||||||
@@ -102,6 +102,16 @@ test('association, modification hors ligne, conflit, rôle et révocation entre
|
|||||||
await page.getByRole('button', { name: 'Retour aux appareils', exact: true }).click();
|
await page.getByRole('button', { name: 'Retour aux appareils', exact: true }).click();
|
||||||
await sync(page);
|
await sync(page);
|
||||||
await expect(page.getByRole('button', { name: 'Utiliser cet appareil pour les automatismes', exact: true })).toBeVisible();
|
await expect(page.getByRole('button', { name: 'Utiliser cet appareil pour les automatismes', exact: true })).toBeVisible();
|
||||||
|
await page.getByRole('button', { name: 'Retour aux données', exact: true }).click();
|
||||||
|
const roleNotice = page.getByText('Les automatismes sont exécutés sur un autre appareil.', { exact: true });
|
||||||
|
await expect(roleNotice).toBeVisible();
|
||||||
|
await page.getByRole('button', { name: 'Plus tard', exact: true }).click();
|
||||||
|
await expect(roleNotice).toHaveCount(0);
|
||||||
|
await page.reload();
|
||||||
|
await expect(page.getByRole('heading', { name: 'Données et sauvegardes', exact: true })).toBeVisible();
|
||||||
|
await expect(roleNotice).toHaveCount(0);
|
||||||
|
await page.goto('/#/sync');
|
||||||
|
await expect(page.getByRole('heading', { name: 'Synchronisation et appareils', exact: true })).toBeVisible();
|
||||||
await page.getByRole('button', { name: 'Retirer cet appareil…', exact: true }).click();
|
await page.getByRole('button', { name: 'Retirer cet appareil…', exact: true }).click();
|
||||||
await page.getByRole('button', { name: 'Confirmer le retrait', exact: true }).click();
|
await page.getByRole('button', { name: 'Confirmer le retrait', exact: true }).click();
|
||||||
await expect(page.getByRole('button', { name: 'Synchroniser maintenant', exact: true })).toBeEnabled();
|
await expect(page.getByRole('button', { name: 'Synchroniser maintenant', exact: true })).toBeEnabled();
|
||||||
|
|||||||
Reference in New Issue
Block a user