Files
Nick Shirokov f1b61b9e9e test(web-test): фокус-клик по полю вместо fillFields для сброса viewport в 18-cell-click
Шаг focus-click пропуска чекбоксов выводил фокус из ТЧ через fillFields({Комментарий}),
что лишний раз перезаписывало значение. clickElement по полю «Комментарий» фокусирует
его без перезаполнения и так же сбрасывает горизонтальный viewport грида. Поведение
шага не меняется (читаются только булевы Товаров), тест зелёный.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 13:47:18 +03:00

265 lines
16 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export const name = 'clickElement({row, column}): cell click on grids + spreadsheet backward-compat';
export const tags = ['cell-click', 'smoke'];
export const timeout = 180000;
export default async function({
navigateSection, navigateLink, openCommand, clickElement, fillFields, fillTableRow,
filterList, readTable, readSpreadsheet, closeForm, getFormState, wait, assert, step, log
}) {
// ── Spreadsheet backward-compat ─────────────────────────────────────────────
await step('spreadsheet: cell click by (row, column) still works (regression guard)', async () => {
await navigateSection('Склад');
await openCommand('Остатки товаров');
await clickElement('Еще');
await clickElement('Установить стандартные настройки');
await clickElement('Сформировать');
await wait(3);
const r = await readSpreadsheet();
assert.ok(r.data?.length > 0, 'В отчёте есть данные');
const firstHeader = r.headers[0];
const before = await getFormState();
const res = await clickElement({ row: 0, column: firstHeader });
log(`spreadsheet click: ${JSON.stringify(res.clicked)}`);
assert.equal(res.clicked?.kind, 'spreadsheetCell', 'kind=spreadsheetCell — без table роутер ушёл в spreadsheet');
await closeForm();
});
// ── Grid cell click: catalog list with dblclick to open item ────────────────
await step('catalog list: dblclick by {row: filter, column} opens the item', async () => {
await navigateSection('Склад');
await openCommand('Контрагенты');
const t = await readTable();
assert.ok(t.rows?.length > 0, 'Список Контрагентов не пуст');
// Используем фикстуру стенда: ООО Север в колонке Наименование
const before = await getFormState();
const res = await clickElement(
{ row: { 'Наименование': 'ООО Север' }, column: 'Наименование' },
{ dblclick: true }
);
log(`clicked: ${JSON.stringify(res.clicked)}`);
assert.equal(res.clicked?.kind, 'gridCell', 'kind=gridCell');
assert.equal(res.clicked?.dblclick, true, 'dblclick=true прокинут');
await wait(1);
const after = await getFormState();
// На синтетическом стенде поведение dblclick по ячейке может не открывать форму,
// если колонка не "главная" — главное, что клик завершился без ошибки и тип события правильный.
if (after.formCount > before.formCount) {
log('форма открылась — закрываем');
await closeForm();
}
});
// ── Grid cell click on tabular section + row by numeric index ──────────────
await step('tabular section: click cell by row:0 + column (table specified)', async () => {
await navigateSection('Склад');
await openCommand('Приходная накладная');
await clickElement('Создать');
await fillFields({ 'Контрагент': 'ООО Север' });
await fillTableRow(
{ 'Номенклатура': 'Товар 01', 'Количество': '5', 'Цена': '100' },
{ table: 'Товары', add: true }
);
await fillTableRow(
{ 'Номенклатура': 'Товар 02', 'Количество': '3', 'Цена': '200' },
{ table: 'Товары', add: true }
);
const res = await clickElement(
{ row: 0, column: 'Количество' },
{ table: 'Товары' }
);
log(`clicked: ${JSON.stringify(res.clicked)}`);
assert.equal(res.clicked?.kind, 'gridCell', 'kind=gridCell');
assert.equal(res.clicked?.row, 0, 'row=0 сохранён в результате');
assert.equal(res.clicked?.column, 'Количество', 'column=Количество');
});
// ── readTable.hasMore on tabular section ───────────────────────────────────
await step('readTable.hasMore: 2-row table shows hasMore.below=false', async () => {
const t = await readTable({ table: 'Товары' });
log(`hasMore: ${JSON.stringify(t.hasMore)}`);
assert.ok(t.hasMore, 'hasMore присутствует в результате');
assert.equal(t.hasMore.below, false, 'hasMore.below=false для двух строк (всё видно)');
});
// ── Error path: row not in DOM, no scroll → understandable error ───────────
await step('row_not_found без scroll бросает ошибку с подсказкой', async () => {
let caught = null;
try {
await clickElement(
{ row: { 'Количество': 'НЕСУЩЕСТВУЮЩЕЕ_ЗНАЧЕНИЕ_123' }, column: 'Количество' },
{ table: 'Товары' } // без scroll
);
} catch (e) {
caught = e;
}
assert.ok(caught, 'Должна быть ошибка');
log(`error: ${caught.message}`);
assert.ok(/not found/i.test(caught.message), 'Сообщение упоминает not found');
assert.ok(/scroll/i.test(caught.message), 'Сообщение содержит подсказку про scroll: true');
});
// ── Error path: out of range numeric row ───────────────────────────────────
await step('row_out_of_range на числовом индексе бросает понятную ошибку', async () => {
let caught = null;
try {
await clickElement(
{ row: 9999, column: 'Количество' },
{ table: 'Товары' }
);
} catch (e) {
caught = e;
}
assert.ok(caught, 'Должна быть ошибка');
log(`error: ${caught.message}`);
assert.ok(/out of range/i.test(caught.message), 'Сообщение упоминает out of range');
assert.ok(/virtualized/i.test(caught.message) || /DOM window/i.test(caught.message),
'Сообщение объясняет про виртуализацию / DOM window');
});
// ── Cleanup the 2-row doc before opening LongDoc ───────────────────────────
await step('cleanup: close 2-row document', async () => {
await closeForm({ save: false });
});
// ── Open LongDoc (30 rows in tabular section, fixtures from ЗаполнитьДокументы) ──
await step('setup: open LongDoc (30-row tabular section)', async () => {
await navigateSection('Склад');
await openCommand('Приходная накладная');
await filterList('LongDoc', { field: 'Комментарий' });
// Открываем единственный найденный LongDoc (фикстура создаётся один раз
// при первом запуске базы — даже после многократных прогонов одна штука).
const t = await readTable();
assert.ok(t.rows?.length >= 1, 'LongDoc должен быть в списке');
const num = t.rows[0]['Номер'];
await clickElement(num, { dblclick: true });
await wait(2);
const f = await getFormState();
assert.equal(f.tables?.[0]?.name, 'Товары', 'Открыта форма документа с ТЧ Товары');
});
// ── Reveal-loop: filter row not in current DOM window, scroll:true разворачивает ──
await step('reveal-loop: scroll:true находит строку Количество=25 в LongDoc', async () => {
const tBefore = await readTable({ table: 'Товары', maxRows: 100 });
log(`loaded=${tBefore.rows.length} hasMore=${JSON.stringify(tBefore.hasMore)}`);
assert.ok(tBefore.hasMore?.below === true || tBefore.rows.length >= 20,
'LongDoc виртуализирован или загружено достаточно строк');
// Целевая Количество=25 — заведомо глубоко в списке (LongDoc заполняет 1..30).
const res = await clickElement(
{ row: { 'Количество': '25,000' }, column: 'Сумма' },
{ table: 'Товары', scroll: true }
);
log(`reveal clicked: ${JSON.stringify(res.clicked)}`);
assert.equal(res.clicked?.kind, 'gridCell', 'reveal-loop нашёл строку');
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(
{ row: 0, column: 'Признак контроля' },
{ table: 'Товары' }
);
log(`right click: ${JSON.stringify(right.clicked)}`);
assert.equal(right.clicked?.kind, 'gridCell', 'kind=gridCell для правой');
assert.equal(right.clicked?.column, 'Признак контроля', 'добрались до самой правой колонки');
// Теперь обратно к Количество — направление ArrowLeft, scroll сдвигает viewport влево.
const left = await clickElement(
{ row: 0, column: 'Количество' },
{ table: 'Товары' }
);
log(`left click: ${JSON.stringify(left.clicked)}`);
assert.equal(left.clicked?.kind, 'gridCell', 'kind=gridCell для левой');
assert.equal(left.clicked?.column, 'Количество', 'вернулись к Количество через ArrowLeft scroll');
});
// ── Focus-click skip checkbox: cluster booleans on right edge, click further right ──
await step('focus-click пропускает checkbox-ячейки при выборе focus-точки', async () => {
// После предыдущего шага viewport уехал вправо. Нужно сбросить — выводим фокус
// из ТЧ кликом по полю «Комментарий» (вне грида), без перезаполнения значения.
await clickElement('Комментарий'); // фокус вне грида → дефолтный viewport
await wait(0.3);
const before = await readTable({ table: 'Товары', maxRows: 5 });
const bools0 = {
ВРезерве: before.rows[0]['В резерве'],
НаКомиссии: before.rows[0]['На комиссии'],
Подарок: before.rows[0]['Подарок'],
};
log(`booleans before: ${JSON.stringify(bools0)}`);
// Клик в дальнюю колонку «Серия» — focus-pick должен выбрать самую правую
// видимую non-frozen non-checkbox ячейку. При дефолтном viewport кластер
// из 3 boolean (ВРезерве/НаКомиссии/Подарок) на правом крае — pick должен
// их пропустить и взять Источник (reference, левее cluster).
const res = await clickElement(
{ row: 0, column: 'Серия' },
{ table: 'Товары' }
);
log(`clicked: ${JSON.stringify(res.clicked)}`);
assert.equal(res.clicked?.kind, 'gridCell', 'клик на Серия (после горизонтального скролла)');
// Главное: booleans в строке 0 НЕ изменились — focus-click не задел чекбоксы.
const after = await readTable({ table: 'Товары', maxRows: 5 });
const bools1 = {
ВРезерве: after.rows[0]['В резерве'],
НаКомиссии: after.rows[0]['На комиссии'],
Подарок: after.rows[0]['Подарок'],
};
log(`booleans after: ${JSON.stringify(bools1)}`);
assert.equal(bools1.ВРезерве, bools0.ВРезерве, 'ВРезерве не переключилось');
assert.equal(bools1.НаКомиссии, bools0.НаКомиссии, 'НаКомиссии не переключилось');
assert.equal(bools1.Подарок, bools0.Подарок, 'Подарок не переключилось');
});
// ── Final cleanup ──────────────────────────────────────────────────────────
await step('cleanup: close LongDoc', async () => {
await closeForm({ save: false });
});
// ── Dynamic list (not tabular section): hasMore.above/below + reveal-loop ──
// Группа БольшойСписок справочника Номенклатура содержит 60 элементов —
// заведомо больше окна виртуализации. В отличие от табчасти LongDoc, это
// ДИНАМИЧЕСКИЙ список: hasMore определяется через turn-кнопки vertButtonScroll.
await step('dyn-list setup: открыть Номенклатуру, развернуть БольшойСписок', async () => {
await navigateSection('Склад');
await openCommand('Номенклатура');
await clickElement('БольшойСписок', { dblclick: true });
await wait(1);
const t = await readTable();
log(`БольшойСписок: shown=${t.shown} hasMore=${JSON.stringify(t.hasMore)}`);
// Зашли в группу — наверху списка, сверху ничего, снизу есть (60 > окна).
assert.equal(t.hasMore?.above, false, 'в начале списка above=false');
assert.equal(t.hasMore?.below, true, '60 элементов > окна → below=true');
});
await step('dyn-list reveal: scroll:true находит Позиция 055 в длинном дин-списке', async () => {
const res = await clickElement(
{ row: { 'Наименование': 'Позиция 055' }, column: 'Наименование' },
{ scroll: true }
);
log(`reveal clicked: ${JSON.stringify(res.clicked)}`);
assert.equal(res.clicked?.kind, 'gridCell', 'reveal-loop на дин-списке нашёл строку');
// После прокрутки к концовой части списка сверху уже есть скрытые строки.
const t = await readTable();
log(`after reveal: hasMore=${JSON.stringify(t.hasMore)}`);
assert.equal(t.hasMore?.above, true, 'после прокрутки вниз above=true');
});
await step('dyn-list cleanup: закрыть список', async () => {
await closeForm();
});
}