155 lines
7.1 KiB
Swift
155 lines
7.1 KiB
Swift
//
|
|
// ItineraryCustomItemTests.swift
|
|
// SportsTimeTests
|
|
//
|
|
// Tests for custom item movement and constraints.
|
|
//
|
|
|
|
import XCTest
|
|
@testable import SportsTime
|
|
|
|
private typealias H = ItineraryTestHelpers
|
|
|
|
@MainActor
|
|
final class ItineraryCustomItemTests: XCTestCase {
|
|
|
|
private let testTripId = H.testTripId
|
|
private let testDate = H.testDate
|
|
|
|
// MARK: - Custom Item Movement Tests
|
|
|
|
func test_customItem_canMoveToAnyDay() {
|
|
// Given: A 5-day trip
|
|
let constraints = ItineraryConstraints(tripDayCount: 5, items: [])
|
|
let itineraryItem = ItineraryItem(tripId: testTripId, day: 1, sortOrder: 1.0, kind: .custom(CustomInfo(title: "Dinner", icon: "🍽️")))
|
|
|
|
// Custom items can go on any day
|
|
for day in 1...5 {
|
|
XCTAssertTrue(constraints.isValidPosition(for: itineraryItem, day: day, sortOrder: 50), "Custom item should be valid on Day \(day)")
|
|
}
|
|
}
|
|
|
|
func test_customItem_canMoveBeforeOrAfterGames() {
|
|
// Given: A day with a game at sortOrder 100
|
|
let game = H.makeGameItem(city: "Detroit", day: 2, sortOrder: 100)
|
|
let constraints = ItineraryConstraints(tripDayCount: 3, items: [game])
|
|
|
|
let customItem = ItineraryItem(tripId: testTripId, day: 2, sortOrder: 50, kind: .custom(CustomInfo(title: "Breakfast", icon: "🍳")))
|
|
|
|
// Before game (sortOrder 50) - VALID
|
|
XCTAssertTrue(constraints.isValidPosition(for: customItem, day: 2, sortOrder: 50), "Custom item before game should be valid")
|
|
|
|
// After game (sortOrder 150) - VALID
|
|
XCTAssertTrue(constraints.isValidPosition(for: customItem, day: 2, sortOrder: 150), "Custom item after game should be valid")
|
|
}
|
|
|
|
func test_customItem_cannotBeMovedOutsideTripRange() {
|
|
// Given: A 3-day trip
|
|
let constraints = ItineraryConstraints(tripDayCount: 3, items: [])
|
|
let customItem = ItineraryItem(tripId: testTripId, day: 1, sortOrder: 1.0, kind: .custom(CustomInfo(title: "Test", icon: "⭐")))
|
|
|
|
// Day 0 (before trip) - INVALID
|
|
XCTAssertFalse(constraints.isValidPosition(for: customItem, day: 0, sortOrder: 50), "Day 0 should be invalid")
|
|
|
|
// Day 4 (after trip) - INVALID
|
|
XCTAssertFalse(constraints.isValidPosition(for: customItem, day: 4, sortOrder: 50), "Day 4 should be invalid (trip is 3 days)")
|
|
}
|
|
|
|
// MARK: - Move Validation Tests
|
|
|
|
func test_moveValidation_customItem_blockedFromRow0() {
|
|
// Row 0 should always be blocked for drops
|
|
|
|
let customItem = H.makeCustomItem(day: 1, sortOrder: 1.0, title: "Moving")
|
|
let dayData = ItineraryDayData(id: 1, dayNumber: 1, date: testDate, games: [], items: [.customItem(customItem)], travelBefore: nil)
|
|
|
|
let controller = ItineraryTableViewController(style: .plain)
|
|
controller.reloadData(days: [dayData], travelValidRanges: [:], itineraryItems: [customItem])
|
|
|
|
// Attempt to move item to row 0
|
|
let source = IndexPath(row: 1, section: 0)
|
|
let proposed = IndexPath(row: 0, section: 0)
|
|
|
|
let result = controller.tableView(controller.tableView, targetIndexPathForMoveFromRowAt: source, toProposedIndexPath: proposed)
|
|
|
|
// Should NOT allow row 0
|
|
XCTAssertNotEqual(result.row, 0, "Row 0 should be blocked for drops")
|
|
}
|
|
|
|
// MARK: - Cross-Day Movement Tests
|
|
|
|
func test_moveItemBetweenDays_updatesDay() {
|
|
// Given: Item on Day 1, move to Day 2
|
|
let item = H.makeCustomItem(day: 1, sortOrder: 1.0, title: "Moving")
|
|
|
|
let day1 = ItineraryDayData(id: 1, dayNumber: 1, date: testDate, games: [], items: [.customItem(item)], travelBefore: nil)
|
|
let day2 = ItineraryDayData(id: 2, dayNumber: 2, date: H.dayAfter(testDate), games: [], items: [], travelBefore: nil)
|
|
|
|
var capturedDay: Int = 0
|
|
let controller = ItineraryTableViewController(style: .plain)
|
|
controller.onCustomItemMoved = { _, day, _ in
|
|
capturedDay = day
|
|
}
|
|
controller.reloadData(days: [day1, day2], travelValidRanges: [:], itineraryItems: [item])
|
|
|
|
// Rows: 0=Day1 header, 1=item, 2=Day2 header
|
|
// Move item (row 1) to after Day2 header (row 2 becomes row 2 after move)
|
|
controller.tableView(controller.tableView, moveRowAt: IndexPath(row: 1, section: 0), to: IndexPath(row: 2, section: 0))
|
|
|
|
XCTAssertEqual(capturedDay, 2, "Item should now belong to Day 2")
|
|
}
|
|
|
|
func test_moveItem_fromLastDayToFirstDay() {
|
|
// Given: 3-day trip with item on Day 3
|
|
// When: Moving to Day 1
|
|
// Then: Day should be 1
|
|
|
|
let item = H.makeCustomItem(day: 3, sortOrder: 1.0, title: "Moving")
|
|
|
|
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: nil)
|
|
let day3 = ItineraryDayData(id: 3, dayNumber: 3, date: H.dayAfter(H.dayAfter(testDate)), games: [], items: [.customItem(item)], travelBefore: nil)
|
|
|
|
var capturedDay: Int = 0
|
|
var capturedSortOrder: Double = 0
|
|
let controller = ItineraryTableViewController(style: .plain)
|
|
controller.onCustomItemMoved = { _, day, sortOrder in
|
|
capturedDay = day
|
|
capturedSortOrder = sortOrder
|
|
}
|
|
controller.reloadData(days: [day1, day2, day3], travelValidRanges: [:], itineraryItems: [item])
|
|
|
|
// Rows: 0=Day1 header, 1=Day2 header, 2=Day3 header, 3=item
|
|
// Move item to after Day1 header
|
|
controller.tableView(controller.tableView, moveRowAt: IndexPath(row: 3, section: 0), to: IndexPath(row: 1, section: 0))
|
|
|
|
XCTAssertEqual(capturedDay, 1, "Item should now be on Day 1")
|
|
XCTAssertEqual(capturedSortOrder, 1.0, accuracy: 0.01, "First item on empty day should get sortOrder 1.0")
|
|
}
|
|
|
|
func test_moveItem_acrossMultipleDays_withGames() {
|
|
// Given: Item on Day 3, games on Day 1
|
|
// When: Moving to Day 1 (after games)
|
|
|
|
let game1 = H.makeRichGame(city: "Detroit", hour: 14)
|
|
let item = H.makeCustomItem(day: 3, sortOrder: 1.0, title: "Moving")
|
|
|
|
let day1 = ItineraryDayData(id: 1, dayNumber: 1, date: testDate, games: [game1], items: [], travelBefore: nil)
|
|
let day2 = ItineraryDayData(id: 2, dayNumber: 2, date: H.dayAfter(testDate), games: [], items: [], travelBefore: nil)
|
|
let day3 = ItineraryDayData(id: 3, dayNumber: 3, date: H.dayAfter(H.dayAfter(testDate)), games: [], items: [.customItem(item)], travelBefore: nil)
|
|
|
|
var capturedDay: Int = 0
|
|
let controller = ItineraryTableViewController(style: .plain)
|
|
controller.onCustomItemMoved = { _, day, _ in
|
|
capturedDay = day
|
|
}
|
|
controller.reloadData(days: [day1, day2, day3], travelValidRanges: [:], itineraryItems: [item])
|
|
|
|
// Rows: 0=Day1 header, 1=games, 2=Day2 header, 3=Day3 header, 4=item
|
|
// Move item to row 2 (after Day1 games, before Day2 header)
|
|
controller.tableView(controller.tableView, moveRowAt: IndexPath(row: 4, section: 0), to: IndexPath(row: 2, section: 0))
|
|
|
|
XCTAssertEqual(capturedDay, 1, "Item moved after Day 1 games should be on Day 1")
|
|
}
|
|
}
|