Files
Sportstime/SportsTimeTests/Features/Progress/GamesHistoryViewModelTests.swift
Trey t 22772fa57f feat(store): add In-App Purchase system with Pro subscription
Implement freemium model with StoreKit 2:
- StoreManager singleton for purchase/restore/entitlements
- ProFeature enum defining gated features
- PaywallView and OnboardingPaywallView for upsell UI
- ProGate view modifier and ProBadge component

Feature gating:
- Trip saving: 1 free trip, then requires Pro
- PDF export: Pro only with badge indicator
- Progress tab: Shows ProLockedView for free users
- Settings: Subscription management section

Also fixes pre-existing test issues with StadiumVisit
and ItineraryOption model signature changes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 11:41:40 -06:00

116 lines
3.9 KiB
Swift

import XCTest
import SwiftData
@testable import SportsTime
@MainActor
final class GamesHistoryViewModelTests: XCTestCase {
var modelContainer: ModelContainer!
var modelContext: ModelContext!
override func setUp() async throws {
let config = ModelConfiguration(isStoredInMemoryOnly: true)
modelContainer = try ModelContainer(
for: StadiumVisit.self, Achievement.self, UserPreferences.self,
configurations: config
)
modelContext = modelContainer.mainContext
}
override func tearDown() async throws {
modelContainer = nil
modelContext = nil
}
func test_GamesHistoryViewModel_GroupsVisitsByYear() async throws {
// Given: Visits in different years
let visit2026 = StadiumVisit(
stadiumId: "stadium-1",
stadiumNameAtVisit: "Stadium 2026",
visitDate: Calendar.current.date(from: DateComponents(year: 2026, month: 6, day: 15))!,
sport: .mlb,
visitType: .game,
dataSource: .fullyManual
)
let visit2025 = StadiumVisit(
stadiumId: "stadium-2",
stadiumNameAtVisit: "Stadium 2025",
visitDate: Calendar.current.date(from: DateComponents(year: 2025, month: 6, day: 15))!,
sport: .mlb,
visitType: .game,
dataSource: .fullyManual
)
modelContext.insert(visit2026)
modelContext.insert(visit2025)
try modelContext.save()
// When: Loading games history
let viewModel = GamesHistoryViewModel(modelContext: modelContext)
await viewModel.loadGames()
// Then: Visits are grouped by year
XCTAssertEqual(viewModel.visitsByYear.keys.count, 2, "Should have 2 years")
XCTAssertTrue(viewModel.visitsByYear.keys.contains(2026))
XCTAssertTrue(viewModel.visitsByYear.keys.contains(2025))
}
func test_GamesHistoryViewModel_FiltersBySport() async throws {
// Given: Visits to different sport stadiums
// Note: This requires stadiums in AppDataProvider to map stadiumId sport
let mlbVisit = StadiumVisit(
stadiumId: "yankee-stadium",
stadiumNameAtVisit: "Yankee Stadium",
visitDate: Date(),
sport: .mlb,
visitType: .game,
dataSource: .fullyManual
)
modelContext.insert(mlbVisit)
try modelContext.save()
// When: Filtering by MLB
let viewModel = GamesHistoryViewModel(modelContext: modelContext)
await viewModel.loadGames()
viewModel.selectedSports = [.mlb]
// Then: Only MLB visits shown
let filteredCount = viewModel.filteredVisits.count
XCTAssertGreaterThanOrEqual(filteredCount, 0, "Filter should work without crashing")
}
func test_GamesHistoryViewModel_SortsMostRecentFirst() async throws {
// Given: Visits on different dates
let oldVisit = StadiumVisit(
stadiumId: "stadium-1",
stadiumNameAtVisit: "Old Stadium",
visitDate: Date().addingTimeInterval(-86400 * 30), // 30 days ago
sport: .mlb,
visitType: .game,
dataSource: .fullyManual
)
let newVisit = StadiumVisit(
stadiumId: "stadium-2",
stadiumNameAtVisit: "New Stadium",
visitDate: Date(),
sport: .mlb,
visitType: .game,
dataSource: .fullyManual
)
modelContext.insert(oldVisit)
modelContext.insert(newVisit)
try modelContext.save()
// When: Loading games
let viewModel = GamesHistoryViewModel(modelContext: modelContext)
await viewModel.loadGames()
// Then: Most recent first within year
let visits = viewModel.allVisits
XCTAssertEqual(visits.first?.stadiumNameAtVisit, "New Stadium", "Most recent should be first")
}
}