Merge remote-tracking branch 'origin/main_desktop_1' into main_laptop_1

This commit is contained in:
2026-10-08 16:46:33 +09:00
14 changed files with 563 additions and 43 deletions
@@ -229,4 +229,22 @@ section("names", () => {
eq("통째 지움 → 이름 빠짐", [b.시트[0].이름정의.length, v("B1")], [0, "#NAME?"]);
});
// ── 5. 104-2 — 행 · 열 넣기 · 지우기 때 조건부서식 · 유효성 범위도 같이(기본 번들 안 — remap 을 그대로 옮겨 적음) ──
section("condfmt_validation_follow_rowcol", () => {
const { C } = require("./helper_spreadsheet_engine.cjs");
const b = book({ A1: 1, B1: 2 });
b.시트[0].조건부서식 = [
{ id: "cf1", 범위: [A.parseRange("A1:A1")], 조건: { 종류: "글포함", 글: "x" }, 스타일: {} },
];
b.시트[0].유효성 = [
{ id: "v1", 범위: [A.parseRange("B1:B1")], 규칙: { 종류: "정수" }, 어기면: "막음", 메시지: "" },
];
C.applyCommand(b, { 종류: "행넣기", 시트: "s1", at: 0, 수: 1 });
eq("조건부서식 — 행 넣은 뒤 범위", A.rangeToA1(b.시트[0].조건부서식[0].범위[0]), "A2:A2");
eq("유효성 — 행 넣은 뒤 범위", A.rangeToA1(b.시트[0].유효성[0].범위[0]), "B2:B2");
C.applyCommand(b, { 종류: "열지우기", 시트: "s1", at: 0, 수: 1 }); // A 열 지움
eq("조건부서식 — 범위를 다 잃으면 규칙째 빠짐", b.시트[0].조건부서식.length, 0);
eq("유효성 — B 열이 A 로 당겨짐", A.rangeToA1(b.시트[0].유효성[0].범위[0]), "A2:A2");
});
process.stdout.write(JSON.stringify(results));
@@ -61,5 +61,40 @@ import { a1, assertEqual, makeBook, makeCtx, makeSheet, v } from "./test_stage2_
assertEqual(book.시트[0].칸[a1(2, 0)], undefined, "위로 당김 · 마지막 자리 빔");
}
// 104-2 — 밀기 · 당기기에도 알약 연결(`연결`)이 따라감.
// A1=1 B1=2 C1=3 · B1 에 알약 k1 · B1:B1 자리에 오른쪽으로 밀기 → 알약도 C1 로.
{
const book = makeBook([
makeSheet(
"s1",
{ [a1(0, 0)]: v(1), [a1(0, 1)]: v(2), [a1(0, 2)]: v(3) },
{ 연결: [{ 키: "k1", 칸: a1(0, 1) }] },
),
]);
const ctx = makeCtx(book, "s1");
insertShiftRight(ctx, { r0: 0, c0: 1, r1: 0, c1: 1 });
assertEqual(book.시트[0].연결, [{ 키: "k1", 칸: a1(0, 2) }], "오른쪽 밀기 · 알약 연결도 C1 로");
}
// A1=1 B1=2 C1=3 · A1 · B1 에 알약 둘 · A1:A1 삭제(왼쪽 당김) → A1 알약은 지운 자리라 빠짐 · B1 알약은
// A1 로 당겨짐.
{
const book = makeBook([
makeSheet(
"s1",
{ [a1(0, 0)]: v(1), [a1(0, 1)]: v(2), [a1(0, 2)]: v(3) },
{
연결: [
{ 키: "k1", 칸: a1(0, 0) },
{ 키: "k2", 칸: a1(0, 1) },
],
},
),
]);
const ctx = makeCtx(book, "s1");
deleteShiftLeft(ctx, { r0: 0, c0: 0, r1: 0, c1: 0 });
assertEqual(book.시트[0].연결, [{ 키: "k2", 칸: a1(0, 0) }], "왼쪽 당김 · 지운 자리 알약은 빠짐 · 나머지는 당겨짐");
}
if (process.exitCode) process.exit(process.exitCode);
console.log("spreadsheet_insert_dialog 시험 끝");
+85
View File
@@ -182,6 +182,91 @@ def test_면적_저장_뒤_도면_표_글_바뀜_자리_크기_그대로_표_없
assert t1_rec["값"]["s1!B2"] == "20.00" and t2_rec["값"] == {"s1!B1": "10.00"} # 표 없어도 기록
NAMED_SHEET = {**SHEET, "이름정의": [{"이름": "도면_T1", "범위": {"r0": 0, "c0": 1, "r1": 1, "c1": 2}}]}
NAMED_BOOK = {**BOOK, "시트": [NAMED_SHEET]}
def test_이름으로_범위를_풂_104_1():
got = sheet_node.area_tables([{"book": NAMED_BOOK, "시트": "s1", "범위": "", "이름": "도면_T1"}])[0]
assert got["ok"] and got["범위"] == "B1:C2" # 이름정의가 가리키는 지금 범위로(저장된 범위는 안 봄)
def test_이름이_지워졌으면_이름없음_104_1():
got = sheet_node.area_tables([{"book": BOOK, "시트": "s1", "범위": "B1:C2", "이름": "도면_없음"}])[0]
assert not got["ok"] and got["까닭"] == area_table.NAME_GONE
def test_define_names_있으면_갱신_없으면_더함_104_1():
added = sheet_node.define_names(
[{"book": BOOK, "시트": "s1", "범위": "B1:C2", "이름": "도면_T9"}]
)[0]
assert added["ok"]
names = added["book"]["시트"][0]["이름정의"]
assert {"이름": "도면_T9", "범위": {"r0": 0, "c0": 1, "r1": 1, "c1": 2}} in names
again = sheet_node.define_names(
[{"book": added["book"], "시트": "s1", "범위": "A1:A1", "이름": "도면_T9"}]
)[0]
kept = [n for n in again["book"]["시트"][0]["이름정의"] if n["이름"] == "도면_T9"]
assert kept == [{"이름": "도면_T9", "범위": {"r0": 0, "c0": 0, "r1": 0, "c1": 0}}] # 갱신(둘 아님)
def _named_link_drawing(folder: Path) -> None:
"""연결표 T1 에 `이름`(104-1) · 이름정의가 가리키는 지금 범위는 B1:C2(저장된 범위는 A1:B2 — 낡음)."""
with store.at(folder):
got = store.read("structure", "rv")
doc = got["문서"]
doc["연결표"] = [{"연결": "T1", "시트": "s1", "범위": "A1:B2", "이름": "도면_T1", "값": {}}]
store.write("structure", "rv", got["판"], doc)
def test_저장_때_이름이_가리키는_범위로_갱신_104_1(world):
system = world["system"]
_named_link_drawing(system)
with store.at(system):
got = store.read("area", "rv")
doc = got["문서"]
doc["시트"][0]["이름정의"] = [{"이름": "도면_T1", "범위": {"r0": 0, "c0": 1, "r1": 1, "c1": 2}}]
store.write("area", "rv", got["판"], doc)
drawing = store.read("structure", "rv")["문서"]
assert drawing["연결표"][0]["범위"] == "B1:C2" # 옛(A1:B2) 아님 — 이름이 가리키는 지금 범위
def test_이름_통째_지워지면_연결_끊고_지운연결_104_1(world):
system = world["system"]
_named_link_drawing(system) # 이름정의 없는 책 — 이름이 없는 것과 같음
with store.at(system):
got = store.read("area", "rv")
store.write("area", "rv", got["판"], got["문서"]) # 면적 저장 → refresh_drawing
drawing = store.read("structure", "rv")["문서"]
assert drawing["연결표"] == [] # 연결 끊김(도면 표 엔티티는 안 지움)
assert drawing["지운연결"] == ["T1"]
def test_이름_정의_엔드포인트_104_1(world):
world["session"]["role"] = "SYSTEM_ADMIN" # system 층 쓰기 권한
client = world["client"]
res = client.post(
"/api/m02/layers/system/area/rv/name",
json={"시트": "s1", "범위": "B1:C2", "이름": "도면_T5"},
)
assert res.status_code == 200, res.text
with store.at(world["system"]):
names = store.read("area", "rv")["문서"]["시트"][0]["이름정의"]
assert {"이름": "도면_T5", "범위": {"r0": 0, "c0": 1, "r1": 1, "c1": 2}} in names
def test_보내기_취소_엔드포인트_104_3(world):
world["session"]["role"] = "SYSTEM_ADMIN" # system 층 쓰기 권한
system = world["system"]
_named_link_drawing(system)
client = world["client"]
res = client.post("/api/m02/layers/system/structures/rv/unlink", json={"연결": "T1"})
assert res.status_code == 200, res.text
with store.at(system):
doc = store.read("structure", "rv")["문서"]
assert doc["연결표"] == [] # 도면 엔티티는 이 길에서 안 건드림(지우기는 도면에서)
def test_프로젝트_층_길도_같은_맞춤(world, tmp_path):
project = tmp_path / "project"
import shutil
+9 -1
View File
@@ -172,7 +172,15 @@ def test_저장_연결표는_남은_표만(got):
now = {"s1!B2": "1.5", "s1!C2": "2"}
assert got["saved"] == [
{"연결": "T1", "장": "", "시트": "s1", "범위": "A1:A1", "글자크기": 3, "값": now},
{"연결": "T2", "장": "", "시트": "s1", "범위": "B2:C2", "글자크기": 2.5, "값": now},
{
"연결": "T2",
"장": "",
"시트": "s1",
"범위": "B2:C2",
"이름": "도면_T2", # 104-1 — 새로 놓은 표만 이름정의(옛 T1 기록은 그대로 범위만)
"글자크기": 2.5,
"값": now,
},
]
assert got["plain"] is False