Files
Aislo/resources/tester/helper_219_1_arrow_owner.cjs
T

185 lines
6.7 KiB
JavaScript

/* PLAN 219-1 단위검증 헬퍼 — 횡단 보기 방향키가 관을 몰래 옮기던 흠.
* 실제 TS(`B05_Profile_UI_Profile_Panel_Tools` · `B06_Section_UI_Cross_Panel_Base`)를 그 자리에서
* 트랜스파일해 Node 로 돌림 · 결과 = 표준출력 JSON 한 벌. 짝 = `test_219_1_arrow_owner.py`.
* 창 · 문서 듣개는 흉내(누름 = 문서 잡기 단계 · 키 = 문서 듣개(횡단 조정창) 뒤 창 듣개(종단도) 차례).
*/
const fs = require("fs");
const path = require("path");
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
const ROOT = path.join(__dirname, "..", "..");
const cache = new Map();
const listeners = { window: {}, document: {} };
const on = (where) => (type, fn) => (listeners[where][type] ??= new Set()).add(fn);
const off = (where) => (type, fn) => listeners[where][type]?.delete(fn);
const session = new Map();
globalThis.sessionStorage = {
getItem: (k) => session.get(k) ?? null,
setItem: (k, v) => session.set(k, String(v)),
removeItem: (k) => session.delete(k),
};
globalThis.window = {
addEventListener: on("window"),
removeEventListener: off("window"),
setTimeout: () => 0,
clearTimeout: () => undefined,
setInterval: () => 0,
clearInterval: () => undefined,
};
globalThis.document = { addEventListener: on("document"), removeEventListener: off("document") };
// 화면 계산 · 이력 · 도구줄은 이 흠과 무관 — 쓰는 꼴만 흉내
const calls = { move: [], edits: 0 };
const STUBS = {
"./B05_Profile_UI_Profile_Alignment": { adjustStation: () => (calls.edits++, {}) },
"./B05_Profile_UI_Profile_Edit": { HOLD_DELAY_MS: 500, HOLD_INTERVAL_MS: 100 },
"./B05_Profile_UI_Profile_History": {
createProfileHistory: () => ({
record: () => undefined,
pause: () => undefined,
resume: () => undefined,
undo: () => undefined,
redo: () => undefined,
canUndo: () => false,
canRedo: () => false,
}),
},
"./B05_Profile_UI_Profile_Tools": {
createProfileTools: () => ({ mode: () => "none", clear: () => undefined }),
},
"./B05_Profile_UI_Profile_Straighten": {},
// [자유지정](215-1) — 화면 설정(@config) 줄기를 끌고 옴 · 이 흠과 무관
"../A00_Common/design/design_profile_free": {
createFreeDraw: () => ({ key: () => false, hint: () => "" }),
},
"../A00_Common/design/design_profile_free_view": {
createFreeView: () => ({ mount: () => undefined, redraw: () => undefined }),
},
};
function loadTs(file) {
const full = path.resolve(file);
if (cache.has(full)) return cache.get(full);
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
cache.set(full, box.exports);
const localRequire = (request) =>
STUBS[request] ??
(request.startsWith(".")
? loadTs(path.join(path.dirname(full), `${request}.ts`))
: require(request));
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
cache.set(full, box.exports);
return box.exports;
}
const tools = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Profile_Panel_Tools.ts"));
const base = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Panel_Base.ts"));
// 엄상준 263.21 관 — 횡단 보기에서 그 관 카드가 골라져 종단도도 그 관 측점을 고른 상태
const pipe = { id: "p1", chainage_m: 263.21, origin: "pipe" };
tools.createPanelTools({
root: { isConnected: true },
base: () => ({}),
alignment: () => null,
edits: () => ({}),
applyEdits: () => undefined,
selectedStationId: () => "irregular:p1",
irregularStations: () => [pipe],
stationIdOf: (s) => `irregular:${s.id}`,
moveStation: (s, to) => calls.move.push([s.chainage_m, to]),
drainage: { pipeChainages: () => [], setPipeChainages: () => undefined },
restore: () => undefined,
restoreSaved: () => undefined,
canRestoreSaved: () => false,
refresh: () => undefined,
});
/** 누른 자리 — 종단도 안이면 closest 가 그 판을 줌(새 창 요소처럼 Element 가 아닌 것도). */
const spot = (inProfile) => ({
closest: (sel) => (inProfile && sel === ".b05-route-profile" ? {} : null),
});
const press = (target) => listeners.document.pointerdown?.forEach((fn) => fn({ target }));
/** 횡단 조정창 흉내 — 관 · 도수로 띠를 고른 동안 `swallowsArrows` 면 삼킴(Cross_Structure_Panel 배선과 같음). */
let side = "";
const crossPanel = (event) => {
if (base.swallowsArrows(side, event.key)) event.preventDefault();
};
listeners.document.keydown = new Set([crossPanel]);
function key(name) {
const event = {
key: name,
target: null,
repeat: false,
defaultPrevented: false,
preventDefault() {
this.defaultPrevented = true;
},
};
listeners.document.keydown.forEach((fn) => fn(event));
listeners.window.keydown?.forEach((fn) => fn(event));
listeners.window.keyup?.forEach((fn) => fn({ key: name }));
return event.defaultPrevented;
}
function run(steps) {
calls.move = [];
calls.edits = 0;
steps();
return { move: calls.move, edits: calls.edits };
}
const out = {
// 횡단 카드를 누른 뒤(조정창 없음) ← → = 안 옮김 — 옛 흠: 263.21 → 263.31
cardThenArrows: run(() => {
press(spot(false));
key("ArrowRight");
key("ArrowLeft");
}),
// 종단도를 누른 뒤 ← → = 지금처럼 0.1 m 이동
profileThenArrows: run(() => {
press(spot(true));
key("ArrowRight");
}),
// 새 창 종단도(다른 창 요소)도 종단도 안
popoutThenArrows: run(() => {
press(Object.create(null, { closest: { value: spot(true).closest } }));
key("ArrowLeft");
}),
// 횡단에서 도수로 띠 고름 · 카드 쪽 누름 = 방향키 넷 다 아무것도 안 옮김(계획고도)
chute: run(() => {
side = "chute";
press(spot(false));
["ArrowRight", "ArrowLeft", "ArrowUp", "ArrowDown"].forEach(key);
}),
pipeSide: run(() => {
side = "pipe";
press(spot(false));
key("ArrowRight");
key("ArrowUp");
}),
// 도수로 고른 채 종단도를 누르면 방향키는 종단도 몫
chuteThenProfile: run(() => {
side = "chute";
press(spot(true));
key("ArrowRight");
}),
// 조정창 있는 벽 고름 · 고름 없음 = 삼키지 않음(조정창 · 종단도 제 길)
swallow: {
wall: base.swallowsArrows("inlet", "ArrowRight"),
none: base.swallowsArrows("", "ArrowRight"),
letter: base.swallowsArrows("chute", "a"),
},
};
side = "";
out.cardUpArrow = run(() => {
press(spot(false));
key("ArrowUp"); // 고름 없는 카드 ▲ = 계획고(24-3b 그대로)
});
process.stdout.write(JSON.stringify(out));