Skip to content

Commit 55ae2ea

Browse files
committed
test: add v2.1.0 regression tests
- Add 18 new tests for v2.1.0 features - Title display duration tests (seconds not ms, defaults) - Flash on meeting start tests (default false, boolean handling) - Startup race condition tests (500ms polling, status transitions) - INIT vs INVALID_URL status text tests - Update test class to use 500ms polling interval - Add INVALID_URL case to mock getStatusText
1 parent cd15f6e commit 55ae2ea

File tree

2 files changed

+206
-4
lines changed

2 files changed

+206
-4
lines changed

tests/actions.test.ts

Lines changed: 205 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ vi.mock('../src/services/calendar-service', () => ({
5454
},
5555
getStatusText: vi.fn((status: string) => {
5656
switch (status) {
57-
case 'INIT': return 'Loading\niCal';
57+
case 'INIT': return 'Please\nSetup';
5858
case 'LOADING': return 'Loading\niCal';
59+
case 'INVALID_URL': return 'Please\nSetup';
5960
case 'LOADED': return '';
6061
default: return 'Error';
6162
}
@@ -116,7 +117,7 @@ class TestAction {
116117
const statusText = getStatusText(calendarCache.status);
117118
action.setTitle(statusText);
118119
}
119-
}, 2000);
120+
}, 500);
120121
}
121122

122123
async onWillDisappear(ev: any): Promise<void> {
@@ -247,7 +248,7 @@ describe('BaseAction', () => {
247248

248249
// Simulate cache becoming available
249250
(calendarCache as any).status = 'LOADED';
250-
vi.advanceTimersByTime(2000);
251+
vi.advanceTimersByTime(500);
251252

252253
expect(testAction.getInterval()).toBeDefined();
253254
});
@@ -399,3 +400,204 @@ describe('Action Settings Integration', () => {
399400
expect(mockSettings.flashOnMeetingStart).toBe(false);
400401
});
401402
});
403+
404+
describe('v2.1.0 Feature Tests', () => {
405+
let testAction: TestAction;
406+
let mockAction: any;
407+
408+
beforeEach(() => {
409+
vi.clearAllMocks();
410+
vi.useFakeTimers();
411+
412+
// Reset mock settings to defaults
413+
mockSettings.titleDisplayDuration = 15;
414+
mockSettings.flashOnMeetingStart = true;
415+
416+
mockAction = {
417+
setTitle: vi.fn(),
418+
setImage: vi.fn(),
419+
showOk: vi.fn()
420+
};
421+
testAction = new TestAction();
422+
423+
// Reset cache
424+
(calendarCache as any).version = 1;
425+
(calendarCache as any).status = 'LOADED';
426+
(calendarCache as any).events = [];
427+
});
428+
429+
afterEach(() => {
430+
vi.useRealTimers();
431+
});
432+
433+
describe('Title Display Duration', () => {
434+
it('should support all valid duration values (5, 10, 15, 30 seconds)', () => {
435+
const validDurations = [5, 10, 15, 30];
436+
437+
for (const duration of validDurations) {
438+
mockSettings.titleDisplayDuration = duration;
439+
expect(mockSettings.titleDisplayDuration).toBe(duration);
440+
}
441+
});
442+
443+
it('should default to 15 seconds when not set', () => {
444+
// Simulate undefined setting
445+
const settings = { titleDisplayDuration: undefined };
446+
const displayDuration = settings.titleDisplayDuration ?? 15;
447+
expect(displayDuration).toBe(15);
448+
});
449+
450+
it('should return duration in seconds (not milliseconds)', () => {
451+
// The getTitleDisplayDuration method should return seconds
452+
// The caller multiplies by 1000 to get milliseconds
453+
mockSettings.titleDisplayDuration = 5;
454+
455+
// Simulate what getTitleDisplayDuration returns
456+
const durationInSeconds = mockSettings.titleDisplayDuration ?? 15;
457+
458+
// This should be 5 (seconds), not 5000 (milliseconds)
459+
expect(durationInSeconds).toBe(5);
460+
expect(durationInSeconds).toBeLessThan(100); // Sanity check it's not in ms
461+
});
462+
});
463+
464+
describe('Flash on Meeting Start', () => {
465+
it('should be a boolean setting', () => {
466+
expect(typeof mockSettings.flashOnMeetingStart).toBe('boolean');
467+
});
468+
469+
it('should default to false for new installations', () => {
470+
// Per v2.1.0 change, default should be false
471+
// Simulate undefined setting (new installation)
472+
const settings = { flashOnMeetingStart: undefined };
473+
// The default behavior: undefined should be treated as false
474+
const flashEnabled = settings.flashOnMeetingStart === true;
475+
expect(flashEnabled).toBe(false);
476+
});
477+
478+
it('should preserve true when explicitly set', () => {
479+
const settings = { flashOnMeetingStart: true };
480+
const flashEnabled = settings.flashOnMeetingStart === true;
481+
expect(flashEnabled).toBe(true);
482+
});
483+
484+
it('should preserve false when explicitly set', () => {
485+
const settings = { flashOnMeetingStart: false };
486+
const flashEnabled = settings.flashOnMeetingStart === true;
487+
expect(flashEnabled).toBe(false);
488+
});
489+
});
490+
491+
describe('Startup Race Condition Fix', () => {
492+
it('should start timer immediately when cache is already LOADED', async () => {
493+
(calendarCache as any).status = 'LOADED';
494+
await testAction.onWillAppear({ action: mockAction });
495+
496+
// Should have interval set immediately
497+
expect(testAction.getInterval()).toBeDefined();
498+
expect(testAction.getWaitingInterval()).toBeUndefined();
499+
});
500+
501+
it('should start timer immediately when cache status is NO_EVENTS', async () => {
502+
(calendarCache as any).status = 'NO_EVENTS';
503+
await testAction.onWillAppear({ action: mockAction });
504+
505+
expect(testAction.getInterval()).toBeDefined();
506+
expect(testAction.getWaitingInterval()).toBeUndefined();
507+
});
508+
509+
it('should wait and poll when cache is INIT', async () => {
510+
(calendarCache as any).status = 'INIT';
511+
await testAction.onWillAppear({ action: mockAction });
512+
513+
// Should be waiting, not running timer
514+
expect(testAction.getWaitingInterval()).toBeDefined();
515+
expect(testAction.getInterval()).toBeUndefined();
516+
});
517+
518+
it('should wait and poll when cache is LOADING', async () => {
519+
(calendarCache as any).status = 'LOADING';
520+
await testAction.onWillAppear({ action: mockAction });
521+
522+
expect(testAction.getWaitingInterval()).toBeDefined();
523+
expect(testAction.getInterval()).toBeUndefined();
524+
});
525+
526+
it('should show Please Setup for INIT status', async () => {
527+
(calendarCache as any).status = 'INIT';
528+
await testAction.onWillAppear({ action: mockAction });
529+
530+
expect(mockAction.setTitle).toHaveBeenCalledWith('Please\nSetup');
531+
});
532+
533+
it('should show Loading iCal for LOADING status', async () => {
534+
(calendarCache as any).status = 'LOADING';
535+
await testAction.onWillAppear({ action: mockAction });
536+
537+
expect(mockAction.setTitle).toHaveBeenCalledWith('Loading\niCal');
538+
});
539+
540+
it('should start timer when cache transitions from LOADING to LOADED', async () => {
541+
(calendarCache as any).status = 'LOADING';
542+
await testAction.onWillAppear({ action: mockAction });
543+
544+
// Initially waiting
545+
expect(testAction.getWaitingInterval()).toBeDefined();
546+
expect(testAction.getInterval()).toBeUndefined();
547+
548+
// Cache becomes ready
549+
(calendarCache as any).status = 'LOADED';
550+
551+
// Advance time for polling interval (500ms in the fix)
552+
vi.advanceTimersByTime(500);
553+
554+
// Should now have timer running
555+
expect(testAction.getInterval()).toBeDefined();
556+
});
557+
558+
it('should poll at 500ms intervals (fast startup response)', async () => {
559+
(calendarCache as any).status = 'LOADING';
560+
await testAction.onWillAppear({ action: mockAction });
561+
562+
// At 400ms, still waiting
563+
vi.advanceTimersByTime(400);
564+
expect(testAction.getWaitingInterval()).toBeDefined();
565+
566+
// Cache becomes ready
567+
(calendarCache as any).status = 'LOADED';
568+
569+
// At 500ms (100ms more), should detect and start
570+
vi.advanceTimersByTime(100);
571+
expect(testAction.getInterval()).toBeDefined();
572+
});
573+
574+
it('should clear waiting interval when cache becomes available', async () => {
575+
(calendarCache as any).status = 'LOADING';
576+
await testAction.onWillAppear({ action: mockAction });
577+
578+
expect(testAction.getWaitingInterval()).toBeDefined();
579+
580+
(calendarCache as any).status = 'LOADED';
581+
vi.advanceTimersByTime(500);
582+
583+
// Waiting interval should be cleared
584+
expect(testAction.getWaitingInterval()).toBeUndefined();
585+
});
586+
});
587+
588+
describe('INIT vs INVALID_URL Status', () => {
589+
it('should show Please Setup for both INIT and INVALID_URL', () => {
590+
// Both statuses should show the same message to guide user to setup
591+
const initText = getStatusText('INIT');
592+
const invalidUrlText = getStatusText('INVALID_URL');
593+
594+
expect(initText).toBe('Please\nSetup');
595+
expect(invalidUrlText).toBe('Please\nSetup');
596+
});
597+
598+
it('should show Loading iCal only for LOADING status', () => {
599+
const loadingText = getStatusText('LOADING');
600+
expect(loadingText).toBe('Loading\niCal');
601+
});
602+
});
603+
});

tests/calendar-service.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ describe('isValidURL', () => {
6161

6262
describe('getStatusText', () => {
6363
it('should return correct text for INIT status', () => {
64-
expect(getStatusText('INIT')).toBe('Loading\niCal');
64+
expect(getStatusText('INIT')).toBe('Please\nSetup');
6565
});
6666

6767
it('should return correct text for LOADING status', () => {

0 commit comments

Comments
 (0)