Files
Aislo/resources/tester/test_app_settings.py
T
eomsangdonandClaude Opus 5.5 db8462c6d4 feat(B07 CAD): 337-2 마-1 오토캐드 마우스 — 누름 더함 · Shift 뺌 · 상자 더함/뺌 · 명령 중 끌기 상자 · 스침 · 그립 누르고-옮기고-누르기 · 그립 숫자 · 보기 전용 문 · 직교 강제 · Shift 임시 직교 · 휠 기본 밂 · 휠 두 번 범위
- 고르기: 누름 = 더함 · Shift = 뺌 · Ctrl = 넣고 빼기 · 상자 = 앞 고름 ∪ 상자 · Shift 상자 = 뺌(H4 · H5) · 명령 안 고르기 단계도 누를 때 첫 점 · 스침 강조(ic/selecting.ts)
- 그립: 쉴 때만 집음(N7) · 누르고 떼면 대기 → 다음 누름에 놓음 · 끌어 놓기(H3) · 거리 숫자(N8) · 확정 · 읽기 전용 도면은 그립 · Delete 막음(H2)
- 직교: F8 = 기준점에서 가로 · 세로 강제 · Shift 누른 동안 뒤집음 · 객체 스냅 점 · 좌표 · 각도 잠금은 안 덮음 · 클릭 사건 snapped 채움(ic/ortho.ts · H8) · 가이드는 F3 와 뗌 · 가이드 잡는 거리 = 화면 픽셀(H9)
- 휠: 기본 밂(앞으로 굴리면 확대 · 오토캐드 · 모든 화면 · ☰ 설정 뒤집기 그대로) · 가운데 단추 두 번 = 범위 줌 · ZOOM · ⛶ 앞 화면 기억(ic/zoom-prompt.ts)
- 시험: mouse-acad 새로 · 휠 방향 시험 손질(input-wheel · input-prefs-cad · input_prefs · app_settings · 164 · 220_1 · 221_1 · 244_8 기대값 뒤집음 · 266 pull 박음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf
2026-10-15 23:41:46 +09:00

189 lines
7.5 KiB
Python

"""PLAN 332-2 — ☰ [설정] 창(`A00_Common/app_settings.ts` · `app_settings_cad.ts` · `app_shell.ts`).
① CAD 별칭 검사(Node) — 영문 · 숫자만 · 겹침 금지 · 명령 이름 금지 · 칸 글 나눔
② 실제 브라우저(sub2 화면 서버 5177 · 로그인 없음 = 이 브라우저만 · 새 창이라 남는 것 없음)
☰ → [설정] = 서랍 닫히고 창 · 탭 셋 전환 · 고쳐도 [저장] 전엔 안 씀 · 닫을 때 물음(취소 = 머묾 ·
버림 = 안 씀 · 저장 = 한 번) · [저장] 한 번 = 한 번 씀 · 저장 뒤 읽기 층 방향 뒤집힘 · 잘못 별칭은 안 씀
"""
from __future__ import annotations
import json
import os
import subprocess
import urllib.request
import pytest
HERE = os.path.dirname(__file__)
ROOT = os.path.join(HERE, "..", "..")
# 화면 서버(OWNERS 자기 줄) — 돌리는 창 포트로 바꿈
BASE = os.environ.get("AISLO_UI_BASE", "http://localhost:5177")
KEY = "aislo:pref:input-prefs"
# 앱 스크립트보다 먼저 — 설정 키에 쓰는 횟수를 셈
COUNT_WRITES = f"""
window.__writes = [];
const set = Storage.prototype.setItem;
Storage.prototype.setItem = function (k, v) {{
if (k === {json.dumps(KEY)}) window.__writes.push(v);
return set.call(this, k, v);
}};
"""
# 앱이 불러온 그 읽기 층 사본(vite 가 ?t= 를 붙였을 수 있음)
WHEEL_IN = (
"async (d) => (await import(performance.getEntriesByType('resource')"
".map((e) => e.name).find((n) => n.includes('/input_prefs.ts')))).wheelIn(d)"
)
@pytest.fixture(scope="module")
def node() -> dict:
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
["node", os.path.join(HERE, "helper_app_settings.cjs")],
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
def test_별칭_검사(node):
assert node["commands"] == 153
assert node["parse"] == [["LL", "CC", "X1"], [], []]
assert node["ok"] == {} # 남의 기본 별칭(C = CIRCLE)을 내 것으로 덮는 건 됨(acad.pgp 식)
assert node["chars"] == {"LINE": "chars", "CIRCLE": "chars"}
assert node["dup"] == {"LINE": "dup", "CIRCLE": "dup", "ARC": "dup"}
assert node["name"] == {"CIRCLE": "name"}
def _up() -> bool:
try:
urllib.request.urlopen(BASE, timeout=3)
return True
except Exception:
return False
def _open_settings(page):
page.click(".app-burger")
page.click(".app-drawer__settings")
page.wait_for_selector(".app-settings")
assert not page.evaluate(
"document.querySelector('.app-header, header')?.classList.contains('is-drawer-open')"
)
def _writes(page) -> list:
return page.evaluate("window.__writes")
def _visible_panel_has(page, selector: str) -> bool:
return page.evaluate(
"(s) => [...document.querySelectorAll('.app-settings .ui-modal__body > div:not([role]), "
".app-settings .ui-modal__body > table')].some((p) => !p.hidden && p.querySelector(s))",
selector,
)
def _save(page):
page.click(".app-settings .ui-modal__actions button")
def _close(page):
page.click(".app-settings .ui-modal__top > button")
@pytest.mark.skipif(not _up(), reason="sub2 화면 서버(5177) 꺼짐")
def test_설정_창_탭_저장_물음():
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(channel="msedge", headless=True)
ctx = browser.new_context(
viewport={"width": 390, "height": 844}, has_touch=True, is_mobile=True
)
ctx.add_init_script(COUNT_WRITES)
page = ctx.new_page()
page.goto(f"{BASE}/#/a01-home")
page.wait_for_selector(".app-burger")
assert page.evaluate(WHEEL_IN, -100) is True # 기본 = 밂(337 · 오토캐드)
# ☰ → [설정] · 탭 셋 · 첫 탭 = 마우스
_open_settings(page)
tabs = page.evaluate(
"[...document.querySelectorAll('.app-settings [role=tab]')]"
".map((b) => [b.dataset.settingsTab, b.getAttribute('aria-selected')])"
)
assert tabs == [["mouse", "true"], ["cad", "false"], ["keys", "false"]]
assert _visible_panel_has(page, "input[type=radio]")
page.click("[data-settings-tab=cad]")
assert _visible_panel_has(page, "input[data-cad-command]")
assert not _visible_panel_has(page, "input[type=radio]")
assert page.locator(".app-settings input[data-cad-command]").count() == 153
page.click("[data-settings-tab=keys]")
assert (
page.evaluate(
"document.querySelector('[data-settings-tab=keys]').getAttribute('aria-selected')"
)
== "true"
)
page.click("[data-settings-tab=mouse]")
# 당김 고름 — [저장] 전엔 안 씀 · 닫으면 물음 · 취소 = 머묾
page.check("input[name=app-settings-wheel][value=pull]")
assert _writes(page) == []
page.keyboard.press("Escape")
page.wait_for_selector(".ui-confirm")
page.click(".ui-confirm button >> nth=0") # 취소
assert page.locator(".app-settings").count() == 1 and _writes(page) == []
# [저장] = 한 번 씀 · 읽기 층 방향 뒤집힘 · 닫을 때 안 물음
_save(page)
assert len(_writes(page)) == 1
assert json.loads(_writes(page)[0]) == {"wheelZoom": "pull", "cadAliases": {}}
assert page.evaluate(WHEEL_IN, 100) is True and page.evaluate(WHEEL_IN, -100) is False
_save(page) # 고친 것 없음 — 또 안 씀
assert len(_writes(page)) == 1
_close(page)
assert page.locator(".app-settings").count() == 0
assert page.locator(".ui-confirm").count() == 0
# CAD 별칭 — 잘못이면 안 씀 · 칸 표시
_open_settings(page)
assert page.is_checked("input[name=app-settings-wheel][value=pull]") # 저장값으로 엶
page.click("[data-settings-tab=cad]")
line = "input[data-cad-command=LINE]"
circle = "input[data-cad-command=CIRCLE]"
for line_v, circle_v in (("L!", ""), ("LL", "ll"), ("", "LINE")):
page.fill(line, line_v)
page.fill(circle, circle_v)
_save(page)
assert page.inner_text(".app-settings__error").strip(), (line_v, circle_v)
assert page.locator(".app-settings input[aria-invalid=true]").count() >= 1
assert len(_writes(page)) == 1
page.fill(line, "ll")
page.fill(circle, "")
_save(page)
assert page.inner_text(".app-settings__error").strip() == ""
assert len(_writes(page)) == 2
assert json.loads(_writes(page)[1]) == {"wheelZoom": "pull", "cadAliases": {"LINE": ["LL"]}}
# 닫을 때 물음 — 버림 = 안 씀 · 저장 = 한 번 더
page.click("[data-settings-tab=mouse]")
page.check("input[name=app-settings-wheel][value=push]")
_close(page)
page.click(".ui-confirm button >> nth=1") # 버리고 넘어감
assert page.locator(".app-settings").count() == 0 and len(_writes(page)) == 2
assert page.evaluate(WHEEL_IN, 100) is True
_open_settings(page)
page.check("input[name=app-settings-wheel][value=push]")
_close(page)
page.click(".ui-confirm button >> nth=2") # 저장 후 넘어감
assert page.locator(".app-settings").count() == 0 and len(_writes(page)) == 3
assert json.loads(_writes(page)[2])["wheelZoom"] == "push"
assert page.evaluate(WHEEL_IN, 100) is False
browser.close()