- 헬퍼 동작 그대로(풀이 함수 본문은 옮겨 담음) · 제품 코드 안 고침 - 확인: 이 묶음 헬퍼를 쓰는 시험 218 개 전 218 통과 → 후 218 통과 (시험별 junit 대조 · 어긋남 0) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
215 lines
8.2 KiB
JavaScript
215 lines
8.2 KiB
JavaScript
/* 462 B05 · B06 한 페이지 시설 줄 한 벌(요구 271-5) — 진짜 `design_facility_lane` · 관 id 장부 · 측점 계획 ·
|
|
* 관 따라감 · 가게를 Node 로 돌려 JSON 한 줄(`test_271_facility_lane.py` 가 판정). 종단도는 가짜(받은 것 기록).
|
|
* node helper_271_facility_lane.cjs
|
|
*/
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const ROOT = path.join(__dirname, "..", "..");
|
|
const { makeLoader } = require("./helper_ts_stub_loader.cjs");
|
|
const CATALOG = require(path.join(__dirname, "helper_322_catalog_module.cjs"));
|
|
|
|
function memoryStorage() {
|
|
const map = new Map();
|
|
return {
|
|
getItem: (k) => (map.has(k) ? map.get(k) : null),
|
|
setItem: (k, v) => map.set(k, String(v)),
|
|
removeItem: (k) => map.delete(k),
|
|
key: (i) => [...map.keys()][i] ?? null,
|
|
get length() {
|
|
return map.size;
|
|
},
|
|
};
|
|
}
|
|
globalThis.window = { sessionStorage: memoryStorage(), localStorage: memoryStorage() };
|
|
globalThis.sessionStorage = globalThis.window.sessionStorage;
|
|
globalThis.localStorage = globalThis.window.localStorage;
|
|
|
|
const anyModule = (extra = {}) =>
|
|
new Proxy(extra, { get: (t, key) => (key in t ? t[key] : () => undefined) });
|
|
const STUBS = {
|
|
"config_frontend.ts": anyModule({
|
|
CURRENT_PROJECT_ID_KEY: "frd_current_project_id",
|
|
API_BASE_URL: "",
|
|
API_TIMEOUT_MS: 1000,
|
|
PIPE_TYPES: [],
|
|
}),
|
|
"ui_template_elements.ts": anyModule(),
|
|
"B04_PreProcess_Api_Fetch.ts": anyModule(),
|
|
// 소단 사본(467) — 끌어오는 줄이 import.meta 를 써 이 로더로 못 읽음 · 소단 몫은 test_272_berm_source(vite 묶음)
|
|
"B06_Section_UI_Page_Persist.ts": anyModule({ bermSpansFromStructures: () => [] }),
|
|
};
|
|
|
|
const resolve = (base) =>
|
|
[base, `${base}.ts`, path.join(base, "index.ts")].find(
|
|
(p) => fs.existsSync(p) && fs.statSync(p).isFile(),
|
|
) ?? `${base}.ts`;
|
|
const { loadTs } = makeLoader({
|
|
root: ROOT,
|
|
interop: true,
|
|
fileStub: (full) => STUBS[path.basename(full)],
|
|
requireFor: (full) => (request) => {
|
|
if (CATALOG.isCatalogRequest(request)) return CATALOG;
|
|
if (request.endsWith(".json"))
|
|
return JSON.parse(fs.readFileSync(path.join(path.dirname(full), request), "utf8"));
|
|
const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" };
|
|
for (const [prefix, dir] of Object.entries(alias))
|
|
if (request.startsWith(prefix))
|
|
return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length))));
|
|
return loadTs(resolve(path.join(path.dirname(full), request)));
|
|
},
|
|
});
|
|
|
|
const design = path.join(ROOT, "A00_Common", "design");
|
|
const { createFacilityLane } = loadTs(path.join(design, "design_facility_lane.ts"));
|
|
const ids = loadTs(path.join(design, "design_pipe_ids.ts"));
|
|
const { useDesignStore } = loadTs(path.join(design, "design_store.ts"));
|
|
const { readState } = loadTs(path.join(ROOT, "A00_Common", "b_page_state.ts"));
|
|
const TYPES = JSON.parse(
|
|
fs.readFileSync(path.join(ROOT, "resources", "master_template", "구조물정의.json"), "utf8"),
|
|
).types;
|
|
|
|
/** 가짜 종단도 — 받은 것 · 편집 옮김 기록. */
|
|
function fakeProfile() {
|
|
const got = {
|
|
structures: null,
|
|
stations: null,
|
|
minCover: null,
|
|
types: 0,
|
|
resets: [],
|
|
stationWrites: 0,
|
|
};
|
|
return {
|
|
got,
|
|
setStructureTypes: (types) => (got.types = types.length),
|
|
setStructures: (next) => (got.structures = next),
|
|
setIrregularStations: (stations) => ((got.stations = stations), (got.stationWrites += 1)),
|
|
setMinCoverPipes: (pipes) => (got.minCover = pipes.map((p) => p.chainage_m)),
|
|
resetStationEdit: (from, to) => got.resets.push([from, to ?? null]),
|
|
};
|
|
}
|
|
const book = (projectId) => readState("pipe-ids", projectId) ?? { next: 1, ids: [] };
|
|
const wall = (id, at) => ({
|
|
structure_id: id,
|
|
type_id: "retaining_wall",
|
|
placement: "interval",
|
|
chainage_m: at,
|
|
start_m: at - 5,
|
|
end_m: at + 5,
|
|
options: {},
|
|
});
|
|
const markView = (lane) => lane.marks().map((m) => [m.structure_id, m.type_id, m.chainage_m]);
|
|
const stationView = (lane) => lane.stations().map((s) => [s.chainage_m, s.id]);
|
|
|
|
const out = {};
|
|
|
|
// ① R3 뒤집기 — B06 만 연 채 351 관 → 360 옮김 → 버림 → 카드가 351 관 id 를 물음 → 다시 실음
|
|
{
|
|
const P = "P271-r3";
|
|
ids.usePipeIds(P);
|
|
const profile = fakeProfile();
|
|
const lane = createFacilityLane(P, () => profile);
|
|
lane.setTypes(TYPES);
|
|
lane.set({ structures: [], pipes: [{ id: "X", chainage_m: 351, facility: "pipe" }] }, false);
|
|
const first = ids.pipeIdAt(351, P);
|
|
ids.followMovedPipe(P, 351, 360); // B06 movePipeTo
|
|
lane.set({ pipes: [{ id: "X", chainage_m: 360, facility: "pipe" }] });
|
|
const moved = ids.pipeIdAt(360, P);
|
|
const ghost = ids.pipeIdAt(351, P); // 버린 뒤 다시 받기 전 카드가 물음(헛 id)
|
|
lane.set({ pipes: [{ id: "X", chainage_m: 351, facility: "pipe" }] }, false); // 목록 다시 실음
|
|
out.r3 = {
|
|
first,
|
|
moved,
|
|
ghostBeforeReload: ghost !== first,
|
|
after: book(P).ids,
|
|
afterId: ids.pipeIdAt(351, P),
|
|
marks: markView(lane),
|
|
};
|
|
}
|
|
|
|
// ② 같은 조작 — B05 먼저 듣는 중(listener 처음부터) 대 B06 먼저(끝에 B05 가 읽음)
|
|
function scenario(P, listenFromStart) {
|
|
ids.usePipeIds(P);
|
|
const profile = fakeProfile();
|
|
const lane = createFacilityLane(P, () => profile);
|
|
const heard = [];
|
|
const listen = () => lane.onChange(() => heard.push(stationView(lane).length));
|
|
if (listenFromStart) listen();
|
|
lane.setTypes(TYPES);
|
|
lane.setStored([wall("w1", 100)]);
|
|
lane.set(
|
|
{
|
|
structures: [wall("w1", 100)],
|
|
pipes: [
|
|
{ id: "A", chainage_m: 60, facility: "pipe" },
|
|
{ id: "B", chainage_m: 200, facility: "pipe" },
|
|
],
|
|
},
|
|
false,
|
|
);
|
|
// B06 폼 — 구조물 옮김 · 관 하나 넣음(새 관 id 없음) · 관 옮김
|
|
lane.set({ structures: [wall("w1", 140)] });
|
|
lane.set({
|
|
pipes: [
|
|
{ id: "A", chainage_m: 60, facility: "pipe" },
|
|
{ chainage_m: 120, facility: "pipe" },
|
|
{ id: "B", chainage_m: 200, facility: "pipe" },
|
|
],
|
|
});
|
|
ids.followMovedPipe(P, 200, 230);
|
|
lane.set({
|
|
pipes: [
|
|
{ id: "A", chainage_m: 60, facility: "pipe" },
|
|
{ chainage_m: 120, facility: "pipe" },
|
|
{ id: "B", chainage_m: 230, facility: "pipe" },
|
|
],
|
|
});
|
|
// 배수유역도가 뒤늦게 같은 목록을 알림(B05 판)
|
|
lane.set({
|
|
pipes: [
|
|
{ id: "A", chainage_m: 60, facility: "pipe" },
|
|
{ chainage_m: 120, facility: "pipe" },
|
|
{ id: "B", chainage_m: 230, facility: "pipe" },
|
|
],
|
|
});
|
|
if (!listenFromStart) listen();
|
|
const ledger = book(P).ids;
|
|
const markIds = lane.marks().map((m) => m.structure_id);
|
|
return {
|
|
marks: markView(lane).map(([id, type, at]) => [id.startsWith("pipe-") ? "pipe" : id, type, at]),
|
|
stations: stationView(lane).map(([at]) => at),
|
|
minCover: profile.got.minCover,
|
|
profileMarks: profile.got.structures.length === lane.marks().length,
|
|
profileStations: JSON.stringify(profile.got.stations) === JSON.stringify(lane.stations()),
|
|
ghostIds: ledger.filter((entry) => !markIds.includes(entry.id)).length,
|
|
resets: profile.got.resets,
|
|
heardB05: listenFromStart ? heard.length : null,
|
|
};
|
|
}
|
|
out.b05First = scenario("P271-b05", true);
|
|
out.b06First = scenario("P271-b06", false);
|
|
|
|
// ③ 노선 따라감(가게 씀 + follow) = 편집 안 옮김 · 다시 실음(같은 목록) = 안 옮김 · 측점 다시 안 씀
|
|
{
|
|
const P = "P271-follow";
|
|
ids.usePipeIds(P);
|
|
const profile = fakeProfile();
|
|
const lane = createFacilityLane(P, () => profile);
|
|
const store = useDesignStore(P, null);
|
|
lane.setTypes(TYPES);
|
|
lane.set({ structures: [], pipes: [{ id: "A", chainage_m: 80, facility: "pipe" }] }, false);
|
|
store.set("pipes", [{ id: "A", chainage_m: 95, source: "user", facility: "pipe" }]);
|
|
store.set("structures", [wall("w9", 150)]);
|
|
lane.follow();
|
|
const followed = {
|
|
stations: stationView(lane).map(([at]) => at),
|
|
resets: [...profile.got.resets],
|
|
};
|
|
const writes = profile.got.stationWrites;
|
|
lane.set({ structures: [wall("w9", 150)] }, false); // 숨었던 B05 가 다시 보이며 같은 목록을 실음
|
|
const reshown = { resets: profile.got.resets.length, writes: profile.got.stationWrites - writes };
|
|
lane.set({ structures: [wall("w9", 170)] }); // 사용자 옮김 = 옮김
|
|
out.follow = { followed, reshown, userMove: profile.got.resets };
|
|
}
|
|
|
|
console.log(JSON.stringify(out));
|