mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-29 16:11:01 +03:00
fix(web-test): readTable разбирает сгруппированную шапку по «Группа / Колонка»
1С кладёт заголовок группы колонок и её листья в ОДНУ строку шапки, различая их только высотой и шириной. Колонка заводилась на каждый бокс с текстом, а строка ключуется по имени — поэтому одноимённые листья соседних групп («План» под «Цена» и под «Количество») схлопывались в один ключ и склеивались через " / ", а заголовки групп становились пустыми колонками. Заголовок группы отличается от настоящей широкой колонки над узкими (паттерн «Исполнитель» над «Срок»/«Выполнена») одним надёжным признаком: его colindex не встречается ни в одной ячейке тела. По нему листья переименовываются в «Группа / Лист» — той же конвенцией, что уже применяет читалка табличного документа, — а заголовок из колонок убирается. Разворот объединённой шапки («Субконто 1/2/3») не задет: под ним боксов-листьев в шапке нет. Заодно у пути записи заголовок ячейки резолвился x-сканом шапки и под группой отдавал «Цена» всем трём листьям — fillTableRow по имени из readTable отвечал notFilled. Теперь имя берётся из общей модели, как в чтении и клике. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ba27f2218f
commit
006e64405c
@@ -558,6 +558,20 @@ export const steps = [
|
||||
validate: { script: 'meta-validate/scripts/meta-validate', flag: '-ObjectPath', path: 'DataProcessors/МногострочнаяШапка' },
|
||||
},
|
||||
|
||||
// Обработка ГруппыКолонок — грид, где шапка сгруппирована ГОРИЗОНТАЛЬНО: родительский
|
||||
// заголовок группы («Цена», «Количество») сверху, под ним листья с ОДИНАКОВЫМИ именами
|
||||
// («План», «Факт») в каждой группе. Отличие от МногострочнаяШапка: родитель здесь —
|
||||
// чистый заголовок группы, своих ячеек в теле у него нет.
|
||||
{
|
||||
name: 'meta-compile: Обработка ГруппыКолонок',
|
||||
script: 'meta-compile/scripts/meta-compile',
|
||||
input: {
|
||||
type: 'DataProcessor', name: 'ГруппыКолонок',
|
||||
},
|
||||
args: { '-JsonPath': '{inputFile}', '-OutputDir': '{workDir}' },
|
||||
validate: { script: 'meta-validate/scripts/meta-validate', flag: '-ObjectPath', path: 'DataProcessors/ГруппыКолонок' },
|
||||
},
|
||||
|
||||
// Обработка ПроверкаДоступности — форма с парами «доступный / недоступный»
|
||||
// элементов каждого типа (кнопка командной панели, обычная кнопка, поле ввода,
|
||||
// флажок, переключатель). Недоступность задаётся декларативно (disabled:true →
|
||||
@@ -1809,6 +1823,73 @@ export const steps = [
|
||||
`,
|
||||
},
|
||||
|
||||
// Обработка ГруппыКолонок — форма с горизонтальными группами колонок в шапке.
|
||||
{
|
||||
name: 'form-add: Форма ГруппыКолонок',
|
||||
script: 'form-add/scripts/form-add',
|
||||
args: { '-ObjectPath': '{workDir}/DataProcessors/ГруппыКолонок.xml', '-FormName': 'ФормаОбработки' },
|
||||
},
|
||||
{
|
||||
name: 'form-compile: Форма ГруппыКолонок',
|
||||
script: 'form-compile/scripts/form-compile',
|
||||
input: {
|
||||
title: 'Группы колонок',
|
||||
events: { OnCreateAtServer: 'ПриСозданииНаСервере' },
|
||||
attributes: [
|
||||
{ name: 'Объект', type: 'DataProcessorObject.ГруппыКолонок', main: true },
|
||||
{ name: 'Таблица', type: 'ValueTable', columns: [
|
||||
{ name: 'Код', type: 'String(10)', title: 'Код' },
|
||||
{ name: 'Товар', type: 'String(50)', title: 'Товар' },
|
||||
{ name: 'ЦенаПлан', type: 'String(50)', title: 'План' },
|
||||
{ name: 'ЦенаФакт', type: 'String(50)', title: 'Факт' },
|
||||
{ name: 'ЦенаОткл', type: 'String(50)', title: 'Откл.' },
|
||||
{ name: 'КолПлан', type: 'String(50)', title: 'План' },
|
||||
{ name: 'КолФакт', type: 'String(50)', title: 'Факт' },
|
||||
]},
|
||||
],
|
||||
elements: [
|
||||
{ table: 'Таблица', path: 'Таблица', changeRowSet: true, columns: [
|
||||
{ input: 'Код', path: 'Таблица.Код', title: 'Код', width: 8 },
|
||||
{ input: 'Товар', path: 'Таблица.Товар', title: 'Товар', width: 20 },
|
||||
// Родитель «Цена» — ЧИСТЫЙ заголовок группы (в теле своих ячеек нет), под ним три
|
||||
// листа. Имена листьев повторяются в соседней группе — на этом readTable схлопывал
|
||||
// значения в один ключ через ' / '.
|
||||
{ columnGroup: 'horizontal', name: 'ГруппаЦена', title: 'Цена', showInHeader: true, children: [
|
||||
{ input: 'ЦенаПлан', path: 'Таблица.ЦенаПлан', title: 'План', width: 12 },
|
||||
{ input: 'ЦенаФакт', path: 'Таблица.ЦенаФакт', title: 'Факт', width: 12 },
|
||||
{ input: 'ЦенаОткл', path: 'Таблица.ЦенаОткл', title: 'Откл.', width: 12 },
|
||||
]},
|
||||
{ columnGroup: 'horizontal', name: 'ГруппаКоличество', title: 'Количество', showInHeader: true, children: [
|
||||
{ input: 'КолПлан', path: 'Таблица.КолПлан', title: 'План', width: 12 },
|
||||
{ input: 'КолФакт', path: 'Таблица.КолФакт', title: 'Факт', width: 12 },
|
||||
]},
|
||||
]},
|
||||
],
|
||||
},
|
||||
args: { '-JsonPath': '{inputFile}', '-OutputPath': '{workDir}/DataProcessors/ГруппыКолонок/Forms/ФормаОбработки/Ext/Form.xml' },
|
||||
validate: { script: 'form-validate/scripts/form-validate', flag: '-FormPath', path: 'DataProcessors/ГруппыКолонок/Forms/ФормаОбработки/Ext/Form.xml' },
|
||||
},
|
||||
{
|
||||
name: 'writeFile: ГруппыКолонок form Module.bsl',
|
||||
writeFile: 'DataProcessors/ГруппыКолонок/Forms/ФормаОбработки/Ext/Form/Module.bsl',
|
||||
content: `&НаСервере
|
||||
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
|
||||
\t// Значения уникальны по колонкам: промах резолвера виден сразу.
|
||||
\tДля Сч = 1 По 3 Цикл
|
||||
\t\tС = Формат(Сч, "ЧН=; ЧГ=");
|
||||
\t\tСтрока = Таблица.Добавить();
|
||||
\t\tСтрока.Код = "К" + С;
|
||||
\t\tСтрока.Товар = "Товар " + С;
|
||||
\t\tСтрока.ЦенаПлан = "Ц-план " + С;
|
||||
\t\tСтрока.ЦенаФакт = "Ц-факт " + С;
|
||||
\t\tСтрока.ЦенаОткл = "Ц-откл " + С;
|
||||
\t\tСтрока.КолПлан = "К-план " + С;
|
||||
\t\tСтрока.КолФакт = "К-факт " + С;
|
||||
\tКонецЦикла;
|
||||
КонецПроцедуры
|
||||
`,
|
||||
},
|
||||
|
||||
// ── 4. DCS for report ──
|
||||
// Сначала добавляем макет ОсновнаяСхемаКомпоновкиДанных к отчёту (регистрируется
|
||||
// в Reports/ОстаткиТоваров.xml + автоматически выставляется MainDataCompositionSchema),
|
||||
@@ -1873,6 +1954,7 @@ export const steps = [
|
||||
'DataProcessor.МножественныйВыбор',
|
||||
'DataProcessor.БезшапочнаяТаблица',
|
||||
'DataProcessor.МногострочнаяШапка',
|
||||
'DataProcessor.ГруппыКолонок',
|
||||
],
|
||||
},
|
||||
args: { '-DefinitionFile': '{inputFile}', '-OutputDir': '{workDir}' },
|
||||
@@ -1915,6 +1997,7 @@ export const steps = [
|
||||
'DataProcessor.МножественныйВыбор: Use View',
|
||||
'DataProcessor.БезшапочнаяТаблица: Use View',
|
||||
'DataProcessor.МногострочнаяШапка: Use View',
|
||||
'DataProcessor.ГруппыКолонок: Use View',
|
||||
'DataProcessor.ПроверкаДоступности: Use View',
|
||||
'DataProcessor.СтраницаНастроек: Use View',
|
||||
],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const name = 'multirow-header: резолвинг колонок на двухэтажной шапке (чтение/клик/заполнение)';
|
||||
export const tags = ['table', 'columns'];
|
||||
export const timeout = 120000;
|
||||
export const timeout = 180000;
|
||||
|
||||
// Стенд «Многострочная шапка» воспроизводит два паттерна, снятых живьём с ERP:
|
||||
//
|
||||
@@ -12,8 +12,12 @@ export const timeout = 120000;
|
||||
// 2. паттерн «Операция» — шапка только у группы «Субконто», а ячеек под ней три и своих
|
||||
// шапок у них НЕТ. Здесь разворот в «Субконто 1/2/3» — правильное поведение, его
|
||||
// обязана сохранить любая правка.
|
||||
//
|
||||
// Вторая часть теста — стенд «Группы колонок»: горизонтальные ГруппыКолонок («Цена», «Количество»)
|
||||
// с ОДИНАКОВЫМИ именами листьев («План»/«Факт»). Родитель здесь, в отличие от «Исполнитель»,
|
||||
// собственных ячеек не имеет — его colindex отсутствует в теле. Листья именуются «Группа / Лист».
|
||||
|
||||
export default async function({ navigateSection, openCommand, clickElement, closeForm, readTable, fillTableRow, getPage, assert, step, log }) {
|
||||
export default async function({ navigateSection, openCommand, clickElement, closeForm, readTable, fillTableRow, getFormState, getPage, assert, step, log }) {
|
||||
|
||||
// Куда клик попал НА САМОМ ДЕЛЕ. Результат clickElement возвращает запрошенное имя колонки
|
||||
// (эхо), а не разрешённую ячейку, — по нему промах неотличим от попадания. Нажатая ячейка
|
||||
@@ -121,6 +125,75 @@ export default async function({ navigateSection, openCommand, clickElement, clos
|
||||
assert.equal(row['Субконто 3'], 'Субконто3 1', '«Субконто 3» не задета');
|
||||
});
|
||||
|
||||
await step('setup: перейти на стенд «Группы колонок»', async () => {
|
||||
await closeForm();
|
||||
await navigateSection('Склад');
|
||||
await openCommand('Группы колонок');
|
||||
});
|
||||
|
||||
await step('groups: листья именуются «Группа / Лист», значения не склеены', async () => {
|
||||
const t = await readTable();
|
||||
log(`columns=${JSON.stringify(t.columns)}`);
|
||||
log(`row0=${JSON.stringify(t.rows[0])}`);
|
||||
|
||||
['Цена / План', 'Цена / Факт', 'Цена / Откл.', 'Количество / План', 'Количество / Факт']
|
||||
.forEach(c => assert.includes(t.columns, c, `колонка «${c}»`));
|
||||
// Голых имён быть не должно: они одинаковы в обеих группах, и строка ключуется по имени —
|
||||
// именно на этом значения «Цена»/«Количество» схлопывались в один ключ через ' / '.
|
||||
assert.ok(!t.columns.includes('План'), `голой «План» быть не должно (columns=${JSON.stringify(t.columns)})`);
|
||||
assert.ok(!t.columns.includes('Факт'), `голой «Факт» быть не должно (columns=${JSON.stringify(t.columns)})`);
|
||||
// Заголовок группы — не колонка данных: своих ячеек у него нет.
|
||||
assert.ok(!t.columns.includes('Цена'), 'заголовок группы «Цена» не колонка');
|
||||
assert.ok(!t.columns.includes('Количество'), 'заголовок группы «Количество» не колонка');
|
||||
|
||||
const r = t.rows[0];
|
||||
assert.equal(r['Цена / План'], 'Ц-план 1', 'Цена / План');
|
||||
assert.equal(r['Цена / Факт'], 'Ц-факт 1', 'Цена / Факт');
|
||||
assert.equal(r['Цена / Откл.'], 'Ц-откл 1', 'Цена / Откл.');
|
||||
assert.equal(r['Количество / План'], 'К-план 1', 'Количество / План');
|
||||
assert.equal(r['Количество / Факт'], 'К-факт 1', 'Количество / Факт');
|
||||
assert.equal(r['Код'], 'К1', 'обычная колонка не задета');
|
||||
});
|
||||
|
||||
await step('groups: getFormState().tables[] даёт те же имена, что readTable', async () => {
|
||||
// У form-state своя ветка вывода колонок — она обязана совпадать с моделью readTable,
|
||||
// иначе имя из tables[] не годится для клика.
|
||||
const t = await readTable();
|
||||
const f = await getFormState();
|
||||
log(`tables=${JSON.stringify(f.tables)}`);
|
||||
assert.deepEqual(f.tables[0].columns, t.columns, 'columns из getFormState == columns из readTable');
|
||||
});
|
||||
|
||||
await step('groups: клик по полному имени попадает в нужную группу', async () => {
|
||||
const res = await clickElement({ row: { 'Код': 'К2' }, column: 'Количество / Факт' });
|
||||
assert.equal(res.clicked?.kind, 'gridCell', 'kind=gridCell');
|
||||
const cell = await focusedCell();
|
||||
log(`focused: ${JSON.stringify(cell)}`);
|
||||
assert.equal(cell?.text, 'К-факт 2', `клик попал в «Количество / Факт» (got «${cell?.text}»)`);
|
||||
});
|
||||
|
||||
await step('groups: короткое имя резолвится через суффикс «Группа / Имя»', async () => {
|
||||
const res = await clickElement({ row: { 'Код': 'К2' }, column: 'Откл.' });
|
||||
assert.equal(res.clicked?.kind, 'gridCell', 'kind=gridCell');
|
||||
const cell = await focusedCell();
|
||||
log(`focused: ${JSON.stringify(cell)}`);
|
||||
assert.equal(cell?.text, 'Ц-откл 2', `«Откл.» нашлось как «Цена / Откл.» (got «${cell?.text}»)`);
|
||||
});
|
||||
|
||||
await step('groups: fillTableRow пишет по имени из readTable', async () => {
|
||||
// Путь записи матчит ячейку по её техническому имени и по заголовку. Заголовок листа под
|
||||
// группой резолвился x-скáном и давал «Цена» для всех трёх листьев — имя из readTable не
|
||||
// находилось вовсе (notFilled).
|
||||
const r = await fillTableRow({ 'Цена / Факт': 'изменён' }, { row: { 'Код': 'К3' } });
|
||||
log(`filled=${JSON.stringify(r.filled)} notFilled=${JSON.stringify(r.notFilled)}`);
|
||||
assert.ok(!r.notFilled, `все поля найдены (notFilled=${JSON.stringify(r.notFilled)})`);
|
||||
const row = (await readTable()).rows.find(x => x['Код'] === 'К3');
|
||||
log(`after fill: ${JSON.stringify(row)}`);
|
||||
assert.equal(row['Цена / Факт'], 'изменён', 'значение попало в «Цена / Факт»');
|
||||
assert.equal(row['Цена / План'], 'Ц-план 3', 'сосед по группе не задет');
|
||||
assert.equal(row['Количество / Факт'], 'К-факт 3', 'одноимённый лист соседней группы не задет');
|
||||
});
|
||||
|
||||
await step('cleanup: закрыть форму', async () => {
|
||||
await closeForm();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user