fix(itinerary): add city to game items for proper constraint validation

Travel constraint validation was not working because ItineraryConstraints
had no game items to validate against - games came from RichGame objects
but were never converted to ItineraryItem for constraint checking.

Changes:
- Add city parameter to ItemKind.game enum case
- Create game ItineraryItems from RichGame data in buildItineraryData()
- Update isValidTravelPosition to compare against actual game sortOrders
- Fix tests to use appropriate game sortOrder conventions

Now travel is properly constrained to appear before arrival city games
and after departure city games.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-18 22:46:40 -06:00
parent 72447c61fe
commit e72da7c5a7
11 changed files with 247 additions and 254 deletions

View File

@@ -49,33 +49,35 @@ final class ItineraryConstraintsTests: XCTestCase {
}
func test_travel_mustBeAfterDepartureGames() {
// Given: Chicago game on Day 1 at sortOrder 100
// Given: Chicago game on Day 1
// Visual boundary is 0: sortOrder < 0 = before games, sortOrder >= 0 = after games
let constraints = makeConstraints(
tripDays: 3,
games: [makeGameItem(city: "Chicago", day: 1, sortOrder: 100)]
games: [makeGameItem(city: "Chicago", day: 1)]
)
let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 1, sortOrder: 50)
let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 1, sortOrder: -1)
// When/Then: Travel before game is invalid
XCTAssertFalse(constraints.isValidPosition(for: travel, day: 1, sortOrder: 50))
// When/Then: Travel before game (negative sortOrder) is invalid on departure day
XCTAssertFalse(constraints.isValidPosition(for: travel, day: 1, sortOrder: -1))
// Travel after game is valid
XCTAssertTrue(constraints.isValidPosition(for: travel, day: 1, sortOrder: 150))
// Travel after game (non-negative sortOrder) is valid
XCTAssertTrue(constraints.isValidPosition(for: travel, day: 1, sortOrder: 1))
}
func test_travel_mustBeBeforeArrivalGames() {
// Given: Detroit game on Day 3 at sortOrder 100
// Given: Detroit game on Day 3
// Visual boundary is 0: sortOrder < 0 = before games, sortOrder >= 0 = after games
let constraints = makeConstraints(
tripDays: 3,
games: [makeGameItem(city: "Detroit", day: 3, sortOrder: 100)]
games: [makeGameItem(city: "Detroit", day: 3)]
)
let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 3, sortOrder: 150)
let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 3, sortOrder: 1)
// When/Then: Travel after arrival game is invalid
XCTAssertFalse(constraints.isValidPosition(for: travel, day: 3, sortOrder: 150))
// When/Then: Travel after arrival game (non-negative sortOrder) is invalid on arrival day
XCTAssertFalse(constraints.isValidPosition(for: travel, day: 3, sortOrder: 1))
// Travel before game is valid
XCTAssertTrue(constraints.isValidPosition(for: travel, day: 3, sortOrder: 50))
// Travel before game (negative sortOrder) is valid
XCTAssertTrue(constraints.isValidPosition(for: travel, day: 3, sortOrder: -1))
}
func test_travel_canBeAnywhereOnRestDays() {
@@ -217,13 +219,12 @@ final class ItineraryConstraintsTests: XCTestCase {
return ItineraryConstraints(tripDayCount: tripDays, items: games)
}
private func makeGameItem(city: String, day: Int, sortOrder: Double = 100) -> ItineraryItem {
// For tests, we use gameId to encode the city
private func makeGameItem(city: String, day: Int, sortOrder: Double = 0) -> ItineraryItem {
return ItineraryItem(
tripId: testTripId,
day: day,
sortOrder: sortOrder,
kind: .game(gameId: "game-\(city)-\(UUID().uuidString.prefix(4))")
kind: .game(gameId: "game-\(city)-\(UUID().uuidString.prefix(4))", city: city)
)
}