clone

fifteen puzzle games for the terminal, with daily puzzles, weekly ladders and xp, built on bubble tea

refactor/root-tui-model
puzzletea / stats / stats_test.go
stats_test.go 3.6K
1package stats23import (4 "testing"5 "time"67 "github.com/FelineStateMachine/puzzletea/puzzle"8 "github.com/FelineStateMachine/puzzletea/store"9)1011func TestWeightsFromDefinitions(t *testing.T) {12 weights := WeightsFromDefinitions([]puzzle.Definition{{13 Name: "Sudoku",14 Modes: []puzzle.ModeDef{15 {Title: "Easy"},16 {Title: "Medium"},17 {Title: "Hard"},18 },19 }})20 if len(weights) != 3 {21 t.Fatalf("len(weights) = %d, want 3", len(weights))22 }23}2425func TestWeightsFromDefinitionsPreservesLegacyModeWeights(t *testing.T) {26 weights := WeightsFromDefinitions([]puzzle.Definition{{27 Name: "Sudoku",28 Variants: []puzzle.VariantDef{29 puzzle.NewVariantDef(puzzle.VariantSpec{Title: "Sudoku", DefaultElo: 1200}),30 },31 LegacyModes: []puzzle.LegacyModeAlias{32 puzzle.NewLegacyModeAlias(puzzle.LegacyModeAliasSpec{33 Title: "Easy",34 TargetVariantID: puzzle.CanonicalVariantID("Sudoku"),35 PresetElo: 600,36 XPWeight: 1,37 }),38 puzzle.NewLegacyModeAlias(puzzle.LegacyModeAliasSpec{39 Title: "Hard",40 TargetVariantID: puzzle.CanonicalVariantID("Sudoku"),41 PresetElo: 1800,42 XPWeight: 7,43 }),44 },45 Modes: []puzzle.ModeDef{46 {ID: puzzle.CanonicalModeID("Easy"), Title: "Easy"},47 {ID: puzzle.CanonicalModeID("Medium"), Title: "Medium"},48 {ID: puzzle.CanonicalModeID("Hard"), Title: "Hard"},49 },50 }})5152 if got, want := weights[ModeKey{GameType: "Sudoku", Mode: "Hard"}], 7; got != want {53 t.Fatalf("legacy Hard weight = %d, want %d", got, want)54 }55 if got, want := weights[ModeKey{GameType: "Sudoku", Mode: "Sudoku"}], 1; got != want {56 t.Fatalf("variant weight = %d, want %d", got, want)57 }58}5960func TestLevelFromXPAndXPForLevel(t *testing.T) {61 if got := LevelFromXP(0); got != 0 {62 t.Fatalf("LevelFromXP(0) = %d, want 0", got)63 }64 if got := XPForLevel(3); got != 29 {65 t.Fatalf("XPForLevel(3) = %d, want 29", got)66 }67}6869func TestComputeDailyStreak(t *testing.T) {70 now := time.Date(2026, time.February, 16, 15, 30, 0, 0, time.Local)71 dates := []time.Time{72 time.Date(2026, time.February, 16, 0, 0, 0, 0, time.Local),73 time.Date(2026, time.February, 15, 0, 0, 0, 0, time.Local),74 time.Date(2026, time.February, 14, 0, 0, 0, 0, time.Local),75 }76 if got := ComputeDailyStreak(dates, now); got != 3 {77 t.Fatalf("ComputeDailyStreak() = %d, want 3", got)78 }79}8081func TestComputeCategoryXP(t *testing.T) {82 weights := Weights{83 {"Sudoku", "Easy"}: 2,84 {"Sudoku", "Hard"}: 8,85 }86 modeStats := []store.ModeStat{87 {GameType: "Sudoku", Mode: "Easy", Victories: 5, DailyVictories: 2},88 {GameType: "Sudoku", Mode: "Hard", Victories: 2},89 }90 if got := ComputeCategoryXP(weights, "Sudoku", modeStats); got != 30 {91 t.Fatalf("ComputeCategoryXP() = %d, want 30", got)92 }9394 elo := 150095 modeStats[0].DifficultyElo = &elo96 if got := ComputeCategoryXP(weights, "Sudoku", modeStats); got != 51 {97 t.Fatalf("ComputeCategoryXP() with Elo = %d, want 51", got)98 }99}100101func TestBuildCardsAndProfile(t *testing.T) {102 weights := Weights{103 {"Sudoku", "Easy"}: 2,104 {"Sudoku", "Hard"}: 8,105 }106 catStats := []store.CategoryStat{{107 GameType: "Sudoku",108 TotalAttempts: 10,109 TotalVictories: 7,110 TimesDaily: 3,111 PreferredMode: "Easy",112 }}113 modeStats := []store.ModeStat{114 {GameType: "Sudoku", Mode: "Easy", Victories: 5, DailyVictories: 2},115 {GameType: "Sudoku", Mode: "Hard", Victories: 2},116 }117 cards := BuildCards(weights, catStats, modeStats)118 if len(cards) != 1 || cards[0].CurrentXP != 30 {119 t.Fatalf("BuildCards() = %#v", cards)120 }121 profile := BuildProfileBanner(catStats, modeStats, weights, nil, false, 4, 17)122 if profile.WeekliesCompleted != 4 || profile.ThisWeekHighestIndex != 17 {123 t.Fatalf("BuildProfileBanner() = %#v", profile)124 }125}