fix(sync-md): sync file changes immediately when window is focused

- File changes now sync immediately without debounce when window is focused
- Only use 10-second debounce when window is unfocused
- This provides better UX when actively working in the app
- Updated tests to reflect the new behavior
This commit is contained in:
Johannes Millan 2025-07-11 16:11:58 +02:00
parent fd08e51319
commit 372d4f7be5
2 changed files with 72 additions and 15 deletions

View file

@ -87,7 +87,7 @@ describe('Sync Manager Debounce Behavior', () => {
});
describe('MD to SP sync debouncing', () => {
it('should debounce MD to SP sync for 10 seconds', async () => {
it('should sync immediately when window is focused', async () => {
// Initialize sync manager - it will create file since getFileStats returns null
initSyncManager(mockConfig);
@ -97,6 +97,30 @@ describe('Sync Manager Debounce Behavior', () => {
// Clear mocks after initial sync
jest.clearAllMocks();
// Window is focused by default
// Trigger file change
mockFileChangeCallback();
// Should sync immediately without waiting
await jest.runAllTimersAsync();
expect(mdToSp).toHaveBeenCalledTimes(1);
expect(mdToSp).toHaveBeenCalledWith('- [ ] Test task', mockConfig.projectId);
});
it('should debounce MD to SP sync for 10 seconds when window is not focused', async () => {
// Initialize sync manager
initSyncManager(mockConfig);
// Let initial sync complete
await jest.runAllTimersAsync();
// Clear mocks after initial sync
jest.clearAllMocks();
// Set window as unfocused
mockWindowFocusCallback(false);
// Trigger file change
mockFileChangeCallback();
@ -118,11 +142,35 @@ describe('Sync Manager Debounce Behavior', () => {
expect(mdToSp).toHaveBeenCalledWith('- [ ] Test task', mockConfig.projectId);
});
it('should reset debounce timer on multiple file changes', async () => {
it('should handle multiple file changes when window is focused', async () => {
initSyncManager(mockConfig);
await jest.runAllTimersAsync();
jest.clearAllMocks();
// Window is focused - each file change triggers immediate sync
// First file change
mockFileChangeCallback();
await jest.runAllTimersAsync();
expect(mdToSp).toHaveBeenCalledTimes(1);
// Second file change
mockFileChangeCallback();
await jest.runAllTimersAsync();
// Should be called again (not debounced when focused)
expect(mdToSp).toHaveBeenCalledTimes(2);
});
it('should reset debounce timer on multiple file changes when unfocused', async () => {
initSyncManager(mockConfig);
await jest.runAllTimersAsync();
jest.clearAllMocks();
// Set window as unfocused
mockWindowFocusCallback(false);
// First file change
mockFileChangeCallback();
@ -144,7 +192,7 @@ describe('Sync Manager Debounce Behavior', () => {
// Run all timers and wait for async operations
await jest.runAllTimersAsync();
// Now sync should be called
// Now sync should be called only once
expect(mdToSp).toHaveBeenCalledTimes(1);
});
@ -202,7 +250,10 @@ describe('Sync Manager Debounce Behavior', () => {
await jest.runAllTimersAsync();
jest.clearAllMocks();
// Trigger file change
// Set window as unfocused first
mockWindowFocusCallback(false);
// Trigger file change while unfocused
mockFileChangeCallback();
// Fast forward to complete the sync
@ -213,8 +264,7 @@ describe('Sync Manager Debounce Behavior', () => {
expect(mdToSp).toHaveBeenCalledTimes(1);
// Window loses focus then gains focus
mockWindowFocusCallback(false);
// Window gains focus after sync completed
mockWindowFocusCallback(true);
await jest.runAllTimersAsync();
@ -228,15 +278,12 @@ describe('Sync Manager Debounce Behavior', () => {
await jest.runAllTimersAsync();
jest.clearAllMocks();
// Window starts focused
mockWindowFocusCallback(true);
// Trigger file change
mockFileChangeCallback();
// Window loses focus
// Start with window unfocused
mockWindowFocusCallback(false);
// Trigger file change while unfocused
mockFileChangeCallback();
// Fast forward 5 seconds (half of debounce time)
jest.advanceTimersByTime(5000);
expect(mdToSp).not.toHaveBeenCalled();

View file

@ -78,12 +78,21 @@ const handleFileChange = (config: LocalUserCfg): void => {
clearTimeout(mdToSpDebounceTimer);
}
// If window is focused, sync immediately without debounce
if (isWindowFocused) {
console.log(
'[sync-md] File change detected while window is focused, syncing immediately',
);
handleMdToSpSync(config);
return;
}
// Mark that we have a pending MD to SP sync
pendingMdToSpSync = config;
// Always use 10 second debounce for MD to SP sync
// Use 10 second debounce for MD to SP sync when window is not focused
console.log(
`[sync-md] File change detected, debouncing for ${SYNC_DEBOUNCE_MS_MD_TO_SP}ms (10 seconds), window focused: ${isWindowFocused}`,
`[sync-md] File change detected while window is unfocused, debouncing for ${SYNC_DEBOUNCE_MS_MD_TO_SP}ms (10 seconds)`,
);
mdToSpDebounceTimer = setTimeout(() => {
@ -208,6 +217,7 @@ const setupWindowFocusTracking = (): void => {
// Trigger the sync immediately with the stored config
const configToSync = pendingMdToSpSync;
pendingMdToSpSync = null;
handleMdToSpSync(configToSync);
} else {
console.log(