feat: completude de l'editeur Excel - mise en forme, sortie, concurrence, cache, outils IA #156
L'editeur lisait les styles mais ne les ecrivait pas, exportait la feuille entiere et laissait le dernier-ecrivain gagner entre processus. - A8 : bouton Mise en forme (gras/italique/souligne, alignements, couleurs via selecteur natif, formats de nombre, fusion, volets figes, largeur/hauteur) et nouvelle route PUT .../xlsx/style (verrou, backup, swap atomique, garde de perte, If-Match) ; A9 : decision "pas de moteur de formule" annoncee dans l'UI ; A10 : undo/redo unifie, piles par fichier conservees au re-rendu - A11 : export de la selection + Markdown/HTML/impression et recherche sur toutes les feuilles ; A12 : concurrence optimiste (If-Match -> 409 reparable, retry qui relit) ; A13 : cache des metadonnees par (chemin, mtime, taille) - A14 : outils IA .xlsm/.csv + search_workbook, analyze_range, edit_xlsx_structure - tests : test_xlsx_styles.py (17), test_spreadsheet_tools.py (34), TestOptimisticConcurrency/TestMetaCache, JSDOM xlsx-viewer 107/107
This commit is contained in:
@@ -241,17 +241,18 @@ test.describe('Excel viewer — troncature et navigation (#153 A8/A9)', () => {
|
||||
await openXlsx(page, LARGE);
|
||||
|
||||
// Un clic sur une cellule active la barre avec son nom et son contenu.
|
||||
// (#156-A7 : la zone Nom est un input, donc on lit sa valeur.)
|
||||
const cell = page.locator('#content-area td[data-cell="B2"]');
|
||||
await cell.click();
|
||||
await expect(page.locator('#xlsx-active-cell')).toHaveText('B2');
|
||||
await expect(page.locator('#xlsx-active-cell')).toHaveValue('B2');
|
||||
const bar = page.locator('#xlsx-formula-input');
|
||||
await expect(bar).toHaveValue(/Operation 1/);
|
||||
|
||||
// Les flèches déplacent la cellule active.
|
||||
await cell.press('ArrowDown');
|
||||
await expect(page.locator('#xlsx-active-cell')).toHaveText('B3');
|
||||
await expect(page.locator('#xlsx-active-cell')).toHaveValue('B3');
|
||||
await page.locator('#content-area td[data-cell="B3"]').press('ArrowRight');
|
||||
await expect(page.locator('#xlsx-active-cell')).toHaveText('C3');
|
||||
await expect(page.locator('#xlsx-active-cell')).toHaveValue('C3');
|
||||
|
||||
// Éditer depuis la barre marque la cellule dirty, Échap annule.
|
||||
await bar.fill('Operation 2 modifiee');
|
||||
@@ -260,3 +261,56 @@ test.describe('Excel viewer — troncature et navigation (#153 A8/A9)', () => {
|
||||
await expect(page.locator('#content-area td.xlsx-dirty')).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
|
||||
// ── #156 P1 — presse-papiers de plage, clavier complet, zone Nom ────────────
|
||||
|
||||
test.describe('Excel viewer — presse-papiers, clavier et zone Nom (#156 P1)', () => {
|
||||
test('coller une plage, la navigation clavier et la zone Nom (#156 A5/A6/A7)', async ({ page }) => {
|
||||
await login(page);
|
||||
await openXlsx(page, LARGE);
|
||||
|
||||
const nameBox = page.locator('#xlsx-active-cell');
|
||||
const cellOf = (ref) => page.locator(`#content-area td[data-cell="${ref}"]`);
|
||||
const textOf = async (ref) => (await cellOf(ref).innerText()).trim();
|
||||
|
||||
// — A7 : la zone Nom est éditable et sert à « Atteindre ».
|
||||
await nameBox.fill('B12');
|
||||
await nameBox.press('Enter');
|
||||
await expect(nameBox).toHaveValue('B12');
|
||||
await expect(cellOf('B12')).toHaveClass(/xlsx-active/);
|
||||
|
||||
// — A6 : Ctrl+A sélectionne la feuille rendue, Ctrl+Home/End ses coins.
|
||||
await cellOf('B12').click();
|
||||
await page.keyboard.press('Control+a');
|
||||
await expect(nameBox).toHaveValue('A1:C500');
|
||||
await page.keyboard.press('Control+Home');
|
||||
await expect(nameBox).toHaveValue('A1');
|
||||
await page.keyboard.press('Control+End');
|
||||
await expect(nameBox).toHaveValue('C500');
|
||||
|
||||
// — A5 : copier une plage puis la coller ailleurs. Le menu contextuel
|
||||
// utilise le presse-papiers interne de la grille : aucune permission
|
||||
// navigateur n'est requise.
|
||||
const src = { a1: await textOf('A1'), b1: await textOf('B1'), b2: await textOf('B2'), c10: await textOf('C10') };
|
||||
await cellOf('A1').click();
|
||||
await cellOf('B2').click({ modifiers: ['Shift'] });
|
||||
await expect(nameBox).toHaveValue('A1:B2');
|
||||
await cellOf('A1').click({ button: 'right' });
|
||||
await page.locator('.xlsx-context-item', { hasText: 'Copier' }).click();
|
||||
await cellOf('A10').click({ button: 'right' });
|
||||
await page.locator('.xlsx-context-item', { hasText: 'Coller' }).click();
|
||||
expect(await textOf('A10')).toBe(src.a1);
|
||||
expect(await textOf('B10')).toBe(src.b1);
|
||||
expect(await textOf('B11')).toBe(src.b2);
|
||||
expect(await textOf('C10')).toBe(src.c10);
|
||||
|
||||
// — A6 : Suppr vide la sélection (et seulement elle).
|
||||
await cellOf('A10').click();
|
||||
await cellOf('B11').click({ modifiers: ['Shift'] });
|
||||
await expect(nameBox).toHaveValue('A10:B11');
|
||||
await page.keyboard.press('Delete');
|
||||
expect(await textOf('A10')).toBe('');
|
||||
expect(await textOf('B11')).toBe('');
|
||||
expect(await textOf('C10')).toBe(src.c10);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user