Files
cc-1c-skills/tests/web-test/12-formstate.test.mjs
T
Nick ShirokovandClaude Opus 4.8 8826a88427 fix(web-test): заголовок формы в состоянии + formHasField по массиву fields
Два задокументированных ассерта бросали ВСЕГДА, то есть были мертвы:

- formTitle читал state.title, которого не заполнял никто: getFormStateScript
  собирал форму без заголовка. Единственным носителем оставалась панель
  открытых окон (activeTab), а она отключается в настройках 1С — на такой базе
  заголовок был недоступен ничем.
- formHasField читал state.fields[name], хотя fields — массив объектов
  {name, value, …}. На массиве это всегда undefined.

getFormState теперь отдаёт title. Берётся он из шапки самой формы: заголовок
лежит в атрибуте (title у .toplineBoxTitle, data-title у родителя), сам элемент
пустой — поэтому поиском по тексту он и не находился.

Выбор шапки — не «первая видимая»: при открытом всплывающем окне видимы ДВЕ,
родителя и окна, и наивное правило отдавало заголовок родителя — правдоподобный
неверный ответ, при котором тест «окно выбора открылось» зеленел бы по
документу. Приоритет взят тот же, что уже отлажен для крестика закрытия в
closeCrossScript: плавающее окно ps<N> с наибольшим индексом → собственная шапка
формы → и только потом панель открытых окон. Привязка к id, а не к тексту — не
ломается на другой локали. Панель осталась последним звеном: она отключаема, а
при всплывающем окне ещё и показывает родителя.

Диагностика раннера (resetState) тоже переведена на title с прежним activeTab
как запасным.

formHasField ищет по массиву и перечисляет доступные имена в ошибке (раньше
Object.keys по массиву давал индексы). formTitle отличает «заголовок недоступен»
(title === null) от несовпадения.

Почему не поймали раньше: из 12 ассертов сюита вызывала 8, и оба сломанных были
среди четырёх невызываемых. Теперь все четыре задействованы на настоящем выводе
getFormState — formTitle/formHasField/noErrors в 12-formstate (включая случай
всплывающего окна), tableRowCount в 09-filter. Отдельного юнит-теста намеренно
нет: состояние для него пришлось бы писать руками, а именно неверное
представление о форме состояния и породило оба дефекта.

Доки приведены к массиву: примеры вида s.fields['X']?.value в regress.md и в
спеке заменены на fields.find(f => f.name === 'X').

Проверено: заголовок на списке, форме элемента и всплывающем окне; каскад
разведён по значениям (шапка выигрывает у панели, при пустой шапке — откат);
позитив и негатив всех четырёх ассертов на реальном состоянии формы; полный
регресс 29/29 до и после, file/name/status идентичны.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 18:51:12 +03:00

118 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export const name = 'getFormState: базовая структура — fields, buttons, tables, openForms';
export const tags = ['formstate', 'smoke'];
export const timeout = 60000;
export default async function({ navigateSection, openCommand, clickElement, closeForm, getFormState, getPage, assert, step, log }) {
await step('basic: getFormState на форме списка возвращает таблицу и команды', async () => {
await navigateSection('Склад');
const s = await openCommand('Контрагенты');
log(`form=${s.form} formCount=${s.formCount} tables=${s.tables?.length} buttons=${s.buttons?.length}`);
assert.ok(s.form != null, 'state.form задан');
assert.equal(s.formCount, 1, 'Открыта одна форма');
assert.ok(Array.isArray(s.openForms) && s.openForms.length === 1, 'openForms — массив с одной записью');
assert.ok(s.tables?.length >= 1, 'На форме списка есть таблица');
assert.ok(s.tables[0].columns?.length >= 2, 'У таблицы есть колонки');
assert.ok(s.buttons?.length >= 1, 'На форме есть кнопки');
// title читается из шапки формы (атрибут .toplineBoxTitle), а не из панели открытых окон —
// она отключаема в настройках 1С. Панель остаётся запасным источником.
assert.formTitle(s, 'Контрагенты', 'Заголовок формы списка');
assert.noErrors(s, 'На только что открытой форме списка ошибок быть не должно');
await closeForm();
});
await step('basic: getFormState на форме элемента возвращает fields с label и value', async () => {
await navigateSection('Склад');
await openCommand('Контрагенты');
await clickElement('ООО Север', { dblclick: true });
const s = await getFormState();
log(`fields count=${s.fields?.length}`);
assert.ok(s.fields?.length >= 1, 'На форме элемента есть поля');
const named = s.fields.find(f => f.name === 'Наименование');
log(`Наименование: label='${named?.label}' value='${named?.value}'`);
assert.formHasField(s, 'Наименование', 'formHasField ищет по массиву fields, а не по ключу');
assert.formTitle(s, 'Контрагент', 'Заголовок формы элемента');
assert.ok(named, 'Должно быть поле Наименование');
assert.equal(named.value, 'ООО Север', 'value поля Наименование');
assert.ok(named.label, 'У поля есть label');
await closeForm();
});
await step('modal: форма выбора Контрагентов открыта как модальная', async () => {
await navigateSection('Склад');
await openCommand('Приходная накладная');
await clickElement('Создать');
const page = await getPage();
// Найти input Контрагент и фокус, затем F4 → откроется модальная форма выбора
const focused = await page.evaluate(`(() => {
const inputs = [...document.querySelectorAll('input')];
const target = inputs.find(i => /Контрагент/i.test(i.id || '') && i.offsetWidth > 0);
if (target) { target.focus(); return target.id; }
return null;
})()`);
log(`focused input id=${focused}`);
await page.keyboard.press('F4');
await page.waitForTimeout(1500);
const s = await getFormState();
log(`after F4: form=${s.form} formCount=${s.formCount} modal=${s.modal}`);
assert.equal(s.modal, true, 'state.modal=true для модальной формы выбора');
assert.ok(s.formCount >= 2, 'formCount >= 2 (родитель + модальная)');
// Заголовок берётся у всплывающего окна, а не у родителя: видимы обе шапки, а панель
// открытых окон в этот момент показывает родителя ('Приходная накладная').
assert.formTitle(s, 'Выбор контрагента', 'title всплывающего окна, а не родительской формы');
await closeForm();
await closeForm({ save: false });
});
await step('tabs: на форме элемента Номенклатуры присутствует tabs[]', async () => {
await navigateSection('Склад');
await openCommand('Номенклатура');
await clickElement('Товары', { dblclick: true });
await clickElement('Товар 01', { dblclick: true });
const s = await getFormState();
log(`tabs: ${JSON.stringify(s.tabs)}`);
assert.ok(Array.isArray(s.tabs), 'state.tabs должен быть массивом');
assert.ok(s.tabs.length >= 2, `На форме Номенклатуры >= 2 табов (got ${s.tabs.length})`);
await closeForm();
});
await step('subordinate-nav: форма элемента Контрагент возвращает state.navigation с КонтактнымиЛицами', async () => {
await navigateSection('Склад');
await openCommand('Контрагенты');
await clickElement('ООО Север', { dblclick: true });
const s = await getFormState();
log(`navigation: ${JSON.stringify(s.navigation)}`);
assert.ok(Array.isArray(s.navigation), 'state.navigation — массив');
assert.ok(s.navigation.length >= 2, 'минимум Основное + один подчинённый');
const main = s.navigation.find(n => n.active);
assert.ok(main && main.name === 'Основное', 'активная ссылка — Основное');
const sub = s.navigation.find(n => /Контактные/.test(n.name));
assert.ok(sub, 'есть ссылка на Контактные лица');
await closeForm();
});
await step('platform-dialogs: открытый «О программе» виден в state.platformDialogs', async () => {
const page = await getPage();
await page.click('#captionbarMore');
await page.waitForTimeout(800);
await page.getByText('О программе...', { exact: true }).click();
await page.waitForTimeout(1500);
const s = await getFormState();
log(`platformDialogs: ${JSON.stringify(s.platformDialogs)}`);
assert.ok(Array.isArray(s.platformDialogs) && s.platformDialogs.length === 1,
'state.platformDialogs — массив с одним элементом');
assert.equal(s.platformDialogs[0].type, 'about', 'type=about');
assert.equal(s.platformDialogs[0].title, 'О программе', 'title');
});
await step('platform-dialog-close: closeForm закрывает платформенный диалог', async () => {
// About остался открыт с предыдущего шага
await closeForm();
const s = await getFormState();
log(`platformDialogs after closeForm: ${s.platformDialogs?.length || 0}`);
assert.ok(!s.platformDialogs?.length, 'после closeForm нет platformDialogs');
});
}