test(11-report): DCS-отчёт ОстаткиТоваров + smoke с быстрым фильтром

Синтетика: добавлен template-add ОсновнаяСхемаКомпоновкиДанных к отчёту
(без него skd-compile писал Template.xml в незарегистрированный путь),
переписан DSL skd-compile — fields внутри dataSets, типы полей, totalFields,
явный settingsVariants со structure и быстрым отбором по Номенклатуре
(@off @user @quickAccess).

Тест 11-report покрывает: регистрацию команды в подсистеме, открытие формы
отчёта с дефолтной кнопкой Сформировать, видимость и структуру быстрого
DCS-фильтра, формирование отчёта, применение фильтра через selectValue
(auto-enable чекбокса + значение), пересчёт с фильтром, снятие фильтра
через fillFields toggle off с восстановлением исходных данных.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-05-03 15:22:22 +03:00
parent 3c596f4550
commit 3ac1d425cd
2 changed files with 134 additions and 8 deletions
@@ -603,21 +603,44 @@ export const steps = [
},
// ── 4. DCS for report ──
// Сначала добавляем макет ОсновнаяСхемаКомпоновкиДанных к отчёту (регистрируется
// в Reports/ОстаткиТоваров.xml + автоматически выставляется MainDataCompositionSchema),
// затем skd-compile наполняет его содержимым.
{
name: 'template-add: ОсновнаяСхемаКомпоновкиДанных к отчёту ОстаткиТоваров',
script: 'template-add/scripts/add-template',
args: {
'-ObjectName': 'ОстаткиТоваров',
'-TemplateName': 'ОсновнаяСхемаКомпоновкиДанных',
'-TemplateType': 'DataCompositionSchema',
'-SrcDir': '{workDir}/Reports',
},
},
{
name: 'skd-compile: Схема отчёта ОстаткиТоваров',
script: 'skd-compile/scripts/skd-compile',
input: {
dataSets: [{
name: 'НаборДанных',
type: 'Query',
query: 'SELECT Номенклатура, Количество, Цена, Сумма FROM Document.ПриходнаяНакладная.Товары',
query: 'ВЫБРАТЬ\n\tТовары.Ссылка КАК Документ,\n\tТовары.Номенклатура КАК Номенклатура,\n\tТовары.Количество КАК Количество,\n\tТовары.Цена КАК Цена,\n\tТовары.Сумма КАК Сумма\nИЗ\n\tДокумент.ПриходнаяНакладная.Товары КАК Товары',
fields: [
{ field: 'Документ', title: 'Документ', type: 'DocumentRef.ПриходнаяНакладная' },
{ field: 'Номенклатура', title: 'Номенклатура', type: 'CatalogRef.Номенклатура' },
{ field: 'Количество', title: 'Количество', type: 'decimal(15,3)' },
{ field: 'Цена', title: 'Цена', type: 'decimal(15,2)' },
{ field: 'Сумма', title: 'Сумма', type: 'decimal(15,2)' },
],
}],
totalFields: ['Количество: Сумма', 'Сумма: Сумма'],
settingsVariants: [{
name: 'Основной',
title: 'Остатки товаров',
settings: {
selection: ['Номенклатура', 'Количество', 'Сумма', 'Auto'],
filter: ['Номенклатура = _ @off @user @quickAccess'],
structure: 'Номенклатура > details',
},
}],
fields: [
{ name: 'Номенклатура', title: 'Номенклатура' },
{ name: 'Количество', title: 'Количество' },
{ name: 'Цена', title: 'Цена' },
{ name: 'Сумма', title: 'Сумма' },
],
},
args: { '-DefinitionFile': '{inputFile}', '-OutputPath': '{workDir}/Reports/ОстаткиТоваров/Templates/ОсновнаяСхемаКомпоновкиДанных/Ext/Template.xml' },
validate: { script: 'skd-validate/scripts/skd-validate', flag: '-TemplatePath', path: 'Reports/ОстаткиТоваров/Templates/ОсновнаяСхемаКомпоновкиДанных/Ext/Template.xml' },
+103
View File
@@ -0,0 +1,103 @@
export const name = 'DCS-отчёт: structured smoke + быстрый пользовательский фильтр';
export const tags = ['report', 'smoke'];
export const timeout = 90000;
export default async function({ navigateSection, openCommand, getFormState, getCommands, clickElement, selectValue, fillFields, readSpreadsheet, closeForm, wait, assert, step, log }) {
await step('navigation: команда отчёта зарегистрирована в подсистеме Склад', async () => {
const r = await navigateSection('Склад');
const flat = (r.commands || []).flat();
log(`commands: ${JSON.stringify(flat)}`);
assert.ok(flat.includes('Остатки товаров'), 'В подсистеме Склад есть команда «Остатки товаров»');
});
await step('open: openCommand отрывает форму отчёта с кнопкой Сформировать', async () => {
const s = await openCommand('Остатки товаров');
log(`form=${s.form} formCount=${s.formCount} buttons=${s.buttons?.map(b => b.name).join(',')}`);
assert.equal(s.formCount, 1, 'Открыта одна форма');
const submit = s.buttons?.find(b => b.name === 'Сформировать');
assert.ok(submit, 'Есть кнопка «Сформировать»');
assert.equal(submit.default, true, '«Сформировать» — кнопка по умолчанию');
});
await step('reset: сброс пользовательских настроек к стандартным', async () => {
// 1С хранит пользовательские настройки между сессиями — сбрасываем к дефолту,
// чтобы тест был идемпотентным независимо от предыдущих прогонов.
await clickElement('Еще');
await clickElement('Установить стандартные настройки');
});
await step('quickAccess: быстрый фильтр Номенклатура виден и выключен по умолчанию', async () => {
const s = await getFormState();
log(`reportSettings: ${JSON.stringify(s.reportSettings)}`);
assert.ok(Array.isArray(s.reportSettings) && s.reportSettings.length === 1, 'Один быстрый фильтр в reportSettings');
const f = s.reportSettings[0];
assert.equal(f.name, 'Номенклатура', 'Имя фильтра — заголовок DCS-поля');
assert.equal(f.enabled, false, '@off — выключен по умолчанию');
assert.equal(f.value, '', 'Значение пустое');
assert.ok(Array.isArray(f.actions) && f.actions.includes('select'), 'Доступно действие select');
});
let baseRowCount = 0;
let baseTotalSum = '';
await step('generate: отчёт без фильтра возвращает все строки', async () => {
await clickElement('Сформировать');
await wait(3);
const r = await readSpreadsheet();
log(`headers=${JSON.stringify(r.headers)} total=${r.total} totals=${JSON.stringify(r.totals)}`);
assert.deepEqual(r.headers, ['Номенклатура', 'Количество', 'Сумма'], 'Заголовки колонок отчёта');
assert.ok(r.data?.length >= 2, 'В отчёте есть строки данных');
assert.ok(r.totals?.['Сумма'], 'Есть итог по Сумме');
baseRowCount = r.data.length;
baseTotalSum = r.totals['Сумма'];
});
await step('apply filter: selectValue включает чекбокс и подставляет значение', async () => {
const r = await selectValue('Номенклатура', 'Товар 02');
log(`selected: ${JSON.stringify(r.selected)}`);
assert.ok(r.selected, 'selectValue вернул объект selected');
const after = await getFormState();
const f = after.reportSettings?.[0];
log(`after filter: ${JSON.stringify(f)}`);
assert.equal(f.enabled, true, 'Чекбокс быстрого фильтра автоматически включился');
assert.equal(f.value, 'Товар 02', 'Подставилось выбранное значение');
});
await step('regenerate: отчёт с фильтром возвращает только подходящие строки', async () => {
await clickElement('Сформировать');
await wait(3);
const r = await readSpreadsheet();
log(`filtered total=${r.total} rows=${r.data?.length} totals=${JSON.stringify(r.totals)}`);
assert.ok(r.data.length < baseRowCount, `Строк меньше чем без фильтра (${r.data.length} < ${baseRowCount})`);
const named = r.data.filter(row => row['Номенклатура']);
assert.ok(named.length >= 1, 'Есть хотя бы одна именованная строка');
assert.ok(named.every(row => row['Номенклатура'] === 'Товар 02'), 'Все именованные строки относятся к «Товар 02»');
const sumKey = Object.keys(r.totals).find(k => k.includes('Сумма'));
assert.ok(sumKey, 'В totals есть колонка Суммы (платформа дописывает контекст фильтра)');
assert.notEqual(r.totals[sumKey], baseTotalSum, 'Итог по Сумме изменился после применения фильтра');
});
await step('clear filter: выключение чекбокса возвращает полный набор данных', async () => {
// Снять быстрый фильтр через toggle off — fillFields с 'false' выключает чекбокс,
// value сохраняется (платформа помнит последний выбор для повторного включения),
// но данные при перерасчёте возвращаются к нефильтрованному набору.
const r = await fillFields({ 'Номенклатура': 'false' });
log(`toggle off: ${JSON.stringify(r.filled)}`);
const after = await getFormState();
assert.equal(after.reportSettings[0].enabled, false, 'Чекбокс выключен');
await clickElement('Сформировать');
await wait(3);
const report = await readSpreadsheet();
log(`after clear: rows=${report.data?.length} totals=${JSON.stringify(report.totals)}`);
assert.equal(report.data.length, baseRowCount, 'Восстановилось исходное число строк');
assert.equal(report.totals['Сумма'], baseTotalSum, 'Восстановился исходный итог по Сумме');
});
await step('cleanup: закрываем форму отчёта', async () => {
const r = await closeForm();
log(`closed=${r.closed} formCount=${r.formCount}`);
assert.equal(r.closed, true, 'Форма закрылась');
});
}