--- phase: 01-semantic-position-model plan: 02 type: execute wave: 2 depends_on: ["01-01"] files_modified: - SportsTimeTests/SortOrderProviderTests.swift - SportsTimeTests/SemanticPositionPersistenceTests.swift autonomous: true must_haves: truths: - "User can persist an item's position, reload, and find it in the same location" - "Moving travel segment to different day updates its day property" - "Inserting between two items gets sortOrder between their values (e.g., 1.0 and 2.0 -> 1.5)" - "Games remain fixed at their schedule-determined positions" - "Custom items can be placed at any sortOrder value (negative, zero, positive)" artifacts: - path: "SportsTimeTests/SortOrderProviderTests.swift" provides: "Unit tests for SortOrderProvider" min_lines: 80 - path: "SportsTimeTests/SemanticPositionPersistenceTests.swift" provides: "Integration tests for position persistence" min_lines: 100 key_links: - from: "SortOrderProviderTests" to: "SortOrderProvider" via: "Test imports and calls provider methods" pattern: "SortOrderProvider\\." - from: "SemanticPositionPersistenceTests" to: "LocalItineraryItem" via: "Creates and persists items via SwiftData" pattern: "LocalItineraryItem" --- Create comprehensive tests verifying the semantic position model works correctly. Purpose: Prove that requirements DATA-01 through DATA-05 and PERS-01 through PERS-03 are satisfied. Tests must verify: sortOrder calculation correctness, midpoint insertion math, day derivation accuracy, and persistence survival across SwiftData reload. Output: Two test files covering unit tests for SortOrderProvider and integration tests for persistence behavior. @~/.claude/get-shit-done/workflows/execute-plan.md @~/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/01-semantic-position-model/01-RESEARCH.md @.planning/phases/01-semantic-position-model/01-01-SUMMARY.md # Source files @SportsTime/Core/Models/Domain/SortOrderProvider.swift @SportsTime/Core/Models/Domain/ItineraryItem.swift @SportsTime/Core/Models/Local/SavedTrip.swift Task 1: Create SortOrderProvider unit tests SportsTimeTests/SortOrderProviderTests.swift Create a new test file `SortOrderProviderTests.swift` with tests for all SortOrderProvider methods. Test cases to include: **initialSortOrder tests:** - `test_initialSortOrder_midnight_returns100`: 00:00 -> 100.0 - `test_initialSortOrder_noon_returns820`: 12:00 -> 100 + 720 = 820.0 - `test_initialSortOrder_7pm_returns1240`: 19:00 -> 100 + 1140 = 1240.0 - `test_initialSortOrder_1159pm_returns1539`: 23:59 -> 100 + 1439 = 1539.0 **sortOrderBetween tests:** - `test_sortOrderBetween_integers_returnsMidpoint`: (1.0, 2.0) -> 1.5 - `test_sortOrderBetween_negativeAndPositive_returnsMidpoint`: (-1.0, 1.0) -> 0.0 - `test_sortOrderBetween_fractionals_returnsMidpoint`: (1.5, 1.75) -> 1.625 **sortOrderBefore tests:** - `test_sortOrderBefore_positive_returnsLower`: 1.0 -> 0.0 - `test_sortOrderBefore_negative_returnsLower`: -1.0 -> -2.0 **sortOrderAfter tests:** - `test_sortOrderAfter_positive_returnsHigher`: 1.0 -> 2.0 - `test_sortOrderAfter_zero_returnsOne`: 0.0 -> 1.0 **needsNormalization tests:** - `test_needsNormalization_wellSpaced_returnsFalse`: items with gaps > 1e-10 - `test_needsNormalization_tinyGap_returnsTrue`: items with gap < 1e-10 - `test_needsNormalization_empty_returnsFalse`: empty array - `test_needsNormalization_singleItem_returnsFalse`: one item **normalize tests:** - `test_normalize_reassignsIntegerSpacing`: after normalize, sortOrders are 1.0, 2.0, 3.0... - `test_normalize_preservesOrder`: relative order unchanged after normalize Use `@testable import SportsTime` at top. Tests compile and pass: ```bash xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/SortOrderProviderTests test 2>&1 | grep -E "(Test Case|passed|failed)" ``` SortOrderProviderTests.swift exists with 16+ test cases, all tests pass Task 2: Create persistence integration tests SportsTimeTests/SemanticPositionPersistenceTests.swift Create a new test file `SemanticPositionPersistenceTests.swift` with integration tests for semantic position persistence. These tests verify PERS-01, PERS-02, PERS-03, and DATA-04 requirements. Test cases to include: **Position persistence (PERS-01):** - `test_itineraryItem_positionSurvivesEncodeDecode`: Create ItineraryItem with specific day/sortOrder, encode to JSON, decode, verify day and sortOrder match exactly - `test_localItineraryItem_positionSurvivesSwiftData`: Create LocalItineraryItem, save to SwiftData ModelContext, fetch back, verify day and sortOrder match **Semantic-only state (PERS-02):** - `test_itineraryItem_allPositionPropertiesAreCodable`: Verify ItineraryItem.day and .sortOrder are included in Codable output (not transient) **Midpoint insertion (PERS-03):** - `test_midpointInsertion_50Times_maintainsPrecision`: Insert 50 times between adjacent items, verify all sortOrders are distinct - `test_midpointInsertion_producesCorrectValue`: Insert between sortOrder 1.0 and 2.0, verify result is 1.5 **Day property updates (DATA-02, DATA-05):** - `test_travelItem_dayCanBeUpdated`: Create travel item with day=1, update to day=3, verify day property changed - `test_item_belongsToExactlyOneDay`: Verify item.day is a single Int, not optional or array **Game immutability (DATA-03):** - `test_gameItem_sortOrderDerivedFromTime`: Create game item for 7pm game, verify sortOrder is ~1240.0 (100 + 19*60) **Custom item flexibility (DATA-04):** - `test_customItem_canBePlacedAtAnyPosition`: Create custom items with sortOrder values at negative (-5.0, before all games), between games (500.0), and after all games (2000.0). Verify all three persist correctly and can coexist on the same day sorted correctly. Use in-memory SwiftData ModelContainer for tests. Note: LocalItineraryItem is standalone with no relationships - it can be registered alone: ```swift let config = ModelConfiguration(isStoredInMemoryOnly: true) let container = try ModelContainer(for: LocalItineraryItem.self, configurations: config) ``` Import XCTest, SwiftData, and `@testable import SportsTime`. Tests compile and pass: ```bash xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/SemanticPositionPersistenceTests test 2>&1 | grep -E "(Test Case|passed|failed)" ``` SemanticPositionPersistenceTests.swift exists with 9+ test cases, all tests pass Task 3: Run full test suite to verify no regressions Run the complete test suite to verify: 1. All new tests pass 2. No existing tests broken by new code 3. Build and test cycle completes successfully If any tests fail, investigate and fix before completing the plan. ```bash xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' test 2>&1 | tail -30 ``` Look for "** TEST SUCCEEDED **" at the end. Full test suite passes with no failures, including all new and existing tests 1. SortOrderProviderTests.swift exists with 16+ test methods covering all SortOrderProvider functions 2. SemanticPositionPersistenceTests.swift exists with 9+ test methods covering persistence requirements 3. All tests pass when run individually and as part of full suite 4. Tests verify the success criteria from ROADMAP.md Phase 1: - Position survives reload (tested via encode/decode and SwiftData) - Travel day update works (tested via day property mutation) - Midpoint insertion works (tested via 50-iteration precision test) - Games use time-based sortOrder (tested via initialSortOrder) - Custom items can be placed anywhere (tested via negative/between/after positions) - 25+ new test cases across 2 test files - All tests pass - Tests directly verify Phase 1 requirements DATA-01 through DATA-05 and PERS-01 through PERS-03 - No regression in existing tests After completion, create `.planning/phases/01-semantic-position-model/01-02-SUMMARY.md`