field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

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 }