From 8f2fa218148d9532890a843f96f432fd278970f9 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Thu, 28 May 2026 21:42:10 +0300 Subject: [PATCH] =?UTF-8?q?fix(web-test):=20deleteTableRow=20=D0=B2=D1=8B?= =?UTF-8?q?=D1=85=D0=BE=D0=B4=D0=B8=D1=82=20=D0=B8=D0=B7=20cell=20edit-mod?= =?UTF-8?q?e=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=20Delete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Delete-клавиша в режиме редактирования ячейки очищает буфер ввода, а не удаляет строку. Это становилось проблемой когда: 1. предыдущий fillTableRow закончил Tab-навигацией в input (например в Number-ячейку соседней колонки), и фокус остался там; 2. сам click на Number/Date ячейку в deleteTableRow автоматически входит в edit-mode (поведение 1С). Фикс: в deleteTableRow проверяем isInputFocusedInGrid дважды — до и после click — и шлём Escape если активен INPUT в целевом гриде. Строка остаётся выделенной после Escape, Delete срабатывает. Дополнительно: isInputFocusedInGridScript / isInputFocusedInGrid теперь принимают опциональный gridSelector — чтобы можно было прицельно проверять конкретный грид на многогрид-формах (а не любой `.grid` на странице). Покрытие: новый шаг в 05-table проверяет сценарий «фокус снаружи грида (Комментарий), потом delete» — гарантирует что post-click Escape ловит автоматический вход в edit-mode при клике на Number-ячейку. Co-Authored-By: Claude Opus 4.7 --- .../web-test/scripts/dom/edit-state.mjs | 14 ++++++++++++-- .../web-test/scripts/engine/core/helpers.mjs | 9 +++++---- .../web-test/scripts/engine/table/grid.mjs | 19 ++++++++++++++++++- tests/web-test/05-table.test.mjs | 14 ++++++++++++++ 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/.claude/skills/web-test/scripts/dom/edit-state.mjs b/.claude/skills/web-test/scripts/dom/edit-state.mjs index d5666950..31aa74c8 100644 --- a/.claude/skills/web-test/scripts/dom/edit-state.mjs +++ b/.claude/skills/web-test/scripts/dom/edit-state.mjs @@ -1,4 +1,4 @@ -// web-test dom/edit-state v1.0 — focus and popup detection inside the 1C web client +// web-test dom/edit-state v1.1 — focus and popup detection inside the 1C web client // Source: https://github.com/Nikolay-Shirokov/cc-1c-skills /** @@ -21,12 +21,22 @@ export function isInputFocusedScript({ allowTextarea = false } = {}) { /** * Is the currently focused INPUT/TEXTAREA inside a `.grid` ancestor? * Used to verify grid edit-mode (active cell editor). + * + * @param {string} [gridSelector] — when given, only `true` if the focused input + * is inside that specific grid. Without it — any `.grid` ancestor counts. + * * Returns boolean. */ -export function isInputFocusedInGridScript() { +export function isInputFocusedInGridScript(gridSelector) { + const sel = gridSelector ? JSON.stringify(gridSelector) : 'null'; return `(() => { const f = document.activeElement; if (!f || (f.tagName !== 'INPUT' && f.tagName !== 'TEXTAREA')) return false; + const sel = ${sel}; + if (sel) { + const grid = document.querySelector(sel); + return !!(grid && grid.contains(f)); + } let n = f; while (n) { if (n.classList?.contains('grid')) return true; diff --git a/.claude/skills/web-test/scripts/engine/core/helpers.mjs b/.claude/skills/web-test/scripts/engine/core/helpers.mjs index fecec2f3..3eda408b 100644 --- a/.claude/skills/web-test/scripts/engine/core/helpers.mjs +++ b/.claude/skills/web-test/scripts/engine/core/helpers.mjs @@ -1,4 +1,4 @@ -// web-test core/helpers v1.20 — private, cross-cutting helpers used by the +// web-test core/helpers v1.21 — private, cross-cutting helpers used by the // public action functions (clickElement/fillFields/selectValue/etc). // Source: https://github.com/Nikolay-Shirokov/cc-1c-skills @@ -94,10 +94,11 @@ export async function isInputFocused({ allowTextarea = false } = {}) { /** * Thin wrapper: is the currently focused INPUT/TEXTAREA inside a `.grid`? - * Used to verify grid edit-mode. + * Used to verify grid edit-mode. Pass `{ gridSelector }` to scope the check + * to a specific grid (when a form has multiple grids). */ -export async function isInputFocusedInGrid() { - return page.evaluate(isInputFocusedInGridScript()); +export async function isInputFocusedInGrid({ gridSelector } = {}) { + return page.evaluate(isInputFocusedInGridScript(gridSelector)); } /** diff --git a/.claude/skills/web-test/scripts/engine/table/grid.mjs b/.claude/skills/web-test/scripts/engine/table/grid.mjs index 70601d5e..bc171a1c 100644 --- a/.claude/skills/web-test/scripts/engine/table/grid.mjs +++ b/.claude/skills/web-test/scripts/engine/table/grid.mjs @@ -1,4 +1,4 @@ -// web-test table/grid v1.19 — Form-grid operations: read table rows, fill rows, delete rows. +// web-test table/grid v1.20 — Form-grid operations: read table rows, fill rows, delete rows. // Source: https://github.com/Nikolay-Shirokov/cc-1c-skills // // "Grid" в терминах 1С — таблица на форме (.gridLine/.gridBody/.grid в DOM): @@ -8,6 +8,7 @@ import { page, ensureConnected } from '../core/state.mjs'; import { detectFormScript, readTableScript, resolveGridScript } from '../../dom.mjs'; import { findDeleteRowCoordsScript, countGridRowsScript } from '../../dom/grid.mjs'; +import { isInputFocusedInGrid } from '../core/helpers.mjs'; import { dismissPendingErrors } from '../core/errors.mjs'; import { waitForStable } from '../core/wait.mjs'; import { clickElement } from '../core/click.mjs'; @@ -63,10 +64,26 @@ export async function deleteTableRow(row, { tab, table } = {}) { const rowsBefore = cellCoords.total; + // Pre-click Escape: leftover edit-mode from a prior fillTableRow Tab-navigation. + // Without it the next mouse click may not select the row reliably (the active + // edit input intercepts the event timing). + if (await isInputFocusedInGrid({ gridSelector })) { + await page.keyboard.press('Escape'); + await page.waitForTimeout(150); + } + // Single click to select the row await page.mouse.click(cellCoords.x, cellCoords.y); await page.waitForTimeout(300); + // Post-click Escape: clicking a Number/Date cell auto-enters edit mode in 1С. + // Delete in edit mode clears the cell buffer instead of deleting the row, so + // we exit edit first. The row remains selected after Escape — Delete acts on it. + if (await isInputFocusedInGrid({ gridSelector })) { + await page.keyboard.press('Escape'); + await page.waitForTimeout(150); + } + // 3. Press Delete to remove the row await page.keyboard.press('Delete'); await waitForStable(); diff --git a/tests/web-test/05-table.test.mjs b/tests/web-test/05-table.test.mjs index c7febb1b..fd6ac340 100644 --- a/tests/web-test/05-table.test.mjs +++ b/tests/web-test/05-table.test.mjs @@ -90,6 +90,20 @@ export default async function({ navigateSection, openCommand, clickElement, fill log(`rows after delete: ${t.rows?.length}, [0]=${t.rows[0]?.['Номенклатура']}`); assert.equal(t.rows?.length, 1, 'Должна остаться 1 строка'); assert.equal(t.rows[0]['Номенклатура'], 'Товар 02', 'Осталась строка Товар 02'); + }); + + await step('delete: фокус вне грида (Комментарий) — delete всё равно должен работать', async () => { + // Воспроизводит сценарий, когда последнее действие было НЕ в табчасти. + // deleteTableRow должен корректно перехватить фокус и удалить строку + // несмотря на то, что click на Number-ячейку входит в edit-mode (post-click Escape). + await fillTableRow({ 'Номенклатура': 'Товар 03', 'Количество': '8' }, { table: 'Товары', add: true }); + // Перевести фокус на Комментарий (вне грида). + await fillFields({ 'Комментарий': 'focus-outside-grid' }); + await deleteTableRow(0, { table: 'Товары' }); + const t = await readTable({ table: 'Товары' }); + log(`rows after delete: ${t.rows?.length}, names=${t.rows.map(r => r['Номенклатура']).join(',')}`); + assert.equal(t.rows?.length, 1, 'Должна остаться 1 строка'); + assert.equal(t.rows[0]['Номенклатура'], 'Товар 03', 'Удалена первая (Товар 02), осталась Товар 03'); await closeForm({ save: false }); }); }