- 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>
113 lines
4.9 KiB
Python
113 lines
4.9 KiB
Python
"""401 프로젝트마다 따로 동작(요구 400~403 · PLAN 572).
|
|
|
|
- 지금 프로젝트 = 탭마다(탭 세션 칸 먼저 · 새 탭 = 마지막 값) · 다른 프로젝트 화면을 그리려 하면 새로 엶
|
|
- 떠남 = 그 프로젝트 세션 칸 · 준비 표식 · 3D 보관함 몫만 지움 · 밖 칸 · 취향 · CAD 복구본 · 다른 탭은 그대로
|
|
- 떠남 물음 = 화면에 붙은 가드 {dirty, save} 를 모아 [취소 · 폐기 · 저장] 한 번
|
|
`helper_401_project_isolation.cjs` 가 진짜 TS 를 가짜 저장소 · 가짜 IndexedDB 위에서 돌림.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
X = "11111111-aaaa-4aaa-8aaa-000000000001"
|
|
Y = "22222222-bbbb-4bbb-8bbb-000000000002"
|
|
|
|
#: 아직 직접 읽는 B · D · E 페이지(572-2 · 572-3 창 몫) — 고치면 여기서 뺌.
|
|
PENDING = {
|
|
"B03_FileInput/B03_FileInput_UI_Page.ts",
|
|
"B03_FileInput/B03_FileInput_UI_Page_Flow.ts",
|
|
"B03_FileInput/B03_FileInput_UI_PlanImport.ts",
|
|
"B04_PreProcess/B04_PreProcess_UI_Page.ts",
|
|
"B07_DesignDetail/B07_DesignDetail_UI_Page.ts",
|
|
"B08_Quantity/B08_Quantity_UI_Page.ts",
|
|
"B09_Estimation/B09_Estimation_UI_Haul_Modal.ts",
|
|
"B09_Estimation/B09_Estimation_UI_Shell.ts",
|
|
"B09_Estimation/B09_Estimation_UI_Tabs_Base_Quote.ts",
|
|
"B09_Estimation/B09_Estimation_UI_Tabs_Bill_Link.ts",
|
|
"B09_Estimation/draft/B09_Draft_Print_Page.ts",
|
|
"B11_Status/B11_Status_UI_Loading.ts",
|
|
"D02_PlanBase/D02_PlanBase_UI_Page.ts",
|
|
"D03_PlanAnalysis/D03_PlanAnalysis_UI_Page.ts",
|
|
"D04_PlanConfirm/D04_PlanConfirm_UI_Page.ts",
|
|
"E02_StabGround/E02_StabGround_UI_Page.ts",
|
|
"E03_StabSection/E03_StabSection_UI_Page.ts",
|
|
"E04_Stab3D/E04_Stab3D_UI_Page.ts",
|
|
"E05_StabReport/E05_StabReport_UI_Page.ts",
|
|
"E06_StabRock/E06_StabRock_UI_Page.ts",
|
|
"E07_StabArea/E07_StabArea_UI_Page.ts",
|
|
}
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def out():
|
|
proc = subprocess.run(
|
|
["node", os.path.join(HERE, "helper_401_project_isolation.cjs")],
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
timeout=60,
|
|
)
|
|
assert proc.returncode == 0, proc.stderr
|
|
return json.loads(proc.stdout)
|
|
|
|
|
|
def test_탭마다_지금_프로젝트(out):
|
|
assert out["newTabStartsWithLast"] == X
|
|
assert out["tabs"] == {"a": X, "b": Y, "fresh": Y, "last": Y}
|
|
|
|
|
|
def test_다른_프로젝트_화면은_새로_엶(out):
|
|
assert out["reload"] == {"first": False, "same": False, "other": True, "count": 1}
|
|
|
|
|
|
def test_떠남은_그_프로젝트_몫만_지움(out):
|
|
leave = out["leave"]
|
|
assert leave["keys"] == leave["expected"] # B · D · E · 옛 키 · 준비 표식 0 · 밖 칸 그대로
|
|
assert leave["entered"] == X # 탭 프로젝트 표식은 남김
|
|
assert leave["local"] == ["1", "1"] # 취향 · CAD 복구본
|
|
assert leave["idb"] == [f"{Y}|u1"] # 보관함 = X 줄만 지움
|
|
assert leave["otherTabKept"] == "1" # 다른 탭 세션은 안 건드림
|
|
|
|
|
|
def test_떠남_물음_한_번(out):
|
|
g = out["guard"]
|
|
assert g["armed"] and g["cleanLeave"] and not g["cleanUnload"] and g["asked0"] == 0
|
|
assert g["dirtyUnload"] and g["cancel"] is False and g["save"] is True
|
|
assert g["messages"] == ["둘째 글", "둘째 글"] # 고친 가드 하나 = 그 글
|
|
assert g["saveFail"] is False and g["discard"] is True
|
|
assert g["saves"] == [2, 1] and g["discards"] == 1 # 고친 가드만 저장
|
|
assert g["samePage"] and g["reArmed"] and g["samePageAsked"] == 0
|
|
|
|
|
|
def test_지금_프로젝트_직접_읽기는_한_곳():
|
|
hits = set()
|
|
tracked = subprocess.run(
|
|
["git", "ls-files", "*.ts"], cwd=ROOT, capture_output=True, text=True, check=True
|
|
).stdout.split() # storage · node_modules 는 훑지 않음
|
|
for rel in tracked:
|
|
text = (ROOT / rel).read_text(encoding="utf-8", errors="ignore")
|
|
if re.search(r"localStorage\.getItem\(\s*CURRENT_PROJECT_ID_KEY", text):
|
|
hits.add(rel)
|
|
assert hits - PENDING == {"A00_Common/b_project_switch.ts"} - PENDING
|
|
assert "A00_Common/b_project_switch.ts" in hits
|
|
|
|
|
|
def test_공용이_도우미를_씀():
|
|
router = (ROOT / "A00_Common/router.ts").read_text(encoding="utf-8")
|
|
assert "export const isProjectRoute" in router and "reloadIfOtherProject()" in router
|
|
group = (ROOT / "A00_Common/group_workflow.ts").read_text(encoding="utf-8")
|
|
assert "guardProjectLeave(root, { dirty, save, discard, message })" in group
|
|
leave = (ROOT / "A00_Common/design/design_leave.ts").read_text(encoding="utf-8")
|
|
assert "guardProjectLeave(root, {" in leave
|
|
state = (ROOT / "A00_Common/b_page_state.ts").read_text(encoding="utf-8")
|
|
for name in ("d02-points", "e02-ground", "e03-prob", "e04-settings", "e05-report"):
|
|
assert f'"{name}": {{ bucket: "draft", scope: "project"' in state
|
|
assert '"e06-rock"' in state and '"e07-settings"' in state
|