TeraCalendarPresentationTests.swift (8130B)
1 import Foundation 2 import SwiftUI 3 @testable import TeraApp 4 import XCTest 5 6 final class TeraCalendarPresentationTests: XCTestCase { 7 private let locale = Locale(identifier: "en_US") 8 9 func testSeptemberFifthNeverMovesAcrossDeviceTimeZones() throws { 10 let timing = try civil(2026, 9, 5) 11 for name in ["America/Vancouver", "UTC", "Pacific/Kiritimati", "Pacific/Pago_Pago", "Asia/Kathmandu"] { 12 let zone = try XCTUnwrap(TimeZone(identifier: name)) 13 XCTAssertEqual(TeraCalendarPresentation(locale: locale, timeZone: zone).summary(timing), "All day, Sep 5, 2026") 14 } 15 } 16 17 func testCivilLocaleOrderMonthNamesAndNumeralsUseTheSameGregorianDate() throws { 18 let value = try TeraCivilDate(year: 2026, month: 9, day: 5) 19 for (identifier, expected) in [("en_US", "Sep 5, 2026"), ("en_GB", "5 Sep 2026"), 20 ("fr_FR", "5 sept. 2026"), ("ja_JP", "2026年9月5日")] 21 { 22 XCTAssertEqual(TeraCivilDateFormat(locale: Locale(identifier: identifier)).string(value), expected, identifier) 23 } 24 let arabic = TeraCivilDateFormat(locale: Locale(identifier: "ar_EG")).string(value) 25 XCTAssertTrue(arabic.contains("٥")) 26 XCTAssertTrue(arabic.contains("٢٠٢٦")) 27 XCTAssertEqual(value.canonical, "2026-09-05") 28 } 29 30 func testExclusiveCivilEndsDisplayOnlyIncludedLeapAndYearBoundaryDays() throws { 31 let format = TeraCalendarPresentation(locale: locale) 32 XCTAssertEqual(try format.summary(civil(2024, 2, 29, end: date(2024, 3, 1))), "All day, Feb 29, 2024") 33 XCTAssertEqual(try format.summary(civil(2024, 2, 28, end: date(2024, 3, 1))), "All day, Feb 28, 2024 – Feb 29, 2024") 34 XCTAssertEqual(try format.summary(civil(2026, 9, 5, end: date(2026, 9, 7))), "All day, Sep 5, 2026 – Sep 6, 2026") 35 XCTAssertEqual(try format.summary(civil(2025, 12, 31, end: date(2026, 1, 2))), "All day, Dec 31, 2025 – Jan 1, 2026") 36 XCTAssertEqual(try format.summary(civil(2026, 9, 5, end: date(2026, 9, 5))), "Date unavailable") 37 } 38 39 func testCivilPredecessorHandlesGregorianCenturyAndSupportedExtremes() throws { 40 for (year, last) in [(UInt16(1900), UInt8(28)), (2000, 29), (2100, 28), (2400, 29)] { 41 XCTAssertEqual(try TeraCivilDate(year: year, month: 3, day: 1).previousDay, 42 try TeraCivilDate(year: year, month: 2, day: last)) 43 } 44 XCTAssertNil(try TeraCivilDate(year: 1, month: 1, day: 1).previousDay) 45 XCTAssertEqual(try TeraCivilDate(year: 9999, month: 12, day: 31).previousDay, 46 try TeraCivilDate(year: 9999, month: 12, day: 30)) 47 XCTAssertTrue(try TeraCalendarPresentation(locale: locale).summary(civil(9999, 12, 31)).contains("9999")) 48 } 49 50 func testTimedDisplayRetainsExactInstantsAndDistinctSourceZones() throws { 51 let timing = TeraCalendarTiming.timeBased(startUnixSeconds: 0, endExclusiveUnixSeconds: 3600, 52 startTimezoneID: "America/Vancouver", endTimezoneID: "Europe/Paris") 53 let zone = try XCTUnwrap(TimeZone(identifier: "UTC")) 54 let text = TeraCalendarPresentation(locale: locale, timeZone: zone).summary(timing) 55 XCTAssertTrue(text.contains("Starts: Jan 1, 1970")) 56 XCTAssertTrue(text.contains("Ends: Jan 1, 1970")) 57 XCTAssertTrue(text.contains("Display time zone: \(zone.identifier)")) 58 XCTAssertTrue(text.contains("Event start: Dec 31, 1969")) 59 XCTAssertTrue(text.contains("(America/Vancouver)")) 60 XCTAssertTrue(text.contains("Event end: Jan 1, 1970")) 61 XCTAssertTrue(text.contains("(Europe/Paris)")) 62 XCTAssertFalse(text.contains("All day")) 63 XCTAssertEqual(timing, .timeBased(startUnixSeconds: 0, endExclusiveUnixSeconds: 3600, 64 startTimezoneID: "America/Vancouver", endTimezoneID: "Europe/Paris")) 65 } 66 67 func testFullUnsignedWireValuesNeverBecomeRoundedOrInventedDisplayDates() { 68 for seconds: UInt64 in [253_402_300_800, (1 << 53) + 1, .max] { 69 let timing = TeraCalendarTiming.timeBased(startUnixSeconds: seconds, endExclusiveUnixSeconds: nil, 70 startTimezoneID: nil, endTimezoneID: nil) 71 let text = TeraCalendarPresentation(locale: locale).summary(timing) 72 XCTAssertTrue(text.contains("Date unavailable")) 73 XCTAssertFalse(text.contains("1970")) 74 XCTAssertFalse(text.contains("Event start")) 75 } 76 } 77 78 func testRepeatedWallTimesRetainDaylightSavingOffsetsAndLocalYearBounds() throws { 79 let zone = try XCTUnwrap(TimeZone(identifier: "America/Vancouver")) 80 let format = TeraCalendarPresentation(locale: locale, timeZone: zone) 81 // Use a historical overlap: current tzdata keeps Vancouver on PDT in late 2026. 82 let timing = TeraCalendarTiming.timeBased(startUnixSeconds: 1_730_622_600, endExclusiveUnixSeconds: 1_730_626_200, 83 startTimezoneID: nil, endTimezoneID: nil) 84 let text = format.summary(timing) 85 XCTAssertTrue(text.contains("PDT"), text) 86 XCTAssertTrue(text.contains("PST"), text) 87 let east = try XCTUnwrap(TimeZone(identifier: "Pacific/Kiritimati")) 88 let upper = TeraCalendarTiming.timeBased(startUnixSeconds: 253_402_300_799, endExclusiveUnixSeconds: nil, 89 startTimezoneID: nil, endTimezoneID: nil) 90 XCTAssertTrue(TeraCalendarPresentation(locale: locale, timeZone: east).summary(upper).contains("Date unavailable")) 91 } 92 93 func testFeedAndDetailAccessibilityIncludeTheIdenticalVisibleTiming() throws { 94 let zone = try XCTUnwrap(TimeZone(identifier: "America/Vancouver")) 95 for timing in try [civil(2026, 9, 5, end: date(2026, 9, 7)), 96 .timeBased(startUnixSeconds: 0, endExclusiveUnixSeconds: 3600, 97 startTimezoneID: "Europe/Paris", endTimezoneID: nil)] 98 { 99 let card = TeraScopeFixtures.card("Calendar example", timing: timing) 100 let visible = TeraCalendarPresentation(locale: locale, timeZone: zone).summary(timing) 101 for presentation: TeraTodayCardPresentation in [.feed, .detail] { 102 XCTAssertTrue(presentation.accessibility(card, locale: locale, timeZone: zone).contains(visible)) 103 } 104 XCTAssertTrue(card.accessibilitySummary(locale: locale, timeZone: zone).contains(visible)) 105 } 106 } 107 108 @MainActor 109 func testProductionCalendarCardScreenshotsRetainRangeAndAccessibleText() async throws { 110 let client = try await TeraScopeFixtures.client(TeraScopeBackend()) 111 let store = TeraMediaStore(runtimeClient: client) 112 let zone = try XCTUnwrap(TimeZone(identifier: "America/Vancouver")) 113 let card = try TeraScopeFixtures.card("Community harvest", timing: civil(2026, 9, 5, end: date(2026, 9, 7))) 114 for (name, presentation, size) in [("feed", TeraTodayCardPresentation.feed, DynamicTypeSize.large), 115 ("detail-accessible", .detail, .accessibility3)] 116 { 117 XCTAssertTrue(presentation.accessibility(card, locale: locale, timeZone: zone) 118 .contains("All day, Sep 5, 2026 – Sep 6, 2026")) 119 let content = TeraTodayCardView(card: card, context: nil, mediaStore: store, presentation: presentation) 120 .environment(\.locale, locale).environment(\.timeZone, zone) 121 .environment(\.dynamicTypeSize, size).environment(\.colorScheme, .light) 122 .padding(16).frame(width: 390).background(.white) 123 let renderer = ImageRenderer(content: content) 124 renderer.scale = 2 125 let image = try XCTUnwrap(renderer.uiImage) 126 XCTAssertEqual(image.size.width, 390) 127 XCTAssertGreaterThan(image.size.height, 100) 128 let attachment = XCTAttachment(image: image) 129 attachment.name = "c050-calendar-\(name)" 130 attachment.lifetime = .keepAlways 131 add(attachment) 132 } 133 _ = try await client.stop() 134 } 135 136 private func civil(_ year: UInt16, _ month: UInt8, _ day: UInt8, 137 end: TeraCivilDate? = nil) throws -> TeraCalendarTiming 138 { 139 try .dateBased(start: date(year, month, day), endExclusive: end) 140 } 141 142 private func date(_ year: UInt16, _ month: UInt8, _ day: UInt8) throws -> TeraCivilDate { 143 try TeraCivilDate(year: year, month: month, day: day) 144 } 145 }