import { describe, it, expect } from "vitest"; /** * Standings Snapshots Tests * Phase 4.2: Test snapshot creation and 7-day comparison logic * * These are unit tests that verify the snapshot logic works correctly. * Integration tests that use the actual database are in the e2e test suite. */ /** * Helper function to simulate 7-day rank change calculation * This mirrors the logic in getSevenDayStandingsChange */ function calculateSevenDayChange( currentRank: number, sevenDayOldRank: number | null ): number { if (sevenDayOldRank === null) { return 0; // No snapshot from 7 days ago } // Positive number = improvement (was 5th, now 3rd = +2) // Negative number = decline (was 3rd, now 5th = -2) return sevenDayOldRank - currentRank; } /** * Helper function to simulate snapshot deduplication logic * This mirrors the logic in createDailySnapshot */ function shouldCreateSnapshot( existingSnapshotDate: string | null, today: string ): boolean { // Don't create if snapshot already exists for today return existingSnapshotDate !== today; } describe("Standings Snapshots", () => { describe("7-Day Rank Change Calculation", () => { it("should calculate positive change when rank improves", () => { const change = calculateSevenDayChange(1, 3); expect(change).toBe(2); // Moved from 3rd to 1st = +2 }); it("should calculate negative change when rank declines", () => { const change = calculateSevenDayChange(5, 2); expect(change).toBe(-3); // Moved from 2nd to 5th = -3 }); it("should return 0 when rank stays the same", () => { const change = calculateSevenDayChange(2, 2); expect(change).toBe(0); }); it("should return 0 when no snapshot exists from 7 days ago", () => { const change = calculateSevenDayChange(1, null); expect(change).toBe(0); }); it("should handle large rank improvements", () => { const change = calculateSevenDayChange(1, 10); expect(change).toBe(9); // Moved from 10th to 1st = +9 }); it("should handle large rank declines", () => { const change = calculateSevenDayChange(10, 1); expect(change).toBe(-9); // Moved from 1st to 10th = -9 }); }); describe("Snapshot Deduplication", () => { it("should create snapshot when no existing snapshot for today", () => { const today = "2025-01-13"; const shouldCreate = shouldCreateSnapshot(null, today); expect(shouldCreate).toBe(true); }); it("should not create snapshot when snapshot already exists for today", () => { const today = "2025-01-13"; const existingDate = "2025-01-13"; const shouldCreate = shouldCreateSnapshot(existingDate, today); expect(shouldCreate).toBe(false); }); it("should create snapshot when last snapshot was yesterday", () => { const today = "2025-01-13"; const existingDate = "2025-01-12"; const shouldCreate = shouldCreateSnapshot(existingDate, today); expect(shouldCreate).toBe(true); }); it("should create snapshot when last snapshot was 7 days ago", () => { const today = "2025-01-13"; const existingDate = "2025-01-06"; const shouldCreate = shouldCreateSnapshot(existingDate, today); expect(shouldCreate).toBe(true); }); }); describe("Snapshot Data Integrity", () => { it("should preserve all tiebreaker data in snapshot", () => { const standing = { totalPoints: 150.5, currentRank: 1, firstPlaceCount: 2, secondPlaceCount: 1, thirdPlaceCount: 0, fourthPlaceCount: 0, fifthPlaceCount: 0, sixthPlaceCount: 0, seventhPlaceCount: 0, eighthPlaceCount: 0, participantsRemaining: 5, }; // Snapshot should contain all the same data const snapshot = { totalPoints: standing.totalPoints, rank: standing.currentRank, firstPlaceCount: standing.firstPlaceCount, secondPlaceCount: standing.secondPlaceCount, thirdPlaceCount: standing.thirdPlaceCount, fourthPlaceCount: standing.fourthPlaceCount, fifthPlaceCount: standing.fifthPlaceCount, sixthPlaceCount: standing.sixthPlaceCount, seventhPlaceCount: standing.seventhPlaceCount, eighthPlaceCount: standing.eighthPlaceCount, participantsRemaining: standing.participantsRemaining, }; expect(snapshot.totalPoints).toBe(standing.totalPoints); expect(snapshot.rank).toBe(standing.currentRank); expect(snapshot.firstPlaceCount).toBe(standing.firstPlaceCount); expect(snapshot.participantsRemaining).toBe(standing.participantsRemaining); }); }); describe("Movement Scenarios", () => { it("should handle team moving from last to first", () => { const scenarios = [ { current: 1, old: 8, expected: 7 }, // Last to first = +7 { current: 8, old: 1, expected: -7 }, // First to last = -7 ]; scenarios.forEach(({ current, old, expected }) => { const change = calculateSevenDayChange(current, old); expect(change).toBe(expected); }); }); it("should handle multiple teams with different movements", () => { const teams = [ { id: "A", currentRank: 1, oldRank: 3, expectedChange: 2 }, { id: "B", currentRank: 2, oldRank: 1, expectedChange: -1 }, { id: "C", currentRank: 3, oldRank: 2, expectedChange: -1 }, { id: "D", currentRank: 4, oldRank: null, expectedChange: 0 }, ]; teams.forEach((team) => { const change = calculateSevenDayChange(team.currentRank, team.oldRank); expect(change).toBe(team.expectedChange); }); }); }); describe("Historical Tracking", () => { it("should maintain chronological order of snapshots", () => { const snapshots = [ { date: "2025-01-13", rank: 1 }, { date: "2025-01-12", rank: 2 }, { date: "2025-01-11", rank: 3 }, { date: "2025-01-10", rank: 2 }, { date: "2025-01-09", rank: 1 }, ]; // Should be sorted by date descending (newest first) const sorted = [...snapshots].toSorted((a, b) => b.date.localeCompare(a.date) ); expect(sorted[0].date).toBe("2025-01-13"); expect(sorted[4].date).toBe("2025-01-09"); }); it("should show rank progression over time", () => { const history = [ { date: "2025-01-09", rank: 5, points: 50 }, { date: "2025-01-10", rank: 4, points: 75 }, { date: "2025-01-11", rank: 3, points: 100 }, { date: "2025-01-12", rank: 2, points: 125 }, { date: "2025-01-13", rank: 1, points: 150 }, ]; // Verify progression shows improvement for (let i = 1; i < history.length; i++) { expect(history[i].rank).toBeLessThan(history[i - 1].rank); expect(history[i].points).toBeGreaterThan(history[i - 1].points); } }); }); });