mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-29 08:01:02 +03:00
fix(web-test): состояние группы по контролу сворачивания, а не по вёрстке содержимого
На боевой форме вскрылись случаи, которые прежнее правило не покрывало. Замеры показали, что любая эвристика «по первому сиблингу за заголовком» нежизнеспособна: - служебная обёртка .logicGroupContainer пишется двумя способами (у таблицы <дочерний>#group_div, у вложенной группы <дочерний>_div) — знали только первый; - display самой обёртки плавает: block до первого тогла, none после; - первые узлы группы могут быть скрыты своей логикой, а видимое содержимое идёт дальше по цепочке — раскрытая группа читалась как свёрнутая, и постусловие роняло успешный клик. Теперь состояние берётся из контрола сворачивания: у варианта «картинка» — кадр gx спрайта hideshow у каретки (полярность обратна дереву в dom/grid.mjs), у варианта «гиперссылка» каретки нет, там принадлежность по отступу — дети группы смещены глубже её заголовка, свободный сосед стоит на уровне заголовка. База отсчёта — левый край блока заголовка (каретка сдвигает текст вправо), обход ограничен: у последней свёрнутой группы границы за ней нет, и первый видимый узел нашёлся через 107 сиблингов в чужой ветке формы. Клик по заголовку группы теперь сперва скроллит цель в вид: цель кликается по координатам, и ниже вьюпорта клик молча не доходил. Ответ на клик отдаёт clicked.group (техническое имя) и clicked.title (текущий заголовок): заголовок ключом быть не может — он повторяется между блоками формы и меняется при раскрытии (CollapsedRepresentationTitle), после чего клик по прежнему тексту не находит элемент. При смене заголовка hint говорит, чем кликать дальше. Фикстура: вложенная группа первым ребёнком, «скрыт первый узел» в двух вариантах контрола, группа с меняющимся заголовком в конце формы (уезжает за вьюпорт). Каждый кейс проверен на красноту без своей правки. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
006e64405c
commit
d0e81a1715
@@ -1,24 +1,38 @@
|
||||
// web-test forms/click-group v1.1 — click handler for collapsible/popup form-group titles.
|
||||
// web-test forms/click-group v1.2 — click handler for collapsible/popup form-group titles.
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
//
|
||||
// Reuses the tree/grid expand vocabulary so the model has ONE mental model:
|
||||
// clickElement('<заголовок группы>', { expand: true }) — раскрыть (идемпотентно)
|
||||
// clickElement('<заголовок группы>', { expand: false }) — свернуть (идемпотентно)
|
||||
// clickElement('<заголовок группы>', { toggle: true }) — переключить
|
||||
// clickElement('<заголовок группы>') — переключить (голый клик)
|
||||
// clickElement('<group title>', { expand: true }) — reveal (idempotent)
|
||||
// clickElement('<group title>', { expand: false }) — hide (idempotent)
|
||||
// clickElement('<group title>', { toggle: true }) — flip
|
||||
// clickElement('<group title>') — flip (bare click)
|
||||
//
|
||||
// target.collapsed приходит из findClickTargetScript (по display первого контент-сиблинга).
|
||||
// target.collapsed comes from findClickTargetScript (groupCollapsed in dom/_shared.mjs).
|
||||
|
||||
import { page } from '../core/state.mjs';
|
||||
import { scrollGroupIntoViewScript } from '../../dom.mjs';
|
||||
import { waitForStable } from '../core/wait.mjs';
|
||||
import { modifierClick, returnFormState } from '../core/helpers.mjs';
|
||||
import { shouldClickToggle } from '../table/grid-toggle.mjs';
|
||||
|
||||
// Group captions repeat across a form's blocks («Показать детализацию» in every block), and a
|
||||
// collapsible group swaps its own caption when expanded (CollapsedRepresentationTitle:
|
||||
// «Показать детализацию» ↔ «Скрыть детализацию»). Compare normalised so a real caption swap
|
||||
// is not confused with nbsp/ё spelling differences.
|
||||
const norm = (s) => (s || '').replace(/ /g, ' ').replace(/ё/gi, 'е').trim().toLowerCase();
|
||||
|
||||
export async function clickFormGroupTarget(target, ctx) {
|
||||
const { formNum, modifier, toggle, expand } = ctx;
|
||||
// shouldClickToggle ждёт { isExpanded }; при неизвестном состоянии (undefined) кликаем всегда.
|
||||
// shouldClickToggle expects { isExpanded }; with an unknown state (undefined) always click.
|
||||
const state = target.collapsed == null ? null : { isExpanded: !target.collapsed };
|
||||
const shouldClick = shouldClickToggle(state, expand, toggle);
|
||||
if (shouldClick) await modifierClick(target.x, target.y, modifier);
|
||||
if (shouldClick) {
|
||||
// The target is clicked by coordinates, and on a long form those go stale or fall outside
|
||||
// the viewport — the click then silently does nothing. Scroll the title into view and take
|
||||
// a fresh point.
|
||||
const pt = await page.evaluate(scrollGroupIntoViewScript(formNum, target.label));
|
||||
await modifierClick(pt?.x ?? target.x, pt?.y ?? target.y, modifier);
|
||||
}
|
||||
await waitForStable(formNum);
|
||||
const result = await returnFormState({
|
||||
clicked: { kind: 'formGroup', name: target.name, toggled: shouldClick, ...(modifier ? { modifier } : {}) },
|
||||
@@ -27,11 +41,22 @@ export async function clickFormGroupTarget(target, ctx) {
|
||||
: 'Group already in desired state.',
|
||||
});
|
||||
|
||||
// Постусловие: клик, который ничего не переключил, — молчаливая ложь (то же правило, что
|
||||
// guard на disabled в core/click.mjs). Проверяем ТОЛЬКО когда клик реально был сделан и
|
||||
// состояние читаемо: при collapsed == null (нераспознанная вёрстка) сверять не с чем.
|
||||
// The technical name is the stable key: it finds the entry in groups[] and clicks the same
|
||||
// group again. The caption cannot do that — it changes when the group expands.
|
||||
const after = (result.groups || []).find(g => g.name === target.label);
|
||||
if (target.label) result.clicked.group = target.label;
|
||||
if (after) {
|
||||
result.clicked.title = after.title;
|
||||
if (norm(after.title) !== norm(target.name)) {
|
||||
result.hint += ` The group is now titled "${after.title}"`
|
||||
+ ` — click it by that caption or by its technical name "${target.label}".`;
|
||||
}
|
||||
}
|
||||
|
||||
// Postcondition: a click that toggled nothing is a silent lie (same rule as the disabled
|
||||
// guard in core/click.mjs). Checked ONLY when a click actually happened and the state is
|
||||
// readable: with collapsed == null (unrecognised layout) there is nothing to compare against.
|
||||
if (shouldClick && target.collapsed != null) {
|
||||
const after = (result.groups || []).find(g => g.name === target.label);
|
||||
if (after && after.collapsed === target.collapsed) {
|
||||
throw new Error(`clickElement: group "${target.name}" did not toggle `
|
||||
+ `(collapsed stayed ${after.collapsed})`);
|
||||
|
||||
Reference in New Issue
Block a user