mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-28 23:51:03 +03:00
fix(web-test): заполнение data-ячеек безголового грида + устранение мигания чекбокса
Два фикса в fillTableRow на гридах:
1. Безголовые гриды (без .gridHead): правка data-ячейки (число/ссылка)
молча возвращала filled:[]. В режиме редактирования INPUT рендерится
в оверлее .inputs ВНЕ .gridBox, поэтому readActiveGridCell не доставал
colindex через предков → синтетическое имя Колонка{N} не матчилось в
Tab-цикле. Теперь colindex резолвится по x-координате INPUT относительно
ячеек тела грида — тем же приёмом, что и шапочная ветка, через единый
synthHeaderlessColumns.
2. Чекбокс-ячейки: безусловный первичный клик по центру ячейки тогглил
чекбокс ДО checkbox-логики, которая затем «доправляла» значение обратно
— лишний клик и видимое мигание (снимаем→ставим) при установке в уже
текущее значение. Детект чекбокса (findCheckboxAtPoint, читает состояние
из DOM без режима редактирования) перенесён ДО первичного клика: кликаем
по иконке только когда текущее состояние != желаемого (0 кликов на
идемпотентном тоггле). Затрагивало любые таблицы, не только безголовые.
Тест 21-headerless расширен шагами на заполнение data-ячеек (число direct,
ссылка dropdown) и клик по data-колонке (через API, без raw DOM).
Полный регресс web-test: 25 passed, 0 failed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
03d90b6960
commit
3a7f1c1763
@@ -1,4 +1,4 @@
|
|||||||
// web-test dom/grid-edit v1.1 — DOM scripts for row-fill (grid edit-time operations)
|
// web-test dom/grid-edit v1.2 — DOM scripts for row-fill (grid edit-time operations)
|
||||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
//
|
//
|
||||||
import { HEADERLESS_GRID_FN } from './_shared.mjs';
|
import { HEADERLESS_GRID_FN } from './_shared.mjs';
|
||||||
@@ -281,9 +281,16 @@ export function readActiveGridCellScript() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!head) {
|
if (!head) {
|
||||||
// Headerless: resolve the editing column name from the cell's colindex via synth.
|
// Headerless: the editing INPUT is rendered in an overlay (.inputs) OUTSIDE
|
||||||
let box = f; while (box && !box.classList?.contains('gridBox')) box = box.parentElement;
|
// the .gridBox, so walking ancestors for colindex fails. Resolve colindex by
|
||||||
const ci = box?.getAttribute('colindex');
|
// matching the input's x against the body cells (same idea as the headed branch).
|
||||||
|
const bl = grid.querySelector('.gridBody .gridLine');
|
||||||
|
let ci = null;
|
||||||
|
if (bl) for (const b of bl.children) {
|
||||||
|
if (b.offsetWidth === 0) continue;
|
||||||
|
const br = b.getBoundingClientRect();
|
||||||
|
if (fr.x >= br.x && fr.x < br.x + br.width) { ci = b.getAttribute('colindex'); break; }
|
||||||
|
}
|
||||||
if (ci != null) {
|
if (ci != null) {
|
||||||
const sc = synthHeaderlessColumns(grid).find(c => c.kind === 'data' && c.colindex === ci);
|
const sc = synthHeaderlessColumns(grid).find(c => c.kind === 'data' && c.colindex === ci);
|
||||||
if (sc) headerText = sc.name;
|
if (sc) headerText = sc.name;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// web-test table/row-fill v1.24 — fillTableRow — заполнение строки табличной части/списка через Tab-навигацию и попутный выбор значений.
|
// web-test table/row-fill v1.25 — fillTableRow — заполнение строки табличной части/списка через Tab-навигацию и попутный выбор значений.
|
||||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -236,6 +236,32 @@ export async function fillTableRow(fields, { tab, add, row, table, scroll } = {}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checkbox cell? Detect BEFORE any click. Clicking the cell center toggles the
|
||||||
|
// checkbox, so an unconditional click here would flip an already-correct value
|
||||||
|
// (visible flicker + redundant write — снимаем→ставим). findCheckboxAtPoint reads
|
||||||
|
// the state straight from the DOM (no edit mode needed), so we branch first and
|
||||||
|
// click the icon only when the current state differs from the desired one.
|
||||||
|
if (firstVal0 !== '') {
|
||||||
|
const checkboxInfo = await page.evaluate(findCheckboxAtPointScript(cellCoords.x, cellCoords.y));
|
||||||
|
if (checkboxInfo !== null) {
|
||||||
|
const desired = ['true', 'да', '1', 'yes'].includes(String(firstVal0).toLowerCase().trim());
|
||||||
|
if (checkboxInfo.checked !== desired) {
|
||||||
|
await page.mouse.click(checkboxInfo.x, checkboxInfo.y);
|
||||||
|
await page.waitForTimeout(300);
|
||||||
|
}
|
||||||
|
const results = [{ field: firstKey0, ok: true, method: 'toggle', value: desired }];
|
||||||
|
await waitForStable(formNum);
|
||||||
|
// If more fields remain, process them on the same row
|
||||||
|
const remaining = { ...fields };
|
||||||
|
delete remaining[firstKey0];
|
||||||
|
if (Object.keys(remaining).length > 0) {
|
||||||
|
const more = await fillTableRow(remaining, { row, table });
|
||||||
|
results.push(...more.filled);
|
||||||
|
}
|
||||||
|
return returnFormState({ filled: results });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Click first (tree grids enter edit on single click; dblclick toggles expand/collapse).
|
// Click first (tree grids enter edit on single click; dblclick toggles expand/collapse).
|
||||||
// Then escalate: dblclick → F4 if needed.
|
// Then escalate: dblclick → F4 if needed.
|
||||||
await page.mouse.click(cellCoords.x, cellCoords.y);
|
await page.mouse.click(cellCoords.x, cellCoords.y);
|
||||||
@@ -271,27 +297,6 @@ export async function fillTableRow(fields, { tab, add, row, table, scroll } = {}
|
|||||||
return returnFormState({ filled: results });
|
return returnFormState({ filled: results });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if clicked cell is a checkbox (toggle-on-click, no edit mode)
|
|
||||||
const checkboxInfo = await page.evaluate(findCheckboxAtPointScript(cellCoords.x, cellCoords.y));
|
|
||||||
if (checkboxInfo !== null) {
|
|
||||||
// Checkbox cell found — click directly on the checkbox icon (not cell center)
|
|
||||||
const desired = ['true', 'да', '1', 'yes'].includes(String(firstVal0).toLowerCase().trim());
|
|
||||||
if (checkboxInfo.checked !== desired) {
|
|
||||||
await page.mouse.click(checkboxInfo.x, checkboxInfo.y);
|
|
||||||
await page.waitForTimeout(300);
|
|
||||||
}
|
|
||||||
const results = [{ field: firstKey0, ok: true, method: 'toggle', value: desired }];
|
|
||||||
await waitForStable(formNum);
|
|
||||||
// If more fields remain, process them on the same row
|
|
||||||
const remaining = { ...fields };
|
|
||||||
delete remaining[firstKey0];
|
|
||||||
if (Object.keys(remaining).length > 0) {
|
|
||||||
const more = await fillTableRow(remaining, { row, table });
|
|
||||||
results.push(...more.filled);
|
|
||||||
}
|
|
||||||
return returnFormState({ filled: results });
|
|
||||||
}
|
|
||||||
|
|
||||||
let inEdit = false;
|
let inEdit = false;
|
||||||
let directEditForm = null;
|
let directEditForm = null;
|
||||||
for (let dw = 0; dw < 4; dw++) {
|
for (let dw = 0; dw < 4; dw++) {
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ export const timeout = 180000;
|
|||||||
// - БезшапочнаяТаблица (ValueTable, отдельная колонка-чекбокс)
|
// - БезшапочнаяТаблица (ValueTable, отдельная колонка-чекбокс)
|
||||||
// - МножественныйВыбор → Через флажки (марк-список, комбинированная ячейка галка+текст)
|
// - МножественныйВыбор → Через флажки (марк-список, комбинированная ячейка галка+текст)
|
||||||
// Деривация колонок едина (synthHeaderlessColumns): Колонка{N} + (checkbox), привязка по colindex.
|
// Деривация колонок едина (synthHeaderlessColumns): Колонка{N} + (checkbox), привязка по colindex.
|
||||||
// Правка data-ячейки на безголовом ValueTable — follow-up (edit-Tab-loop row-fill), здесь не тестируется.
|
// Правка data-ячейки на безголовом ValueTable (число/ссылка через edit-Tab-loop) — тоже покрыта:
|
||||||
|
// в режиме редактирования INPUT рендерится в оверлее вне .gridBox, colindex резолвится по
|
||||||
|
// x-координате относительно ячеек тела (grid-edit v1.2), иначе synth-имя Колонка{N} не матчилось.
|
||||||
|
|
||||||
export default async function({
|
export default async function({
|
||||||
navigateLink, clickElement, fillTableRow, readTable, getFormState, wait, assert, step, log, closeForm
|
navigateLink, clickElement, fillTableRow, readTable, getFormState, wait, assert, step, log, closeForm
|
||||||
@@ -65,6 +67,30 @@ export default async function({
|
|||||||
assert.equal(r1['(checkbox)'], want, `клик переключил галку Позиция 003 в ${want}`);
|
assert.equal(r1['(checkbox)'], want, `клик переключил галку Позиция 003 в ${want}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await step('fillTableRow: правка числовой data-ячейки Колонка2 по индексу (method=direct)', async () => {
|
||||||
|
const r = await fillTableRow({ 'Колонка2': '777' }, { row: 1 });
|
||||||
|
log(`filled: ${JSON.stringify(r.filled)}`);
|
||||||
|
assert.ok(r.filled?.[0]?.ok && r.filled[0].method === 'direct', 'method=direct, ok');
|
||||||
|
const t = await readTable({ maxRows: 5 });
|
||||||
|
assert.ok(t.rows[1]['Колонка2'].startsWith('777'), `Колонка2 строки 1 = 777, got ${t.rows[1]['Колонка2']}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
await step('fillTableRow: правка ссылочной data-ячейки Колонка1 по фильтру (выбор из списка)', async () => {
|
||||||
|
const r = await fillTableRow({ 'Колонка1': 'Позиция 001' }, { row: { 'Колонка1': 'Позиция 003' } });
|
||||||
|
log(`filled: ${JSON.stringify(r.filled)}`);
|
||||||
|
assert.ok(r.filled?.[0]?.ok, 'ok');
|
||||||
|
assert.ok(['dropdown', 'form'].includes(r.filled[0].method), `method dropdown|form, got ${r.filled[0].method}`);
|
||||||
|
const t = await readTable({ maxRows: 5 });
|
||||||
|
assert.equal(t.rows.filter(x => x['Колонка1'] === 'Позиция 001').length, 2, 'две строки Позиция 001 (была 001 + перезаписанная 003)');
|
||||||
|
});
|
||||||
|
|
||||||
|
await step('clickElement: клик по data-ячейке резолвит синтетическую колонку Колонка3', async () => {
|
||||||
|
const r = await clickElement({ row: { 'Колонка1': 'Позиция 002' }, column: 'Колонка3' });
|
||||||
|
log(`clicked: ${JSON.stringify(r.clicked)}`);
|
||||||
|
assert.equal(r.clicked?.kind, 'gridCell', 'клик по ячейке грида');
|
||||||
|
assert.equal(r.clicked?.column, 'Колонка3', 'резолвнутая колонка — синтетическая Колонка3');
|
||||||
|
});
|
||||||
|
|
||||||
// ── Марк-список (комбинированная ячейка галка+текст в одной .gridBox) ────────
|
// ── Марк-список (комбинированная ячейка галка+текст в одной .gridBox) ────────
|
||||||
await step('марк-список: readTable → (checkbox) + Колонка1, расщепление одной ячейки', async () => {
|
await step('марк-список: readTable → (checkbox) + Колонка1, расщепление одной ячейки', async () => {
|
||||||
await navigateLink('Обработка.МножественныйВыбор');
|
await navigateLink('Обработка.МножественныйВыбор');
|
||||||
|
|||||||
Reference in New Issue
Block a user