- 페이지: 층 고름(resolve) · 층 맞춤(settleLayer) · 띠 · 나무 · 층 줄 갈아 끼움(build) 한 길 · 주소 replaceState · 새 층 구조물 표 - 편집 칸 권한 = 함수(UI_Main isAdmin) · 「새 항목」 = 고른 것 권한으로 가림 · 불러오기 장부 = 열 때 층 · 권한(Hopyo_Side) - 왼쪽 칸 펼침 · 단가산출 · 일위대가 층 그대로 - 낡은 시험(층 단추 해시 이동 · 사람 나무 · 도면 칸 층 묶음 · 새 항목 · 머리 고침 · 불러오기 권한 · 실무문서 차례 · home) 새 꼴로 — test_m02_practice 도면 차례 글은 이미 낡아 실패하던 것 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
164 lines
8.4 KiB
Python
164 lines
8.4 KiB
Python
"""일위대가 사전조합 층 따로(PLAN 134-3) — 페이지 층과 따로 · 명시 owner · 자유 폴더 나무(158-1).
|
|
|
|
- 서버 길: 명시 owner 를 넘기면 그 주인만(null = 자기 층) — 남의 개인을 보는 중에도 사전조합 길에 안 섞임 ·
|
|
안 넘기면 지금처럼 페이지 층 주인(Node · fetch 대역)
|
|
- 나무: 자유 폴더(158-1) — 적어 둔 폴더 차례 · 자리 없는 문서 = 뿌리 · 문서 수 = 아래 폴더까지
|
|
- 사전조합 층 단추 = 해시를 바꿔 페이지를 다시 세우지 않음(replaceState · 그 컨테이너만 다시 그림) ·
|
|
구조물 조합 층 단추는 그대로 해시 이동 · 주소 `pre` 에 남음 · 고름에 owner · canWrite
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
M02 = Path(HERE).parents[1] / "M02_MasterTemplete"
|
|
|
|
|
|
def _text(name: str) -> str:
|
|
return (M02 / name).read_text(encoding="utf-8")
|
|
|
|
|
|
def _run(rows, view=None):
|
|
proc = subprocess.run(
|
|
["node", os.path.join(HERE, "helper_m02_precombo_scope.cjs")],
|
|
input=json.dumps({"rows": rows, "view": view}, ensure_ascii=False),
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
check=True,
|
|
)
|
|
return json.loads(proc.stdout)
|
|
|
|
|
|
def test_명시_owner_길은_페이지_층_주인과_안_섞임():
|
|
urls = _run([])["urls"]
|
|
assert urls[0] == "/api/m02/layers/personal/templates/labor/x1?owner=7%2F99"
|
|
assert urls[1] == "/api/m02/layers/personal/templates/precombo/SC-000001"
|
|
assert urls[2] == "/api/m02/layers/company/templates?owner=8"
|
|
assert urls[3] == "/api/m02/layers/system/templates/precombo/SC-000002"
|
|
|
|
|
|
def test_나무는_자유_폴더_자리_없는_문서는_뿌리():
|
|
rows = [
|
|
{"이름": "SC-000001", "종류": "precombo", "구분": "C 사면안정", "상세구분": "옹벽"},
|
|
{"이름": "SC-000002", "종류": "precombo"},
|
|
{"이름": "SC-000003", "종류": "precombo"},
|
|
]
|
|
view = {
|
|
"폴더": [["옹벽"], ["옹벽", "찰쌓기"], ["빈 폴더"]],
|
|
"자리": {"SC-000001": ["옹벽", "찰쌓기"], "SC-000009": ["옹벽"]}, # 없는 문서 자리 = 무시
|
|
}
|
|
tree = _run(rows, view)["tree"]
|
|
assert tree["docs"] == ["SC-000002", "SC-000003"] and tree["count"] == 3 # 구분과 무관
|
|
wall, empty = tree["folders"]
|
|
assert (wall["path"], wall["count"], wall["docs"]) == (["옹벽"], 1, [])
|
|
assert wall["folders"][0] == {
|
|
"path": ["옹벽", "찰쌓기"],
|
|
"count": 1,
|
|
"docs": ["SC-000001"],
|
|
"folders": [],
|
|
}
|
|
assert (empty["path"], empty["count"]) == (["빈 폴더"], 0)
|
|
# 폴더를 못 받으면(빈 자리) 모두 뿌리 · 폴더 없음
|
|
flat = _run(rows)["tree"]
|
|
assert flat["docs"] == ["SC-000001", "SC-000002", "SC-000003"] and flat["folders"] == []
|
|
|
|
|
|
def test_사전조합_층_단추는_페이지를_다시_안_세움():
|
|
page = _text("M02_MasterTemplete_UI_Page.ts")
|
|
pre = page.split("const setPre = (want: Root): void => {")[1].split("};")[0]
|
|
assert "history.replaceState(" in pre and "side.refreshPrecombo()" in pre
|
|
assert "navigateTo" not in pre and "setActiveLayer" not in pre # 페이지 층 · 해시 이동 없음
|
|
assert "entry.pre = pre.root;" in pre # 구조물 조합 층 이동(go) 때도 주소에 남음
|
|
bar = page.split("const preBar = (add: HTMLElement): HTMLElement =>")[1].split(
|
|
"const side = buildSide("
|
|
)[0]
|
|
# 720-1 — 다른 층 = 저장 안 한 고침 물음 → 그 층 나무 + 목록표
|
|
go = bar.split("go: (next) =>")[1].split("reselect:")[0]
|
|
assert ".confirmLeave()" in go and "setPre(next), void main.open(precomboList(pre))" in go
|
|
sk = page.split("const skBar = (")[1].split("const side = buildSide(")[0]
|
|
assert ".confirmLeave()" in sk and "void main.open(sangeunList(sk))" in sk.split("reselect:")[0]
|
|
# 구조물 조합 층 단추 한 벌(720-4 제자리 바꿈)
|
|
assert page.count("layerBar(") == 1
|
|
assert "layerBar(tree.add, () => void main.openTable(layer))," in page
|
|
assert "precombo: {\n scope: () => pre,\n bar: preBar," in page # 706-1 more 덧붙음
|
|
|
|
|
|
def test_고름에_owner_권한_서고_길에_명시_owner():
|
|
side = _text("M02_MasterTemplete_UI_Side_Precombo.ts")
|
|
assert "PRECOMBO_TITLE" not in side # 716-3 — 제목은 locale 키 「일위대가」
|
|
assert "owner: scope.owner," in side and "canWrite: scope.canWrite," in side
|
|
lib_side = _text("M02_MasterTemplete_UI_Side_Library.ts") # 716-4 서고 칸 한 벌
|
|
assert "listTemplates(at.layer, null, at.owner)" in lib_side
|
|
main = _text("M02_MasterTemplete_UI_Main.ts")
|
|
assert (
|
|
"const writable = (at: Selection): boolean => at.canWrite ?? canEdit(at.layer, isAdmin());"
|
|
in main
|
|
)
|
|
assert "readTemplate(next.layer, next.kind, next.name, next.projectId, next.owner)" in main
|
|
assert "at.owner,\n );" in main # saveTemplate
|
|
assert "owner: next.owner ?? null," in main and "mode, owner: at.owner ?? null" in main
|
|
lib = _text("M02_MasterTemplete_Library_List.ts")
|
|
assert "getActiveOwner" not in lib and lib.count("opt.owner") >= 6
|
|
detail = _text("M02_MasterTemplete_Labor_Detail.ts")
|
|
assert "opt.owner !== undefined" in detail
|
|
|
|
|
|
def test_분류_고정_폴더_지움_자유_폴더_메뉴_끌어_옮기기():
|
|
pre_side = _text("M02_MasterTemplete_UI_Side_Precombo.ts")
|
|
side = _text("M02_MasterTemplete_UI_Side_Library.ts") # 716-4 서고 칸 한 벌
|
|
for gone in ("groupRows", "fetchStructureDef", "Lib_Unsorted", "shape"):
|
|
assert gone not in side and gone not in pre_side, gone
|
|
assert "fetchPrecomboTree(at.layer, at.owner, kind)" in side
|
|
assert "editPrecomboTree(at.layer, body, 판, at.owner, kind)" in side
|
|
menu = side.split("const menuAt = (")[1].split("\n };")[0]
|
|
for label in ('L("M02_TreeAddFolder")', "opt.text.newHere", 'L("M02_TreeRename")'):
|
|
assert label in menu, label
|
|
assert 'item(L("M02_Delete"), () => void remove([target]))' in menu
|
|
assert 'opt.scope().canWrite ? undefined : tc("ReadOnly")' in menu # 읽기만 층 = 흐림
|
|
remove = side.split("const remove = async (")[1].split("\n };")[0]
|
|
assert "showConfirmDialog(ask" in remove and 'op: "delete", 대상:' in remove # 안에 든 것째
|
|
assert 'newHere: tc("Lib_NewHere")' in pre_side
|
|
assert '({ 이름: name, 구분: "", 상세구분: "", 단위: null, 담은로직: [] })' in pre_side
|
|
make = side.split("const make = (path: string[]): void => {")[1].split("\n };")[0]
|
|
assert "{ 문서: opt.blank(name) }" in make
|
|
assert 'if (path.length) await edit({ op: "move", 키: got.이름, 새경로: path });' in make
|
|
assert "attachTreeDrag({" in side and "if (canWrite) drag.arm(row, node.id);" in side
|
|
assert "openTreeMenu(event, menuAt([]));" in side # 빈 자리 = 뿌리 메뉴
|
|
assert "open: toggled.get(id) ?? count > 0," in side
|
|
lib = _text("M02_MasterTemplete_Library_List.ts")
|
|
assert "export const nameDialog = (" in lib
|
|
|
|
|
|
def test_목록표_빈_구분_미분류_고르기_창_새_분류_거름():
|
|
"""149-2 — 목록표 빈 구분 = 미분류 · 고르기 창 거름 = 품셈 구분 + 사전조합 새 분류"""
|
|
lib = _text("M02_MasterTemplete_Library_List.ts")
|
|
assert '(r) => r.구분 || tc("Lib_Unsorted")],' in lib # 717-2a 열 표(COLUMNS)
|
|
pick = _text("M02_MasterTemplete_Labor_Pick.ts")
|
|
assert "new Map([...subs, ...classes].map((s) => [s.name, new Set(s.details)]))" in pick
|
|
assert "classes = precomboClasses(def);" in pick
|
|
assert 'x.row.구분 || tc("Lib_Unsorted")' in pick # 서고 줄 작은 글 = 새 분류 · 빈 값 미분류
|
|
# 거름은 칸 값 그대로 — 품셈 구분을 고르면 서고 줄 빠지고 새 분류를 고르면 로직 줄 빠짐
|
|
assert "(!sub.select.value || x.row.구분 === sub.select.value)" in pick
|
|
assert "(!sub.select.value || x.구분 === sub.select.value)" in pick
|
|
|
|
|
|
def test_왼쪽_칸_차례_일위대가_구조물조합_구분선_불러오기_716_3():
|
|
side = _text("M02_MasterTemplete_UI_Side.ts")
|
|
order = side[side.index("m02-side__lists") :]
|
|
marks = [
|
|
"sangeunSec,", # 717-2a 맨 위
|
|
"precomboSec,",
|
|
"summary,",
|
|
"createDivider()",
|
|
"hopyo.title",
|
|
"drawing ?",
|
|
"practice ?",
|
|
]
|
|
at = [order.index(m) for m in marks]
|
|
assert at == sorted(at)
|
|
|
|
assert 'L("M02_PrecomboTitle")' in side and "buildOptionsSection" not in side
|