"""스프레드시트 손잡이 `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"] == 19 def test_손잡이_setLink_몸(): src = (ROOT / "A00_Common" / "spreadsheet" / "spreadsheet.ts").read_text(encoding="utf-8") assert "setLink(key: string, cell: string | null, info?: LinkInfo): boolean;" in src body = src.split(" setLink(key, cell, info) {")[1].split(" destroy() {")[0] assert "if (readOnly) return false;" in body assert "linkCommand(ctx.book, ctx.sheet().id, key, cell, info)" in body assert "if (command) dispatch(command);" in body # 되돌리기 이력에 쌓임(setDoc 아님) assert "loadLinks();" in body