diff --git a/A00_Common/spreadsheet/spreadsheet_toolbar.ts b/A00_Common/spreadsheet/spreadsheet_toolbar.ts index 9a934bf69..3f4ad3eda 100644 --- a/A00_Common/spreadsheet/spreadsheet_toolbar.ts +++ b/A00_Common/spreadsheet/spreadsheet_toolbar.ts @@ -62,7 +62,7 @@ const BORDER_LINES: BorderLine[] = [ type TextKey = Parameters[0]; -const UNIT_GROUPS: [label: TextKey, items: string[]][] = [ +export const UNIT_GROUPS: [label: TextKey, items: string[]][] = [ ["UnitGroup_area", ["㎡", "㎥", "m", "㎝", "㎜", "㎞", "Φ"]], ["UnitGroup_weight", ["kg", "t", "ℓ", "㎖"]], ["UnitGroup_count", ["개", "본", "식", "EA", "매"]], @@ -70,7 +70,7 @@ const UNIT_GROUPS: [label: TextKey, items: string[]][] = [ ["UnitGroup_op", ["×", "÷", "±", "≤", "≥", "Σ"]], ]; /** 숫자 앞에 붙는 단위(관 지름 Φ300) — 나머지는 뒤에 붙음 */ -const UNIT_PREFIX = ["Φ"]; +export const UNIT_PREFIX = ["Φ"]; const UNIT_RECENT_MAX = 8; const UNIT_RECENT_KEY = "aislo-ss-recent-units"; @@ -95,7 +95,7 @@ function pushRecentUnit(unit: string): string[] { /** 숫자 형식 코드 끝 따옴표 글자(전에 붙인 단위)를 떼고 새 단위를 붙임 — 없으면 "0" 에서 시작. * 앞 단위(Φ)는 코드 앞에 붙임(`"Φ"0`) — 앞에 붙은 앞 단위는 먼저 뗌. */ -function attachUnitToFormat(code: string, unit: string): string { +export function attachUnitToFormat(code: string, unit: string): string { const base = code || "0"; if (!UNIT_PREFIX.includes(unit)) return base.replace(/"[^"]*"\s*$/, "") + `"${unit}"`; let plain = base; diff --git a/resources/tester/spreadsheet/spreadsheet_link_set_entry.ts b/resources/tester/spreadsheet/spreadsheet_link_set_entry.ts index 8a74f2d55..277bb2143 100644 --- a/resources/tester/spreadsheet/spreadsheet_link_set_entry.ts +++ b/resources/tester/spreadsheet/spreadsheet_link_set_entry.ts @@ -174,4 +174,19 @@ eq( linkHues(["철근|ton"]).get("철근|ton"), ); +// clearLinksCommand(81-4, 메인 판 함수에 맞춤) — 시트 하나뿐인 문서 · 거듭 불러도 안전 +const singleSheetBook = (): Workbook => + ({ + 종류: "통합문서", + 판: 1, + 열: "t", + 서식: [{}], + 시트: [{ id: "s1", 이름: "하나", 칸: {}, 연결: [{ 키: "k|m", 칸: "A1", 이름: "k" }] }], + 활성: "s1", + }) as unknown as Workbook; +b = singleSheetBook(); +run(b, clearLinksCommand(b)); +eq("한 시트 문서 — 모두 해제 뒤 연결 없음", links(b), [null]); +eq("거듭 불러도 안전 = null", clearLinksCommand(b), null); + console.log(JSON.stringify({ pass, fail: failures.length, failures })); diff --git a/resources/tester/spreadsheet/spreadsheet_toolbar_unit_entry.ts b/resources/tester/spreadsheet/spreadsheet_toolbar_unit_entry.ts new file mode 100644 index 000000000..ef70a469e --- /dev/null +++ b/resources/tester/spreadsheet/spreadsheet_toolbar_unit_entry.ts @@ -0,0 +1,39 @@ +/* 단위 Φ(81-5) 순수 길 시험 진입 — vite --ssr 로 번들해 Node 로 돌림(메인 판 attachUnitToFormat · + * UNIT_PREFIX · UNIT_GROUPS 에 맞춤 — 81-3·81-4·81-5 겹침 정리). + * 결과 = JSON {pass, fail, failures} 한 줄(`test_spreadsheet_toolbar_unit.py` 가 읽음) */ +import { + attachUnitToFormat, + UNIT_GROUPS, + UNIT_PREFIX, +} from "../../../A00_Common/spreadsheet/spreadsheet_toolbar"; + +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 PHI = "Φ"; + +// Φ 는 길이 묶음(UnitGroup_area) 끝 — 다른 묶음엔 안 들어감 · 앞 단위 목록에 있음 +const lengthGroup = UNIT_GROUPS.find(([label]) => label === "UnitGroup_area")!; +eq("Φ = 길이 묶음 끝", lengthGroup[1][lengthGroup[1].length - 1], PHI); +eq( + "Φ 는 다른 묶음에 없음", + UNIT_GROUPS.filter(([label]) => label !== "UnitGroup_area").some((g) => g[1].includes(PHI)), + false, +); +eq("Φ 는 앞 단위 목록에 있음", UNIT_PREFIX.includes(PHI), true); + +// 보통 단위(뒤에 붙음) — 지금처럼 +eq("보통 단위 서식 — 뒤", attachUnitToFormat("0", "㎡"), '0"㎡"'); +eq("보통 단위 서식 — 옛 단위 바꿈", attachUnitToFormat('0"㎡"', "㎝"), '0"㎝"'); + +// Φ(지름) — 숫자 앞(메인 판 attachUnitToFormat 이 앞/뒤를 한 함수에서 갈라 처리) +eq("Φ 서식 — 앞", attachUnitToFormat("0", PHI), '"Φ"0'); +eq("Φ 서식 — 빈 코드도 0 기준", attachUnitToFormat("", PHI), '"Φ"0'); +eq("Φ 서식 — 옛 Φ 바꿈(겹쳐 안 붙음)", attachUnitToFormat('"Φ"0', PHI), '"Φ"0'); +eq("Φ 서식 — 뒤 단위와 공존", attachUnitToFormat('0"㎡"', PHI), '"Φ"0"㎡"'); + +console.log(JSON.stringify({ pass, fail: failures.length, failures })); diff --git a/resources/tester/test_spreadsheet_set_link.py b/resources/tester/test_spreadsheet_set_link.py index 45766837a..35cb688c2 100644 --- a/resources/tester/test_spreadsheet_set_link.py +++ b/resources/tester/test_spreadsheet_set_link.py @@ -55,7 +55,7 @@ def test_setLink_순수_길(): assert run.stdout.strip(), run.stderr result = json.loads(run.stdout) assert result["fail"] == 0, result["failures"] - assert result["pass"] == 35 + assert result["pass"] == 37 def test_손잡이_setLink_몸(): diff --git a/resources/tester/test_spreadsheet_toolbar_unit.py b/resources/tester/test_spreadsheet_toolbar_unit.py new file mode 100644 index 000000000..2df38cb7c --- /dev/null +++ b/resources/tester/test_spreadsheet_toolbar_unit.py @@ -0,0 +1,57 @@ +"""단위 Φ(81-5) — `spreadsheet_toolbar.ts` UNIT_GROUPS 길이 묶음 끝에 Φ · 숫자 앞에 붙임. + +순수 길(`attachUnitToFormat` · `UNIT_PREFIX` · `UNIT_GROUPS`)을 vite `--ssr` 로 +번들해 Node 로 돌림(81장 겹침 정리 — 메인 판 함수에 맞춤). 화면(단위 ▾ 팝오버 · 누르면 붙음)은 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_toolbar_unit_entry.ts" +VITE = ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js" + + +@pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음") +def test_단위_파이_순수_길(): + with tempfile.TemporaryDirectory(prefix="spreadsheet_toolbar_unit_") 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_toolbar_unit_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"] == 9 diff --git a/resources/tester/test_spreadsheet_unit_prefix.py b/resources/tester/test_spreadsheet_unit_prefix.py index ff96c3f63..9710e049d 100644 --- a/resources/tester/test_spreadsheet_unit_prefix.py +++ b/resources/tester/test_spreadsheet_unit_prefix.py @@ -25,13 +25,15 @@ def _text() -> str: @pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음") def test_형식_코드_앞_단위_뒤_단위(): src = _text() - prefix = re.search(r"^const UNIT_PREFIX = .*;$", src, re.M) - func = re.search(r"^function attachUnitToFormat[\s\S]*?^}", src, re.M) + prefix = re.search(r"^(?:export )?const UNIT_PREFIX = .*;$", src, re.M) + func = re.search(r"^(?:export )?function attachUnitToFormat[\s\S]*?^}", src, re.M) assert prefix and func script = ( - prefix.group(0) + prefix.group(0).replace("export ", "") + "\n" - + func.group(0).replace(": string", "") # 형 이름 뗌 — Node 가 그대로 돌게 + + func.group(0) + .replace("export ", "") + .replace(": string", "") # 형 이름 뗌 — Node 가 그대로 돌게 + """ const f = attachUnitToFormat; console.log(JSON.stringify([