From ddebd7b6dfb09065c528ad40c20e30d4c525ded9 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Tue, 12 May 2026 15:51:41 +0300 Subject: [PATCH] =?UTF-8?q?feat(web-test):=20M5-pre=20#2=20=E2=80=94=20?= =?UTF-8?q?=D1=81=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D0=BD=D0=BE=D0=B9=20=D1=82?= =?UTF-8?q?=D0=B8=D0=BF=20=D0=98=D1=81=D1=82=D0=BE=D1=87=D0=BD=D0=B8=D0=BA?= =?UTF-8?q?=20+=2003-fillfields/composite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Расширение синтетики: реквизит Источник составного типа (CatalogRef.Контрагенты + CatalogRef.Номенклатура + CatalogRef.Организации) добавлен в шапку ПриходнаяНакладная и в ТЧ Товары. meta-compile принимает составной тип через строковый синтаксис `A + B + C` (см. SKILL.md:56) — эмитит три `` элемента с правильным `d5p1:` префиксом. Элемент ТЧ-колонки переименован в ИсточникТЧ (path/title оставлены оригинальные) — иначе form-compile генерирует одинаковые companion-имена (`ИсточникКонтекстноеМеню`) для шапки и ТЧ, и платформа отказывает в открытии формы документа: "К сожалению, возникла непредвиденная ошибка" (server-side, без полезного stack). TODO в form-compile-bugs.md: учитывать путь поля при генерации companion-имён, чтобы избежать конфликта. Новый шаг 03-fillfields/composite (~25s) — покрывает selectValue с параметром `{type}` на составном поле: - Шапка: selectValue('Источник', 'ООО Север', {type:'Контрагенты'}) → method:'form', type:'Контрагенты', выбор через каталог-форму. - ТЧ: fillTableRow({Источник: {value:'Альфа', type:'Организации'}}, {row:0}) → method:'form', type:'Организации' (quickChoice=true → без формы выбора, прямой dropdown). fillFields на composite без type выбрасывает понятную ошибку с инструкцией «specify the type: selectValue(...,{type:'ИмяТипа'})» — поведение API стабильно. timeout 03-fillfields поднят с 60000 → 120000ms (6 шагов суммарно ~63s, новый composite step добавляет ~25s). Полный регресс **18/18 зелёный** (8m 28.7s). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../integration/build-webtest-config.test.mjs | 10 ++++++ tests/web-test/03-fillfields.test.mjs | 36 +++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/tests/skills/integration/build-webtest-config.test.mjs b/tests/skills/integration/build-webtest-config.test.mjs index 101593a7..f3837a2b 100644 --- a/tests/skills/integration/build-webtest-config.test.mjs +++ b/tests/skills/integration/build-webtest-config.test.mjs @@ -144,6 +144,9 @@ export const steps = [ // (04-selectvalue/direct-form проверяет open-form path; история обходит его). { name: 'Контрагент', type: 'CatalogRef.Контрагенты', choiceHistoryOnInput: 'DontUse' }, { name: 'Склад', type: 'String', length: 50 }, + // Источник — составной тип (для 03-fillfields/composite). + // Платформа покажет селектор типа в UI перед выбором значения. + { name: 'Источник', type: 'CatalogRef.Контрагенты + CatalogRef.Номенклатура + CatalogRef.Организации' }, { name: 'Комментарий', type: 'String', length: 200 }, ], tabularSections: [{ @@ -154,6 +157,8 @@ export const steps = [ { name: 'Цена', type: 'Number', length: 15, precision: 2 }, { name: 'Сумма', type: 'Number', length: 15, precision: 2 }, { name: 'Согласовано', type: 'Boolean' }, + // Источник — составной тип в ТЧ (для edit-dblclick через выбор типа) + { name: 'Источник', type: 'CatalogRef.Контрагенты + CatalogRef.Номенклатура + CatalogRef.Организации' }, ], }], }, @@ -595,6 +600,7 @@ export const steps = [ { input: 'Организация', path: 'Объект.Организация', title: 'Организация' }, { input: 'Контрагент', path: 'Объект.Контрагент', title: 'Контрагент' }, { input: 'Склад', path: 'Объект.Склад', title: 'Склад' }, + { input: 'Источник', path: 'Объект.Источник', title: 'Источник' }, { input: 'Комментарий', path: 'Объект.Комментарий', title: 'Комментарий' }, { table: 'Товары', path: 'Объект.Товары', title: 'Товары', changeRowSet: true, columns: [ { input: 'Номенклатура', path: 'Объект.Товары.Номенклатура', title: 'Номенклатура' }, @@ -602,6 +608,10 @@ export const steps = [ { input: 'Цена', path: 'Объект.Товары.Цена', title: 'Цена' }, { input: 'Сумма', path: 'Объект.Товары.Сумма', title: 'Сумма' }, { check: 'Согласовано', path: 'Объект.Товары.Согласовано', title: 'Согласовано' }, + // Имя элемента отличается от Источник (в шапке) — иначе ContextMenu + // companion-имена дублируются в одной форме. form-compile использует + // имя элемента, не путь, для генерации companion-имён. + { input: 'ИсточникТЧ', path: 'Объект.Товары.Источник', title: 'Источник' }, ]}, ], }, diff --git a/tests/web-test/03-fillfields.test.mjs b/tests/web-test/03-fillfields.test.mjs index 06b7e7f5..39b4f47e 100644 --- a/tests/web-test/03-fillfields.test.mjs +++ b/tests/web-test/03-fillfields.test.mjs @@ -1,10 +1,10 @@ export const name = 'fillFields: text, checkbox, date, dropdown, reference'; export const tags = ['fillfields', 'smoke']; -export const timeout = 60000; +export const timeout = 120000; const findField = (state, name) => state.fields?.find(f => f.name === name || f.label === name); -export default async function({ navigateSection, openCommand, clickElement, fillFields, filterList, closeForm, getFormState, assert, step, log }) { +export default async function({ navigateSection, openCommand, clickElement, fillFields, fillTableRow, selectValue, filterList, closeForm, getFormState, assert, step, log }) { await step('text+checkbox+date+dropdown: fillFields на Номенклатура', async () => { await navigateSection('Склад'); @@ -122,4 +122,36 @@ export default async function({ navigateSection, openCommand, clickElement, fill await closeForm({ save: false }); }); + + await step('composite: selectValue с {type} в шапке и ТЧ накладной', async () => { + // ПриходнаяНакладная.Источник — составной тип: + // CatalogRef.Контрагенты + CatalogRef.Номенклатура + CatalogRef.Организации + // fillFields без type→ошибка с подсказкой «specify the type»; + // selectValue('Источник', value, {type:'Контрагенты'}) выбирает тип в диалоге. + await navigateSection('Склад'); + await openCommand('Приходная накладная'); + await clickElement('Создать'); + + // Шапка: выбор Контрагента в составном поле + const headRes = await selectValue('Источник', 'ООО Север', { type: 'Контрагенты' }); + log('header: type=' + headRes.selected?.type + ' method=' + headRes.selected?.method); + assert.equal(headRes.selected?.method, 'form', 'composite header → method=form'); + assert.equal(headRes.selected?.type, 'Контрагенты', 'type=Контрагенты выбран'); + + const state1 = await getFormState(); + const headField = state1.fields?.find(f => f.name === 'Источник'); + assert.equal(headField?.value, 'ООО Север', 'значение в шапке установилось'); + + // ТЧ: добавить строку, выбрать тип Организация (квик-чойс — без формы выбора) + await clickElement('Добавить'); + const rowRes = await fillTableRow( + { Источник: { value: 'Альфа', type: 'Организации' } }, + { row: 0 }, + ); + log('row: ' + JSON.stringify(rowRes.filled?.[0])); + assert.equal(rowRes.filled?.[0]?.ok, true, 'composite row → ok'); + assert.equal(rowRes.filled?.[0]?.type, 'Организации', 'выбран тип Организации в ТЧ'); + + await closeForm({ save: false }); + }); }