refactor(b06): 관 목록을 담는 자리를 한 벌로 합침
B05 는 관 목록 전체를 세션 스냅샷으로, B06 은 바뀐 것만 따로 담고 있어 담는 꼴이 둘이었음. 저장 순서를 맞춰 결과는 같게 했으나 자리가 둘인 한 같은 사고가 다시 남. B06 의 추가·삭제·이동을 모두 B05 가 쓰는 스냅샷(pipes)에 쓰도록 바꾸고, B06 전용 예약 경로(culvertedit 세션·queueAdd·queueRemove·queueMove)를 걷어 냄. 옛 세션에 남아 있던 이동 예약만 저장 때 한 번 흘려보내게 읽기를 남김. 이제 관을 넣고 빼고 옮기는 길이 좌측 폼·목록·종단 우클릭·측점선 끌기 모두 한 함수이고, 담기는 자리도 한 곳임. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fe1QWPTfw11PaKh2LjwXdR
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
"""B06 종단에서 넣고 뺀 **계곡 통과 시설**이 세션에 제대로 쌓이는지(2026-09-12 사용자 지시).
|
||||
"""관 편집이 **한 자리에만** 쌓이는지(2026-09-12 사용자: 담는 꼴도 한 벌이어야 한다).
|
||||
|
||||
B05·B06 은 한 페이지이므로 관 추가·삭제도 양쪽에서 되어야 한다. 다만 정본에 바로 쓰지
|
||||
않고 **세션에 예약**했다가 [저장]·[확정]에서 한 번에 내보낸다(CLAUDE.md 5장). 넣었다 바로
|
||||
빼면 예약만 걷어 내야 하고, 뺐다 다시 넣어도 마찬가지다 — 그러지 않으면 저장 때 있지도
|
||||
않은 관을 지우거나 같은 자리에 둘이 선다.
|
||||
B05 는 관 목록 전체를 세션 스냅샷(`pipes`)으로 담고, B06 은 바뀐 것만 따로 담고 있었다.
|
||||
저장 순서에 따라 한쪽이 다른 쪽을 덮어 **넣은 관이 사라지는** 일이 생겼다. 그래서 관
|
||||
목록은 두 페이지가 같은 스냅샷을 쓰도록 합쳤다.
|
||||
|
||||
여기서 지키는 것은 둘이다.
|
||||
① 저장기(`createCulvertOptionWriter`)에는 **관을 넣고 빼는 길이 없다** — 남아 있으면
|
||||
관 목록을 담는 자리가 다시 둘이 된다.
|
||||
② 옛 세션에 남아 있던 **이동 예약은 저장 때 흘려보낸다** — 합치기 전에 만들어 둔
|
||||
예약이 조용히 사라지면 안 된다.
|
||||
|
||||
실제 화면 코드를 그대로 컴파일해 Node 로 돌린다(다단 하한 시험과 같은 방식).
|
||||
"""
|
||||
@@ -25,15 +30,28 @@ const { writeFileSync } = require("node:fs");
|
||||
const Module = require("node:module");
|
||||
|
||||
// 화면 코드가 번들러 별칭(`@config/…`)을 쓴다 — Node 에는 없으니 빈 껍데기로 돌려준다.
|
||||
// 이 시험이 보는 것은 세션 예약 로직뿐이라 값은 안 쓴다.
|
||||
const load = Module._load;
|
||||
const saved = [];
|
||||
Module._load = function (request, parent, isMain) {
|
||||
if (request.startsWith("@config/") || request.startsWith("@util/") || request.startsWith("@ui/"))
|
||||
return new Proxy({}, { get: () => () => undefined });
|
||||
return load.call(this, request, parent, isMain);
|
||||
const mod = load.call(this, request, parent, isMain);
|
||||
// 관 정본 읽기·쓰기는 서버로 나가므로 가로챈다.
|
||||
if (request.includes("B04_PreProcess_Api_Fetch")) {
|
||||
return {
|
||||
...mod,
|
||||
fetchDetailPipePoints: async () => ({
|
||||
pipe_points: [
|
||||
{ chainage_m: 120, lonlat: [0, 0], source: "stream" },
|
||||
{ chainage_m: 200, lonlat: [0, 0], source: "stream" },
|
||||
],
|
||||
}),
|
||||
saveDetailPipePoints: async (_projectId, points) => saved.push(points),
|
||||
};
|
||||
}
|
||||
return mod;
|
||||
};
|
||||
|
||||
// 세션 저장소 흉내 — 화면 코드는 window.sessionStorage 만 쓴다.
|
||||
const store = new Map();
|
||||
global.window = {
|
||||
sessionStorage: {
|
||||
@@ -43,39 +61,36 @@ global.window = {
|
||||
},
|
||||
};
|
||||
|
||||
const { createCulvertOptionWriter } = require(process.argv[3]);
|
||||
const EDIT_KEY = "edit";
|
||||
const writer = createCulvertOptionWriter(
|
||||
const api = require(process.argv[3]);
|
||||
const writer = api.createCulvertOptionWriter(
|
||||
() => "p1",
|
||||
() => "opt",
|
||||
undefined,
|
||||
() => "move",
|
||||
() => EDIT_KEY,
|
||||
);
|
||||
const read = () => JSON.parse(store.get(EDIT_KEY) ?? '{"adds":[],"removes":[]}');
|
||||
const steps = {};
|
||||
|
||||
writer.queueAdd(120, "pipe");
|
||||
writer.queueAdd(140);
|
||||
steps.afterTwoAdds = read();
|
||||
// ① 관을 넣고 빼는 길이 저장기에 남아 있으면 안 된다.
|
||||
const methods = Object.keys(writer).sort();
|
||||
|
||||
// 방금 넣은 자리를 다시 빼면 **추가 예약만** 사라진다(정본에는 아직 없다).
|
||||
writer.queueRemove(140);
|
||||
steps.afterUndoAdd = read();
|
||||
// ② 합치기 전 세션에 남아 있던 이동 예약은 저장 때 흘려보낸다.
|
||||
store.set("move", JSON.stringify({ "120.00": 135 }));
|
||||
|
||||
// 정본에 있던 관을 빼면 삭제 예약으로 남는다.
|
||||
writer.queueRemove(200);
|
||||
steps.afterRemove = read();
|
||||
|
||||
// 뺀 자리를 다시 넣으면 삭제 예약만 걷어 낸다(새로 만들지 않는다).
|
||||
writer.queueAdd(200);
|
||||
steps.afterUndoRemove = read();
|
||||
|
||||
// 같은 자리를 두 번 넣어도 하나만 남는다.
|
||||
writer.queueAdd(120);
|
||||
steps.afterDuplicateAdd = read();
|
||||
|
||||
writeFileSync(process.argv[2], JSON.stringify(steps));
|
||||
api
|
||||
.flushCulvertOptions("p1", "opt", "move")
|
||||
.then(() => {
|
||||
writeFileSync(
|
||||
process.argv[2],
|
||||
JSON.stringify({
|
||||
methods,
|
||||
savedChainages: (saved[0] ?? []).map((point) => point.chainage_m),
|
||||
moveLeft: store.get("move") ?? null,
|
||||
}),
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
writeFileSync(process.argv[2], JSON.stringify({ error: String(error) }));
|
||||
process.exitCode = 1;
|
||||
});
|
||||
"""
|
||||
|
||||
|
||||
@@ -112,24 +127,13 @@ def _run(tmp_path: Path) -> dict:
|
||||
return json.loads(result.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def test_추가와_삭제_예약이_서로를_걷어_낸다(tmp_path: Path) -> None:
|
||||
steps = _run(tmp_path)
|
||||
def test_관_목록을_담는_자리는_하나뿐이다(tmp_path: Path) -> None:
|
||||
result = _run(tmp_path)
|
||||
assert "error" not in result, result.get("error")
|
||||
|
||||
# 넣은 둘이 그대로 쌓인다.
|
||||
assert [entry["chainage_m"] for entry in steps["afterTwoAdds"]["adds"]] == [120.0, 140.0]
|
||||
assert steps["afterTwoAdds"]["adds"][0]["facility"] == "pipe"
|
||||
assert steps["afterTwoAdds"]["removes"] == []
|
||||
# ① 저장기에는 구간값 예약과 내보내기만 있다 — 관을 넣고 빼는 길은 스냅샷 한 곳뿐이다.
|
||||
assert result["methods"] == ["flush", "queue"]
|
||||
|
||||
# 방금 넣은 자리를 빼면 추가 예약만 사라지고 삭제 예약은 안 생긴다.
|
||||
assert [entry["chainage_m"] for entry in steps["afterUndoAdd"]["adds"]] == [120.0]
|
||||
assert steps["afterUndoAdd"]["removes"] == []
|
||||
|
||||
# 정본에 있던 관은 삭제 예약으로 남는다.
|
||||
assert steps["afterRemove"]["removes"] == ["200.00"]
|
||||
|
||||
# 뺀 자리를 다시 넣으면 삭제 예약만 걷힌다 — 새 관을 만들지 않는다.
|
||||
assert steps["afterUndoRemove"]["removes"] == []
|
||||
assert [entry["chainage_m"] for entry in steps["afterUndoRemove"]["adds"]] == [120.0]
|
||||
|
||||
# 같은 자리를 두 번 넣어도 하나만 남는다.
|
||||
assert [entry["chainage_m"] for entry in steps["afterDuplicateAdd"]["adds"]] == [120.0]
|
||||
# ② 옛 세션의 이동 예약은 저장 때 반영되고 비워진다.
|
||||
assert result["savedChainages"] == [135, 200]
|
||||
assert result["moveLeft"] is None
|
||||
|
||||
Reference in New Issue
Block a user