mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-29 16:11:01 +03:00
fix(web-test): handle calculator/calendar popups and type inheritance in fillTableRow
When filling composite-type cells (String+Number+Date), the previous code
tried paste first and only fell back to type selection if paste was rejected.
This failed when rows inherited a type from previous rows — paste succeeded
but the value was silently converted to the wrong type.
Now when { value, type } is specified:
1. Always clear the cell first (Shift+F4) to reset inherited type
2. Open type dialog (F4) and select the requested type
3. Dismiss calculator/calendar popups with Escape
4. Re-enter edit mode if focus was lost, then paste the value
Tested with primitive types (Число/Строка/Дата) and reference types
(Физическое лицо via selection form). Regression-tested existing
composite-type flow in Операции.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
b1187865ec
commit
53219cae6c
@@ -2054,20 +2054,27 @@ export async function fillTableRow(fields, { tab, add, row } = {}) {
|
|||||||
const info = pending.get(matchedKey);
|
const info = pending.get(matchedKey);
|
||||||
const text = info.value;
|
const text = info.value;
|
||||||
|
|
||||||
// === Fill this cell: clipboard paste (trusted event) ===
|
// If user specified a type, always clear and use type selection flow
|
||||||
await page.keyboard.press('Control+A');
|
if (info.type) {
|
||||||
await page.evaluate(`navigator.clipboard.writeText(${JSON.stringify(text)})`);
|
await page.keyboard.press('Shift+F4'); // Clear cell to reset any inherited type
|
||||||
await page.keyboard.press('Control+V');
|
await page.waitForTimeout(500);
|
||||||
await page.waitForTimeout(1500);
|
await page.keyboard.press('F4');
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
// Check if paste was rejected (composite-type cell blocks text input until type is selected)
|
const typeForm = await page.evaluate(`(() => {
|
||||||
const inputAfterPaste = await page.evaluate(`document.activeElement?.value || ''`);
|
const forms = {};
|
||||||
if (!inputAfterPaste && text) {
|
document.querySelectorAll('[id]').forEach(el => {
|
||||||
// Paste rejected — cell requires type selection first via F4
|
if (el.offsetWidth === 0 && el.offsetHeight === 0) return;
|
||||||
if (info.type) {
|
const m = el.id.match(/^form(\\d+)_/);
|
||||||
await page.keyboard.press('F4');
|
if (m) forms[m[1]] = true;
|
||||||
await page.waitForTimeout(1000);
|
});
|
||||||
const typeForm = await page.evaluate(`(() => {
|
const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum});
|
||||||
|
return nums.length > 0 ? Math.max(...nums) : null;
|
||||||
|
})()`);
|
||||||
|
if (typeForm !== null && await isTypeDialog(typeForm)) {
|
||||||
|
await pickFromTypeDialog(typeForm, info.type);
|
||||||
|
await waitForStable(typeForm);
|
||||||
|
// After type selection, check if a selection form opened (ref types)
|
||||||
|
const selForm = await page.evaluate(`(() => {
|
||||||
const forms = {};
|
const forms = {};
|
||||||
document.querySelectorAll('[id]').forEach(el => {
|
document.querySelectorAll('[id]').forEach(el => {
|
||||||
if (el.offsetWidth === 0 && el.offsetHeight === 0) return;
|
if (el.offsetWidth === 0 && el.offsetHeight === 0) return;
|
||||||
@@ -2077,57 +2084,101 @@ export async function fillTableRow(fields, { tab, add, row } = {}) {
|
|||||||
const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum});
|
const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum});
|
||||||
return nums.length > 0 ? Math.max(...nums) : null;
|
return nums.length > 0 ? Math.max(...nums) : null;
|
||||||
})()`);
|
})()`);
|
||||||
if (typeForm !== null && await isTypeDialog(typeForm)) {
|
if (selForm === null) {
|
||||||
await pickFromTypeDialog(typeForm, info.type);
|
// No selection form — primitive type (Number, Date, String).
|
||||||
await waitForStable(typeForm);
|
// After type pick a popup may appear (with delay):
|
||||||
// After type selection, the selection form should open
|
// - Calculator (.calculate) for Number
|
||||||
const selForm = await page.evaluate(`(() => {
|
// - Calendar (.frameCalendar) for Date
|
||||||
const forms = {};
|
// - Direct INPUT for String
|
||||||
document.querySelectorAll('[id]').forEach(el => {
|
// Wait for popup, dismiss with Escape, then paste value.
|
||||||
if (el.offsetWidth === 0 && el.offsetHeight === 0) return;
|
await page.waitForTimeout(800);
|
||||||
const m = el.id.match(/^form(\\d+)_/);
|
let hasPopup = await page.evaluate(`(() => {
|
||||||
if (m) forms[m[1]] = true;
|
const calc = document.querySelector('.calculate');
|
||||||
});
|
if (calc && calc.offsetWidth > 0) return 'calculator';
|
||||||
const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum});
|
const cal = document.querySelector('.frameCalendar');
|
||||||
return nums.length > 0 ? Math.max(...nums) : null;
|
if (cal && cal.offsetWidth > 0) return 'calendar';
|
||||||
|
return null;
|
||||||
})()`);
|
})()`);
|
||||||
if (selForm === null) {
|
if (!hasPopup) {
|
||||||
info.filled = true;
|
await page.waitForTimeout(600);
|
||||||
results.push({ field: matchedKey, cell: cell.fullName,
|
hasPopup = await page.evaluate(`(() => {
|
||||||
error: 'no_selection_form',
|
const calc = document.querySelector('.calculate');
|
||||||
message: `After selecting type "${info.type}", no selection form opened` });
|
if (calc && calc.offsetWidth > 0) return 'calculator';
|
||||||
continue;
|
const cal = document.querySelector('.frameCalendar');
|
||||||
|
if (cal && cal.offsetWidth > 0) return 'calendar';
|
||||||
|
return null;
|
||||||
|
})()`);
|
||||||
}
|
}
|
||||||
const pickResult = await pickFromSelectionForm(selForm, matchedKey, text, formNum);
|
if (hasPopup) {
|
||||||
|
await page.keyboard.press('Escape');
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
}
|
||||||
|
// Ensure we are in an editable INPUT for this cell
|
||||||
|
const afterEl = await page.evaluate(`(() => {
|
||||||
|
const f = document.activeElement;
|
||||||
|
return { tag: f?.tagName, id: f?.id || '' };
|
||||||
|
})()`);
|
||||||
|
if (afterEl.tag !== 'INPUT' && afterEl.tag !== 'TEXTAREA') {
|
||||||
|
const cellRect = await page.evaluate(`(() => {
|
||||||
|
const el = document.getElementById(${JSON.stringify(cell.id)});
|
||||||
|
if (!el) return null;
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
|
||||||
|
})()`);
|
||||||
|
if (cellRect) {
|
||||||
|
await page.mouse.dblclick(cellRect.x, cellRect.y);
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await page.evaluate(`navigator.clipboard.writeText(${JSON.stringify(text)})`);
|
||||||
|
await page.keyboard.press('Control+a');
|
||||||
|
await page.keyboard.press('Control+v');
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
await page.keyboard.press('Tab');
|
||||||
|
await page.waitForTimeout(500);
|
||||||
info.filled = true;
|
info.filled = true;
|
||||||
results.push(pickResult.ok
|
results.push({ field: matchedKey, cell: cell.fullName, ok: true, method: 'type-direct', type: info.type });
|
||||||
? { field: matchedKey, cell: cell.fullName, ok: true, method: 'form', type: info.type }
|
|
||||||
: { field: matchedKey, cell: cell.fullName,
|
|
||||||
error: pickResult.error, message: pickResult.message });
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// F4 opened something but not a type dialog — close and report
|
const pickResult = await pickFromSelectionForm(selForm, matchedKey, text, formNum);
|
||||||
if (typeForm !== null) {
|
|
||||||
await page.keyboard.press('Escape');
|
|
||||||
await page.waitForTimeout(300);
|
|
||||||
}
|
|
||||||
info.filled = true;
|
info.filled = true;
|
||||||
results.push({ field: matchedKey, cell: cell.fullName,
|
results.push(pickResult.ok
|
||||||
error: 'paste_rejected',
|
? { field: matchedKey, cell: cell.fullName, ok: true, method: 'form', type: info.type }
|
||||||
message: `Cell "${matchedKey}" rejected text input. F4 did not open type dialog` });
|
: { field: matchedKey, cell: cell.fullName,
|
||||||
await page.keyboard.press('Tab');
|
error: pickResult.error, message: pickResult.message });
|
||||||
await page.waitForTimeout(500);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
// No type specified — can't fill this composite-type cell
|
|
||||||
info.filled = true;
|
|
||||||
results.push({ field: matchedKey, cell: cell.fullName,
|
|
||||||
error: 'type_required',
|
|
||||||
message: `Cell "${matchedKey}" rejected text input (composite-type). Use { value: '...', type: 'Тип' } syntax` });
|
|
||||||
await page.keyboard.press('Tab');
|
|
||||||
await page.waitForTimeout(500);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// F4 opened something but not a type dialog — close and report
|
||||||
|
if (typeForm !== null) {
|
||||||
|
await page.keyboard.press('Escape');
|
||||||
|
await page.waitForTimeout(300);
|
||||||
|
}
|
||||||
|
info.filled = true;
|
||||||
|
results.push({ field: matchedKey, cell: cell.fullName,
|
||||||
|
error: 'type_dialog_failed',
|
||||||
|
message: `Cell "${matchedKey}": F4 did not open type dialog for type "${info.type}"` });
|
||||||
|
await page.keyboard.press('Tab');
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Fill this cell: clipboard paste (trusted event) ===
|
||||||
|
await page.keyboard.press('Control+A');
|
||||||
|
await page.evaluate(`navigator.clipboard.writeText(${JSON.stringify(text)})`);
|
||||||
|
await page.keyboard.press('Control+V');
|
||||||
|
await page.waitForTimeout(1500);
|
||||||
|
|
||||||
|
// Check if paste was rejected (composite-type cell blocks text input until type is selected)
|
||||||
|
const inputAfterPaste = await page.evaluate(`document.activeElement?.value || ''`);
|
||||||
|
if (!inputAfterPaste && text) {
|
||||||
|
// No type specified — can't fill this composite-type cell
|
||||||
|
info.filled = true;
|
||||||
|
results.push({ field: matchedKey, cell: cell.fullName,
|
||||||
|
error: 'type_required',
|
||||||
|
message: `Cell "${matchedKey}" rejected text input (composite-type). Use { value: '...', type: 'Тип' } syntax` });
|
||||||
|
await page.keyboard.press('Tab');
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for EDD autocomplete (indicates reference field)
|
// Check for EDD autocomplete (indicates reference field)
|
||||||
@@ -2313,10 +2364,61 @@ export async function fillTableRow(fields, { tab, add, row } = {}) {
|
|||||||
return nums.length > 0 ? Math.max(...nums) : null;
|
return nums.length > 0 ? Math.max(...nums) : null;
|
||||||
})()`);
|
})()`);
|
||||||
if (selForm === null) {
|
if (selForm === null) {
|
||||||
|
// No selection form — primitive type (Number, Date, String).
|
||||||
|
// After type pick a popup may appear (with delay):
|
||||||
|
// - Calculator (.calculate) for Number
|
||||||
|
// - Calendar (.frameCalendar) for Date
|
||||||
|
// - Direct INPUT for String
|
||||||
|
// Wait for popup, dismiss with Escape, then paste value.
|
||||||
|
await page.waitForTimeout(800);
|
||||||
|
let hasPopup = await page.evaluate(`(() => {
|
||||||
|
const calc = document.querySelector('.calculate');
|
||||||
|
if (calc && calc.offsetWidth > 0) return 'calculator';
|
||||||
|
const cal = document.querySelector('.frameCalendar');
|
||||||
|
if (cal && cal.offsetWidth > 0) return 'calendar';
|
||||||
|
return null;
|
||||||
|
})()`);
|
||||||
|
if (!hasPopup) {
|
||||||
|
// Second check after extra wait
|
||||||
|
await page.waitForTimeout(600);
|
||||||
|
hasPopup = await page.evaluate(`(() => {
|
||||||
|
const calc = document.querySelector('.calculate');
|
||||||
|
if (calc && calc.offsetWidth > 0) return 'calculator';
|
||||||
|
const cal = document.querySelector('.frameCalendar');
|
||||||
|
if (cal && cal.offsetWidth > 0) return 'calendar';
|
||||||
|
return null;
|
||||||
|
})()`);
|
||||||
|
}
|
||||||
|
if (hasPopup) {
|
||||||
|
await page.keyboard.press('Escape');
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
}
|
||||||
|
// Ensure we are in an editable INPUT for this cell
|
||||||
|
const afterEl = await page.evaluate(`(() => {
|
||||||
|
const f = document.activeElement;
|
||||||
|
return { tag: f?.tagName, id: f?.id || '' };
|
||||||
|
})()`);
|
||||||
|
if (afterEl.tag !== 'INPUT' && afterEl.tag !== 'TEXTAREA') {
|
||||||
|
// Focus lost — re-enter edit mode via dblclick
|
||||||
|
const cellRect = await page.evaluate(`(() => {
|
||||||
|
const el = document.getElementById(${JSON.stringify(cell.id)});
|
||||||
|
if (!el) return null;
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
|
||||||
|
})()`);
|
||||||
|
if (cellRect) {
|
||||||
|
await page.mouse.dblclick(cellRect.x, cellRect.y);
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await page.evaluate(`navigator.clipboard.writeText(${JSON.stringify(text)})`);
|
||||||
|
await page.keyboard.press('Control+a');
|
||||||
|
await page.keyboard.press('Control+v');
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
await page.keyboard.press('Tab');
|
||||||
|
await page.waitForTimeout(500);
|
||||||
info.filled = true;
|
info.filled = true;
|
||||||
results.push({ field: matchedKey, cell: cell.fullName,
|
results.push({ field: matchedKey, cell: cell.fullName, ok: true, method: 'type-direct', type: info.type });
|
||||||
error: 'no_selection_form',
|
|
||||||
message: `After selecting type "${info.type}", no selection form opened` });
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const pickResult = await pickFromSelectionForm(selForm, matchedKey, text, formNum);
|
const pickResult = await pickFromSelectionForm(selForm, matchedKey, text, formNum);
|
||||||
|
|||||||
Reference in New Issue
Block a user