diff --git a/app/components/navbar.tsx b/app/components/navbar.tsx index 5b466f9b..7af72fae 100644 --- a/app/components/navbar.tsx +++ b/app/components/navbar.tsx @@ -154,10 +154,10 @@ export default function Navbar() { > {mounted ? ( isDark ? ( - + ) : ( {mounted ? ( isDark ? ( - + ) : ( ) diff --git a/components/InteractiveViewer.test.tsx b/components/InteractiveViewer.test.tsx index db2e52ac..009538cf 100644 --- a/components/InteractiveViewer.test.tsx +++ b/components/InteractiveViewer.test.tsx @@ -600,4 +600,4 @@ describe('InteractiveViewer', () => { expect(glowLayer).toBeTruthy(); }); }); -}); +}); \ No newline at end of file diff --git a/components/InteractiveViewer.tsx b/components/InteractiveViewer.tsx index 8fb8d314..dcedb4d1 100644 --- a/components/InteractiveViewer.tsx +++ b/components/InteractiveViewer.tsx @@ -22,7 +22,7 @@ interface ParallaxParticle { } /** Builds a stable set of contribution-square particles for the parallax layer. - * Deterministic math prevents random values from causing SSR/CSR mismatches. */ + * Deterministic math prevents random values from causing SSR/CSR mismatches. */ function buildParticles(): ParallaxParticle[] { const colors = ['#10b981', '#8b5cf6', '#06b6d4', '#3b82f6', '#f59e0b']; return Array.from( @@ -110,6 +110,7 @@ export default function InteractiveViewer({ const activeTooltipRef = useRef(null); const startPointerPos = useRef({ x: 0, y: 0 }); const [mounted, setMounted] = useState(false); + useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect setMounted(true); @@ -455,4 +456,4 @@ export default function InteractiveViewer({ )} ); -} +} \ No newline at end of file diff --git a/components/dashboard/ComparisonStatsCard.test.tsx b/components/dashboard/ComparisonStatsCard.test.tsx index 6931b59b..ca527fc3 100644 --- a/components/dashboard/ComparisonStatsCard.test.tsx +++ b/components/dashboard/ComparisonStatsCard.test.tsx @@ -138,6 +138,11 @@ describe('ComparisonStatsCard', () => { /> ); +const progressSegments = container.querySelectorAll( + '.w-full.bg-gray-700\\/50 div, .relative div' + ); + + const allDivs = Array.from(container.querySelectorAll('div')); const emeraldElement = container.querySelector('[className*="emerald"]') || container.querySelector('.text-emerald-400'); diff --git a/components/dashboard/DashboardClient.test.tsx b/components/dashboard/DashboardClient.test.tsx index cc9e5bf7..2489da61 100644 --- a/components/dashboard/DashboardClient.test.tsx +++ b/components/dashboard/DashboardClient.test.tsx @@ -178,6 +178,14 @@ const mockSecondData = { graphData: { nodes: [], links: [] }, }; +const mockPeriod = { + kind: 'year' as const, + label: '2026', + from: '2026-01-01T00:00:00.000Z', + to: '2026-12-31T23:59:59.999Z', + year: '2026', +}; + const initialDataWithHigherStreak = { ...mockInitialData, stats: { @@ -194,14 +202,6 @@ const secondDataWithLowerStreak = { }, }; -const mockPeriod = { - kind: 'year' as const, - label: '2026', - from: '2026-01-01T00:00:00.000Z', - to: '2026-12-31T23:59:59.999Z', - year: '2026', -}; - describe('DashboardClient', () => { beforeEach(() => { vi.restoreAllMocks(); @@ -327,6 +327,7 @@ describe('DashboardClient', () => { const generateLink = screen.getByRole('link', { name: /generate your own/i }); expect(generateLink.getAttribute('href')).toBe('/'); }); + // ========================================================================= // ISSUE OBJECTIVE: Verify error is shown when comparing with same username // ========================================================================= @@ -413,34 +414,35 @@ describe('DashboardClient', () => { const tags = screen.getAllByText(/Consistency Beast/i); expect(tags).toHaveLength(2); }); -}); -it('shows Most Consistent badge for profile with higher peak streak in compare mode', async () => { - const mockFetch = vi.fn().mockResolvedValue({ - ok: true, - json: async () => secondDataWithLowerStreak, - }); - vi.stubGlobal('fetch', mockFetch); + it('shows Most Consistent badge for profile with higher peak streak in compare mode', async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => secondDataWithLowerStreak, + }); - render( - - ); + vi.stubGlobal('fetch', mockFetch); - fireEvent.click(screen.getByText('Compare Profile')); + render( + + ); - fireEvent.change(screen.getByPlaceholderText('Enter GitHub Username'), { - target: { value: 'JhaSourav07' }, - }); + fireEvent.click(screen.getByText('Compare Profile')); + + fireEvent.change(screen.getByPlaceholderText('Enter GitHub Username'), { + target: { value: 'JhaSourav07' }, + }); - fireEvent.click(screen.getByText('Compare')); + fireEvent.click(screen.getByText('Compare')); - await waitFor(() => { - expect(screen.getByText('Exit Compare Mode')).toBeDefined(); - }); + await waitFor(() => { + expect(screen.getByText('Exit Compare Mode')).toBeDefined(); + }); - expect(screen.getByText(/Most Consistent/i)).toBeDefined(); -}); + expect(screen.getByText(/Most Consistent/i)).toBeDefined(); + }); +}); \ No newline at end of file diff --git a/lib/cache.test.ts b/lib/cache.test.ts index 82953239..ae99b622 100644 --- a/lib/cache.test.ts +++ b/lib/cache.test.ts @@ -226,11 +226,9 @@ describe('TTLCache', () => { const cache = new TTLCache(); cache.set('user', 'octocat', 5_000); - // Check at 1 second (before expiry at 5 seconds) vi.advanceTimersByTime(1_000); expect(cache.get('user')).toBe('octocat'); - // Check at 4 seconds (still before expiry) vi.advanceTimersByTime(3_000); expect(cache.get('user')).toBe('octocat'); @@ -242,28 +240,24 @@ describe('TTLCache', () => { const cache = new TTLCache(); cache.set('user', 'octocat', 5_000); - // Advance exactly to TTL expiry time - // At this point Date.now() === expiresAt, so > check fails and value is returned vi.advanceTimersByTime(5_000); expect(cache.get('user')).toBe('octocat'); cache.destroy(); }); + it('returns correct values around the exact TTL boundary', () => { vi.useFakeTimers(); const cache = new TTLCache(); cache.set('key', 'value', 1000); - // 999ms -> still valid vi.advanceTimersByTime(999); expect(cache.get('key')).toBe('value'); - // 1000ms exact boundary -> still valid vi.advanceTimersByTime(1); expect(cache.get('key')).toBe('value'); - // 1001ms -> expired vi.advanceTimersByTime(1); expect(cache.get('key')).toBeNull(); @@ -275,7 +269,6 @@ describe('TTLCache', () => { const cache = new TTLCache(); cache.set('user', 'octocat', 5_000); - // Advance just past TTL expiry time vi.advanceTimersByTime(5_001); expect(cache.get('user')).toBeNull(); @@ -329,18 +322,11 @@ describe('TTLCache', () => { const cache = new TTLCache(); cache.set('user', 'octocat', 5_000); - // Advance to 3 seconds (before expiry) vi.advanceTimersByTime(3_000); - - // Overwrite the key with a new 5-second TTL cache.set('user', 'new-octocat', 5_000); - - // Advance another 3 seconds (total 6 seconds, but only 3 since last set) vi.advanceTimersByTime(3_000); - // Should still be available because TTL was reset expect(cache.get('user')).toBe('new-octocat'); - cache.destroy(); }); @@ -349,18 +335,11 @@ describe('TTLCache', () => { const cache = new TTLCache(); cache.set('user', 'octocat', 5_000); - // Advance to 3 seconds vi.advanceTimersByTime(3_000); - - // Overwrite with new 2-second TTL cache.set('user', 'new-octocat', 2_000); - - // Advance another 3 seconds (total 6 from start, 3 from new set) vi.advanceTimersByTime(3_000); - // Should be expired because new TTL (2s) has passed expect(cache.get('user')).toBeNull(); - cache.destroy(); }); }); @@ -515,7 +494,7 @@ describe('TTLCache', () => { }); describe('edge cases and error handling', () => { - // FIX: New test explicitly targeting the -5000 boundary for Issue #1398 + // FIX: Test targeting the -5000 boundary for Issue #1398 it('throws RangeError when setting a value with -5000 TTL', () => { const cache = new TTLCache(); expect(() => cache.set('key', 'value', -5000)).toThrow(RangeError); @@ -529,6 +508,26 @@ describe('TTLCache', () => { cache.set(null as unknown as string, 'value', 60_000); }).toThrow(TypeError); expect(cache.size()).toBe(0); + cache.destroy(); + }); + + // FIX: New test targeting the Infinity boundary for Issue #1400 + it('caps Infinity TTL to a realistic maximum threshold without throwing', () => { + vi.useFakeTimers(); + const cache = new TTLCache(); + + // Should handle Infinity gracefully without throwing errors + expect(() => cache.set('infinity-key', 'value', Infinity)).not.toThrow(); + + // The item should be successfully stored and retrievable + expect(cache.get('infinity-key')).toBe('value'); + + // Advance by a large safe amount (e.g., 30 days) to ensure it stays valid + // or gets capped safely without overflowing internal Date math + vi.advanceTimersByTime(1000 * 60 * 60 * 24 * 30); + + // Assert it didn't break down internally and returns a clean result (either still alive or gracefully expired) + expect(['value', null]).toContain(cache.get('infinity-key')); cache.destroy(); }); @@ -598,12 +597,8 @@ describe('TTLCache', () => { vi.useFakeTimers(); const cache = new TTLCache(); cache.set('short', 'lived', 1); - // Immediately at creation time, should exist expect(cache.get('short')).toBe('lived'); - // Advance 1ms vi.advanceTimersByTime(1); - // Now it should be expired or at boundary - // (depends on exact timing, but get() should handle it gracefully) const result = cache.get('short'); expect([null, 'lived']).toContain(result); cache.destroy(); @@ -611,21 +606,17 @@ describe('TTLCache', () => { it('does not throw when ttlMs is Number.EPSILON', () => { const cache = new TTLCache(); - expect(() => { cache.set('key', 'value', Number.EPSILON); }).not.toThrow(); - cache.destroy(); }); it('does not throw when ttlMs is a very small positive number', () => { const cache = new TTLCache(); - expect(() => { cache.set('key', 'value', 0.0001); }).not.toThrow(); - cache.destroy(); }); @@ -641,6 +632,7 @@ describe('TTLCache', () => { cache.destroy(); }); + // FIX: New test targeting the NaN boundary for Issue #1399 it('resolves NaN TTL to the default standard TTL duration', () => { vi.useFakeTimers(); @@ -795,4 +787,4 @@ describe('DistributedCache', () => { ); cache.destroy(); }); -}); +}); \ No newline at end of file