TeraCalendarEditingTests.swift (5700B)
1 import Foundation 2 @testable import TeraApp 3 import XCTest 4 5 final class TeraCalendarEditingTests: XCTestCase { 6 func testCivilComponentsRetainIncompleteInputAndCanonicalizeWithoutDateParsing() throws { 7 let partial = TeraCivilDateInput(raw: "2024--") 8 XCTAssertNil(partial.value) 9 let month = partial.replacing(1, with: "2") 10 XCTAssertEqual(month, "2024-2-") 11 let leap = TeraCivilDateInput(raw: month).replacing(2, with: "29") 12 XCTAssertEqual(leap, "2024-2-29") 13 XCTAssertEqual(TeraCivilDateInput(raw: leap).canonicalOrRaw, "2024-02-29") 14 XCTAssertEqual(TeraCivilDateInput(raw: leap).value, try TeraCivilDate(year: 2024, month: 2, day: 29)) 15 XCTAssertNil(TeraCivilDateInput(raw: "2023-02-29").value) 16 XCTAssertNil(TeraCivilDateInput(raw: "2026-09-05-extra").value) 17 let arabic = TeraCivilDateInput(raw: "٢٠٢٦-٠٩-").replacing(2, with: "٠٥") 18 XCTAssertEqual(arabic, "٢٠٢٦-٠٩-٠٥") 19 XCTAssertEqual(TeraCivilDateInput(raw: arabic).canonicalOrRaw, "2026-09-05") 20 XCTAssertEqual(TeraCivilDateInput(raw: "2026-09-05").replacing(0, with: "2"), "2-09-05") 21 XCTAssertNil(TeraCivilDateInput(raw: "--").replacing(0, with: "")) 22 } 23 24 func testNewFormUsesLocalCivilTodayAndSeparateCheckedInstantDefaults() throws { 25 let zone = try XCTUnwrap(TimeZone(identifier: "America/Vancouver")) 26 let form = TeraAddPresentation.newForm(type: .createEvent, identifier: { String(repeating: "a", count: 32) }, 27 clock: .fixed(unixSeconds: 1_788_568_200), timeZone: zone) 28 XCTAssertEqual(form.eventTimezone, "America/Vancouver") 29 XCTAssertEqual(form.eventStartDate, "2026-09-04") 30 XCTAssertEqual(form.eventEndDate, "2026-09-05") 31 XCTAssertEqual(form.eventStartUnixSeconds, 1_788_571_800) 32 XCTAssertEqual(form.eventEndUnixSeconds, 1_788_575_400) 33 } 34 35 func testExclusiveNextDayUsesGregorianComponentsAtLeapAndDomainBoundaries() throws { 36 XCTAssertEqual(try TeraCivilDate(year: 2024, month: 2, day: 28).nextDay?.canonical, "2024-02-29") 37 XCTAssertEqual(try TeraCivilDate(year: 2024, month: 2, day: 29).nextDay?.canonical, "2024-03-01") 38 XCTAssertEqual(try TeraCivilDate(year: 1900, month: 2, day: 28).nextDay?.canonical, "1900-03-01") 39 XCTAssertEqual(try TeraCivilDate(year: 2025, month: 12, day: 31).nextDay?.canonical, "2026-01-01") 40 XCTAssertNil(try TeraCivilDate(year: 9999, month: 12, day: 31).nextDay) 41 } 42 43 func testHistoricalSpringGapNeverBecomesANormalizedInstant() throws { 44 let zone = try XCTUnwrap(TimeZone(identifier: "America/Vancouver")) 45 let gap = try TeraWallTime(date: TeraCivilDate(year: 2024, month: 3, day: 10), hour: 2, minute: 30) 46 XCTAssertEqual(gap.instants(in: zone), []) 47 XCTAssertEqual(TeraWallTime(date: gap.date, hour: 3, minute: 30).instants(in: zone).count, 1) 48 XCTAssertEqual(TeraWallTime(date: gap.date, hour: 25, minute: 0).instants(in: zone), []) 49 } 50 51 func testHistoricalOverlapOffersBothExactInstantsWithoutDiscardingSeconds() throws { 52 let zone = try XCTUnwrap(TimeZone(identifier: "America/Vancouver")) 53 let wall = try TeraWallTime(date: TeraCivilDate(year: 2024, month: 11, day: 3), hour: 1, minute: 30, second: 17) 54 let expected: [UInt64] = [1_730_622_617, 1_730_626_217] 55 XCTAssertEqual(wall.instants(in: zone), expected) 56 for instant in expected { 57 XCTAssertEqual(TeraWallTime.from(instant, timeZone: zone)?.instants(in: zone), expected) 58 let date = try XCTUnwrap(TeraCalendarEditing.pickerInstant(instant)) 59 XCTAssertEqual(TeraCalendarEditing.pickerSeconds(date), instant) 60 } 61 } 62 63 func testSkippedLocalDayRemainsAValidCivilDateAndHasNoTimedInstant() throws { 64 let zone = try XCTUnwrap(TimeZone(identifier: "Pacific/Apia")) 65 let date = try TeraCivilDate(year: 2011, month: 12, day: 30) 66 XCTAssertEqual(TeraCivilDateInput(raw: date.canonical).value, date) 67 XCTAssertEqual(TeraWallTime(date: date, hour: 12, minute: 0).instants(in: zone), []) 68 XCTAssertEqual(date.nextDay?.canonical, "2011-12-31") 69 } 70 71 func testUnsupportedUnsignedValuesAndDatesCannotSilentlySeedOrRoundPickers() { 72 for value: UInt64 in [0, 253_402_300_800, (1 << 53) + 1, .max] { 73 XCTAssertNil(TeraCalendarEditing.pickerInstant(value)) 74 } 75 for value in [Double.nan, .infinity, -1, 253_402_300_800] { 76 XCTAssertNil(TeraCalendarEditing.pickerSeconds(Date(timeIntervalSince1970: value))) 77 } 78 var form = TeraAddForm.empty(.createEvent) 79 TeraCalendarEditing.initialize(&form, now: .max, timeZone: .gmt) 80 XCTAssertNil(form.eventStartDate) 81 XCTAssertNil(form.eventEndDate) 82 XCTAssertNil(form.eventStartUnixSeconds) 83 XCTAssertNil(form.eventEndUnixSeconds) 84 } 85 86 @MainActor 87 func testModeAndZoneChangesPreserveEveryOtherUnsavedField() async throws { 88 let client = try await TeraScopeFixtures.client(TeraScopeBackend()) 89 let store = TeraAddStore(runtimeClient: client, initialType: .createEvent, clock: .fixed(unixSeconds: 1_788_568_200)) 90 store.updateForm(\.title, "Unsaved harvest") 91 store.updateForm(\.content, "Unsaved details") 92 store.updateForm(\.eventStartDate, "2026-09-") 93 store.updateForm(\.eventEndDate, "2026-09-07") 94 store.updateForm(\.eventTimezone, "America/Vancouver") 95 let original = store.form 96 var expected = original 97 expected.eventTiming = .allDay 98 store.updateForm(\.eventTiming, .allDay) 99 XCTAssertEqual(store.form, expected) 100 store.updateForm(\.eventTiming, .timed) 101 XCTAssertEqual(store.form, original) 102 expected = original 103 expected.eventTimezone = "Pacific/Kiritimati" 104 store.updateForm(\.eventTimezone, "Pacific/Kiritimati") 105 XCTAssertEqual(store.form, expected) 106 _ = try await client.stop() 107 } 108 }