Files
Aislo/resources/tester/test_m02_area_old_links.py
T

288 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""114 — 이름 없는 옛 보낸 범위 연결이 행 넣기를 따라감(화면 · 서버 한 바퀴).
- 114-1 면적 상세를 열 때 화면(`sentRanges` + 책)이 `도면_{연결}` 이름정의를 둠 → 행 넣기 → 면적 [저장]
→ 서버 `refresh_drawing` 이 그 이름을 연결표 줄에 적고 도면 표 칸 글 = 옮겨진 칸.
- 114-2 셀 밀기 · 당기기(칸 삽입 · 삭제 창)에 이름 따라감 · 카드 도면 저장도 표 없는 연결표 줄 뺌(`fitLinks`).
- 114-3 아래 막대 도면 알약(`spreadsheet_link_range.rangePills`) · 상태(표 있음/없음 · 값 맞음/다름 · 범위 없음)
· 누르면 범위 고름 · × = 보내기 취소 · 상태는 150ms 모아 한 번.
TS 는 Node 로(`AreaToDrawing` 은 그 자리 변환 · 스프레드시트 모듈은 `helper_spreadsheet_d_bundle.cjs`).
"""
from __future__ import annotations
import json
import shutil
import subprocess
from pathlib import Path
import pytest
from common_util import common_util_spreadsheet as sheet_node
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from resources.tester.test_m02_area_table import world # noqa: F401 — 같은 임시 층
ROOT = Path(__file__).resolve().parents[2]
SCRIPT = r"""
const fs = require("fs");
const path = require("path");
const ROOT = process.argv[1];
const { load, SS } = require(path.join(ROOT, "resources/tester/spreadsheet/helper_spreadsheet_d_bundle.cjs"));
const ts = require(path.join(ROOT, "config/node_modules/typescript"));
const input = JSON.parse(fs.readFileSync(0, "utf8"));
const ss = (name) => load(path.join(SS, name + ".ts"));
let drawingNow = input.drawing;
const tsLoad = (file, stubs) => {
const src = fs.readFileSync(path.join(ROOT, file), "utf8");
const js = ts.transpileModule(src, {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
new Function("exports", "module", "require", js)(box.exports, box, (n) => {
if (!(n in stubs)) throw new Error("모르는 가져오기 " + n);
return stubs[n];
});
return box.exports;
};
const toDrawing = tsLoad("M02_MasterTemplete/M02_MasterTemplete_AreaToDrawing.ts", {
"@config/config_frontend": { API_BASE_URL: "/api" },
"@ui/ui_template_elements": {},
"@ui/ui_template_modal": {},
"./M02_MasterTemplete_Api_Fetch": {
getActiveOwner: () => null,
readTemplate: async () => ({ 문서: drawingNow }),
},
"../A00_Common/spreadsheet/spreadsheet_address": ss("spreadsheet_address"),
"../A00_Common/spreadsheet/spreadsheet_area_table": ss("spreadsheet_area_table"),
});
const { applyCommand } = ss("spreadsheet_commands");
(async () => {
const book = input.book;
const area = { layer: "system", projectId: null, kind: "area", name: "rv", label: "x" };
const before = JSON.stringify(book);
const noBook = await toDrawing.sentRanges(area, () => {});
const unchanged = JSON.stringify(book) === before;
const marks = await toDrawing.sentRanges(area, () => {}, book);
const opened = {
marks: marks.map((m) => [m.이름, m.범위이름 ?? null]),
noBook: noBook.map((m) => m.범위이름 ?? null),
unchanged,
names: book.시트[0].이름정의,
};
applyCommand(book, { 종류: "행넣기", 시트: "s1", at: 0, 수: 1 }); // 1행 위에 한 행
// 114-2 — 셀 밀기 · 당기기(칸 삽입 · 삭제 창)에 이름 따라감 · 되돌리기 한 걸음
const dlg = ss("spreadsheet_insert_dialog");
const E7J17 = { r0: 6, c0: 4, r1: 16, c1: 9 };
const shift = (fn, range) => {
const b = { 종류: "통합문서", 판: 1, 열: "t", 서식: [{}], 시트: [
{ id: "s1", 이름: "s1", 칸: { E7: { 값: 1 } }, 이름정의: [{ 이름: "도면_T1", 범위: E7J17 }] },
] };
const sent = [];
const ctx = { book: b, readOnly: false, selection: { 시트: "s1" }, sheet: () => b.시트[0],
dispatch: (c) => (sent.push(c.종류 === "묶음" ? c.명령.map((x) => x.종류).join("+") : c.종류), applyCommand(b, c)) };
dlg[fn](ctx, range);
const n = b.시트[0].이름정의.map((x) => x.범위);
return { names: n, sent, cell: Object.keys(b.시트[0].칸) };
};
const shifted = {
downInside: shift("insertShiftDown", { r0: 9, c0: 4, r1: 9, c1: 9 }),
downAbove: shift("insertShiftDown", { r0: 4, c0: 4, r1: 4, c1: 9 }),
downPartial: shift("insertShiftDown", { r0: 4, c0: 4, r1: 4, c1: 5 }),
upAll: shift("deleteShiftUp", E7J17),
right: shift("insertShiftRight", { r0: 6, c0: 3, r1: 16, c1: 3 }),
left: shift("deleteShiftLeft", { r0: 6, c0: 0, r1: 16, c1: 1 }),
};
// 114-2 — 카드 도면 저장도 표 없는 연결표 줄을 뺌(도면 상세 저장과 같은 몸)
const tbl = (id) => ({ type: "Table", shapeData: { cells: [[{ text: "x" }]], style: { 연결: id } } });
const row = (id) => ({ 연결: id, 시트: "s1", 범위: "A1", 값: {} });
const fit = {
kept: toDrawing.fitLinks({ 도면: { entities: [tbl("T1")] }, 연결표: [row("T1"), row("T2")] }).연결표,
plain: "연결표" in toDrawing.fitLinks({ 도면: { entities: [tbl("T9")] } }),
};
// 114-3 — 아래 막대 도면 알약 · 상태(표 있음/없음 · 값 맞음/다름 · 범위 없음)
const states = {};
const pills = {};
if (input.drawing3) {
drawingNow = input.drawing3;
const book3 = input.book3;
const marks3 = await toDrawing.sentRanges(area, () => {}, book3);
const st = await Promise.all(marks3.map((m) => m.상태(book3)));
states.first = st.map((s) => [s.글, s.종류]);
book3.시트[0].칸.B1 = { 값: 99, 서식: 1 }; // 저장 안 한 고침
states.edited = [(await marks3[0].상태(book3)).글];
const mk = (tag, o = {}) => ({ tag, className: o.className ?? "", text: o.text ?? "",
attrs: o.attrs ?? {}, children: [...(o.children ?? [])], title: "", on: {},
style: { props: {}, setProperty(k, v) { this.props[k] = v; } },
addEventListener(k, f) { this.on[k] = f; }, append(...x) { this.children.push(...x); } });
const lr = tsLoad("A00_Common/spreadsheet/spreadsheet_link_range.ts", {
"@ui/ui_template_elements": { el: mk, createTag: (l, v) => mk("span", { className: `ui-tag ui-tag--${v}`, text: l }) },
"./spreadsheet_address": ss("spreadsheet_address"),
"./spreadsheet_link_color": ss("spreadsheet_link_color"),
"./spreadsheet_names": ss("spreadsheet_names"),
"./spreadsheet_selection": ss("spreadsheet_selection"),
});
const got = new Map(marks3.map((m, i) => [m, st[i]]));
const picked = [];
const cancelled = [];
let backs = 0;
const ctx = { book: book3, readOnly: false, selection: { 시트: "s1" }, sheet: () => book3.시트[0],
showSheet: () => {}, select: (s) => picked.push(s) };
const made = lr.rangePills(ctx, marks3, (m) => got.get(m), () => (backs += 1),
(m) => cancelled.push(m.이름), { pick: "누르면 고름", cancel: "보내기 취소" });
made[0].on.click();
made[1].children.find((c) => c.className === "ss-link__x").on.click({ stopPropagation() {} });
const ro = lr.rangePills({ ...ctx, readOnly: true }, marks3, () => undefined, () => {}, () => {}, { pick: "", cancel: "" });
pills.made = made.map((p) => ({
text: p.children[0].text,
tag: p.children[1] && p.children[1].className.startsWith("ui-tag") ? [p.children[1].className, p.children[1].text] : null,
x: p.children.some((c) => c.className === "ss-link__x"),
cls: p.className,
hue: typeof p.style.props["--ss-link-h"],
}));
pills.picked = picked.map((s) => s.범위[0]);
pills.backs = backs;
pills.cancelled = cancelled;
pills.roX = ro.map((p) => p.children.some((c) => c.className === "ss-link__x"));
// 상태 모음 — 잇단 schedule 은 150ms 뒤 한 번만
let calls = 0;
let done = 0;
const fake = [{ 상태: () => (calls += 1, { 글: "x", 종류: "success" }) }];
const rs = lr.createRangeStates(() => fake, () => book3, () => (done += 1));
rs.schedule(); rs.schedule(); rs.schedule();
const early = calls;
await new Promise((r) => setTimeout(r, 250));
pills.gather = { early, calls, done, got: rs.get(fake[0]) };
}
console.log(JSON.stringify({ opened, book, shifted, fit, states, pills }));
})().catch((e) => {
console.error(e);
process.exit(1);
});
"""
T1_TABLE = {
"type": "Table",
"shapeData": {
"origin": {"x": 0, "y": 0},
"columnWidths": [9, 9],
"rowHeights": [3, 3],
"cells": [[{"text": "낡음"}, {"text": "낡음"}], [None, {"text": "낡음"}]],
"style": {"fontSize": 2.5, "연결": "T1"},
},
}
OLD_ROW = {"연결": "T1", "시트": "s1", "범위": "A1:B2", "글자크기": 2.5, "값": {}}
def _node(payload: dict) -> dict:
if shutil.which("node") is None:
pytest.skip("node 가 없음")
proc = subprocess.run(
["node", "-e", SCRIPT, str(ROOT)],
input=json.dumps(payload, ensure_ascii=False),
capture_output=True,
text=True,
encoding="utf-8",
timeout=120,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout.strip().splitlines()[-1])
def _texts(cells: list) -> list:
return [[c and c.get("text") for c in row] for row in cells]
def test_옛_연결_열기_행_넣기_저장_도면_표_값_따라감_114_1(world): # noqa: F811
system = world["system"]
with store.at(system):
got = store.read("structure", "rv")
drawing = {**got["문서"], "연결표": [OLD_ROW]}
drawing["도면"] = {**drawing["도면"], "entities": [T1_TABLE]}
store.write("structure", "rv", got["판"], drawing)
area = store.read("area", "rv")
want = sheet_node.area_tables([{"book": area["문서"], "시트": "s1", "범위": "A1:B2"}])[0]
out = _node({"book": area["문서"], "drawing": drawing})
opened = out["opened"]
assert opened["noBook"] == [None] and opened["unchanged"] # 책 없으면 안 고침(옛 길)
assert opened["marks"] == [["T1 도면", "도면_T1"]] # 그 이름으로 점선
assert opened["names"] == [{"이름": "도면_T1", "범위": {"r0": 0, "c0": 0, "r1": 1, "c1": 1}}]
book = out["book"]
assert book["시트"][0]["이름정의"][0]["범위"] == {
"r0": 1,
"c0": 0,
"r1": 2,
"c1": 1,
} # 행 넣기 따라감
with store.at(system):
store.write("area", "rv", area["판"], book) # 면적 [저장] → refresh_drawing
doc = store.read("structure", "rv")["문서"]
row = doc["연결표"][0]
assert (row["이름"], row["범위"]) == ("도면_T1", "A2:B3")
cells = doc["도면"]["entities"][0]["shapeData"]["cells"]
assert _texts(cells) == _texts(want["표"]["shapeData"]["cells"]) # 옮겨진 칸 = 보낸 그 칸
assert _texts(cells)[0] == ["제목", "1,234.50"]
def test_셀_밀기_당기기에_이름_따라감_띠에_걸치기만_하면_그대로_114_2(world): # noqa: F811
with store.at(world["system"]):
area = store.read("area", "rv")
s = _node({"book": area["문서"], "drawing": {"연결표": []}})["shifted"]
box = lambda r0, c0, r1, c1: [{"r0": r0, "c0": c0, "r1": r1, "c1": c1}] # noqa: E731
assert s["downInside"]["names"] == box(6, 4, 17, 9) # 범위 안에 밀어 넣음 = 늘어남 E7:J18
assert s["downAbove"]["names"] == box(7, 4, 17, 9) # 위에서 밀림 = 통째로 E8:J18
assert s["downPartial"]["names"] == box(6, 4, 16, 9) # 띠(E:F)에 걸치기만 = 그대로
assert s["upAll"]["names"] == [] # 다 지운 자리 = 이름 빠짐(저장 때 연결 끊김)
assert s["right"]["names"] == box(6, 5, 16, 10) # F7:K17
assert s["left"]["names"] == box(6, 2, 16, 7) # C7:H17
assert s["downInside"]["sent"] == ["시트통째+칸"] # 한 묶음 = 되돌리기 한 걸음
assert s["downPartial"]["sent"] == ["칸"] # 안 바뀌면 시트통째 안 끼움
assert s["downAbove"]["cell"] == ["E8"] # 값도 함께 밀림
def test_카드_도면_저장도_표_없는_연결표_줄_뺌_114_2(world): # noqa: F811
with store.at(world["system"]):
area = store.read("area", "rv")
fit = _node({"book": area["문서"], "drawing": {"연결표": []}})["fit"]
assert fit["kept"] == [{"연결": "T1", "시트": "s1", "범위": "A1", "값": {}, "장": ""}]
assert fit["plain"] is False # 기록 없던 도면은 칸을 안 만듦
def test_아래_막대_도면_알약_상태_누르면_고름_x_는_보내기_취소_114_3(world): # noqa: F811
with store.at(world["system"]):
area = store.read("area", "rv")
book = area["문서"]
now = sheet_node.area_tables([{"book": book, "시트": "s1", "범위": "A1:B2"}])[0]["값"]
drawing3 = {
"도면": {"entities": [T1_TABLE, {**T1_TABLE, "shapeData": {"style": {"연결": "T3"}}}]},
"연결표": [
{**OLD_ROW, "값": now}, # 이름 없는 옛 연결 · 표 있음 · 값 같음
{"연결": "T2", "시트": "s1", "범위": "B1", "값": {}}, # 도면에 표 없음
{
"연결": "T3",
"시트": "s1",
"범위": "A1",
"이름": "도면_없음",
"값": {},
}, # 이름 지워짐
],
}
out = _node({"book": book, "drawing": {"연결표": []}, "drawing3": drawing3, "book3": book})
assert out["states"]["first"] == [
["✓", "success"], # 값 맞음 · 다름 = 기호(128-2)
["도면에 표 없음", "danger"],
["범위 없음", "danger"],
]
assert out["states"]["edited"] == ["✗"] # 저장 전 칸 고침 = 지금 책으로 판정
p = out["pills"]
assert [x["text"] for x in p["made"]] == ["T1 도면 A1:B2", "T2 도면 B1", "T3 도면"]
assert p["made"][0]["tag"] == ["ui-tag ui-tag--success", "✓"] # 공용 꼬리표
assert p["made"][1]["tag"] == ["ui-tag ui-tag--danger", "도면에 표 없음"]
assert all(x["x"] and x["cls"] == "ss-link ss-link--range" for x in p["made"])
assert all(x["hue"] == "string" for x in p["made"]) # 색 = 시트 위 테두리 색
assert (
p["picked"] == [{"r0": 0, "c0": 0, "r1": 1, "c1": 1}] and p["backs"] == 1
) # 누르면 범위 고름
assert p["cancelled"] == ["T2 도면"] # × = 보내기 취소(화면에서만 · 저장 때 기록)
assert p["roX"] == [False, False, False] # 읽기 = × 없음
assert p["gather"] == {"early": 0, "calls": 1, "done": 1, "got": {"글": "x", "종류": "success"}}