- 나무 = 구조물군 › 공통 + 종류 뼈대 먼저(빈 폴더도) · 목록 밖 값 뒤 · 미분류 맨 뒤 - 폴더 수 = 문서 수 · 문서 든 폴더만 처음 펼침 · 손으로 펴고 접은 것 기억 - 폴더 오른쪽 클릭 [여기에 새 사전조합] = 그 분류로 생성 → 상세 엶(못 쓰는 층은 흐림) - 이름 창(nameDialog) 목록표 밖으로 꺼내 함께 씀 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SE9MxeXTu2uaDZ55BJowAY
133 lines
7.1 KiB
Python
133 lines
7.1 KiB
Python
"""일위대가 사전조합 층 따로(PLAN 134-3) — 페이지 층과 따로 · 명시 owner · 2단 폴더 나무.
|
|
|
|
- 서버 길: 명시 owner 를 넘기면 그 주인만(null = 자기 층) — 남의 개인을 보는 중에도 사전조합 길에 안 섞임 ·
|
|
안 넘기면 지금처럼 페이지 층 주인(Node · fetch 대역)
|
|
- 나무: 구분 › 상세구분 2단 · 빈 구분 = 미분류 맨 뒤 · 빈 상세구분 = 구분 바로 아래
|
|
- 사전조합 층 단추 = 해시를 바꿔 페이지를 다시 세우지 않음(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, classes=None):
|
|
proc = subprocess.run(
|
|
["node", os.path.join(HERE, "helper_m02_precombo_scope.cjs")],
|
|
input=json.dumps({"rows": rows, "classes": classes}, 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_나무는_구분_상세구분_2단_미분류는_맨_뒤():
|
|
rows = [
|
|
{"이름": "SC-000003", "종류": "precombo", "구분": "C 사면안정", "상세구분": "옹벽"},
|
|
{"이름": "SC-000001", "종류": "precombo", "구분": "", "상세구분": "07장"},
|
|
{"이름": "SC-000002", "종류": "precombo", "구분": "A 횡단배수", "상세구분": ""},
|
|
{"이름": "SC-000004", "종류": "precombo", "구분": "C 사면안정", "상세구분": "돌쌓기"},
|
|
{"이름": "SC-000005", "종류": "precombo", "구분": "A 횡단배수", "상세구분": "공통"},
|
|
]
|
|
groups = _run(rows)["groups"]
|
|
assert [g["구분"] for g in groups] == ["A 횡단배수", "C 사면안정", "미분류"]
|
|
assert groups[0]["상세"] == [["공통", ["SC-000005"]], ["", ["SC-000002"]]]
|
|
assert groups[1]["상세"] == [["돌쌓기", ["SC-000004"]], ["옹벽", ["SC-000003"]]]
|
|
assert groups[2]["상세"] == [["", ["SC-000001"]]] # 구분 없으면 상세구분도 안 봄
|
|
|
|
|
|
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 = (): HTMLElement =>")[1].split("const side = buildSide(")[0]
|
|
assert "go: (next) => setPre(next)," in bar and "main.open(precomboList(pre))" in bar
|
|
# 구조물 조합 층 단추는 그대로 해시 이동 한 벌
|
|
assert page.count('layerBar("') == 1
|
|
assert 'layerBar("구조물조합", tree.add, () => void main.openTable(layer)),' in page
|
|
assert "precombo: { scope: () => pre, bar: preBar }," in page
|
|
|
|
|
|
def test_고름에_owner_권한_서고_길에_명시_owner():
|
|
side = _text("M02_MasterTemplete_UI_Side_Precombo.ts")
|
|
assert 'export const PRECOMBO_TITLE = "일위대가 사전조합";' in side
|
|
assert "owner: scope.owner," in side and "canWrite: scope.canWrite," in side
|
|
assert "listTemplates(at.layer, null, at.owner)" in 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_분류_뼈대_폴더_고정_빈_폴더도_목록_밖_값_뒤_미분류_맨_뒤():
|
|
"""149-1 — 분류 차례대로 빈 폴더도 · 목록 밖 지금 값은 뒤 · 미분류는 문서 있을 때만 맨 뒤"""
|
|
classes = [
|
|
{"name": "A 횡단배수", "details": ["공통", "배관", "BOX암거"]},
|
|
{"name": "C 사면안정", "details": ["공통", "돌쌓기"]},
|
|
]
|
|
rows = [
|
|
{"이름": "SC-000001", "종류": "precombo", "구분": "", "상세구분": ""},
|
|
{"이름": "SC-000002", "종류": "precombo", "구분": "C 사면안정", "상세구분": "돌쌓기"},
|
|
{"이름": "SC-000003", "종류": "precombo", "구분": "건설품셈 공통", "상세구분": "07장"},
|
|
{"이름": "SC-000004", "종류": "precombo", "구분": "A 횡단배수", "상세구분": "옛 종류"},
|
|
]
|
|
groups = _run(rows, classes)["groups"]
|
|
assert [g["구분"] for g in groups] == ["A 횡단배수", "C 사면안정", "건설품셈 공통", "미분류"]
|
|
assert groups[0]["상세"] == [
|
|
["공통", []],
|
|
["배관", []],
|
|
["BOX암거", []],
|
|
["옛 종류", ["SC-000004"]],
|
|
]
|
|
assert groups[1]["상세"] == [["공통", []], ["돌쌓기", ["SC-000002"]]]
|
|
assert groups[3]["상세"] == [["", ["SC-000001"]]]
|
|
# 문서 없으면 미분류 폴더 없음 · 뼈대는 그대로
|
|
empty = _run([], classes)["groups"]
|
|
assert [g["구분"] for g in empty] == ["A 횡단배수", "C 사면안정"]
|
|
|
|
|
|
def test_나무_폴더_문서수_처음_펼침_폴더_메뉴_생성():
|
|
side = _text("M02_MasterTemplete_UI_Side_Precombo.ts")
|
|
assert "groupRows(rows, unsorted, shape)" in side
|
|
assert "fetchStructureDef().then(precomboClasses" in side
|
|
assert "const count = children.reduce((n, c) => n + docs(c), 0);" in side # 수 = 문서 수
|
|
assert "open: toggled.get(id) ?? count > 0," in side # 문서 든 폴더만 펼침
|
|
assert "if (!rows.length && !shape.length)" in side # 뼈대 있으면 「없음」 아님
|
|
assert 'label: tc("Lib_NewHere")' in side and "run: () => make(...at)" in side
|
|
assert 'disabledReason: opt.scope().canWrite ? undefined : tc("ReadOnly")' in side
|
|
head = "const make = (구분: string, 상세구분: string): void => {"
|
|
make = side.split(head)[1].split("\n };")[0]
|
|
assert "const 문서 = { 이름: name, 구분, 상세구분, 단위: null, 담은로직: [] };" in make
|
|
assert 'newLibrary(at.layer, "precombo", { 문서 }, at.owner)' in make
|
|
lib = _text("M02_MasterTemplete_Library_List.ts")
|
|
assert "export const nameDialog = (" in lib
|