feat(web-test): fillTableRow заполняет ячейку-выбор-из-списка через форму выбора

Поле с кнопкой выбора и обработчиком НачалоВыбора (значение выбирается из программного
списка — например колонка Тип в типовой Консоли запросов) раньше заполнялось plain-paste,
который молча откатывался → ok:true/method:direct (ложный успех). Теперь движок детектит
такую ячейку и выбирает значение из формы выбора.

- dom/grid-edit.mjs: readActiveGridCellScript отдаёт buttonKind активной ячейки
  (ref/calc/date/choice по кнопке _DLB/_CB и её классу).
- engine/table/row-fill.mjs v1.20: для kind=choice — F4 → pickFromTypeDialog
  (скан/Ctrl+F/OK) → method:choice; если после выбора открылась форма значения,
  это составная ячейка (нужен {value,type}). Ветка добавлена в Tab-цикл и directEditPick.
- engine/forms/select-value.mjs v1.21: умный dismiss диалога типов на путях
  not_found/multiple — Escape только пока диалог открыт, больше не закрывает
  исходную форму слепым Escape×3.
- Стенд: строковая колонка-выбор ТипЗначения (НачалоВыбора → ПоказатьВыборЭлемента)
  в ДеревоНоменклатуры; тест 16 покрывает method:choice и негатив not_found.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-05-31 17:26:37 +03:00
parent 52478a6c39
commit 7c9769c644
5 changed files with 178 additions and 12 deletions
@@ -812,7 +812,12 @@ export const steps = [
{ name: 'Номенклатура', type: 'CatalogRef.Номенклатура', title: 'Номенклатура' },
{ name: 'Цена', type: 'Number(15,2)', title: 'Цена' },
{ name: 'Картинка', type: 'Boolean', title: 'Картинка' },
// Строковая колонка-выбор-из-списка: значение выбирается обработчиком НачалоВыбора
// через СписокТипов.ПоказатьВыборЭлемента (как колонка Тип в типовой Консоли запросов).
{ name: 'ТипЗначения', type: 'String', title: 'Тип значения' },
]},
// Список значений для программного выбора (ПоказатьВыборЭлемента).
{ name: 'СписокТипов', type: 'ValueList' },
],
elements: [
{ table: 'Дерево', path: 'Дерево', initialTreeView: 'ExpandTopLevel', changeRowSet: true,
@@ -824,6 +829,9 @@ export const steps = [
{ picField: 'ДеревоКартинка', path: 'Дерево.Картинка', title: 'Картинка', valuesPicture: 'StdPicture.Favorites', loadTransparent: true },
// CheckBoxField на тот же булев — для кросс-проверки состояния картинки.
{ check: 'ДеревоКартинкаФлаг', path: 'Дерево.Картинка', title: 'Флаг' },
// Поле-выбор-из-списка с кнопкой выбора и обработчиком НачалоВыбора.
{ input: 'ДеревоТипЗначения', path: 'Дерево.ТипЗначения', title: 'Тип значения',
choiceButton: true, on: ['StartChoice'], handlers: { StartChoice: 'ДеревоТипЗначенияНачалоВыбора' } },
]},
],
},
@@ -836,6 +844,10 @@ export const steps = [
content: `&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
\tЗаполнитьУровень(Дерево.ПолучитьЭлементы(), Справочники.Номенклатура.ПустаяСсылка());
\tСписокТипов.Добавить("Строка");
\tСписокТипов.Добавить("Число");
\tСписокТипов.Добавить("Дата");
\tСписокТипов.Добавить("Булево");
КонецПроцедуры
&НаСервере
@@ -877,6 +889,24 @@ export const steps = [
\t\tТекущиеДанные.Картинка = НЕ ТекущиеДанные.Картинка;
\tКонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ДеревоТипЗначенияНачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка)
\tСтандартнаяОбработка = Ложь;
\tОписаниеОповещения = Новый ОписаниеОповещения("ТипЗначенияЗавершениеВыбора", ЭтотОбъект);
\tСписокТипов.ПоказатьВыборЭлемента(ОписаниеОповещения, НСтр("ru = 'Выбрать тип'"));
КонецПроцедуры
&НаКлиенте
Процедура ТипЗначенияЗавершениеВыбора(ВыбранныйЭлемент, ДополнительныеПараметры) Экспорт
\tЕсли ВыбранныйЭлемент = Неопределено Тогда
\t\tВозврат;
\tКонецЕсли;
\tТекущиеДанные = Элементы.Дерево.ТекущиеДанные;
\tЕсли ТекущиеДанные <> Неопределено Тогда
\t\tТекущиеДанные.ТипЗначения = ВыбранныйЭлемент.Значение;
\tКонецЕсли;
КонецПроцедуры
`,
},
+25 -1
View File
@@ -24,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, 'Товары', 'есть Товары');
@@ -61,6 +61,30 @@ export default async function({ navigateLink, clickElement, closeForm, readTable
assert.equal(tovar01['Цена'], '1 500,00', 'Цена обновилась до 1 500,00');
});
await step('choice-cell: fillTableRow задаёт ТипЗначения через форму выбора (НачалоВыбора)', async () => {
// Колонка-строка с кнопкой выбора + обработчиком НачалоВыбора → СписокТипов.ПоказатьВыборЭлемента
// («Выбрать тип»). Plain-paste тут не годится — движок открывает форму выбора и выбирает из списка.
const r = await fillTableRow({ ТипЗначения: 'Число' }, { row: 1 });
log(`filled: ${JSON.stringify(r.filled)}`);
const cell = r.filled?.find(f => f.field === 'ТипЗначения');
assert.ok(cell, 'поле ТипЗначения в результате');
assert.equal(cell.ok, true, 'ok=true');
assert.equal(cell.method, 'choice', 'method=choice (выбор из списка, не direct)');
const t = await readTable('Дерево');
const tovar01 = t.rows.find(row => row['Номенклатура'] === 'Товар 01');
assert.equal(tovar01['Тип значения'], 'Число', 'ТипЗначения = Число');
});
await step('choice-cell-negative: несуществующий тип → ok:false/not_found (форма не закрывается)', async () => {
// not_found гасит только диалог выбора типа (умный dismiss), исходная форма остаётся —
// следующие шаги (picture) это подтверждают.
const r = await fillTableRow({ ТипЗначения: 'Нетакоготипа' }, { row: 1 });
const cell = r.filled?.find(f => f.field === 'ТипЗначения');
assert.ok(cell, 'поле ТипЗначения в результате');
assert.equal(cell.ok, false, 'ok=false для несуществующего типа');
assert.equal(cell.error, 'not_found', 'error=not_found');
});
await step('picture: колонка-картинка (pic:0/\'\') + кросс-проверка чекбоксом', async () => {
const t = await readTable('Дерево');
const t15 = t.rows.find(r => r['Номенклатура'] === 'Товар 15'); // Цена 1500 > 1000 → иконка