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:
@@ -243,24 +243,27 @@ final class ItineraryReorderingLogicTests: XCTestCase {
|
||||
// MARK: - travelRow Tests
|
||||
|
||||
func test_travelRow_findsCorrectRow() {
|
||||
// Semantic model: travelRow finds travel in the section AFTER the day header
|
||||
// Travel must be positioned within its correct day section
|
||||
let items = buildFlatItems([
|
||||
.day(1),
|
||||
.game("Detroit", day: 1),
|
||||
.travel(from: "Detroit", to: "Chicago", day: 2),
|
||||
.day(2),
|
||||
.travel(from: "Chicago", to: "Milwaukee", day: 3),
|
||||
.day(3)
|
||||
.travel(from: "Detroit", to: "Chicago", day: 2), // Row 3: in day 2 section
|
||||
.day(3),
|
||||
.travel(from: "Chicago", to: "Milwaukee", day: 3) // Row 5: in day 3 section
|
||||
])
|
||||
|
||||
XCTAssertEqual(Logic.travelRow(in: items, forDay: 2), 2)
|
||||
XCTAssertEqual(Logic.travelRow(in: items, forDay: 3), 4)
|
||||
XCTAssertEqual(Logic.travelRow(in: items, forDay: 2), 3)
|
||||
XCTAssertEqual(Logic.travelRow(in: items, forDay: 3), 5)
|
||||
}
|
||||
|
||||
func test_travelRow_noTravelOnDay_returnsNil() {
|
||||
// Travel is in day 2 section, so day 1 has no travel
|
||||
let items = buildFlatItems([
|
||||
.day(1),
|
||||
.travel(from: "Detroit", to: "Chicago", day: 2),
|
||||
.day(2)
|
||||
.day(2),
|
||||
.travel(from: "Detroit", to: "Chicago", day: 2) // In day 2 section
|
||||
])
|
||||
|
||||
XCTAssertNil(Logic.travelRow(in: items, forDay: 1))
|
||||
|
||||
@@ -108,7 +108,7 @@ enum ItineraryTestHelpers {
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -194,11 +194,15 @@ final class ItineraryTravelConstraintTests: XCTestCase {
|
||||
// MARK: - Travel Movement Tests
|
||||
|
||||
func test_travel_moveToValidDay_callsCallback() {
|
||||
// Given: Travel with valid range 2-4
|
||||
// Given: Travel with valid range 2-3
|
||||
let travel = H.makeTravelSegment(from: "Chicago", to: "Detroit")
|
||||
let travelItem = ItineraryRowItem.travel(travel, dayNumber: 2)
|
||||
|
||||
// Travel model item for sortOrder lookup
|
||||
let travelModelItem = H.makeTravelItem(from: "Chicago", to: "Detroit", day: 2, sortOrder: 1.0)
|
||||
|
||||
let day1 = ItineraryDayData(id: 1, dayNumber: 1, date: testDate, games: [], items: [], travelBefore: nil)
|
||||
let day2 = ItineraryDayData(id: 2, dayNumber: 2, date: H.dayAfter(testDate), games: [], items: [], travelBefore: travel)
|
||||
let day2 = ItineraryDayData(id: 2, dayNumber: 2, date: H.dayAfter(testDate), games: [], items: [travelItem], travelBefore: nil)
|
||||
let day3 = ItineraryDayData(id: 3, dayNumber: 3, date: H.dayAfter(H.dayAfter(testDate)), games: [], items: [], travelBefore: nil)
|
||||
|
||||
var capturedTravelId: String = ""
|
||||
@@ -211,12 +215,12 @@ final class ItineraryTravelConstraintTests: XCTestCase {
|
||||
controller.reloadData(
|
||||
days: [day1, day2, day3],
|
||||
travelValidRanges: ["travel:chicago->detroit": 2...3],
|
||||
itineraryItems: []
|
||||
itineraryItems: [travelModelItem]
|
||||
)
|
||||
|
||||
// Rows: 0=Day1 header, 1=travel, 2=Day2 header, 3=Day3 header
|
||||
// Move travel (row 1) to row 3 (after Day2, before Day3 header means Day 3)
|
||||
controller.tableView(controller.tableView, moveRowAt: IndexPath(row: 1, section: 0), to: IndexPath(row: 3, section: 0))
|
||||
// Rows: 0=Day1 header, 1=Day2 header, 2=travel, 3=Day3 header
|
||||
// Move travel (row 2) to row 3 (after Day3 header = Day 3)
|
||||
controller.tableView(controller.tableView, moveRowAt: IndexPath(row: 2, section: 0), to: IndexPath(row: 3, section: 0))
|
||||
|
||||
XCTAssertEqual(capturedTravelId, "travel:chicago->detroit")
|
||||
XCTAssertEqual(capturedDay, 3, "Travel should now be on Day 3")
|
||||
@@ -228,18 +232,21 @@ final class ItineraryTravelConstraintTests: XCTestCase {
|
||||
// Given: Travel with valid range Days 2-3
|
||||
let travel = H.makeTravelSegment(from: "Chicago", to: "Detroit")
|
||||
let travelId = "travel:chicago->detroit"
|
||||
let travelItem = ItineraryRowItem.travel(travel, dayNumber: 2)
|
||||
let travelModelItem = H.makeTravelItem(from: "Chicago", to: "Detroit", day: 2, sortOrder: 1.0)
|
||||
|
||||
let day1 = ItineraryDayData(id: 1, dayNumber: 1, date: testDate, games: [], items: [], travelBefore: nil)
|
||||
let day2 = ItineraryDayData(id: 2, dayNumber: 2, date: H.dayAfter(testDate), games: [], items: [], travelBefore: travel)
|
||||
let day2 = ItineraryDayData(id: 2, dayNumber: 2, date: H.dayAfter(testDate), games: [], items: [travelItem], travelBefore: nil)
|
||||
let day3 = ItineraryDayData(id: 3, dayNumber: 3, date: H.dayAfter(H.dayAfter(testDate)), games: [], items: [], travelBefore: nil)
|
||||
|
||||
let controller = ItineraryTableViewController(style: .plain)
|
||||
let validRanges = [travelId: 2...3]
|
||||
controller.reloadData(days: [day1, day2, day3], travelValidRanges: validRanges)
|
||||
controller.reloadData(days: [day1, day2, day3], travelValidRanges: validRanges, itineraryItems: [travelModelItem])
|
||||
|
||||
// Travel is at row 1 (after Day1 header at row 0)
|
||||
// Try to move it to Day 1 area (row 0 or 1) - should snap back to valid range
|
||||
let source = IndexPath(row: 1, section: 0)
|
||||
// Rows: 0=Day1 header, 1=Day2 header, 2=travel, 3=Day3 header
|
||||
// Travel is at row 2 (after Day2 header at row 1)
|
||||
// Try to move it to Day 1 area (row 0) - should snap back to valid range
|
||||
let source = IndexPath(row: 2, section: 0)
|
||||
let proposed = IndexPath(row: 0, section: 0)
|
||||
|
||||
let result = controller.tableView(controller.tableView, targetIndexPathForMoveFromRowAt: source, toProposedIndexPath: proposed)
|
||||
|
||||
Reference in New Issue
Block a user