- 고르기: 누름 = 더함 · 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
189 lines
7.5 KiB
Python
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()
|