/** * Value Change Animation Tests * Tests for animation behavior when gauge value changes */ describe('Value Change Animation', () => { describe('Animation Start Position', () => { it('should start animation from last rendered progress when available', () => { const lastRenderedProgress = 0.5; const prevPercent = 0; const isFirstAnimation = false; const hasLastRenderedProgress = lastRenderedProgress !== undefined && !isFirstAnimation; const animationStartPercent = hasLastRenderedProgress ? lastRenderedProgress : prevPercent; expect(animationStartPercent).toBe(0.5); }); it('should start animation from prevPercent for first animation', () => { const lastRenderedProgress = 0.5; const prevPercent = 0; const isFirstAnimation = true; const hasLastRenderedProgress = lastRenderedProgress !== undefined && !isFirstAnimation; const animationStartPercent = hasLastRenderedProgress ? lastRenderedProgress : prevPercent; expect(animationStartPercent).toBe(0); }); it('should start animation from prevPercent when no last rendered progress', () => { const lastRenderedProgress = undefined; const prevPercent = 0.3; const isFirstAnimation = false; const hasLastRenderedProgress = lastRenderedProgress !== undefined && !isFirstAnimation; const animationStartPercent = hasLastRenderedProgress ? lastRenderedProgress : prevPercent; expect(animationStartPercent).toBe(0.3); }); }); describe('Animation Duration', () => { it('should use configured duration for all animations', () => { const configuredDuration = 3000; expect(configuredDuration).toBe(3000); }); it('should respect short durations for responsive updates', () => { const configuredDuration = 200; expect(configuredDuration).toBe(200); }); }); describe('Animation Delay', () => { it('should use configured delay for first animation', () => { const configuredDelay = 500; const isFirstAnimation = true; const effectiveDelay = isFirstAnimation ? configuredDelay : 0; expect(effectiveDelay).toBe(500); }); it('should skip delay for subsequent animations', () => { const configuredDelay = 500; const isFirstAnimation = false; const effectiveDelay = isFirstAnimation ? configuredDelay : 0; expect(effectiveDelay).toBe(0); }); }); describe('Percentage Calculation', () => { it('should calculate percentage correctly', () => { const minValue = 0; const maxValue = 100; const value = 50; const percentage = (value - minValue) / (maxValue - minValue); expect(percentage).toBe(0.5); }); it('should handle non-zero min values', () => { const minValue = 20; const maxValue = 80; const value = 50; const percentage = (value - minValue) / (maxValue - minValue); expect(percentage).toBe(0.5); }); it('should handle negative values', () => { const minValue = -100; const maxValue = 100; const value = 0; const percentage = (value - minValue) / (maxValue - minValue); expect(percentage).toBe(0.5); }); }); describe('prevProgress Preservation (Bug Fix)', () => { /** * This test catches the critical bug where setupContext was resetting * prevProgress to 0 on every call, causing animations to always start from 0 * instead of the current visual position. */ it('should preserve prevProgress when rebuilding context (not first animation)', () => { // Simulate existing context with prevProgress at 0.6 (60% through animation) const existingContext = { prevProgress: 0.6 }; const isFirstAnimation = false; // The new setupContext should preserve this value const existingPrevProgress = existingContext?.prevProgress; const newPrevProgress = isFirstAnimation ? 0 : (existingPrevProgress ?? 0); expect(newPrevProgress).toBe(0.6); }); it('should reset prevProgress to 0 for first animation only', () => { const existingContext = { prevProgress: 0.6 }; const isFirstAnimation = true; // First animation should start from 0 regardless of existing progress const existingPrevProgress = existingContext?.prevProgress; const newPrevProgress = isFirstAnimation ? 0 : (existingPrevProgress ?? 0); expect(newPrevProgress).toBe(0); }); it('should handle undefined existing context gracefully', () => { const existingContext = undefined as any; const isFirstAnimation = false; const existingPrevProgress = existingContext?.prevProgress; const newPrevProgress = isFirstAnimation ? 0 : (existingPrevProgress ?? 0); // Should fall back to 0 when no existing context expect(newPrevProgress).toBe(0); }); it('should use lastRenderedProgress as animation start when available', () => { const lastRenderedProgress = 0.75; const prevPercent = 0.5; const isFirstAnimation = false; const hasLastRenderedProgress = lastRenderedProgress !== undefined && !isFirstAnimation; const animationStartPercent = hasLastRenderedProgress ? lastRenderedProgress : prevPercent; // Should use lastRenderedProgress (0.75) not prevPercent (0.5) expect(animationStartPercent).toBe(0.75); }); it('should NOT use lastRenderedProgress for first animation', () => { const lastRenderedProgress = 0.75; const prevPercent = 0; const isFirstAnimation = true; const hasLastRenderedProgress = lastRenderedProgress !== undefined && !isFirstAnimation; const animationStartPercent = hasLastRenderedProgress ? lastRenderedProgress : prevPercent; // First animation should use prevPercent (0) not lastRenderedProgress expect(animationStartPercent).toBe(0); }); }); });