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
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
// 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);
|
||||
});
|
||||
Reference in New Issue
Block a user