Files
flowdeck/e2e/v7694_settings_close.spec.js
T
bruno 7468e497c6
FlowDeck CI / lint (push) Successful in 1m34s
FlowDeck CI / test (push) Failing after 15m40s
FlowDeck CI / docker (push) Skipped
fix: le bouton close de Settings ferme en un clic (v7.69.4)
Le bouton close de Settings devait être cliqué deux fois. Symptomatique depuis
le menu utilisateur ; depuis /workspaces ça marchait.

Cause : closeSettings() faisait history.back() PUIS window.location.reload()
après un délai FIXE de 100 ms. Le retour est une navigation asynchrone : quand
elle n'est pas terminée en 100 ms, le reload() retombe sur /settings et le
panneau « se ferme puis revient ». La profondeur d'historique diffère selon le
parcours — d'où le caractère intermittent et le fait que /workspaces
échappait au symptôme.

Correctif : location.replace() vers la page d'origine. Une seule navigation
déterministe — ni history.back(), ni popstate, ni rechargement différé. Aucun
chemin ne peut plus se terminer sans navigation. Effet secondaire assumé et
souhaitable : « Retour » ne rouvre plus Settings.

La branche window.opener ne fait plus de return inconditionnel : window.close()
échoue en silence quand l'onglet n'a pas été ouvert par du script, et l'ancien
code abandonnait alors sans navigation de repli — le bouton semblait mort.

CONTRÔLE NÉGATIF FAIT — le même spec a été exécuté contre deux builds :
  ancien closeSettings() -> le parcours menu-utilisateur ÉCHOUE
                            (Received: "…/settings"), /workspaces passe ;
  correctif              -> les deux passent.
Le test verrouille aussi que le panneau position:fixed n'est plus peint
(.settings-overlay absent), qui est le symptôme exact rapporté.

Correction de diagnostic : le diagnostic posé en v7.69.3 (« Alpine ne lie pas
le composant x-data="settingsInit()" ») était FAUX — un artefact de mon banc
d'essai target="_blank", pas le parcours réel. La page étant réactive après le
premier clic, le handler s'exécutait bien. ROADMAP.md et CHANGELOG.md § v7.69.3
corrigés pour ne pas laisser une piste fausse en héritage.

pytest 1421 passed / 0 failed · ruff OK · OpenAPI 526 chemins / 7.69.4
e2e 18/18 verts sur l'instance déployée
2026-10-08 22:02:08 -04:00

73 lines
3.2 KiB
JavaScript

// v7.69.4 — le bouton close de Settings doit fermer le panneau, y compris
// quand Settings est ouvert depuis le MENU UTILISATEUR (navigation partielle).
//
// `shouldIntercept()` accepte `/settings`, donc ce clic passe par fdLoad()
// (htmx + history.pushState) SANS rechargement de page. L'ancien closeSettings()
// faisait `history.back()` → popstate → swap PARTIEL de .main-wrapper, et le
// panneau `position:fixed` restait peint : « ça ferme mais ça reste ouvert ».
// Depuis /workspaces, c'est un chargement complet → pas de symptôme.
const { test, expect } = require('@playwright/test');
const FD_BASE = process.env.FD_BASE_URL || 'http://localhost:8081';
const USER = process.env.FD_USER || '[email protected]';
const PASS = process.env.FD_PASS || 'e2e-secret-123';
async function login(page) {
await page.goto(`${FD_BASE}/auth/login?provider=local`, { waitUntil: 'domcontentloaded' });
await page.fill('#email', USER);
await page.fill('#password', PASS);
await page.click('.btn-primary');
await page.waitForURL('**/workspaces', { timeout: 8000 }).catch(() => {});
}
const csrfOf = async (page) => {
const c = await page.context().cookies();
return (c.find((x) => x.name === 'csrf_token') || {}).value || '';
};
async function makeWorkspace(page) {
const r = await page.request.post(`${FD_BASE}/api/workspaces`, {
headers: { 'X-CSRF-Token': await csrfOf(page) },
data: { name: `e2e-cls-${Date.now()}-${Math.random().toString(16).slice(2, 6)}` },
});
const j = await r.json();
return j.id || (j.workspace && j.workspace.id);
}
// ouvre Settings depuis le menu utilisateur — le chemin qui provoque le bug
async function openSettingsFromUserMenu(page) {
await page.locator('.workspace-avatar, .user-avatar').first().click();
await page.locator('.um-item[href="/settings"]').first().click();
await page.waitForSelector('button.settings-close', { timeout: 8000 });
}
test('close ferme Settings ouvert depuis le menu utilisateur (nav partielle)', async ({ page }) => {
await login(page);
const id = await makeWorkspace(page);
await page.goto(`${FD_BASE}/local-workspace?ws=${id}`, { waitUntil: 'domcontentloaded' });
await openSettingsFromUserMenu(page);
expect(page.url()).toContain('/settings');
await page.locator('button.settings-close').click();
await page.waitForTimeout(900);
// plus sur /settings…
expect(page.url()).not.toContain('/settings');
expect(page.url()).toContain('/local-workspace');
// …et surtout : le panneau `position:fixed` ne doit PLUS être peint. C'est
// le symptôme exact rapporté (« ça ferme mais ça reste ouvert »).
expect(await page.locator('.settings-overlay').count()).toBe(0);
await expect(page.locator('button.settings-close')).toHaveCount(0);
});
test('close ferme Settings ouvert par chargement complet depuis /workspaces', async ({ page }) => {
await login(page);
await page.goto(`${FD_BASE}/workspaces`, { waitUntil: 'domcontentloaded' });
await openSettingsFromUserMenu(page);
expect(page.url()).toContain('/settings');
await page.locator('button.settings-close').click();
await page.waitForTimeout(900);
expect(page.url()).not.toContain('/settings');
expect(await page.locator('.settings-overlay').count()).toBe(0);
});