Browse Source

fix: resolve crashes during video-only file loading

- Added null check for appState.vizData in sync.js to prevent TypeError during visualization reset.
- Implemented key validation guards in db.js manual offset functions to avoid IndexedDB DataErrors.
- Updated fileLoader.js to bypass manual offset lookup when no JSON filename is available.
- Added regression test 'tests/regression_video_only.test.js' to verify stable partial load paths.
refactor/sync-centralize
RUSHIL AMBARISH KADU 2 months ago
parent
commit
2ebeaf0fab
  1. 8
      steps/src/db.js
  2. 6
      steps/src/fileLoader.js
  3. 15
      steps/src/sync.js
  4. 73
      steps/tests/regression_video_only.test.js

8
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;
}

6
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`);

15
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 ---

73
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 += `<p class="pass"><b>PASS:</b> ${description}</p>`;
} catch (error) {
console.error(`❌ FAIL: ${description}`, error);
resultsEl.innerHTML += `<p class="fail"><b>FAIL:</b> ${description}<br><pre>${error.stack || error}</pre></p>`;
}
})();
}
// 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");
}
});
Loading…
Cancel
Save