field_ios

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

TeraTodayCacheFirstTests.swift (6807B)


      1 @testable import TeraApp
      2 import XCTest
      3 
      4 @MainActor
      5 final class TeraTodayCacheFirstTests: XCTestCase {
      6   func testInitialLocalContentAppearsBeforeAStalledRefreshAndSurvivesCancellation() async throws {
      7     for starting in [true, false] {
      8       for cards in [[], [TeraScopeFixtures.card("saved")]] {
      9         let backend = try TeraScopeBackend()
     10         await backend.setPage(page(cards))
     11         let (client, store) = try await makeStore(backend)
     12         let refresh = await backend.pause(.refresh)
     13         let task = Task {
     14           if starting {
     15             await store.start()
     16           } else {
     17             await store.reload()
     18           }
     19         }
     20         await refresh.entered.wait()
     21         XCTAssertEqual(store.cards, cards)
     22         XCTAssertEqual(store.presentation.content, cards.isEmpty ? .empty : .available)
     23         XCTAssertEqual(store.presentation.refresh, .refreshing)
     24         XCTAssertEqual(store.presentation.freshness, .unconfirmed)
     25         XCTAssertFalse(store.presentation.isReading)
     26         let counts = await backend.counts
     27         XCTAssertEqual(counts[.page], 1)
     28         XCTAssertEqual(counts[.refresh], 1)
     29         task.cancel()
     30         await task.value
     31         XCTAssertEqual(store.cards, cards)
     32         XCTAssertEqual(store.presentation.refresh, .idle)
     33         store.stop()
     34         // Release only after the caller has completed without a relay result.
     35         await refresh.resume.open()
     36         _ = try await client.stop()
     37       }
     38     }
     39   }
     40 
     41   func testSuccessfulRefreshReplacesTheAlreadyVisibleLocalPage() async throws {
     42     let backend = try TeraScopeBackend()
     43     await backend.setPage(page([TeraScopeFixtures.card("saved")]))
     44     let (client, store) = try await makeStore(backend)
     45     let refresh = await backend.pause(.refresh)
     46     let task = Task { await store.reload() }
     47     await refresh.entered.wait()
     48     XCTAssertEqual(store.cards.map(\.id), ["saved"])
     49     await backend.setPage(page([TeraScopeFixtures.card("updated")]))
     50     let read = await backend.pause(.page)
     51     await refresh.resume.open()
     52     await read.entered.wait()
     53     XCTAssertEqual(store.cards.map(\.id), ["saved"])
     54     XCTAssertTrue(store.presentation.isReading)
     55     XCTAssertEqual(store.presentation.freshness, .unconfirmed)
     56     await read.resume.open()
     57     await task.value
     58     XCTAssertEqual(store.cards.map(\.id), ["updated"])
     59     XCTAssertEqual(store.presentation.freshness, .refreshed(contentGeneration: 1))
     60     _ = try await client.stop()
     61   }
     62 
     63   func testCancelingTheFirstLocalReadDoesNotStartRefreshOrClaimNetworkActivity() async throws {
     64     let backend = try TeraScopeBackend()
     65     let (client, store) = try await makeStore(backend)
     66     let read = await backend.pause(.page)
     67     let task = Task { await store.reload() }
     68     await read.entered.wait()
     69     XCTAssertEqual(store.presentation.refresh, .idle)
     70     XCTAssertTrue(store.presentation.isReading)
     71     XCTAssertEqual(store.presentation.content, .notLoaded)
     72     task.cancel()
     73     await task.value
     74     XCTAssertFalse(store.presentation.isReading)
     75     XCTAssertEqual(store.presentation.refresh, .idle)
     76     let counts = await backend.counts
     77     XCTAssertNil(counts[.refresh])
     78     await read.resume.open()
     79     _ = try await client.stop()
     80   }
     81 
     82   func testLateRefreshedPageCannotReplaceANewerLocalReload() async throws {
     83     let backend = try TeraScopeBackend()
     84     let (client, store) = try await makeStore(backend)
     85     let refresh = await backend.pause(.refresh)
     86     let old = Task { await store.reload() }
     87     await refresh.entered.wait()
     88     await backend.setPage(page([TeraScopeFixtures.card("obsolete")]))
     89     let oldPage = await backend.pause(.page)
     90     await refresh.resume.open()
     91     await oldPage.entered.wait()
     92     await backend.setPage(page([TeraScopeFixtures.card("current")]))
     93     await store.reload(refreshProjection: false)
     94     await oldPage.resume.open()
     95     await old.value
     96     XCTAssertEqual(store.cards.map(\.id), ["current"])
     97     XCTAssertEqual(store.presentation.freshness, .unconfirmed)
     98     XCTAssertFalse(store.presentation.isReading)
     99     _ = try await client.stop()
    100   }
    101 
    102   func testLateCachedPaginationCannotAppendToTheRefreshedPageAtTheSameTime() async throws {
    103     let backend = try TeraScopeBackend()
    104     await backend.setPage(page([TeraScopeFixtures.card("saved")], next: "old-next"))
    105     await backend.setPage(page([TeraScopeFixtures.card("old-next")]), cursor: "old-next")
    106     let (client, store) = try await makeStore(backend)
    107     let refresh = await backend.pause(.refresh)
    108     let reload = Task { await store.reload() }
    109     await refresh.entered.wait()
    110     XCTAssertTrue(store.canLoadNextPage)
    111     let oldPage = await backend.pause(.page)
    112     let pagination = Task { await store.loadNextPage() }
    113     await oldPage.entered.wait()
    114     await backend.setPage(page([TeraScopeFixtures.card("updated")], next: "new-next"))
    115     await backend.setPage(page([TeraScopeFixtures.card("new-next")]), cursor: "new-next")
    116     await refresh.resume.open()
    117     await reload.value
    118     XCTAssertEqual(store.cards.map(\.id), ["updated"])
    119     XCTAssertFalse(store.isLoadingNextPage)
    120     await oldPage.resume.open()
    121     await pagination.value
    122     XCTAssertEqual(store.cards.map(\.id), ["updated"])
    123     await store.loadNextPage()
    124     XCTAssertEqual(store.cards.map(\.id), ["updated", "new-next"])
    125     _ = try await client.stop()
    126   }
    127 
    128   func testScopeChangeDuringRefreshClearsCacheAndRejectsLateSuccess() async throws {
    129     let backend = try TeraScopeBackend()
    130     let (client, store) = try await makeStore(backend)
    131     let refresh = await backend.pause(.refresh)
    132     let old = Task { await store.reload() }
    133     await refresh.entered.wait()
    134     XCTAssertEqual(store.cards.map(\.id), ["wss://first.example"])
    135     store.configure(snapshot: TeraScopeFixtures.snapshot(relay: "second"))
    136     XCTAssertTrue(store.cards.isEmpty)
    137     XCTAssertEqual(store.presentation.content, .notLoaded)
    138     await store.reload(refreshProjection: false)
    139     let counts = await backend.counts
    140     await refresh.resume.open()
    141     await old.value
    142     XCTAssertEqual(store.cards.map(\.id), ["wss://second.example"])
    143     XCTAssertEqual(store.presentation.freshness, .unconfirmed)
    144     let current = await backend.counts
    145     XCTAssertEqual(current[.page], counts[.page])
    146     _ = try await client.stop()
    147   }
    148 
    149   private func makeStore(_ backend: TeraScopeBackend) async throws -> (TeraRuntimeClient, TeraTodayStore) {
    150     let client = try await TeraScopeFixtures.client(backend)
    151     let store = TeraTodayStore(runtimeClient: client, clock: .fixed(unixSeconds: 1))
    152     store.configure(snapshot: TeraScopeFixtures.snapshot())
    153     return (client, store)
    154   }
    155 
    156   private func page(_ cards: [TeraTodayCard], next: String? = nil) -> TeraTodayPage {
    157     TeraTodayPage(asOfUnixSeconds: 1, items: cards, nextCursor: next, calendar: TeraScopeFixtures.viewerCalendar(asOf: 1))
    158   }
    159 }