From 7de2689c188a5d75f88c8660665aa9663b45684c Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Fri, 29 May 2026 20:51:02 +0300 Subject: [PATCH] =?UTF-8?q?test(web-test):=20=D0=BA=D0=B0=D1=80=D1=82?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D1=87=D0=BD=D0=B0=D1=8F=20=D0=BA=D0=BE=D0=BB?= =?UTF-8?q?=D0=BE=D0=BD=D0=BA=D0=B0=20=D0=B2=20=D1=81=D1=82=D0=B5=D0=BD?= =?UTF-8?q?=D0=B4=D0=B5=20=D0=94=D0=B5=D1=80=D0=B5=D0=B2=D0=BE=D0=9D=D0=BE?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=BA=D0=BB=D0=B0=D1=82=D1=83=D1=80=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Регресс-покрытие picture-колонок readTable на синтетическом стенде (без зависимости от реальных баз). В обработку ДеревоНоменклатуры: - булева колонка Картинка + PictureField (ValuesPicture=StdPicture.Favorites, loadTransparent) — иконка у позиций Цена>1000; - CheckBoxField Флаг на тот же булев (кросс-проверка состояния); - Selection-обработчик ДеревоВыбор — инверсия по двойному клику. 16-tree-form: обновлён deepEqual колонок (+Картинка +Флаг), добавлены шаги presence/кросс-проверка (pic:0 ⟺ флаг) и Selection-toggle. Полный регресс web-test зелёный. Co-Authored-By: Claude Opus 4.8 --- .../integration/build-webtest-config.test.mjs | 31 +++++++++++++--- tests/web-test/16-tree-form.test.mjs | 36 ++++++++++++++++--- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/tests/skills/integration/build-webtest-config.test.mjs b/tests/skills/integration/build-webtest-config.test.mjs index 3ecf0828..7c6e1131 100644 --- a/tests/skills/integration/build-webtest-config.test.mjs +++ b/tests/skills/integration/build-webtest-config.test.mjs @@ -811,13 +811,20 @@ export const steps = [ { name: 'Дерево', type: 'ValueTree', columns: [ { name: 'Номенклатура', type: 'CatalogRef.Номенклатура', title: 'Номенклатура' }, { name: 'Цена', type: 'Number(15,2)', title: 'Цена' }, + { name: 'Картинка', type: 'Boolean', title: 'Картинка' }, ]}, ], elements: [ - { table: 'Дерево', path: 'Дерево', initialTreeView: 'ExpandTopLevel', changeRowSet: true, columns: [ - { input: 'Номенклатура', path: 'Дерево.Номенклатура', readOnly: true, title: 'Номенклатура' }, - { input: 'Цена', path: 'Дерево.Цена', title: 'Цена' }, - ]}, + { table: 'Дерево', path: 'Дерево', initialTreeView: 'ExpandTopLevel', changeRowSet: true, + on: ['Selection'], handlers: { Selection: 'ДеревоВыбор' }, + columns: [ + { input: 'Номенклатура', path: 'Дерево.Номенклатура', readOnly: true, title: 'Номенклатура' }, + { input: 'Цена', path: 'Дерево.Цена', title: 'Цена' }, + // PictureField на булев Картинка — иконка-значение (frame-based, как ЭДО). + { picField: 'ДеревоКартинка', path: 'Дерево.Картинка', title: 'Картинка', valuesPicture: 'StdPicture.Favorites', loadTransparent: true }, + // CheckBoxField на тот же булев — для кросс-проверки состояния картинки. + { check: 'ДеревоКартинкаФлаг', path: 'Дерево.Картинка', title: 'Флаг' }, + ]}, ], }, args: { '-JsonPath': '{inputFile}', '-OutputPath': '{workDir}/DataProcessors/ДеревоНоменклатуры/Forms/ФормаОбработки/Ext/Form.xml' }, @@ -849,11 +856,27 @@ export const steps = [ \t\tНовыйУзел = КоллекцияЭлементов.Добавить(); \t\tНовыйУзел.Номенклатура = Выборка.Ссылка; \t\tНовыйУзел.Цена = Выборка.Цена; +\t\t// Детерминированный микс: иконка у позиций дороже 1000. +\t\t// У групп Цена = NULL (реквизит только для элементов) — сравнение пропускаем. +\t\tЕсли НЕ Выборка.ЭтоГруппа Тогда +\t\t\tНовыйУзел.Картинка = Выборка.Цена > 1000; +\t\tКонецЕсли; \t\tЕсли Выборка.ЭтоГруппа Тогда \t\t\tЗаполнитьУровень(НовыйУзел.ПолучитьЭлементы(), Выборка.Ссылка); \t\tКонецЕсли; \tКонецЦикла; КонецПроцедуры + +&НаКлиенте +Процедура ДеревоВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка) +\tТекущиеДанные = Дерево.НайтиПоИдентификатору(ВыбраннаяСтрока); +\tЕсли ТекущиеДанные = Неопределено Тогда +\t\tВозврат; +\tКонецЕсли; +\tЕсли Поле.Имя = "ДеревоКартинка" Тогда +\t\tТекущиеДанные.Картинка = НЕ ТекущиеДанные.Картинка; +\tКонецЕсли; +КонецПроцедуры `, }, diff --git a/tests/web-test/16-tree-form.test.mjs b/tests/web-test/16-tree-form.test.mjs index 4df3ec19..27e47980 100644 --- a/tests/web-test/16-tree-form.test.mjs +++ b/tests/web-test/16-tree-form.test.mjs @@ -1,12 +1,16 @@ export const name = 'tree-form: FormDataTree edit (ДеревоНоменклатуры obrabotka)'; -export const tags = ['tree', 'table']; +export const tags = ['tree', 'table', 'picture']; export const timeout = 90000; // ДеревоНоменклатуры obrabotka: реквизит формы Дерево типа ДеревоЗначений // заполняется в ПриСозданииНаСервере рекурсивным обходом справочника Номенклатура. -// Колонка Цена — Number, editable; колонка Номенклатура — CatalogRef, readOnly. +// Колонки: Номенклатура (CatalogRef, readOnly), Цена (Number, editable), +// Картинка (PictureField на булев, ValuesPicture=StdPicture.Favorites, иконка у Цена>1000), +// Флаг (CheckBoxField на тот же булев — кросс-проверка). Selection-обработчик +// ДеревоВыбор инвертирует Картинка по двойному клику в колонке. // Покрывает: 05-table/edit-form (fillTableRow method:'direct' на FormDataTree-колонке) -// + 08-hierarchy/tree-edit (expand узла + edit Цены внутри expanded группы). +// + 08-hierarchy/tree-edit (expand узла + edit Цены внутри expanded группы) +// + readTable picture-колонки (pic:N/'') и Selection-toggle. export default async function({ navigateLink, clickElement, closeForm, readTable, fillTableRow, assert, step, log }) { @@ -20,7 +24,7 @@ export default async function({ navigateLink, clickElement, closeForm, readTable await step('read-roots: на верхнем уровне видны группы (Товары, Услуги, БольшойСписок)', async () => { const t = await readTable('Дерево'); log(`columns=${t.columns?.join(',')} rows=${t.rows?.length}`); - assert.deepEqual(t.columns, ['Номенклатура', 'Цена'], 'колонки: Номенклатура + Цена'); + assert.deepEqual(t.columns, ['Номенклатура', 'Цена', 'Картинка', 'Флаг'], 'колонки: Номенклатура + Цена + Картинка + Флаг'); assert.equal(t.rows.length, 3, '3 корневые строки'); const names = t.rows.map(r => r['Номенклатура']); assert.includes(names, 'Товары', 'есть Товары'); @@ -57,6 +61,30 @@ export default async function({ navigateLink, clickElement, closeForm, readTable assert.equal(tovar01['Цена'], '1 500,00', 'Цена обновилась до 1 500,00'); }); + await step('picture: колонка-картинка (pic:0/\'\') + кросс-проверка чекбоксом', async () => { + const t = await readTable('Дерево'); + const t15 = t.rows.find(r => r['Номенклатура'] === 'Товар 15'); // Цена 1500 > 1000 → иконка + const t02 = t.rows.find(r => r['Номенклатура'] === 'Товар 02'); // Цена 200 < 1000 → нет + assert.ok(t15 && t02, 'Товар 15 и Товар 02 видны'); + assert.equal(t15['Картинка'], 'pic:0', 'Товар 15 — иконка показана (pic:0)'); + assert.equal(t15['Флаг'], 'true', 'Товар 15 — флаг true'); + assert.equal(t02['Картинка'], '', 'Товар 02 — иконки нет (пусто)'); + assert.equal(t02['Флаг'], 'false', 'Товар 02 — флаг false'); + // Инвариант: иконка есть тогда и только тогда, когда флаг true. + const items = t.rows.filter(r => (r['Номенклатура'] || '').startsWith('Товар ')); + assert.ok(items.length >= 15, 'видны все 15 товаров'); + assert.ok(items.every(r => (r['Картинка'] === 'pic:0') === (r['Флаг'] === 'true')), + 'по всем товарам: картинка ⟺ флаг'); + }); + + await step('picture-toggle: Selection инвертирует Картинка по двойному клику', async () => { + await clickElement({ row: { 'Номенклатура': 'Товар 02' }, column: 'Картинка' }, { dblclick: true }); + const t = await readTable('Дерево'); + const t02 = t.rows.find(r => r['Номенклатура'] === 'Товар 02'); + assert.equal(t02['Картинка'], 'pic:0', 'после двойного клика иконка появилась'); + assert.equal(t02['Флаг'], 'true', 'и флаг стал true'); + }); + await step('cleanup: закрыть форму', async () => { await closeForm(); });