feat: Escape du menu slash retire le « / » + bouton « commenter » à droite du bloc (v7.66.0)
- SM.escClose() : un seul implément, appelé par les deux chemins de fermeture du menu slash (focus dans le bloc → onKd, focus dans l'entrée du menu → listener document). Retire le « / » d'ouverture seulement, remet le caret, sync() + autoSave(). - Bouton .block-comment-btn à droite du bloc (gouttière 64px) : icône bulle de conversation + , visible au survol/focus comme le handle, masqué sur mobile (2 breakpoints) et sur page verrouillée ; onclick = E.commentBlock(idx). Remplace l'ancien .block-actions de renderBlock (opacity:0 sans règle de survol → un + invisible qui capait les clics dans la gouttière). - fmtComment() restaure la plage de la toolbar (_fmtRestore) avant d'ancrer : commentBlock() ne se replie plus sur this._sel (plage périmée éventuelle). Tests: pytest tests/ -q -n auto → 1386 passed ; ruff check app tests → OK ; e2e v766 (Esc → menu fermé + bloc vide ; bouton masqué → visible au survol, à droite du wrapper, ancre = bloc visé) + v764/v762/v7641/v763/ regression_editor_mount → 7 passed sur :8081.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
const { test, expect } = require('@playwright/test');
|
||||
|
||||
/**
|
||||
* v7.66 — deux ajustements éditeur :
|
||||
* - « / » ouvre le menu slash → Escape ferme le menu ET retire le « / »
|
||||
* - survol d'un bloc → bouton à droite (bulle + ) qui commente le bloc
|
||||
* Instance de test attendue sur FD_BASE_URL (défaut 8081).
|
||||
*/
|
||||
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';
|
||||
|
||||
test.use({ serviceWorkers: 'block' });
|
||||
|
||||
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');
|
||||
const ok = await page
|
||||
.waitForURL('**/workspaces', { timeout: 8000 })
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
if (!ok) {
|
||||
const resp = await page.request.post(`${FD_BASE}/auth/register`, {
|
||||
data: { email: USER, password: PASS, name: 'E2E' },
|
||||
});
|
||||
if (resp.status() !== 409) expect(resp.ok()).toBeTruthy();
|
||||
await page.goto(`${FD_BASE}/workspaces`, { waitUntil: 'domcontentloaded' });
|
||||
}
|
||||
}
|
||||
|
||||
async function newPage(page, body) {
|
||||
const cookies = await page.context().cookies();
|
||||
const csrf = (cookies.find((c) => c.name === 'csrf_token') || {}).value || '';
|
||||
const created = await page.request.post(
|
||||
`${FD_BASE}/board/api/pages?title=v766 ${Date.now()}`,
|
||||
{ headers: { 'X-CSRF-Token': csrf } }
|
||||
);
|
||||
expect(created.status(), await created.text()).toBeLessThan(400);
|
||||
const { id } = await created.json();
|
||||
if (body) {
|
||||
const bs = await page.request.post(`${FD_BASE}/board/api/pages/${id}/blocks`, {
|
||||
headers: { 'X-CSRF-Token': csrf },
|
||||
data: { title: 'v766', blocks: body },
|
||||
});
|
||||
expect(bs.status(), await bs.text()).toBeLessThan(400);
|
||||
}
|
||||
await page.goto(`${FD_BASE}/pages/${id}`, { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForSelector('#_blocksCt [data-bid]', { timeout: 15000 });
|
||||
return id;
|
||||
}
|
||||
|
||||
test('Escape du menu slash retire le caractère /', async ({ page }) => {
|
||||
await login(page);
|
||||
await newPage(page, [{ id: 'b1', type: 'paragraph', content: '' }]);
|
||||
const blk = page.locator('#_blocksCt [data-bid]').first();
|
||||
await blk.click();
|
||||
await page.keyboard.type('/');
|
||||
await expect(page.locator('#_slashMenu')).toBeVisible({ timeout: 5000 });
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(page.locator('#_slashMenu')).toBeHidden();
|
||||
// le « / » a été retiré — pas de caractère résiduel
|
||||
await expect(blk).toHaveText('');
|
||||
await page.screenshot({ path: 'shots/v766-slash-esc.png' });
|
||||
});
|
||||
|
||||
test('bouton commenter à droite du bloc', async ({ page }) => {
|
||||
await login(page);
|
||||
// 2 blocs : l'éditeur focalise le 1er au chargement (`:focus-within` le
|
||||
// montre) → on mesure sur le 2e, ni focalisé ni survolé.
|
||||
await newPage(page, [
|
||||
{ id: 'b1', type: 'paragraph', content: 'Premier bloc' },
|
||||
{ id: 'b2', type: 'paragraph', content: 'Un bloc a commenter' },
|
||||
]);
|
||||
await expect(page.locator('#_blocksCt .block-comment-btn')).toHaveCount(2);
|
||||
const wrap = page.locator('#_blocksCt .block-wrapper').nth(1);
|
||||
const btn = page.locator('#_blocksCt .block-comment-btn').nth(1);
|
||||
|
||||
// masqué tant qu'on ne survole pas / ne focalise pas le bloc
|
||||
await expect.poll(() => btn.evaluate((el) => getComputedStyle(el).opacity)).toBe('0');
|
||||
await wrap.hover();
|
||||
await expect.poll(() => btn.evaluate((el) => getComputedStyle(el).opacity)).toBe('1');
|
||||
|
||||
// bien à droite du bloc (gouttière 64px)
|
||||
const wb = await wrap.boundingBox();
|
||||
const bb = await btn.boundingBox();
|
||||
expect(bb.x).toBeGreaterThan(wb.x + wb.width - 1);
|
||||
await page.screenshot({ path: 'shots/v766-comment-btn.png' });
|
||||
|
||||
// clic → tiroir → envoi → ancre = le bloc visé (et pas le 1er)
|
||||
await btn.click();
|
||||
const drawer = page.locator('.comments-drawer');
|
||||
await expect(drawer).toBeVisible();
|
||||
await drawer.locator('textarea').fill('Commentaire via le bouton de droite');
|
||||
await drawer.locator('.ct-send').click();
|
||||
await expect(page.locator('.comment-thread')).toHaveCount(1);
|
||||
await expect(page.locator('#_blocksCt .fd-anchor[data-cid]')).toHaveText('Un bloc a commenter');
|
||||
});
|
||||
Reference in New Issue
Block a user