feat(spreadsheet): 손잡이 setLink — 알약 키를 칸에 잇기 · 풀기 · 알약 누름과 같은 규칙 · 되돌리기 됨 (PLAN 39-4 D4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W4uKaV4ZBhsJN4GHG7yRj4
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
/* setLink 순수 길(`spreadsheet_link_set.ts`) 시험 진입 — vite --ssr 로 번들해 Node 로 돌림.
|
||||
* 결과 = JSON {pass, fail, failures} 한 줄(`test_spreadsheet_set_link.py` 가 읽음) */
|
||||
import { applyCommand } from "../../../A00_Common/spreadsheet/spreadsheet_commands";
|
||||
import { linkCommand } from "../../../A00_Common/spreadsheet/spreadsheet_link_set";
|
||||
import type { Command, Workbook } from "../../../A00_Common/spreadsheet/spreadsheet_types";
|
||||
|
||||
let pass = 0;
|
||||
const failures: string[] = [];
|
||||
const eq = (name: string, got: unknown, want: unknown): void => {
|
||||
if (JSON.stringify(got) === JSON.stringify(want)) pass += 1;
|
||||
else failures.push(`${name}: ${JSON.stringify(got)} ≠ ${JSON.stringify(want)}`);
|
||||
};
|
||||
|
||||
const book = (): Workbook =>
|
||||
({
|
||||
종류: "통합문서",
|
||||
판: 1,
|
||||
열: "t",
|
||||
서식: [{}],
|
||||
시트: [
|
||||
{
|
||||
id: "s1",
|
||||
이름: "면적산출",
|
||||
칸: { B1: { 값: 12.5 } },
|
||||
연결: [{ 키: "깔기|㎡", 이름: "깔기", 단위: "㎡", 칸: "B6" }],
|
||||
},
|
||||
{ id: "s2", 이름: "둘", 칸: {}, 연결: [{ 키: "치기|㎥", 이름: "치기", 칸: "C3" }] },
|
||||
],
|
||||
활성: "s1",
|
||||
}) as unknown as Workbook;
|
||||
const links = (b: Workbook) => b.시트.map((s) => s.연결 ?? null);
|
||||
const run = (b: Workbook, c: Command | null | false) => {
|
||||
if (!c) return null;
|
||||
return applyCommand(b, c).undo;
|
||||
};
|
||||
|
||||
// 새 키를 칸에 — 이름 · 단위 · 자재가 같이 들어감 · 소문자 · 빈칸 주소도 받음
|
||||
let b = book();
|
||||
const mt = { 코드: "MT000008", 이름: "치즐", 규격: "", 단위: "EA" };
|
||||
let undo = run(
|
||||
b,
|
||||
linkCommand(b, "s1", "자재|MT000008", " d2 ", { 이름: "치즐", 단위: "EA", 자재: mt }),
|
||||
);
|
||||
eq("새 연결", b.시트[0].연결?.[1], {
|
||||
키: "자재|MT000008",
|
||||
칸: "D2",
|
||||
이름: "치즐",
|
||||
단위: "EA",
|
||||
자재: mt,
|
||||
});
|
||||
// 되돌리기 = 처음 그대로
|
||||
run(b, undo);
|
||||
eq("되돌림", links(b), links(book()));
|
||||
|
||||
// 있는 키를 다른 칸으로 — 옛 칸 빠지고 이름 · 단위는 있던 것
|
||||
b = book();
|
||||
run(b, linkCommand(b, "s1", "깔기|㎡", "B1"));
|
||||
eq("옮김", b.시트[0].연결, [{ 키: "깔기|㎡", 칸: "B1", 이름: "깔기", 단위: "㎡" }]);
|
||||
|
||||
// 같은 칸에 다른 키 — 그 칸 옛 키 빠짐(칸 하나 = 키 하나)
|
||||
b = book();
|
||||
run(b, linkCommand(b, "s1", "새|m", "B6", { 이름: "새", 단위: "m" }));
|
||||
eq("칸 하나 키 하나", b.시트[0].연결, [{ 키: "새|m", 칸: "B6", 이름: "새", 단위: "m" }]);
|
||||
|
||||
// 다른 시트에 있던 키를 이 시트로 — 저쪽 연결 빠짐(연결 칸 없으면 `연결` 지움) · 되돌리기 한 번에 둘 다
|
||||
b = book();
|
||||
undo = run(b, linkCommand(b, "s1", "치기|㎥", "A1"));
|
||||
eq("시트 넘어 옮김", links(b), [
|
||||
[
|
||||
{ 키: "깔기|㎡", 이름: "깔기", 단위: "㎡", 칸: "B6" },
|
||||
{ 키: "치기|㎥", 칸: "A1", 이름: "치기" },
|
||||
],
|
||||
null,
|
||||
]);
|
||||
run(b, undo);
|
||||
eq("시트 넘어 되돌림", links(b), links(book()));
|
||||
|
||||
// 풀기 — 그 키만 빠지고 마지막이면 `연결` 칸이 없어짐
|
||||
b = book();
|
||||
run(b, linkCommand(b, "s1", "깔기|㎡", null));
|
||||
eq("풀기", links(b), [null, [{ 키: "치기|㎥", 이름: "치기", 칸: "C3" }]]);
|
||||
|
||||
// 바뀔 것 없음 = null · 틀린 주소 = false · 없는 시트 = false
|
||||
b = book();
|
||||
eq("이미 그 칸", linkCommand(b, "s1", "깔기|㎡", "b6"), null);
|
||||
eq("없는 키 풀기", linkCommand(b, "s1", "없음", null), null);
|
||||
eq("틀린 주소", linkCommand(b, "s1", "깔기|㎡", "가1"), false);
|
||||
eq("빈 주소", linkCommand(b, "s1", "깔기|㎡", ""), false);
|
||||
eq("없는 시트", linkCommand(b, "s9", "깔기|㎡", "A1"), false);
|
||||
eq("문서 그대로", links(b), links(book()));
|
||||
|
||||
console.log(JSON.stringify({ pass, fail: failures.length, failures }));
|
||||
@@ -0,0 +1,68 @@
|
||||
"""스프레드시트 손잡이 `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"] == 13
|
||||
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user