feat(480 드롭다운): 마우스로 고른 뒤 한 박자 뒤 초점 뺌 — 키보드 고름 그대로 · 공용 진입 · CAD iframe · 종단 새 창 세 자리
- ui_template_native_menu.ts releaseSelectAfterPick — 창 캡처 pointerdown · keydown · change · 아직 그 select 에 초점일 때만 blur - 시험 test_280_6_select_release 새로 · 옛 가짜 import 두 헬퍼(282-1 · 224-1c)에 새 함수 흉내 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -117,7 +117,7 @@ const js = ts.transpileModule(read("A00_Common", "design", "design_profile_popou
|
||||
const mod = {};
|
||||
new Function("exports", "require", "window", "document", js)(
|
||||
mod,
|
||||
() => ({ showToast() {} }),
|
||||
() => ({ showToast() {}, releaseSelectAfterPick() {} }),
|
||||
main,
|
||||
mainDoc,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/* 280-6 드롭다운 초점 빼기(`ui_template/ui_template_native_menu.ts` `releaseSelectAfterPick`) 단위검증 헬퍼 —
|
||||
* 그 자리에서 트랜스파일해 Node 로 돌리고 값을 JSON 으로 냄. 가짜 창 · 가짜 select · 가짜 타이머로 판정. */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const js = ts.transpileModule(
|
||||
fs.readFileSync(path.join(ROOT, "ui_template", "ui_template_native_menu.ts"), "utf8"),
|
||||
{ compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 } },
|
||||
).outputText;
|
||||
const box = { exports: {} };
|
||||
new Function("exports", "module", "require", js)(box.exports, box, require);
|
||||
const m = box.exports;
|
||||
|
||||
// 가짜 창 — 캡처 듣기 · 타이머(손으로 돌림) · 문서 하나
|
||||
function setup() {
|
||||
const listeners = [];
|
||||
const timers = [];
|
||||
const doc = { activeElement: null };
|
||||
const win = {
|
||||
addEventListener: (type, fn, capture) => listeners.push({ type, fn, capture }),
|
||||
setTimeout: (fn) => timers.push(fn),
|
||||
};
|
||||
m.releaseSelectAfterPick(win);
|
||||
const el = (tagName) => {
|
||||
const node = { tagName, ownerDocument: doc, blurs: 0 };
|
||||
node.blur = () => {
|
||||
node.blurs += 1;
|
||||
doc.activeElement = null;
|
||||
};
|
||||
return node;
|
||||
};
|
||||
const fire = (type, target) =>
|
||||
listeners.filter((l) => l.type === type).forEach((l) => l.fn({ type, target }));
|
||||
const tick = () => timers.splice(0).forEach((fn) => fn());
|
||||
return { listeners, doc, el, fire, tick };
|
||||
}
|
||||
|
||||
// 차례 하나 — 초점을 select 에 두고 입력들을 쏜 뒤 change · (처리) · 한 틱
|
||||
function run(inputs, tag = "SELECT", moveFocus = false) {
|
||||
const t = setup();
|
||||
const target = t.el(tag);
|
||||
t.doc.activeElement = target;
|
||||
inputs.forEach((type) => t.fire(type, target));
|
||||
t.fire("change", target);
|
||||
const before = target.blurs; // 한 틱 전엔 안 뺌
|
||||
if (moveFocus) t.doc.activeElement = t.el("INPUT");
|
||||
t.tick();
|
||||
return { before, blurs: target.blurs };
|
||||
}
|
||||
|
||||
const src = (rel) => fs.readFileSync(path.join(ROOT, rel), "utf8");
|
||||
const setupOnce = setup();
|
||||
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
mouse: run(["pointerdown"]),
|
||||
keyboard: run(["keydown"]),
|
||||
none: run([]),
|
||||
input: run(["pointerdown"], "INPUT"),
|
||||
moved: run(["pointerdown"], "SELECT", true),
|
||||
mouseThenKey: run(["pointerdown", "keydown"]),
|
||||
keyThenMouse: run(["keydown", "pointerdown"]),
|
||||
listeners: setupOnce.listeners.map((l) => [l.type, l.capture]),
|
||||
wiring: {
|
||||
main: /releaseSelectAfterPick\(window\)/.test(src("A00_Common/main.ts")),
|
||||
cad: /releaseSelectAfterPick\(frame\.contentWindow\)/.test(
|
||||
src("ui_template/cad_host/cad_host.ts"),
|
||||
),
|
||||
popout: /releaseSelectAfterPick\(next\)/.test(
|
||||
src("A00_Common/design/design_profile_popout.ts"),
|
||||
),
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -134,7 +134,7 @@ const { withToolLead } = load(`${leadSrc}\nexports.withToolLead = withToolLead;`
|
||||
|
||||
// ---- 진짜 attachProfilePopout ----
|
||||
const { attachProfilePopout } = load(read("A00_Common", "design", "design_profile_popout.ts"), {
|
||||
require: () => ({ showToast() {} }),
|
||||
require: () => ({ showToast() {}, releaseSelectAfterPick() {} }),
|
||||
document,
|
||||
window,
|
||||
getComputedStyle: () => ({ backgroundColor: "" }),
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""280-6 드롭다운 — 마우스로 고르면 한 박자 뒤 초점 뺌 · 키보드로 고르면 그대로 · 부르는 자리 셋.
|
||||
|
||||
Node 헬퍼(`helper_280_6_select_release.cjs`)가 `ui_template/ui_template_native_menu.ts` 를 트랜스파일해
|
||||
가짜 창 · 가짜 select · 가짜 타이머로 돌리고, 여기서 값을 판정함.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def r():
|
||||
proc = subprocess.run(
|
||||
["node", os.path.join(HERE, "helper_280_6_select_release.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def test_마우스로_고르면_한_박자_뒤_초점_뺌(r):
|
||||
assert r["mouse"] == {"before": 0, "blurs": 1}
|
||||
assert r["keyThenMouse"]["blurs"] == 1
|
||||
assert r["none"]["blurs"] == 1 # 입력 없이 온 change(스크립트 · 손가락)도 뺌
|
||||
|
||||
|
||||
def test_키보드로_고르면_초점_남음(r):
|
||||
assert r["keyboard"]["blurs"] == 0
|
||||
assert r["mouseThenKey"]["blurs"] == 0 # 마우스로 펼친 뒤 ↑↓ + Enter
|
||||
|
||||
|
||||
def test_select_밖_이미_옮긴_초점은_안_건드림(r):
|
||||
assert r["input"]["blurs"] == 0
|
||||
assert r["moved"]["blurs"] == 0
|
||||
|
||||
|
||||
def test_창_캡처_듣기와_부르는_자리_셋(r):
|
||||
assert sorted(r["listeners"]) == [["change", True], ["keydown", True], ["pointerdown", True]]
|
||||
assert r["wiring"] == {"main": True, "cad": True, "popout": True}
|
||||
Reference in New Issue
Block a user