field_ios

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

TeraTodayContextTests.swift (8844B)


      1 import Combine
      2 @testable import TeraApp
      3 import XCTest
      4 
      5 @MainActor
      6 final class TeraTodayContextTests: XCTestCase {
      7   func testReplacementPreservesOnlyPresentSelectionAndIgnoresUnknownChoice() async throws {
      8     let backend = try TeraScopeBackend()
      9     let client = try await TeraScopeFixtures.client(backend)
     10     let first = context("first"), second = context("second")
     11     let store = TeraTodayStore(runtimeClient: client, contexts: [first, second], selectedContextID: second.id)
     12     await store.reload(refreshProjection: false)
     13     let original = store.scopeGeneration
     14     store.replaceContexts([first, second, second], selectedID: nil)
     15     store.selectContext(id: "absent")
     16     XCTAssertEqual(store.scopeGeneration, original)
     17     XCTAssertEqual(store.cards.map(\.id), second.relayURLs)
     18     store.replaceContexts([second, first], selectedID: "absent")
     19     XCTAssertEqual(store.selectedContext, second)
     20     store.replaceContexts([first], selectedID: second.id)
     21     XCTAssertEqual(store.selectedContext, first)
     22     XCTAssertTrue(store.cards.isEmpty)
     23     store.replaceContexts([], selectedID: nil)
     24     XCTAssertNil(store.selectedContext)
     25     XCTAssertFalse(store.canLoadNextPage)
     26     store.stop()
     27     _ = try await client.stop()
     28   }
     29 
     30   func testFirstConfigurationReconcilesInjectedChoicesWithRuntimeSnapshot() async throws {
     31     let backend = try TeraScopeBackend()
     32     let client = try await TeraScopeFixtures.client(backend)
     33     let stale = context("old")
     34     let store = TeraTodayStore(runtimeClient: client, contexts: [stale])
     35     await store.reload(refreshProjection: false)
     36     let snapshot = TeraScopeFixtures.snapshot()
     37     store.configure(snapshot: snapshot)
     38     XCTAssertTrue(store.cards.isEmpty)
     39     XCTAssertEqual(store.contexts, [.defaultContext(snapshot: snapshot)])
     40     XCTAssertEqual(store.selectedContext?.id, "default")
     41     store.stop()
     42     _ = try await client.stop()
     43   }
     44 
     45   func testAccountAndProfileChangesInvalidateSameNamedScopeButEvidenceDoesNot() async throws {
     46     let backend = try TeraScopeBackend()
     47     let client = try await TeraScopeFixtures.client(backend)
     48     let store = TeraTodayStore(runtimeClient: client)
     49     store.configure(snapshot: TeraScopeFixtures.snapshot())
     50     await store.reload(refreshProjection: false)
     51     var generation = store.scopeGeneration
     52     store.configure(snapshot: TeraScopeFixtures.snapshot(evidence: TeraScopeFixtures.evidence(observedAt: 9)))
     53     XCTAssertEqual(store.scopeGeneration, generation)
     54     XCTAssertFalse(store.cards.isEmpty)
     55     for snapshot in [TeraScopeFixtures.snapshot(profile: "public"), TeraScopeFixtures.snapshot(account: "b", profile: "public")] {
     56       let paused = await backend.pause(.page)
     57       let old = Task { await store.reload(refreshProjection: false) }
     58       await paused.entered.wait()
     59       store.configure(snapshot: snapshot)
     60       XCTAssertNotEqual(store.scopeGeneration, generation)
     61       XCTAssertTrue(store.cards.isEmpty)
     62       XCTAssertEqual(store.selectedContext?.id, "default")
     63       await paused.resume.open()
     64       await old.value
     65       XCTAssertTrue(store.cards.isEmpty)
     66       generation = store.scopeGeneration
     67       await store.reload(refreshProjection: false)
     68       XCTAssertEqual(store.scopeGeneration, generation)
     69     }
     70     store.stop()
     71     _ = try await client.stop()
     72   }
     73 
     74   func testFirstPageSwitchClearsBeforeLabelForCachedAndEmptyDestinations() async throws {
     75     for cached in [false, true] {
     76       try await assertSwitch(nextPage: false, cached: cached)
     77     }
     78   }
     79 
     80   func testSameIDReplacementClearsBeforePublishingChangedLabelAndDefinition() async throws {
     81     let backend = try TeraScopeBackend()
     82     let client = try await TeraScopeFixtures.client(backend)
     83     let original = context("first")
     84     let store = TeraTodayStore(runtimeClient: client, contexts: [original])
     85     await store.reload(refreshProjection: false)
     86     let paused = await backend.pause(.page)
     87     let old = Task { await store.reload(refreshProjection: false) }
     88     await paused.entered.wait()
     89     let updated = TeraLocalNetwork(
     90       schemaVersion: 1, id: original.id, label: "Updated network", relayURLs: ["wss://second.example"],
     91       locality: nil, followedAuthors: [], generation: 2
     92     )
     93     var published = false
     94     let subscription = store.$contexts.dropFirst().sink { values in
     95       published = true
     96       XCTAssertEqual(values, [updated])
     97       XCTAssertTrue(store.cards.isEmpty)
     98       XCTAssertEqual(store.presentation.content, .notLoaded)
     99     }
    100     store.replaceContexts([updated], selectedID: nil)
    101     XCTAssertTrue(published)
    102     XCTAssertEqual(store.selectedContext, updated)
    103     await TeraScopeFixtures.eventually { store.cards.map(\.id) == updated.relayURLs }
    104     await paused.resume.open()
    105     await old.value
    106     XCTAssertEqual(store.cards.map(\.id), updated.relayURLs)
    107     subscription.cancel()
    108     store.stop()
    109     _ = try await client.stop()
    110   }
    111 
    112   func testNextPageSwitchClearsBeforeLabelForCachedAndEmptyDestinations() async throws {
    113     for cached in [false, true] {
    114       try await assertSwitch(nextPage: true, cached: cached)
    115     }
    116   }
    117 
    118   func testContextSwitchClearsSupportingStoresBeforeLabelAndRejectsLateResults() async throws {
    119     let backend = try TeraScopeBackend()
    120     let client = try await TeraScopeFixtures.client(backend)
    121     let stores = TeraProductStores(runtimeClient: client)
    122     stores.configure(snapshot: TeraScopeFixtures.snapshot())
    123     stores.search.updateQuery("old")
    124     await stores.search.search()
    125     await stores.me.reload()
    126     XCTAssertFalse(stores.search.results.isEmpty)
    127     XCTAssertNotNil(stores.me.snapshot)
    128     let searchPause = await backend.pause(.search), mePause = await backend.pause(.me)
    129     let search = Task { await stores.search.search() }, me = Task { await stores.me.reload() }
    130     await searchPause.entered.wait()
    131     await mePause.entered.wait()
    132     var published = false
    133     let subscription = stores.today.$selectedContextID.dropFirst().sink { selected in
    134       published = true
    135       XCTAssertEqual(selected, "second")
    136       XCTAssertTrue(stores.search.results.isEmpty)
    137       XCTAssertNil(stores.me.snapshot)
    138       XCTAssertEqual(stores.me.observationState, .stopped)
    139     }
    140     stores.today.replaceContexts([context("second")], selectedID: nil)
    141     XCTAssertTrue(published)
    142     await searchPause.resume.open()
    143     await mePause.resume.open()
    144     await search.value
    145     await me.value
    146     XCTAssertTrue(stores.search.results.isEmpty)
    147     XCTAssertNil(stores.me.snapshot)
    148     await stores.me.reload()
    149     let actual = await backend.lastMeContext
    150     XCTAssertEqual(actual, stores.today.selectedContext)
    151     subscription.cancel()
    152     stores.stop()
    153     _ = try await client.stop()
    154   }
    155 
    156   private func assertSwitch(nextPage: Bool, cached: Bool) async throws {
    157     let backend = try TeraScopeBackend()
    158     let client = try await TeraScopeFixtures.client(backend)
    159     let first = context("first"), second = context("second")
    160     let store = TeraTodayStore(runtimeClient: client, contexts: [first, second])
    161     await backend.setPage(page(["old"], cursor: "next"))
    162     await backend.setPage(page(["old-next"]), cursor: "next")
    163     await store.reload(refreshProjection: false)
    164     let paused = await backend.pause(.page)
    165     let old = Task {
    166       if nextPage {
    167         await store.loadNextPage()
    168       } else {
    169         await store.reload(refreshProjection: false)
    170       }
    171     }
    172     await paused.entered.wait()
    173     let expected = cached ? ["new"] : []
    174     await backend.setPage(page(expected))
    175     var changes = 0
    176     let subscription = store.$selectedContextID.dropFirst().sink { selected in
    177       changes += 1
    178       XCTAssertEqual(selected, second.id)
    179       XCTAssertTrue(store.cards.isEmpty)
    180       XCTAssertEqual(store.presentation.content, .notLoaded)
    181       XCTAssertFalse(store.isLoadingNextPage)
    182       XCTAssertFalse(store.canLoadNextPage)
    183     }
    184     let generation = store.scopeGeneration
    185     store.selectContext(id: second.id)
    186     XCTAssertNotEqual(store.scopeGeneration, generation)
    187     XCTAssertEqual(changes, 1)
    188     XCTAssertEqual(store.selectedContext?.label, "second")
    189     await TeraScopeFixtures.eventually { store.presentation.content == (cached ? .available : .empty) }
    190     await paused.resume.open()
    191     await old.value
    192     XCTAssertEqual(store.cards.map(\.id), expected)
    193     XCTAssertEqual(store.selectedContext, second)
    194     subscription.cancel()
    195     store.stop()
    196     _ = try await client.stop()
    197   }
    198 
    199   private func context(_ id: String) -> TeraLocalNetwork {
    200     TeraLocalNetwork(schemaVersion: 1, id: id, label: id, relayURLs: ["wss://\(id).example"],
    201                      locality: nil, followedAuthors: [], generation: 1)
    202   }
    203 
    204   private func page(_ ids: [String], cursor: String? = nil) -> TeraTodayPage {
    205     TeraTodayPage(asOfUnixSeconds: 1, items: ids.map(TeraScopeFixtures.card), nextCursor: cursor, calendar: TeraScopeFixtures.viewerCalendar(asOf: 1))
    206   }
    207 }