Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions app/components/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,10 @@ export default function Navbar() {
>
{mounted ? (
isDark ? (
<Moon
size={18}
className="transition-transform duration-300 group-hover:-rotate-12"
/>
<Moon
size={18}
className="transition-transform duration-300 group-hover:-rotate-12"
/>
) : (
<Sun
size={18}
Expand Down Expand Up @@ -242,7 +242,7 @@ export default function Navbar() {
>
{mounted ? (
isDark ? (
<Moon size={18} />
<Moon size={18} />
) : (
<Sun size={18} />
)
Expand Down
2 changes: 1 addition & 1 deletion components/InteractiveViewer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -600,4 +600,4 @@ describe('InteractiveViewer', () => {
expect(glowLayer).toBeTruthy();
});
});
});
});
5 changes: 3 additions & 2 deletions components/InteractiveViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -110,6 +110,7 @@ export default function InteractiveViewer({
const activeTooltipRef = useRef<ActiveTooltipState | null>(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);
Expand Down Expand Up @@ -455,4 +456,4 @@ export default function InteractiveViewer({
)}
</div>
);
}
}
5 changes: 5 additions & 0 deletions components/dashboard/ComparisonStatsCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
66 changes: 34 additions & 32 deletions components/dashboard/DashboardClient.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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();
Expand Down Expand Up @@ -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
// =========================================================================
Expand Down Expand Up @@ -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(
<DashboardClient
initialData={initialDataWithHigherStreak}
username="Shivangi1515"
period={mockPeriod}
/>
);
vi.stubGlobal('fetch', mockFetch);

fireEvent.click(screen.getByText('Compare Profile'));
render(
<DashboardClient
initialData={initialDataWithHigherStreak}
username="Shivangi1515"
period={mockPeriod}
/>
);

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();
});
});
56 changes: 24 additions & 32 deletions lib/cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,9 @@ describe('TTLCache', () => {
const cache = new TTLCache<string>();
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');

Expand All @@ -242,28 +240,24 @@ describe('TTLCache', () => {
const cache = new TTLCache<string>();
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<string>();
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();

Expand All @@ -275,7 +269,6 @@ describe('TTLCache', () => {
const cache = new TTLCache<string>();
cache.set('user', 'octocat', 5_000);

// Advance just past TTL expiry time
vi.advanceTimersByTime(5_001);
expect(cache.get('user')).toBeNull();

Expand Down Expand Up @@ -329,18 +322,11 @@ describe('TTLCache', () => {
const cache = new TTLCache<string>();
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();
});

Expand All @@ -349,18 +335,11 @@ describe('TTLCache', () => {
const cache = new TTLCache<string>();
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();
});
});
Expand Down Expand Up @@ -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<string>();
expect(() => cache.set('key', 'value', -5000)).toThrow(RangeError);
Expand All @@ -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<string>();

// 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();
});
Expand Down Expand Up @@ -598,34 +597,26 @@ describe('TTLCache', () => {
vi.useFakeTimers();
const cache = new TTLCache<string>();
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();
});

it('does not throw when ttlMs is Number.EPSILON', () => {
const cache = new TTLCache<string>();

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<string>();

expect(() => {
cache.set('key', 'value', 0.0001);
}).not.toThrow();

cache.destroy();
});

Expand All @@ -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();
Expand Down Expand Up @@ -795,4 +787,4 @@ describe('DistributedCache', () => {
);
cache.destroy();
});
});
});
Loading