feat(web-test): fillTableRow редактирует строку по фильтру { col: value } + scroll

fillTableRow теперь принимает row как объектный фильтр (одна/несколько колонок,
AND-матч) — как clickElement — и опцию scroll:true для строк за пределами
DOM-окна виртуализации. Фильтр резолвится в числовой индекс один раз в начале
через переиспользование resolveRowIndexByFilter из click-cell.mjs (без дублей
matching/reveal); дальше существующий код row-mode не тронут. row:<число> —
полная обратная совместимость.

Побочно починен баг в общем reveal-цикле (его же использует clickElement scroll):
детектор конца списка опирался на текст первой колонки + selIdx, поэтому на
табчасти с однотипной первой колонкой ложно срабатывал на втором PageDown.
Теперь основной признак конца — hasBelow===false, а сигнатура снимка строится
по всей строке (snapshotGridScript).

Версии: click-cell v1.4, dom/grid v1.9, row-fill v1.22.
Регресс tests/web-test: 22/22 зелёные (live E2E на синтетическом стенде).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-06-01 22:03:06 +03:00
co-authored by Claude Opus 4.8
parent ffb380187f
commit c147fd5cb7
6 changed files with 117 additions and 28 deletions
+14
View File
@@ -43,6 +43,20 @@ export default async function({ navigateSection, openCommand, clickElement, fill
assert.equal(t.rows[0]['Количество'], '10,000', 'Количество строки 0 = 10');
});
await step('edit by filter: найти строку по значению ячейки { Номенклатура: Товар 02 } и изменить Цену', async () => {
const r = await fillTableRow(
{ 'Цена': '250' },
{ table: 'Товары', row: { 'Номенклатура': 'Товар 02' } }
);
log(`filter-edit result: ${JSON.stringify(r.filled)}`);
const t = await readTable({ table: 'Товары' });
log(`rows after filter-edit: ${JSON.stringify(t.rows)}`);
// Должна измениться именно строка Товар 02 (индекс 1), а не Товар 01 (индекс 0).
assert.equal(t.rows[1]['Номенклатура'], 'Товар 02', 'Фильтр нашёл строку Товар 02');
assert.equal(t.rows[1]['Цена'], '250,00', 'Цена строки Товар 02 = 250');
assert.equal(t.rows[0]['Номенклатура'], 'Товар 01', 'Строка Товар 01 не тронута');
});
await step('tab-loop: изменить два числовых поля в строке 1 одним вызовом', async () => {
const r = await fillTableRow(
{ 'Количество': '7', 'Цена': '150' },
+15
View File
@@ -154,6 +154,21 @@ export default async function({
assert.equal(res.clicked?.column, 'Сумма', 'column сохранён');
});
// ── fillTableRow by filter + scroll: тот же reveal-путь, что у clickElement ──
await step('fillTableRow: row-фильтр + scroll:true редактирует глубокую строку LongDoc', async () => {
// Количество=28 заведомо за пределами стартового DOM-окна (LongDoc 1..30).
const r = await fillTableRow(
{ 'Цена': '888' },
{ table: 'Товары', row: { 'Количество': '28' }, scroll: true }
);
log(`filled: ${JSON.stringify(r.filled)}`);
assert.ok(r.filled?.every(f => f.ok), 'все ячейки заполнены без ошибок');
const t = await readTable({ table: 'Товары', maxRows: 50 });
const row28 = t.rows.find(x => x['Количество'] === '28,000');
assert.ok(row28, 'строка Количество=28 в текущем окне после reveal');
assert.equal(row28['Цена'], '888,00', 'Цена строки 28 изменена через фильтр+scroll');
});
// ── Horizontal scroll: вправо до последней колонки, потом обратно влево ────
await step('horizontal scroll: вправо до Признак контроля, потом влево к Количество', async () => {
const right = await clickElement(