feat: protect unsaved camera work

This commit is contained in:
Codex
2026-08-28 13:09:55 +09:00
parent 513de2b51f
commit b03eccde50
10 changed files with 192 additions and 17 deletions
+46
View File
@@ -44,6 +44,7 @@ test("Acoustic Camera가 8×8 CPU 실행·결과 저장·stale·재실행 흐름
const objectUrls = [];
const originalAnchorClick = windowObject.HTMLAnchorElement.prototype.click;
const originalRevoke = windowObject.URL.revokeObjectURL.bind(windowObject.URL);
const originalConfirm = windowObject.confirm.bind(windowObject);
windowObject.HTMLAnchorElement.prototype.click = function captureDownload() {
downloads.push({ fileName: this.download, url: this.href });
};
@@ -73,6 +74,10 @@ test("Acoustic Camera가 8×8 CPU 실행·결과 저장·stale·재실행 흐름
"8×8 CPU 입력 검증",
);
assertEqual(documentObject.querySelector("#grid-preset").value, "custom", "custom grid preset");
assert(
documentObject.documentElement.dataset.unsavedWork.includes("Experiment"),
"edited Experiment is marked unsaved",
);
documentObject.querySelector("#run-button").click();
await waitFor(
@@ -85,6 +90,20 @@ test("Acoustic Camera가 8×8 CPU 실행·결과 저장·stale·재실행 흐름
assert(documentObject.querySelectorAll("#metric-grid .metric-card").length >= 9, "result metrics rendered");
assert(!documentObject.querySelector("#metric-grid").classList.contains("is-hidden"), "metrics visible");
assert(documentObject.querySelector("#result-badge").textContent.includes("CPU"), "backend badge rendered");
assert(documentObject.documentElement.dataset.unsavedWork.includes("Result"), "completed Result is unsaved");
assert(
documentObject.documentElement.dataset.unsavedWork.includes("비교 기록"),
"comparison summary is unsaved",
);
let replacementConfirmCount = 0;
windowObject.confirm = () => {
replacementConfirmCount += 1;
return false;
};
documentObject.querySelector("#run-button").click();
await waitFor(() => replacementConfirmCount === 1, "unsaved Result replacement confirmation");
assertEqual(documentObject.documentElement.dataset.runState, "done", "declined replacement keeps result");
windowObject.confirm = originalConfirm;
await nextPaint(windowObject);
assert(canvas.toDataURL() !== initialCanvas, "result heatmap changed the scene canvas");
@@ -94,6 +113,10 @@ test("Acoustic Camera가 8×8 CPU 실행·결과 저장·stale·재실행 흐름
"Result JSON download",
);
const resultDocument = await fetch(resultDownload.url).then((response) => response.json());
assert(
!documentObject.documentElement.dataset.unsavedWork.includes("Result"),
"Result export clears Result dirty state",
);
const [scene, experiment] = await Promise.all([
loadJson("/assets/sample-scenes/single-tone.scene.json"),
loadJson("/assets/sample-scenes/single-tone.experiment.json"),
@@ -156,7 +179,30 @@ test("Acoustic Camera가 8×8 CPU 실행·결과 저장·stale·재실행 흐름
const historyText = documentObject.querySelector("#history-list").textContent;
assert(historyText.includes("rectangular baseline"), "baseline history retained");
assert(historyText.includes("Fermat spiral"), "comparison array history added");
documentObject.querySelector("#export-history-button").click();
const historyDownload = await waitFor(
() => downloads.find((download) => download.fileName.endsWith(".comparison-history.json")),
"comparison history download",
);
const historyDocument = await fetch(historyDownload.url).then((response) => response.json());
assertEqual(historyDocument.kind, "micarrsim.comparison-history", "comparison history kind");
assertEqual(historyDocument.items.length, 2, "both comparison summaries exported");
assert(
!documentObject.documentElement.dataset.unsavedWork.includes("비교 기록"),
"history export clears comparison dirty state",
);
documentObject.querySelector("#export-result-button").click();
documentObject.querySelector("#export-experiment-button").click();
await waitFor(
() => !documentObject.documentElement.dataset.unsavedWork.includes("Result") &&
!documentObject.documentElement.dataset.unsavedWork.includes("Experiment"),
"final Camera documents saved",
);
assertEqual(documentObject.documentElement.dataset.unsavedWork, "", "all Camera work is saved");
} finally {
windowObject.confirm = originalConfirm;
windowObject.HTMLAnchorElement.prototype.click = originalAnchorClick;
windowObject.URL.revokeObjectURL = originalRevoke;
for (const url of objectUrls) originalRevoke(url);