Files
Aislo/resources/tester/helper_b05_routeedit_cross_width.cjs
T

242 lines
8.9 KiB
JavaScript

/* 계획노선 편집 창 **측점 법선 · 고름 연동**(계획서 22-1 · 382-2) 단위검증 헬퍼 —
* `B05_Profile_UI_RouteEdit_Cross.ts` 를 트랜스파일해 Node 로 돌린다.
* 계산 묶음 · 가게 · 그림 조각은 가짜로 채운다 — 공용 고름(`design_selection`)은 진짜.
* 묶음 측점 = 일반측점 grid:2(40) · grid:3(60) · grid:5(100) · 관 pipe:a(80) · 원지반 표본 좌우 20 m.
*/
const fs = require("fs");
const path = require("path");
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
const ROOT = path.join(__dirname, "..", "..");
function memoryStorage() {
const map = new Map();
return {
getItem: (key) => (map.has(key) ? map.get(key) : null),
setItem: (key, value) => map.set(key, String(value)),
removeItem: (key) => map.delete(key),
key: (index) => [...map.keys()][index] ?? null,
get length() {
return map.size;
},
};
}
const session = memoryStorage();
Object.defineProperty(session, "length", { get: () => session._keys().length });
session._keys = () => {
const keys = [];
for (let i = 0; i < 1000; i += 1) {
const key = session.key(i);
if (key === null) break;
keys.push(key);
}
return keys;
};
// `Object.keys(sessionStorage)` 가 저장된 키를 돌려주도록 실제 브라우저처럼 속성으로도 둔다.
const sessionProxy = new Proxy(session, {
ownKeys: () => [...new Set(session._keys())],
getOwnPropertyDescriptor: (target, key) =>
session._keys().includes(key)
? { enumerable: true, configurable: true, value: target.getItem(key) }
: Reflect.getOwnPropertyDescriptor(target, key),
});
globalThis.window = { localStorage: memoryStorage(), sessionStorage: sessionProxy };
const fakeEl = () => ({
className: "",
textContent: "",
innerHTML: "",
querySelector: () => fakeEl(),
replaceChildren() {},
setAttribute() {},
append() {},
getBoundingClientRect: () => ({ width: 300, height: 200 }),
});
globalThis.document = { createElement: fakeEl };
// node 에는 ResizeObserver 가 없음 — 횡단 판 크기 따라가기는 이 시험 밖
globalThis.ResizeObserver = class {
observe() {}
disconnect() {}
};
const grid = (k, chainageM) => ({ key: `grid:${k}`, kind: "grid", chainageM });
let stations = [
grid(2, 40),
grid(3, 60),
{ key: "pipe:a", kind: "pipe", chainageM: 80 },
grid(5, 100),
];
let footprint = null;
let route = { v: 1 };
// 접힌 판 펴기 — 몸(side)에 묶인 패널 묶음 흉내
// 393-1 — 측점 눈금이 연 판은 기억 안 함(remember 거짓)
const panels = {
now: null,
current: () => panels.now,
open: (id, remember = true) => ((panels.now = id), (panels.remember = remember)),
};
// 393-2 — 판 그림 = B06 카드(가짜) · 그린 단면 id 를 적음
const cards = [];
const side = { append() {} };
const STUBS = {
"../A00_Common/b_svg_zoom_pan": { attachSvgZoomPan: () => {} },
"../common_util/common_util_station": { stationLabel: (m) => String(m) },
"./B05_Profile_UI_RouteEdit_Cross_Draw": {
buildCrossSvg: () => ({ svg: {}, layer: {}, width: 1, height: 1, content: {} }),
CROSS_PLOT_PAD: 0,
summarizeCross: () => "",
},
"./B05_Profile_UI_RouteEdit_Chrome": { sidePanelsOf: new WeakMap([[side, panels]]) },
"./B05_Profile_UI_RouteEdit_Cross_Card": {
routeCrossSource: (projectId, pipe, chainageM, pane) => ({
section: { station_id: `b05-${pane}-s${chainageM}` },
}),
routeCrossCard: (source) => (cards.push(source.section.station_id), fakeEl()),
},
"../A00_Common/design/design_pipeline_page": {
pagePipeline: () => ({
output: (name) => (name === "ground" ? { stations } : null),
input: () => route,
subscribe: () => () => undefined,
}),
},
"../A00_Common/design/design_store": {
useDesignStore: () => ({
subscribe: () => () => undefined,
get: (name) => (name === "footprint" ? footprint : null),
}),
},
"./logic/B05_Profile_Pipeline_Detail": {
stationSection: (station) => ({
chainage_m: station.chainageM,
label: null,
samples: [-20, 0, 20].map((offset_m) => ({ offset_m, elevation_m: 100, valid: true })),
}),
},
};
const cache = new Map();
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) => {
if (STUBS[request]) return STUBS[request];
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
return require(request);
};
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
cache.set(full, box.exports);
return box.exports;
}
const { createCrossPreview } = loadTs(
path.join(ROOT, "B05_Profile", "B05_Profile_UI_RouteEdit_Cross.ts"),
);
const pick = loadTs(path.join(ROOT, "A00_Common", "design", "design_selection.ts"));
// 페이지 노선 id 한 칸(386-2b) — 판이 읽는 적용값 = 이 노선 것만
const pageRoute = loadTs(path.join(ROOT, "A00_Common", "design", "design_page_route.ts"));
const record = (key, chainage_m, left, right, leftCut, rightCut) => ({
key,
chainage_m,
left_edge_m: 2.5,
left_end_m: left,
right_edge_m: -2.5,
right_end_m: right,
left_cut: leftCut,
right_cut: rightCut,
});
async function run() {
let shownCount = 0;
const cross = createCrossPreview({
projectId: "p-1",
side,
request: () => ({ vertices: [], min_radius_m: 12, station_interval_m: 20 }),
onShown: () => (shownCount += 1),
});
const out = {};
out.before = cross.watched();
await cross.open(100);
out.empty = { watched: cross.watched() };
pageRoute.setPageRouteId("p-1", 3);
session.setItem("aislo:draft:cross-display:p-1:3", "15");
await cross.refresh();
out.applied15 = { watched: cross.watched() };
session.setItem("aislo:draft:cross-display:p-1:12", "8.5");
session.setItem("aislo:draft:cross-display:p-2:99", "40");
await cross.refresh();
out.otherRoute = { watched: cross.watched() }; // 다른 노선 · 다른 프로젝트 값은 안 봄
pageRoute.setPageRouteId("p-1", 12);
await cross.refresh();
out.latestRoute = { watched: cross.watched() };
session.setItem("aislo:draft:cross-display:p-1:12", "abc");
await cross.refresh();
out.badValue = { watched: cross.watched() };
// 382-2 발자국 기록 = 비탈 끝 좌 · 우 · 깎기 여부
footprint = { records: [record("grid:5", 100, 7.5, -6, true, false)] };
out.footprint = cross.watched();
// 노선 고침 — 같은 열쇠 측점이 102 로 밀리고 발자국도 새로(열쇠로 다시 짚음)
stations = [
grid(2, 40),
grid(3, 60),
{ key: "pipe:a", kind: "pipe", chainageM: 80 },
grid(5, 102),
];
footprint = { records: [record("grid:5", 102, 9, -4, false, true)] };
route = { v: 2 };
cards.length = 0;
await cross.refresh();
out.afterEdit = cross.watched();
out.cardsAfterEdit = cards.slice();
// 평면 눈금 클릭 = 공용 고름에 적음 · 판이 접혀 있으면 폄
const heard = [];
pick.subscribeSelection("p-1", (selection, from) =>
heard.push({ at: selection.chainageM, from }),
);
panels.now = null;
await cross.open(41);
out.planClick = {
selection: pick.readSelection("p-1"),
heard: heard.slice(),
panel: panels.now,
remember: panels.remember,
watched: cross.watched(),
};
// 다른 보기 고름 — 일반측점 = 그 측점(판 폄) · 관 측점 · 구조물 id = 마름모 · 풂 = 없음
panels.now = null;
pick.selectDesign("p-1", { chainageM: 60, structureId: null }, { from: "b06-card" });
out.otherGrid = { watched: cross.watched(), panel: panels.now };
pick.selectDesign("p-1", { chainageM: 80, structureId: null }, { from: "profile" });
out.otherPipe = cross.watched();
pick.selectDesign("p-1", { chainageM: 60, structureId: "s-1" }, { from: "list" });
out.otherStructure = cross.watched();
const before = shownCount;
pick.selectDesign("p-1", { chainageM: null, structureId: null }, { from: "escape" });
out.cleared = { watched: cross.watched(), redrawn: shownCount > before };
// 393-4 — 보기 반폭 한 벌(B06 입력칸 · B05 판) = 세션 조절값 > 측점 저장 최댓값 > 기본 10
const { displayHalfWidth } = loadTs(
path.join(ROOT, "B06_Section", "B06_Section_Display_Width.ts"),
);
const saved = [12, 14, null].map((w) => ({ design: { display_half_width_m: w } }));
out.halfWidth = {
none: displayHalfWidth("p-9", 5, []),
saved: displayHalfWidth("p-9", 5, saved),
};
session.setItem("aislo:draft:cross-display:p-9:5", "18");
out.halfWidth.session = displayHalfWidth("p-9", 5, saved);
out.halfWidth.otherRoute = displayHalfWidth("p-9", 6, []);
out.halfWidth.noRoute = displayHalfWidth("p-9", null, saved);
return out;
}
run().then((out) => console.log(JSON.stringify(out)));