mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-19 00:59:40 +03:00
fix(web-test): детектировать форму-декорацию без полей ввода
Страницы настроек (напр. «Администрирование → Интернет-поддержка и
сервисы») собраны из гиперссылок, frameButton и сворачиваемых групп —
без единого input.editInput / textarea / a.press. detectForm/detectForms
считали такую форму отсутствующей → getFormState = {form:null,
formCount:0}, навык её не видел.
Расширен союзный селектор детекции (.staticTextHyper/.frameButton/
.checkbox/.radio/.tumblerItem/.grid). detectForm двухуровневый: обычные
формы выбираются по редактируемым контролам (поведение не меняется), по
расширенному счёту — только когда у формы нет ни одного поля ввода.
form0 (рабочий стол) по-прежнему исключён фильтром n>0.
Регресс: обработка-фикстура СтраницаНастроек (форма без командной панели:
гиперссылка + сворачиваемая группа) в подсистеме Администрирование +
тест 25-decoration-form. Полный набор 29 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// web-test dom shared v1.4 — embedded JS function constants
|
||||
// web-test dom shared v1.5 — embedded JS function constants
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
/**
|
||||
* Shared function strings embedded into page.evaluate() generators.
|
||||
@@ -308,34 +308,54 @@ function resolveColumnByName(model, name) {
|
||||
|| null;
|
||||
}`;
|
||||
|
||||
// Селекторы детекции формы. EDIT_SEL — «редактируемые» контролы (поля/кнопки): их наличие
|
||||
// исторически = «это форма». Но страницы настроек/справки (напр. «Интернет-поддержка и сервисы»)
|
||||
// собраны только из гиперссылок/frameButton/групп и НЕ имеют ни одного из этих трёх → форма не
|
||||
// детектировалась (form=null). ANY_SEL добавляет контентные/интерактивные классы декораций, чтобы
|
||||
// такие формы регистрировались. form0 (рабочий стол, тоже полон гиперссылок) исключается фильтром n>0.
|
||||
const FORM_DETECT_EDIT_SEL = 'input.editInput[id], textarea[id], a.press[id]';
|
||||
const FORM_DETECT_ANY_SEL = FORM_DETECT_EDIT_SEL + ', .staticTextHyper[id], .frameButton[id], .checkbox[id], .radio[id], .tumblerItem[id], .grid[id]';
|
||||
|
||||
/** Detect active form number. Picks form with most visible elements, skipping form0.
|
||||
* When modalSurface is visible — prefer the highest-numbered form (modal dialog). */
|
||||
export const DETECT_FORM_FN = HAS_VISIBLE_MODAL_FN + `
|
||||
function detectForm() {
|
||||
const counts = {};
|
||||
document.querySelectorAll('input.editInput[id], textarea[id], a.press[id]').forEach(el => {
|
||||
const editSel = ${JSON.stringify(FORM_DETECT_EDIT_SEL)};
|
||||
const anySel = ${JSON.stringify(FORM_DETECT_ANY_SEL)};
|
||||
const editCounts = {}; // строгие поля/кнопки
|
||||
const anyCounts = {}; // + контентные декорации
|
||||
document.querySelectorAll(anySel).forEach(el => {
|
||||
if (el.offsetWidth === 0) return;
|
||||
const m = el.id.match(/^form(\\d+)_/);
|
||||
if (m) counts[m[1]] = (counts[m[1]] || 0) + 1;
|
||||
if (!m) return;
|
||||
anyCounts[m[1]] = (anyCounts[m[1]] || 0) + 1;
|
||||
if (el.matches(editSel)) editCounts[m[1]] = (editCounts[m[1]] || 0) + 1;
|
||||
});
|
||||
const nums = Object.keys(counts).map(Number);
|
||||
const nums = Object.keys(anyCounts).map(Number);
|
||||
if (!nums.length) return null;
|
||||
const candidates = nums.filter(n => n > 0);
|
||||
if (!candidates.length) return nums[0];
|
||||
// When modal surface is visible, prefer the highest-numbered form (modal dialog)
|
||||
if (hasVisibleModal()) {
|
||||
const maxForm = Math.max(...candidates);
|
||||
if (counts[maxForm] >= 1) return maxForm;
|
||||
if (anyCounts[maxForm] >= 1) return maxForm;
|
||||
}
|
||||
return candidates.reduce((best, n) => counts[n] > counts[best] ? n : best);
|
||||
// Двухуровневый выбор: пока есть формы с редактируемыми контролами — выбираем по ним (прежнее
|
||||
// поведение, обычные формы не сдвигаются). Только когда у ВСЕХ кандидатов их нет (info-страница) —
|
||||
// выбираем по расширенному счёту.
|
||||
const editable = candidates.filter(n => editCounts[n] > 0);
|
||||
const pool = editable.length ? editable : candidates;
|
||||
const metric = editable.length ? editCounts : anyCounts;
|
||||
return pool.reduce((best, n) => metric[n] > metric[best] ? n : best);
|
||||
}`;
|
||||
|
||||
/** Detect all open forms + modal state. Returns { activeForm, allForms, formCount, modal }.
|
||||
* Works even when the open-windows tab bar is hidden. */
|
||||
export const DETECT_FORMS_FN = HAS_VISIBLE_MODAL_FN + `
|
||||
function detectForms() {
|
||||
const anySel = ${JSON.stringify(FORM_DETECT_ANY_SEL)};
|
||||
const counts = {};
|
||||
document.querySelectorAll('input.editInput[id], textarea[id], a.press[id]').forEach(el => {
|
||||
document.querySelectorAll(anySel).forEach(el => {
|
||||
if (el.offsetWidth === 0) return;
|
||||
const m = el.id.match(/^form(\\d+)_/);
|
||||
if (m) counts[m[1]] = (counts[m[1]] || 0) + 1;
|
||||
|
||||
@@ -1257,6 +1257,51 @@ export const steps = [
|
||||
`,
|
||||
},
|
||||
|
||||
// Обработка СтраницаНастроек — форма-«страница настроек» БЕЗ полей ввода: только
|
||||
// гиперссылка-декорация + сворачиваемая группа (как «Администрирование → Интернет-поддержка
|
||||
// и сервисы» в реальных конфигурациях). commandBarLocation:None убирает автокомандную панель,
|
||||
// поэтому у формы НЕТ ни одного input.editInput / textarea / a.press — это воспроизводит баг
|
||||
// детекции формы (detectForm возвращал null → getFormState = «No form detected»). Регресс на
|
||||
// расширенный селектор детекции (dom/_shared.mjs). Открывается командой раздела и navigateLink.
|
||||
{
|
||||
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/СтраницаНастроек' },
|
||||
},
|
||||
{
|
||||
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: 'Страница настроек',
|
||||
// Без командной панели → на форме нет ни одного a.press.
|
||||
properties: { commandBarLocation: 'None' },
|
||||
attributes: [
|
||||
{ name: 'Объект', type: 'DataProcessorObject.СтраницаНастроек', main: true },
|
||||
],
|
||||
elements: [
|
||||
// Гиперссылка-декорация → .staticTextHyper (кликабельна через findClickTargetScript).
|
||||
{ label: 'СсылкаТехническаяИнформация', title: 'Техническая информация', hyperlink: true },
|
||||
// Сворачиваемая группа с дочерней декорацией (пустая группа не рендерится).
|
||||
{ group: 'collapsible', name: 'ГруппаКлассификаторы', title: 'Классификаторы и курсы валют',
|
||||
collapsed: true, showTitle: true, children: [
|
||||
{ label: 'ПояснениеКлассификаторы', title: 'Загрузка курсов валют и классификаторов' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
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' },
|
||||
},
|
||||
|
||||
// Обработка МножественныйВыбор — основная форма с 4 полями типа «список значений».
|
||||
{
|
||||
name: 'form-add: Основная форма МножественныйВыбор',
|
||||
@@ -1761,6 +1806,7 @@ export const steps = [
|
||||
'DataProcessor.ТестовыеОшибки',
|
||||
'DataProcessor.ДеревоНоменклатуры',
|
||||
'DataProcessor.ПроверкаДоступности',
|
||||
'DataProcessor.СтраницаНастроек',
|
||||
],
|
||||
},
|
||||
args: { '-DefinitionFile': '{inputFile}', '-OutputDir': '{workDir}' },
|
||||
@@ -1786,6 +1832,7 @@ export const steps = [
|
||||
'DataProcessor.БезшапочнаяТаблица: Use View',
|
||||
'DataProcessor.МногострочнаяШапка: Use View',
|
||||
'DataProcessor.ПроверкаДоступности: Use View',
|
||||
'DataProcessor.СтраницаНастроек: Use View',
|
||||
],
|
||||
},
|
||||
args: { '-JsonPath': '{inputFile}', '-OutputDir': '{workDir}' },
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
export const name = 'decoration-form: форма без полей ввода (гиперссылки/группы) детектируется';
|
||||
export const tags = ['formstate', 'smoke'];
|
||||
export const timeout = 90000;
|
||||
|
||||
// Обработка СтраницаНастроек — форма-«страница настроек» БЕЗ единого input.editInput /
|
||||
// textarea / a.press (commandBarLocation:None): только гиперссылка-декорация + сворачиваемая
|
||||
// группа. Воспроизводит реальный кейс «Администрирование → Интернет-поддержка и сервисы».
|
||||
// Регресс на расширенный селектор детекции формы (dom/_shared.mjs, DETECT_FORM_FN /
|
||||
// DETECT_FORMS_FN): до фикса detectForm возвращал null → getFormState = «No form detected»
|
||||
// (form:null, formCount:0), навык такую форму НЕ видел.
|
||||
|
||||
export default async function({ navigateSection, openCommand, navigateLink, getFormState, closeForm, assert, step, log }) {
|
||||
|
||||
await step('раздел: открыть «Страница настроек» командой из «Администрирование»', async () => {
|
||||
await navigateSection('Администрирование');
|
||||
const r = await openCommand('Страница настроек');
|
||||
log(`form=${r.form} formCount=${r.formCount} activeTab=${r.activeTab}`);
|
||||
assert.ok(r.form != null, 'форма распознана (form != null) — до фикса тут был null');
|
||||
assert.ok(r.formCount >= 1, 'formCount >= 1');
|
||||
const hy = (r.hyperlinks || []).map(h => h.name);
|
||||
assert.includes(hy, 'Техническая информация', 'гиперссылка-декорация видна в состоянии');
|
||||
assert.equal((r.fields || []).length, 0, 'полей ввода на форме нет (декорации-only)');
|
||||
await closeForm();
|
||||
});
|
||||
|
||||
await step('navigateLink: та же форма через «Обработка.СтраницаНастроек»', async () => {
|
||||
const r = await navigateLink('Обработка.СтраницаНастроек');
|
||||
log(`form=${r.form} formCount=${r.formCount} activeTab=${r.activeTab}`);
|
||||
assert.ok(r.form != null, 'форма распознана через navigateLink');
|
||||
assert.ok(r.formCount >= 1, 'formCount >= 1');
|
||||
const state = await getFormState();
|
||||
const hy = (state.hyperlinks || []).map(h => h.name);
|
||||
assert.includes(hy, 'Техническая информация', 'гиперссылка видна и в getFormState');
|
||||
await closeForm();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user