diff --git a/A00_Common/spreadsheet/spreadsheet.ts b/A00_Common/spreadsheet/spreadsheet.ts index 061d08b7..25f43b0e 100644 --- a/A00_Common/spreadsheet/spreadsheet.ts +++ b/A00_Common/spreadsheet/spreadsheet.ts @@ -48,6 +48,16 @@ export interface SpreadsheetOptions { onChange?: (book: Workbook) => void; /** 고름이 바뀜 — 시트 id · 활성 칸 A1 · 범위 A1 */ onSelect?: (selection: { 시트: string; 칸: string; 범위: string }) => void; + /** ⚠ 임시 — 브레인 브라우저 검증용: 문맥을 `window.__aisloSheet` 에 걺(book · 고름 · 명령 읽기). + * M02 [스프레드시트 시험] 단추만 켬 · 산출근거 카드는 안 켬 · 1단계 검증 뒤 지움. */ + inspect?: boolean; +} + +declare global { + interface Window { + /** ⚠ 임시 — `inspect` 로 연 표의 문맥(브레인 검증용) */ + __aisloSheet?: SpreadsheetContext; + } } export interface SpreadsheetHandle { @@ -139,6 +149,7 @@ export function createSpreadsheet( grid.render(); select(ctx.selection); + if (opts.inspect) window.__aisloSheet = ctx; // ⚠ 임시 — 브레인 검증용 function notify(message: string): void { toast.textContent = message; @@ -244,6 +255,7 @@ export function createSpreadsheet( return ctx.engine.snapshot(); }, destroy() { + if (window.__aisloSheet === ctx) delete window.__aisloSheet; clearTimeout(toastTimer); detachKeys(); detachMouse(); diff --git a/resources/tester/spreadsheet/harness_spreadsheet_d.cjs b/resources/tester/spreadsheet/harness_spreadsheet_d.cjs index 8d220b14..01fee11f 100644 --- a/resources/tester/spreadsheet/harness_spreadsheet_d.cjs +++ b/resources/tester/spreadsheet/harness_spreadsheet_d.cjs @@ -10,8 +10,116 @@ window.ssSample = JSON.parse(JSON.stringify(sample)); window.ss = createSpreadsheet(host, JSON.parse(JSON.stringify(sample)), { onChange: () => window.ssChanges++, onSelect: (s) => (window.ssSelect = s), + inspect: true, }); window.ssCreate = createSpreadsheet; /** 칸 입력 값(A1) */ window.ssCell = (a1, sheet = "s1") => window.ss.getDoc().시트.find((s) => s.id === sheet).칸[a1] ?? null; + +/* 한 번에 도는 시나리오 — 탭이 새로 읽혀도 끊기지 않게 페이지 안에서 합성 사건으로 조작하고 수치를 모음. + * (믿음 입력 — 진짜 키 · 마우스 · 한글 — 은 ORCA keypress · type · mouse 로 따로 잼) */ +window.ssScenario = () => { + const ta = () => document.querySelector(".ss-input"); + const at = (a1) => { + const d = document.querySelector(`[data-a1="${a1}"]`).getBoundingClientRect(); + return { + clientX: d.left + d.width / 2, + clientY: d.top + d.height / 2, + bubbles: true, + button: 0, + }; + }; + const grid = () => document.querySelector(".ss-grid-host"); + const click = (a1, extra = {}) => { + grid().dispatchEvent(new MouseEvent("mousedown", { ...at(a1), ...extra })); + window.dispatchEvent(new MouseEvent("mouseup", at(a1))); + }; + const dragTo = (from, to) => { + grid().dispatchEvent(new MouseEvent("mousedown", from)); + window.dispatchEvent(new MouseEvent("mousemove", to)); + window.dispatchEvent(new MouseEvent("mouseup", to)); + }; + const key = (k, o = {}) => + document.activeElement.dispatchEvent( + new KeyboardEvent("keydown", { key: k, bubbles: true, cancelable: true, ...o }), + ); + const type = (text) => { + const t = document.activeElement; + t.setRangeText(text, t.selectionStart, t.selectionEnd, "end"); + t.dispatchEvent(new Event("input", { bubbles: true })); + }; + const shown = (a1) => document.querySelector(`[data-a1="${a1}"]`).textContent; + const sel = () => `${ssSelect.칸}|${ssSelect.범위}`; + const out = {}; + // 한글 첫 글자 — compositionstart 로 편집 시작 · 같은 textarea 그대로 + click("L2"); + ta().dispatchEvent(new CompositionEvent("compositionstart", { data: "" })); + out.imeEditing = + document.querySelector(".ss").classList.contains("ss--editing") && + document.activeElement === ta(); + type("한"); + key("Enter"); + out.ime = [ssCell("L2"), sel()]; + // 수 · 식 · 참조 가리키기(방향키 + 누르기) · 재계산 + click("L3"); + type("2"); + key("Enter"); + type("="); + key("ArrowUp"); + out.pointKey = ta().value; + type("*"); + click("J5"); + out.pointClick = [ + ta().value, + [...document.querySelectorAll(".fg-ref")].map((x) => x.dataset.ref), + ]; + key("Enter"); + out.calc = [ssCell("L4"), shown("L4")]; + click("L3"); + type("5"); + key("Enter"); + out.recalc = shown("L4"); + // 되돌리기 · 다시 + key("z", { ctrlKey: true }); + out.undo = [ssCell("L3"), shown("L4")]; + key("y", { ctrlKey: true }); + out.redo = [ssCell("L3"), shown("L4")]; + // Delete(서식 남김) · 되돌림 + click("B4"); + key("Delete"); + out.del = ssCell("B4"); + key("z", { ctrlKey: true }); + out.delUndo = ssCell("B4"); + // 병합 · Shift/Ctrl 방향 + click("A4"); + key("ArrowRight", { shiftKey: true }); + key("ArrowRight", { shiftKey: true }); + out.shift = sel(); + click("A3"); + key("ArrowDown", { ctrlKey: true }); + out.ctrl = sel(); + // 끌어 고르기 · Ctrl+Enter + dragTo(at("P2"), at("Q3")); + out.drag = sel(); + type("=L3+1"); + key("Enter", { ctrlKey: true }); + out.ctrlEnter = ["P2", "Q2", "P3", "Q3"].map((k) => ssCell(k)?.식); + // 수식 입력줄로 고치기 + click("N2"); + const bar = document.querySelector(".ss-formula"); + bar.focus(); + bar.value = "=J4*10"; + bar.dispatchEvent(new Event("input", { bubbles: true })); + key("Enter"); + out.bar = [ssCell("N2"), shown("N2"), sel()]; + // F2 — 방향키가 글자 자리만 + click("N2"); + key("F2"); + const before = sel(); + key("ArrowLeft"); + out.f2 = [before === sel(), document.querySelector(".ss").classList.contains("ss--editing")]; + key("Escape"); + out.errors = window.ssErrors; + return JSON.stringify(out); +}; diff --git a/resources/tester/spreadsheet/harness_spreadsheet_d.html b/resources/tester/spreadsheet/harness_spreadsheet_d.html index be9d4089..19349816 100644 --- a/resources/tester/spreadsheet/harness_spreadsheet_d.html +++ b/resources/tester/spreadsheet/harness_spreadsheet_d.html @@ -23,6 +23,12 @@
+