218 lines
7.2 KiB
Swift
218 lines
7.2 KiB
Swift
//
|
|
// ItinerarySectionBuilderTests.swift
|
|
// SportsTimeTests
|
|
//
|
|
// Tests for ItinerarySectionBuilder pure static function.
|
|
//
|
|
|
|
import Testing
|
|
import CoreLocation
|
|
@testable import SportsTime
|
|
|
|
@Suite("ItinerarySectionBuilder")
|
|
struct ItinerarySectionBuilderTests {
|
|
|
|
// MARK: - Helpers
|
|
|
|
private func makeTripDays(count: Int, startDate: Date = TestClock.now) -> [Date] {
|
|
(0..<count).map {
|
|
TestClock.calendar.date(byAdding: .day, value: $0, to: TestClock.calendar.startOfDay(for: startDate))!
|
|
}
|
|
}
|
|
|
|
private func makeTrip(
|
|
cities: [String] = ["New York", "Boston"],
|
|
startDate: Date = TestClock.now,
|
|
daysPerStop: Int = 1,
|
|
gameIds: [[String]] = []
|
|
) -> (Trip, [Date]) {
|
|
let stops = TestFixtures.tripStops(cities: cities, startDate: startDate, daysPerStop: daysPerStop)
|
|
var stopsWithGames = stops
|
|
for (i, ids) in gameIds.enumerated() where i < stopsWithGames.count {
|
|
stopsWithGames[i] = TripStop(
|
|
stopNumber: stopsWithGames[i].stopNumber,
|
|
city: stopsWithGames[i].city,
|
|
state: stopsWithGames[i].state,
|
|
coordinate: stopsWithGames[i].coordinate,
|
|
arrivalDate: stopsWithGames[i].arrivalDate,
|
|
departureDate: stopsWithGames[i].departureDate,
|
|
games: ids,
|
|
isRestDay: stopsWithGames[i].isRestDay
|
|
)
|
|
}
|
|
let trip = TestFixtures.trip(stops: stopsWithGames)
|
|
let totalDays = cities.count * daysPerStop
|
|
let days = makeTripDays(count: totalDays, startDate: startDate)
|
|
return (trip, days)
|
|
}
|
|
|
|
// MARK: - Tests
|
|
|
|
@Test("builds one section per day")
|
|
func buildsSectionsForEachDay() {
|
|
let startDate = TestClock.calendar.startOfDay(for: TestClock.now)
|
|
let (trip, days) = makeTrip(cities: ["New York", "Boston", "Philadelphia"], startDate: startDate)
|
|
|
|
let sections = ItinerarySectionBuilder.build(
|
|
trip: trip,
|
|
tripDays: days,
|
|
games: [:],
|
|
travelOverrides: [:],
|
|
itineraryItems: [],
|
|
allowCustomItems: false
|
|
)
|
|
|
|
// Should have travel + day sections
|
|
let daySections = sections.filter {
|
|
if case .day = $0 { return true }
|
|
return false
|
|
}
|
|
#expect(daySections.count == days.count)
|
|
}
|
|
|
|
@Test("games filtered correctly by date")
|
|
func gamesOnFiltersCorrectly() {
|
|
let startDate = TestClock.calendar.startOfDay(for: TestClock.now)
|
|
let gameDate = startDate
|
|
let game = TestFixtures.game(sport: .mlb, city: "New York", dateTime: gameDate)
|
|
let richGame = TestFixtures.richGame(game: game, homeCity: "New York")
|
|
|
|
let (trip, days) = makeTrip(
|
|
cities: ["New York", "Boston"],
|
|
startDate: startDate,
|
|
gameIds: [[game.id]]
|
|
)
|
|
|
|
let sections = ItinerarySectionBuilder.build(
|
|
trip: trip,
|
|
tripDays: days,
|
|
games: [game.id: richGame],
|
|
travelOverrides: [:],
|
|
itineraryItems: [],
|
|
allowCustomItems: false
|
|
)
|
|
|
|
// Find the first day section and verify it has a game
|
|
let firstDaySection = sections.first {
|
|
if case .day(1, _, _) = $0 { return true }
|
|
return false
|
|
}
|
|
|
|
if case .day(_, _, let gamesOnDay) = firstDaySection {
|
|
#expect(gamesOnDay.count == 1)
|
|
#expect(gamesOnDay.first?.game.id == game.id)
|
|
} else {
|
|
Issue.record("Expected day section with games")
|
|
}
|
|
}
|
|
|
|
@Test("travel segments appear in sections")
|
|
func travelSegmentsAppear() {
|
|
let startDate = TestClock.calendar.startOfDay(for: TestClock.now)
|
|
let travel = TestFixtures.travelSegment(from: "New York", to: "Boston")
|
|
let (baseTrip, days) = makeTrip(
|
|
cities: ["New York", "Boston"],
|
|
startDate: startDate,
|
|
daysPerStop: 1
|
|
)
|
|
|
|
// Create trip with travel segment
|
|
var trip = baseTrip
|
|
trip.travelSegments = [travel]
|
|
|
|
// Build without overrides - travel appears at default position
|
|
let sectionsDefault = ItinerarySectionBuilder.build(
|
|
trip: trip,
|
|
tripDays: days,
|
|
games: [:],
|
|
travelOverrides: [:],
|
|
itineraryItems: [],
|
|
allowCustomItems: false
|
|
)
|
|
|
|
let travelSections = sectionsDefault.filter {
|
|
if case .travel = $0 { return true }
|
|
return false
|
|
}
|
|
|
|
// TravelPlacement should place the travel segment
|
|
#expect(travelSections.count == 1)
|
|
|
|
// Verify travel appears before day 2 (arrival day)
|
|
if let travelIdx = sectionsDefault.firstIndex(where: {
|
|
if case .travel = $0 { return true }
|
|
return false
|
|
}) {
|
|
let nextIdx = sectionsDefault.index(after: travelIdx)
|
|
if nextIdx < sectionsDefault.count,
|
|
case .day(let dayNum, _, _) = sectionsDefault[nextIdx] {
|
|
#expect(dayNum == 2)
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test("custom items included when allowCustomItems is true")
|
|
func customItemsIncluded() {
|
|
let startDate = TestClock.calendar.startOfDay(for: TestClock.now)
|
|
let (trip, days) = makeTrip(cities: ["New York"], startDate: startDate)
|
|
|
|
let customItem = ItineraryItem(
|
|
tripId: trip.id,
|
|
day: 1,
|
|
sortOrder: 1.0,
|
|
kind: .custom(CustomInfo(title: "Lunch at Joe's", icon: "fork.knife"))
|
|
)
|
|
|
|
let sectionsWithItems = ItinerarySectionBuilder.build(
|
|
trip: trip,
|
|
tripDays: days,
|
|
games: [:],
|
|
travelOverrides: [:],
|
|
itineraryItems: [customItem],
|
|
allowCustomItems: true
|
|
)
|
|
|
|
let sectionsWithoutItems = ItinerarySectionBuilder.build(
|
|
trip: trip,
|
|
tripDays: days,
|
|
games: [:],
|
|
travelOverrides: [:],
|
|
itineraryItems: [customItem],
|
|
allowCustomItems: false
|
|
)
|
|
|
|
let customSectionsWithItems = sectionsWithItems.filter { $0.isCustomItem }
|
|
let addButtonSections = sectionsWithItems.filter {
|
|
if case .addButton = $0 { return true }
|
|
return false
|
|
}
|
|
let customSectionsWithoutItems = sectionsWithoutItems.filter { $0.isCustomItem }
|
|
|
|
#expect(customSectionsWithItems.count == 1)
|
|
#expect(addButtonSections.count >= 1)
|
|
#expect(customSectionsWithoutItems.count == 0)
|
|
}
|
|
|
|
@Test("stableTravelAnchorId format is consistent")
|
|
func stableTravelAnchorIdFormat() {
|
|
let segment = TestFixtures.travelSegment(from: "New York", to: "Boston")
|
|
let id = ItinerarySectionBuilder.stableTravelAnchorId(segment, at: 0)
|
|
#expect(id.hasPrefix("travel:0:"))
|
|
#expect(id.contains("->"))
|
|
}
|
|
|
|
@Test("empty trip produces no sections")
|
|
func emptyTripProducesNoSections() {
|
|
let trip = TestFixtures.trip(stops: [])
|
|
let sections = ItinerarySectionBuilder.build(
|
|
trip: trip,
|
|
tripDays: [],
|
|
games: [:],
|
|
travelOverrides: [:],
|
|
itineraryItems: [],
|
|
allowCustomItems: false
|
|
)
|
|
#expect(sections.isEmpty)
|
|
}
|
|
}
|