- 나무 부품 — Ctrl · Shift 클릭 고름(TreePanel_Pick) · 고른 줄 배경 · 층 단추 줄 「N개 고름」 · 오른쪽 클릭 메뉴가 고른 것 전부(복사 · 옮기기 · 지우기) · Ctrl+C · Ctrl+V · Delete · 복사는 읽기 층에서도 · 붙여넣기는 쓸 수 있는 층만 · 콜백 api.many · api.paste · clipKind(일위대가 쪽이 붙일 자리)
- 서버 — POST /layers/{층}/items/paste(여러 항목 · 폴더 · 새 SC/SP 키 · 본뜬 = 원본 · 같은 이름 「(2)」 · 세 파일 · 입력 연결 · 자재 조합 · 빈 폴더까지 · M02_Layer_Access 쓰기 층만) · 나무 고침 대상 여러 개(옮기기 · 지우기 한 번 저장 · 막히면 안 씀)
- 층 단추 줄 「N개 고름」 · 「+」 오른쪽 끝
- 시험 test_m02_tree_paste 새로 · 고르기 규칙 Node 시험
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
125 lines
4.6 KiB
Python
125 lines
4.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""M02 구조물 라이브러리(PLAN 26-3a) 자료 셈 회귀검증 — 폴더 나무 · 빈 폴더 · 깊이 5 · 찾기 ·
|
|
폴더 목록.
|
|
|
|
항목 경로 = 폴더 이름 배열 + 마지막 마디가 항목 이름 · 찾는 글이 있으면 맞는 항목과 위 폴더만.
|
|
Node 헬퍼(`helper_m02_treepanel.cjs`)가 TS 를 트랜스파일해 돌리고, 여기서 값을 판정한다.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
|
|
import pytest
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def results():
|
|
proc = subprocess.run(
|
|
["node", os.path.join(HERE, "helper_m02_treepanel.cjs")],
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
timeout=60,
|
|
)
|
|
assert proc.returncode == 0, proc.stderr
|
|
return json.loads(proc.stdout)
|
|
|
|
|
|
def test_빈_폴더도_나무에_있음(results):
|
|
tops = [f["path"] for f in results["all"]["folders"]]
|
|
assert ["빈 폴더"] in tops
|
|
dol = next(f for f in results["all"]["folders"] if f["path"] == ["돌기슭막이"])
|
|
assert ["돌기슭막이", "빈 아래"] in [f["path"] for f in dol["folders"]]
|
|
|
|
|
|
def test_맨_위_항목은_폴더_밖(results):
|
|
assert results["all"]["items"] == ["c4"]
|
|
assert results["count"] == 4
|
|
|
|
|
|
def test_깊이_5_항목까지_이어짐(results):
|
|
node = next(f for f in results["all"]["folders"] if f["path"] == ["다섯"])
|
|
for depth in range(2, 5):
|
|
node = node["folders"][0]
|
|
assert len(node["path"]) == depth
|
|
assert node["items"] == ["c3"]
|
|
|
|
|
|
def test_찾기는_맞는_항목과_위_폴더만(results):
|
|
assert results["foundCount"] == 1
|
|
assert [f["path"] for f in results["found"]["folders"]] == [["돌기슭막이"]]
|
|
assert results["found"]["folders"][0]["items"] == ["c2"] # 빈 폴더는 안 보임
|
|
assert results["none"] == {"path": [], "items": [], "folders": []}
|
|
|
|
|
|
def test_키로도_찾음(results):
|
|
assert results["byKey"] == 1
|
|
|
|
|
|
def test_찾기는_낱말_모두_AND(results):
|
|
"""PLAN 29-6 — 띄어쓰기로 나눈 낱말을 모두 담은 것만 · 대소문자 무시 · 빈 글 = 전부."""
|
|
assert results["and"] == [True, True, False, True, True]
|
|
assert results["andTree"] == 1 # 「돌기슭막이 찰쌓기」 = c2 하나
|
|
|
|
|
|
def test_폴더_목록과_위_길(results):
|
|
assert ["다섯", "둘", "셋", "넷"] in results["folders"]
|
|
assert ["빈 폴더"] in results["folders"]
|
|
assert results["ancestors"] == [
|
|
["다섯"],
|
|
["다섯", "둘"],
|
|
["다섯", "둘", "셋"],
|
|
["다섯", "둘", "셋", "넷"],
|
|
]
|
|
assert results["ancestorsTop"] == []
|
|
assert results["ancestorsNone"] == []
|
|
|
|
|
|
def test_안_또는_같음_판정(results):
|
|
assert results["under"] == [True, True, False, False]
|
|
|
|
|
|
def test_잎_이름과_폴더(results):
|
|
assert results["leaf"] == "1.2m"
|
|
assert results["dir"] == ["돌기슭막이", "메쌓기"]
|
|
|
|
|
|
def test_여러_개_고르기_Ctrl_Shift():
|
|
"""PLAN 30-2 — Ctrl = 하나 넣고 빼기(줄 동작 막음) · Shift = 기준에서 여기까지(보이는 차례) ·
|
|
그냥 클릭 = 그것 하나(줄 동작 그대로)."""
|
|
import re
|
|
|
|
pick = os.path.join(
|
|
HERE, "..", "..", "M02_MasterTemplete", "M02_MasterTemplete_TreePanel_Pick.ts"
|
|
)
|
|
src = open(pick, encoding="utf-8").read()
|
|
found = re.search(r"^export function pickClick[\s\S]*?^}", src, re.M)
|
|
assert found, "pickClick 을 못 찾음"
|
|
script = (
|
|
found.group(0).replace("export ", "")
|
|
+ "\nconst order = ['a', 'b', 'c', 'd', 'e'];"
|
|
+ "\nconst show = (r) => [[...r.picked].sort(), r.anchor, r.handled];"
|
|
+ "\nconst one = pickClick(new Set(), null, 'b', {ctrl: false, shift: false}, order);"
|
|
+ "\nconst two = pickClick(one.picked, one.anchor, 'd', {ctrl: true, shift: false}, order);"
|
|
+ "\nconst off = pickClick(two.picked, two.anchor, 'b', {ctrl: true, shift: false}, order);"
|
|
+ "\nconst range = pickClick(one.picked, 'b', 'e', {ctrl: false, shift: true}, order);"
|
|
+ "\nconst back = pickClick(one.picked, 'd', 'a', {ctrl: false, shift: true}, order);"
|
|
+ "\nconsole.log(JSON.stringify([one, two, off, range, back].map(show)));"
|
|
)
|
|
proc = subprocess.run(
|
|
["node", "--experimental-strip-types", "--no-warnings",
|
|
"--input-type=module-typescript", "-e", script],
|
|
capture_output=True, text=True, encoding="utf-8", timeout=60,
|
|
) # fmt: skip
|
|
assert proc.returncode == 0, proc.stderr
|
|
assert json.loads(proc.stdout) == [
|
|
[["b"], "b", False],
|
|
[["b", "d"], "d", True],
|
|
[["d"], "b", True],
|
|
[["b", "c", "d", "e"], "b", True],
|
|
[["a", "b", "c", "d"], "d", True],
|
|
]
|