"""스프레드시트 손잡이 `setLink` — 알약 키를 칸에 잇기 · 풀기(PLAN 39-4 D4). 순수 길(`spreadsheet_link_set.ts` · DOM 모름)을 vite `--ssr` 로 번들해 Node 로 돌림 — 새 연결 · 옮김 · 칸 하나 = 키 하나 · 시트 넘어 옮김 · 풀기 · 되돌리기 · 바뀔 것 없음 · 틀린 주소. 손잡이 몸(`spreadsheet.ts`)은 글로 확인 — 화면 조작은 ORCA. """ from __future__ import annotations import json import shutil import subprocess import tempfile from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] ENTRY = ROOT / "resources" / "tester" / "spreadsheet" / "spreadsheet_link_set_entry.ts" VITE = ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js" @pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음") def test_setLink_순수_길(): with tempfile.TemporaryDirectory(prefix="spreadsheet_link_set_") as outdir: entry_rel = "../" + str(ENTRY.relative_to(ROOT)).replace("\\", "/") build = subprocess.run( [ "node", str(VITE), "build", "--configLoader", "runner", "--ssr", entry_rel, "--outDir", outdir, ], cwd=str(ROOT), capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=120, ) assert build.returncode == 0, f"vite ssr 번들 실패:\n{build.stdout}\n{build.stderr}" run = subprocess.run( ["node", str(Path(outdir) / "spreadsheet_link_set_entry.js")], capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=60, ) assert run.stdout.strip(), run.stderr result = json.loads(run.stdout) assert result["fail"] == 0, result["failures"] assert result["pass"] == 29 def test_손잡이_setLink_몸(): src = (ROOT / "A00_Common" / "spreadsheet" / "spreadsheet.ts").read_text(encoding="utf-8") assert ( "setLink(key: string, cell: string | null, info?: LinkInfo, replace?: boolean): boolean;" in src ) body = src.split(" setLink(key, cell, info, replace) {")[1].split(" destroy() {")[0] assert "if (readOnly) return false;" in body assert "linkCommand(ctx.book, ctx.sheet().id, key, cell, info, replace)" in body assert "if (command) dispatch(command);" in body # 되돌리기 이력에 쌓임(setDoc 아님) assert "loadLinks();" in body def test_알약_끌어_놓기_색(): """40-3 ② ③ — 알약 끌어 칸에 놓기(다른 알약 칸이면 물음) · 이은 알약 · 칸 = 키 색 · 두 테마 밝기.""" src = (ROOT / "A00_Common" / "spreadsheet" / "spreadsheet_links.ts").read_text(encoding="utf-8") assert "b.draggable = !ctx.readOnly;" in src and 'addEventListener("drop", onDropEvent)' in src assert "await link(item, toA1(at.r, at.c));" in src # 칸 하나에 알약 여럿 — 다른 알약 칸이면 함께 · 바꾸기 · 취소 물음(알약 누름 · 끌어 놓기 한 길) body = src.split("async function link(")[1].split("\n }\n")[0] assert "linksAt(ctx.book, sheet.id, 칸, item.키)" in body and "askShare(" in body assert 'choice === "바꾸기"' in body assert 'className: "ss-link-count"' in src and 'ctx.grid.root.title = names.join(" · ")' in src assert 'if (!b.draggable) b.addEventListener("mousedown"' in src # 끌기 막지 않음 assert "const hues = linkHues(found().keys());" in src and 'className: "ss-link-cell"' in src css = (ROOT / "A00_Common" / "spreadsheet" / "spreadsheet_links.css").read_text( encoding="utf-8" ) assert ( "--ss-link-l: 31%;" in css and css.count("--ss-link-l: 66%;") == 2 ) # 밝음 · 어두움(설정 · 시스템) assert "ss-link-flash" in css # 반짝임 그대로