- 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.
100 lines
4.2 KiB
JavaScript
100 lines
4.2 KiB
JavaScript
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');
|
|
});
|