diff --git a/steps/src/db.js b/steps/src/db.js index 613afdb..096e170 100644 --- a/steps/src/db.js +++ b/steps/src/db.js @@ -92,8 +92,8 @@ export function saveFileWithMetadata(key, file) { export function saveManualOffset(filename, offset) { return new Promise(async (resolve, reject) => { const database = await getDB(); - if (!database) { - resolve(); // Fail silently if DB is not available + if (!database || !filename) { + resolve(); // Fail silently if DB is not available or filename is missing return; } const transaction = database.transaction(["manualOffsets"], "readwrite"); @@ -115,7 +115,7 @@ export function saveManualOffset(filename, offset) { export function loadManualOffset(filename) { return new Promise(async (resolve) => { const database = await getDB(); - if (!database) { + if (!database || !filename) { resolve(null); return; } @@ -142,7 +142,7 @@ export function loadManualOffset(filename) { export function deleteManualOffset(filename) { return new Promise(async (resolve) => { const database = await getDB(); - if (!database) { + if (!database || !filename) { resolve(); return; } diff --git a/steps/src/fileLoader.js b/steps/src/fileLoader.js index 904b164..7d2390c 100644 --- a/steps/src/fileLoader.js +++ b/steps/src/fileLoader.js @@ -401,10 +401,8 @@ async function calculateAndSetOffset() { } // 1. Try to load a manually saved offset for this specific file pair. - // We use the JSON filename as the primary key, but ideally, it should be a combo. - // For now, sticking to the user request: "if the user uploads a similarly named file". - // We'll use the JSON filename as the key. - const savedOffset = await loadManualOffset(appState.jsonFilename); + // We use the JSON filename as the primary key. + const savedOffset = appState.jsonFilename ? await loadManualOffset(appState.jsonFilename) : null; if (savedOffset !== null) { console.log(`Applying saved manual offset: ${savedOffset}ms`); diff --git a/steps/src/sync.js b/steps/src/sync.js index 5742c14..5e22eb1 100644 --- a/steps/src/sync.js +++ b/steps/src/sync.js @@ -29,9 +29,18 @@ import { saveManualOffset } from "./db.js"; export function resetVisualization() { appState.isPlaying = false; playPauseBtn.textContent = "Play"; - const numFrames = appState.vizData.radarFrames.length; - timelineSlider.max = numFrames > 0 ? numFrames - 1 : 0; - updateFrame(0, true); // Update to the first frame and force video seek + + if (appState.vizData) { + const numFrames = appState.vizData.radarFrames.length; + timelineSlider.max = numFrames > 0 ? numFrames - 1 : 0; + updateFrame(0, true); // Update to the first frame and force video seek + } else { + timelineSlider.max = 0; + timelineSlider.value = 0; + if (videoPlayer.src) { + videoPlayer.currentTime = 0; + } + } } // --- NEW Playback Control Functions --- diff --git a/steps/tests/regression_video_only.test.js b/steps/tests/regression_video_only.test.js new file mode 100644 index 0000000..bc5e2ac --- /dev/null +++ b/steps/tests/regression_video_only.test.js @@ -0,0 +1,73 @@ +import { handleFiles } from "../src/fileLoader.js"; +import { appState } from "../src/state.js"; +import { initDB } from "../src/db.js"; + +const resultsEl = document.getElementById('results'); + +function test(description, testFunction) { + (async () => { + try { + await testFunction(); + console.log(`✅ PASS: ${description}`); + resultsEl.innerHTML += `
PASS: ${description}
`; + } catch (error) { + console.error(`❌ FAIL: ${description}`, error); + resultsEl.innerHTML += `FAIL: ${description}
${error.stack || error}`;
+ }
+ })();
+}
+
+// Mocking dependencies for regression test
+async function setupMocks() {
+ return new Promise((resolve) => {
+ initDB(() => {
+ console.log("Test DB initialized");
+ resolve();
+ });
+ });
+}
+
+URL.createObjectURL = () => "blob:mock-video-url";
+URL.revokeObjectURL = () => {};
+
+window.p5 = class MockP5 {
+ constructor(sketch) { sketch(this); }
+ createCanvas() { return { parent: () => {} }; }
+ noLoop() {}
+ redraw() {}
+};
+
+test("Regression: handleFiles should not crash when loading only a video", async () => {
+ // 1. Setup - clear vizData
+ appState.vizData = null;
+ appState.videoFilename = "";
+
+ const videoPlayer = document.getElementById('video-player');
+ const mockVideoFile = new File(['fake video'], "test.mp4", { type: "video/mp4" });
+
+ // Mock video events
+ const triggerEvents = () => {
+ setTimeout(() => {
+ videoPlayer.dispatchEvent(new Event('loadedmetadata'));
+ videoPlayer.dispatchEvent(new Event('canplaythrough'));
+ }, 10);
+ };
+
+ const observer = new MutationObserver(triggerEvents);
+ observer.observe(videoPlayer, { attributes: true, attributeFilter: ['src'] });
+
+ // 2. Execution
+ try {
+ await handleFiles([mockVideoFile]);
+ } catch (e) {
+ throw new Error(`Crash detected during video-only load: ${e.message}`);
+ }
+
+ // 3. Verification
+ await new Promise(resolve => setTimeout(resolve, 100));
+ observer.disconnect();
+
+ if (appState.videoFilename !== "test.mp4") {
+ throw new Error("Video filename not set correctly in appState");
+ }
+});