mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-12 17:04:57 +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:
@@ -2054,20 +2054,27 @@ export async function fillTableRow(fields, { tab, add, row } = {}) {
|
||||
const info = pending.get(matchedKey);
|
||||
const text = info.value;
|
||||
|
||||
// === 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) {
|
||||
// Paste rejected — cell requires type selection first via F4
|
||||
if (info.type) {
|
||||
await page.keyboard.press('F4');
|
||||
await page.waitForTimeout(1000);
|
||||
const typeForm = await page.evaluate(`(() => {
|
||||
// If user specified a type, always clear and use type selection flow
|
||||
if (info.type) {
|
||||
await page.keyboard.press('Shift+F4'); // Clear cell to reset any inherited type
|
||||
await page.waitForTimeout(500);
|
||||
await page.keyboard.press('F4');
|
||||
await page.waitForTimeout(1000);
|
||||
const typeForm = await page.evaluate(`(() => {
|
||||
const forms = {};
|
||||
document.querySelectorAll('[id]').forEach(el => {
|
||||
if (el.offsetWidth === 0 && el.offsetHeight === 0) return;
|
||||
const m = el.id.match(/^form(\\d+)_/);
|
||||
if (m) forms[m[1]] = true;
|
||||
});
|
||||
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 = {};
|
||||
document.querySelectorAll('[id]').forEach(el => {
|
||||
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});
|
||||
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, the selection form should open
|
||||
const selForm = await page.evaluate(`(() => {
|
||||
const forms = {};
|
||||
document.querySelectorAll('[id]').forEach(el => {
|
||||
if (el.offsetWidth === 0 && el.offsetHeight === 0) return;
|
||||
const m = el.id.match(/^form(\\d+)_/);
|
||||
if (m) forms[m[1]] = true;
|
||||
});
|
||||
const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum});
|
||||
return nums.length > 0 ? Math.max(...nums) : 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 (selForm === null) {
|
||||
info.filled = true;
|
||||
results.push({ field: matchedKey, cell: cell.fullName,
|
||||
error: 'no_selection_form',
|
||||
message: `After selecting type "${info.type}", no selection form opened` });
|
||||
continue;
|
||||
if (!hasPopup) {
|
||||
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;
|
||||
})()`);
|
||||
}
|
||||
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;
|
||||
results.push(pickResult.ok
|
||||
? { field: matchedKey, cell: cell.fullName, ok: true, method: 'form', type: info.type }
|
||||
: { field: matchedKey, cell: cell.fullName,
|
||||
error: pickResult.error, message: pickResult.message });
|
||||
results.push({ field: matchedKey, cell: cell.fullName, ok: true, method: 'type-direct', type: info.type });
|
||||
continue;
|
||||
}
|
||||
// F4 opened something but not a type dialog — close and report
|
||||
if (typeForm !== null) {
|
||||
await page.keyboard.press('Escape');
|
||||
await page.waitForTimeout(300);
|
||||
}
|
||||
const pickResult = await pickFromSelectionForm(selForm, matchedKey, text, formNum);
|
||||
info.filled = true;
|
||||
results.push({ field: matchedKey, cell: cell.fullName,
|
||||
error: 'paste_rejected',
|
||||
message: `Cell "${matchedKey}" rejected text input. F4 did not open type dialog` });
|
||||
await page.keyboard.press('Tab');
|
||||
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);
|
||||
results.push(pickResult.ok
|
||||
? { field: matchedKey, cell: cell.fullName, ok: true, method: 'form', type: info.type }
|
||||
: { field: matchedKey, cell: cell.fullName,
|
||||
error: pickResult.error, message: pickResult.message });
|
||||
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)
|
||||
@@ -2313,10 +2364,61 @@ export async function fillTableRow(fields, { tab, add, row } = {}) {
|
||||
return nums.length > 0 ? Math.max(...nums) : 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;
|
||||
results.push({ field: matchedKey, cell: cell.fullName,
|
||||
error: 'no_selection_form',
|
||||
message: `After selecting type "${info.type}", no selection form opened` });
|
||||
results.push({ field: matchedKey, cell: cell.fullName, ok: true, method: 'type-direct', type: info.type });
|
||||
continue;
|
||||
}
|
||||
const pickResult = await pickFromSelectionForm(selForm, matchedKey, text, formNum);
|
||||
|
||||
Reference in New Issue
Block a user