- b_project_switch: currentProjectId(탭 세션 먼저 · 새 탭 = 마지막 값) · setCurrentProject · forgetProject · leaveProject(그 프로젝트 세션 칸 · 준비 표식 · 3D 보관함 몫) · reloadIfOtherProject(다른 프로젝트 화면 = 새로 엶) · guardProjectLeave · askProjectLeave([취소 · 폐기 · 저장] 한 번) · linkRouter(라우터가 이어 줌) · 화면 모듈은 부를 때만 들임
- b_asset_cache purgeProject · b_page_state 등록표 D02 · E 일곱 칸(옛 키 한 번 옮김) · router isProjectRoute
- 가드 helper: design_leave = 공용 가드 위 껍질(B05 고침 지문에 D · E 칸 안 섞임) · group_workflow { dirty, save, discard } · b_workflow_nav · b_page_scaffold · overlay · design_store · design_page 직접 읽기 → currentProjectId
- 시험 test_401_project_isolation 새로(탭 둘 흉내 · 떠남 · 보관함 · 가드 · 직접 읽기 한 곳) · 290_1 시드에 D02 · E 옛 키
- 낡은 시험 고침: 298_3 · 346_4 · 384_s · 520_3 헬퍼 = 공용 가드 · 탭 프로젝트 흉내 더함 · 298_3 탭 닫기 듣개 자리 b_project_switch 로 · 302_1 라우터 판별 글 isProjectRoute 꼴로 · 343_3 헬퍼 가짜 IndexedDB 내보냄
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
121 lines
4.4 KiB
JavaScript
121 lines
4.4 KiB
JavaScript
/* 298-3 옛 페이지 풀림 단위검증 헬퍼 — `ui_template_window_watch` · `ui_template_overlay_drag` ·
|
|
* `design_leave` 를 그 자리에서 트랜스파일 · 페이지를 다섯 번 다시 세운 뒤 창 듣개 수 · 옛 노드 풀림.
|
|
* `node --expose-gc` 로 부름 · 결과 JSON 을 stdout 으로.
|
|
*/
|
|
const path = require("path");
|
|
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
|
const { makeLoader, memoryStorage } = require("./helper_ts_stub_loader.cjs");
|
|
|
|
const ROOT = path.join(__dirname, "..", "..");
|
|
|
|
const storage = memoryStorage();
|
|
const win = new EventTarget();
|
|
const doc = new EventTarget();
|
|
const counts = { window: 0, document: 0 };
|
|
for (const [name, target] of [
|
|
["window", win],
|
|
["document", doc],
|
|
]) {
|
|
const add = target.addEventListener.bind(target);
|
|
target.addEventListener = (...args) => {
|
|
counts[name] += 1;
|
|
return add(...args);
|
|
};
|
|
}
|
|
Object.assign(win, {
|
|
sessionStorage: storage,
|
|
localStorage: storage,
|
|
setTimeout,
|
|
innerWidth: 1500,
|
|
innerHeight: 900,
|
|
});
|
|
globalThis.window = win;
|
|
globalThis.document = doc;
|
|
globalThis.sessionStorage = storage;
|
|
globalThis.localStorage = storage;
|
|
globalThis.requestAnimationFrame = (fn) => (fn(), 1);
|
|
|
|
let guard = null;
|
|
const STUBS = {
|
|
"@config/config_frontend": {
|
|
ROUTES: { B05_PROFILE: "/b05-profile", B06_SECTION: "/b06-section" },
|
|
},
|
|
"@ui/ui_template_locale": { t: (key) => key },
|
|
"@ui/ui_template_leave_dialog": { leaveOrStay: async () => "stay" },
|
|
"../router": { currentRoute: () => "/b05-profile", setLeaveGuard: (fn) => (guard = fn) },
|
|
};
|
|
|
|
const { loadTs } = makeLoader({
|
|
root: ROOT,
|
|
target: ts.ScriptTarget.ES2022,
|
|
requireFor: (full) => (request) => {
|
|
if (request in STUBS) return STUBS[request];
|
|
if (request.startsWith("@ui/"))
|
|
return loadTs(path.join(ROOT, "ui_template", `${request.slice(4)}.ts`));
|
|
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
|
|
return require(request);
|
|
},
|
|
});
|
|
|
|
const drag = loadTs(path.join(ROOT, "ui_template", "ui_template_overlay_drag.ts"));
|
|
// 공용 떠남 가드(401) — 라우터 흉내를 이어 둠(같은 한 벌을 design_leave 가 부름)
|
|
loadTs(path.join(ROOT, "A00_Common", "b_project_switch.ts")).linkRouter(STUBS["../router"]);
|
|
const leave = loadTs(path.join(ROOT, "A00_Common", "design", "design_leave.ts"));
|
|
|
|
let settles = 0;
|
|
function fakeElement() {
|
|
const node = new EventTarget();
|
|
node.style = {};
|
|
node.isConnected = true;
|
|
node.offsetWidth = 300;
|
|
node.offsetHeight = 40;
|
|
node.classList = {
|
|
contains: () => false,
|
|
add() {},
|
|
toggle() {},
|
|
remove: () => (settles += 1), // detach 가 부름 = 자리 다시 앉힘
|
|
};
|
|
node.querySelector = () => null;
|
|
node.getBoundingClientRect = () => ({ left: 10, top: 80 });
|
|
return node;
|
|
}
|
|
|
|
/** 페이지 한 벌 — 떠남 확인 + 진행단계 패널 · 옛 페이지 노드의 약한 손잡이를 돌려줌. */
|
|
function mountPage() {
|
|
const page = fakeElement();
|
|
const panel = fakeElement();
|
|
page.panel = panel; // 페이지 나무 = 패널을 품음
|
|
leave.guardDesignLeave("p1", page, async () => {});
|
|
page.dragHandle = drag.makePanelDraggable(panel, fakeElement(), "pos");
|
|
return { page: new WeakRef(page), panel: new WeakRef(panel), keep: page };
|
|
}
|
|
|
|
(async () => {
|
|
const out = {};
|
|
let live = mountPage();
|
|
out.afterFirst = { ...counts };
|
|
const old = [];
|
|
for (let index = 0; index < 4; index += 1) {
|
|
live.keep.isConnected = false; // 다시 세움 — 옛 페이지는 화면에서 빠지고 버려짐
|
|
old.push({ page: live.page, panel: live.panel });
|
|
live = mountPage();
|
|
}
|
|
out.afterFive = { ...counts };
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
globalThis.gc();
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
globalThis.gc();
|
|
out.oldAlive = old.filter((pair) => pair.page.deref() || pair.panel.deref()).length;
|
|
out.liveAlive = Boolean(live.page.deref() && live.panel.deref());
|
|
// 살아 있는 패널은 창 크기 바뀜을 여전히 들음 · 버려진 패널은 안 부름
|
|
settles = 0;
|
|
win.dispatchEvent(new Event("resize"));
|
|
out.resizeSettles = settles;
|
|
// 떠남 확인 — 살아 있는 페이지만(고침 없음 = 안 막음)
|
|
const event = new Event("beforeunload", { cancelable: true });
|
|
win.dispatchEvent(event);
|
|
out.unloadBlocked = event.defaultPrevented;
|
|
out.guardSet = typeof guard === "function";
|
|
process.stdout.write(JSON.stringify(out));
|
|
})();
|