feat(spreadsheet): 단위 묶음에 지름 Φ 추가(81-5)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MPcuokNm1ZR2kqgGGDfaJA
This commit is contained in:
@@ -134,6 +134,7 @@ const TEXT = {
|
||||
UnitGroup_op: ["연산·비교", "Operators"],
|
||||
UnitRecent: ["최근 씀", "Recently used"],
|
||||
UnitAsFormat: ["숫자 형식으로 붙이기", "Attach as number format"],
|
||||
UnitPhiTitle: ["지름(Φ) — 숫자 앞에 붙음", "Diameter (Φ) — attached before the number"],
|
||||
FormulaHint_label: [
|
||||
"엑셀 수식까지: 엑셀에서 Ctrl+` → 복사 → 여기 Ctrl+V · 끝나면 엑셀 Ctrl+` 다시",
|
||||
"Formulas too: in Excel Ctrl+` → copy → paste here (Ctrl+V) · then Ctrl+` again in Excel",
|
||||
|
||||
@@ -62,8 +62,11 @@ const BORDER_LINES: BorderLine[] = [
|
||||
|
||||
type TextKey = Parameters<typeof st>[0];
|
||||
|
||||
const UNIT_GROUPS: [label: TextKey, items: string[]][] = [
|
||||
["UnitGroup_area", ["㎡", "㎥", "m", "㎝", "㎜", "㎞"]],
|
||||
/** 지름 기호 — 숫자 앞에 붙임(81-5 · 자재 규격에서 압도적 · Φ300). */
|
||||
export const PHI_UNIT = "Φ";
|
||||
|
||||
export const UNIT_GROUPS: [label: TextKey, items: string[]][] = [
|
||||
["UnitGroup_area", ["㎡", "㎥", "m", "㎝", "㎜", "㎞", PHI_UNIT]],
|
||||
["UnitGroup_weight", ["kg", "t", "ℓ", "㎖"]],
|
||||
["UnitGroup_count", ["개", "본", "식", "EA", "매"]],
|
||||
["UnitGroup_rate", ["m/s", "°", "%", "‰"]],
|
||||
@@ -92,11 +95,17 @@ function pushRecentUnit(unit: string): string[] {
|
||||
}
|
||||
|
||||
/** 숫자 형식 코드 끝 따옴표 글자(전에 붙인 단위)를 떼고 새 단위를 붙임 — 없으면 "0" 에서 시작. */
|
||||
function attachUnitToFormat(code: string, unit: string): string {
|
||||
export function attachUnitToFormat(code: string, unit: string): string {
|
||||
const base = (code || "0").replace(/"[^"]*"\s*$/, "");
|
||||
return base + `"${unit}"`;
|
||||
}
|
||||
|
||||
/** Φ(지름 기호) 는 숫자 앞 — 서식 코드 머리의 글자를 바꿔 끼움(81-5). */
|
||||
export function prependUnitToFormat(code: string, unit: string): string {
|
||||
const base = (code || "0").replace(/^\s*"[^"]*"/, "");
|
||||
return `"${unit}"` + base;
|
||||
}
|
||||
|
||||
/** 활성 칸에 지금 걸린 서식 — 칸 자기 것 없으면 열 · 행 · 시트 기본(`Sheet.기본.서식`) · 그다음 0. */
|
||||
function styleAt(ctx: SpreadsheetContext, r: number, c: number): CellStyle {
|
||||
const sheet = ctx.sheet();
|
||||
@@ -407,12 +416,22 @@ export function mountToolbar(ctx: SpreadsheetContext): PartHandle {
|
||||
}
|
||||
} else if (unitAsFormat.checked) {
|
||||
const { r, c } = ctx.selection.활성;
|
||||
patch({ 형식: attachUnitToFormat(styleAt(ctx, r, c).형식 ?? "", unit) });
|
||||
const 형식 = styleAt(ctx, r, c).형식 ?? "";
|
||||
patch({
|
||||
형식: unit === PHI_UNIT ? prependUnitToFormat(형식, unit) : attachUnitToFormat(형식, unit),
|
||||
});
|
||||
} else {
|
||||
const { r, c } = ctx.selection.활성;
|
||||
const sheet = ctx.sheet();
|
||||
const key = toA1(r, c);
|
||||
const text = cellToText(sheet.칸[key]) + unit;
|
||||
const before = cellToText(sheet.칸[key]);
|
||||
// Φ 는 글 앞 — 강제 텍스트 홑낫표(')가 있으면 그 뒤에(숫자 앞) 끼워 넣음(81-5)
|
||||
const text =
|
||||
unit === PHI_UNIT
|
||||
? before[0] === "'"
|
||||
? "'" + unit + before.slice(1)
|
||||
: unit + before
|
||||
: before + unit;
|
||||
ctx.dispatch({ 종류: "칸", 시트: sheet.id, 칸: { [key]: textToCell(text, sheet.칸[key]) } });
|
||||
}
|
||||
renderRecent(pushRecentUnit(unit));
|
||||
@@ -436,6 +455,7 @@ export function mountToolbar(ctx: SpreadsheetContext): PartHandle {
|
||||
for (const u of items) {
|
||||
const b = createButton({ label: u, variant: "ghost", onClick: () => insertUnit(u) });
|
||||
b.className += " ss-toolbar__unit-btn";
|
||||
if (u === PHI_UNIT) b.title = st("UnitPhiTitle");
|
||||
grid.append(b);
|
||||
}
|
||||
unitPop.append(
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/* 단위 Φ(81-5) 순수 길 시험 진입 — vite --ssr 로 번들해 Node 로 돌림.
|
||||
* 결과 = JSON {pass, fail, failures} 한 줄(`test_spreadsheet_toolbar_unit.py` 가 읽음) */
|
||||
import {
|
||||
attachUnitToFormat,
|
||||
PHI_UNIT,
|
||||
prependUnitToFormat,
|
||||
UNIT_GROUPS,
|
||||
} 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)}`);
|
||||
};
|
||||
|
||||
// Φ 는 길이 묶음(UnitGroup_area) 끝 — 다른 묶음엔 안 들어감
|
||||
const lengthGroup = UNIT_GROUPS.find(([label]) => label === "UnitGroup_area")!;
|
||||
eq("Φ = 길이 묶음 끝", lengthGroup[1][lengthGroup[1].length - 1], PHI_UNIT);
|
||||
eq(
|
||||
"Φ 는 다른 묶음에 없음",
|
||||
UNIT_GROUPS.filter(([label]) => label !== "UnitGroup_area").some((g) => g[1].includes(PHI_UNIT)),
|
||||
false,
|
||||
);
|
||||
|
||||
// 보통 단위(뒤에 붙음) — 지금처럼
|
||||
eq("보통 단위 서식 — 뒤", attachUnitToFormat("0", "㎡"), '0"㎡"');
|
||||
eq("보통 단위 서식 — 옛 단위 바꿈", attachUnitToFormat('0"㎡"', "㎝"), '0"㎝"');
|
||||
|
||||
// Φ(지름) — 숫자 앞
|
||||
eq("Φ 서식 — 앞", prependUnitToFormat("0", PHI_UNIT), '"Φ"0');
|
||||
eq("Φ 서식 — 빈 코드도 0 기준", prependUnitToFormat("", PHI_UNIT), '"Φ"0');
|
||||
eq("Φ 서식 — 옛 Φ 바꿈(겹쳐 안 붙음)", prependUnitToFormat('"Φ"0', PHI_UNIT), '"Φ"0');
|
||||
eq("Φ 서식 — 뒤 단위와 공존", prependUnitToFormat('0"㎡"', PHI_UNIT), '"Φ"0"㎡"');
|
||||
|
||||
console.log(JSON.stringify({ pass, fail: failures.length, failures }));
|
||||
@@ -0,0 +1,57 @@
|
||||
"""단위 Φ(81-5) — `spreadsheet_toolbar.ts` UNIT_GROUPS 길이 묶음 끝에 Φ · 숫자 앞에 붙임.
|
||||
|
||||
순수 길(`attachUnitToFormat` · `prependUnitToFormat` · `UNIT_GROUPS`)을 vite `--ssr` 로
|
||||
번들해 Node 로 돌림. 화면(단위 ▾ 팝오버 · 누르면 붙음)은 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"] == 8
|
||||
Reference in New Issue
Block a user