TeraTodayPresentationTests.swift (9113B)
1 @testable import TeraApp 2 import XCTest 3 4 @MainActor 5 final class TeraTodayPresentationTests: XCTestCase { 6 func testContentAvailabilityIsIndependentOfRunningSuccessfulAndFailedRefresh() async throws { 7 for cached in [nil, [], [TeraScopeFixtures.card("cached")]] as [[TeraTodayCard]?] { 8 for fails in [false, true] { 9 try await assertRefresh(cached: cached, fails: fails) 10 } 11 } 12 } 13 14 func testReadFailureRetainsCachedContentAndDoesNotInventAnEmptyResult() async throws { 15 for cached in [false, true] { 16 let backend = try TeraScopeBackend() 17 let (client, store) = try await makeStore(backend) 18 if cached { 19 await store.reload(refreshProjection: false) 20 } 21 let expected = store.presentation.content 22 let firstRead = await backend.pause(.page, fails: true) 23 let pause = await backend.pause(.page, fails: true) 24 let task = Task { await store.reload() } 25 await firstRead.entered.wait() 26 await firstRead.resume.open() 27 await pause.entered.wait() 28 XCTAssertEqual(store.presentation.refresh, .completed) 29 XCTAssertEqual(store.presentation.content, expected) 30 XCTAssertTrue(store.presentation.isReading) 31 XCTAssertEqual(store.presentation.freshness, .unconfirmed) 32 await pause.resume.open() 33 await task.value 34 XCTAssertEqual(store.presentation.content, expected) 35 XCTAssertNotNil(store.presentation.readFailure) 36 XCTAssertEqual(store.presentation.refresh, .completed) 37 XCTAssertEqual(store.presentation.freshness, .unconfirmed) 38 XCTAssertFalse(store.presentation.isReading) 39 XCTAssertTrue(store.presentation.accessibilityStatus.contains("Saved posts could not be read.")) 40 XCTAssertFalse(store.presentation.accessibilityStatus.contains("Refresh failed.")) 41 XCTAssertEqual(store.cards.isEmpty, !cached) 42 _ = try await client.stop() 43 } 44 } 45 46 func testCanceledRefreshStopsActivityWithoutDiscardingCachedContent() async throws { 47 let backend = try TeraScopeBackend() 48 let (client, store) = try await makeStore(backend) 49 await store.reload(refreshProjection: false) 50 let cards = store.cards 51 let pause = await backend.pause(.refresh) 52 let task = Task { await store.reload() } 53 await pause.entered.wait() 54 task.cancel() 55 await task.value 56 XCTAssertEqual(store.cards, cards) 57 XCTAssertEqual(store.presentation.content, .available) 58 XCTAssertEqual(store.presentation.refresh, .idle) 59 XCTAssertEqual(store.presentation.freshness, .unconfirmed) 60 XCTAssertFalse(store.presentation.isReading) 61 XCTAssertFalse(store.presentation.accessibilityStatus.contains("Checking for updates.")) 62 await pause.resume.open() 63 _ = try await client.stop() 64 } 65 66 func testOldReadCancellationCannotClearTheNewRefreshActivity() async throws { 67 let backend = try TeraScopeBackend() 68 let (client, store) = try await makeStore(backend) 69 await store.reload(refreshProjection: false) 70 let reading = await backend.pause(.page) 71 let old = Task { await store.reload(refreshProjection: false) } 72 await reading.entered.wait() 73 let refreshing = await backend.pause(.refresh) 74 let current = Task { await store.reload() } 75 await refreshing.entered.wait() 76 old.cancel() 77 await old.value 78 XCTAssertEqual(store.presentation.refresh, .refreshing) 79 XCTAssertEqual(store.presentation.content, .available) 80 XCTAssertFalse(store.presentation.isReading) 81 XCTAssertTrue(store.presentation.accessibilityStatus.contains("Checking for updates.")) 82 await reading.resume.open() 83 await refreshing.resume.open() 84 await current.value 85 XCTAssertEqual(store.presentation.refresh, .completed) 86 XCTAssertEqual(store.presentation.freshness, .refreshed(contentGeneration: 1)) 87 _ = try await client.stop() 88 } 89 90 func testMissingContextDoesNotClaimAnEmptyReadOrSuccessfulRefresh() async throws { 91 let backend = try TeraScopeBackend() 92 let client = try await TeraScopeFixtures.client(backend) 93 let store = TeraTodayStore(runtimeClient: client) 94 await store.reload() 95 XCTAssertEqual(store.presentation.content, .notLoaded) 96 XCTAssertEqual(store.presentation.refresh, .idle) 97 XCTAssertEqual(store.presentation.freshness, .unconfirmed) 98 XCTAssertNotNil(store.presentation.readFailure) 99 let counts = await backend.counts 100 XCTAssertNil(counts[.refresh]) 101 XCTAssertNil(counts[.page]) 102 _ = try await client.stop() 103 } 104 105 func testPaginationReadFailureAndRecoveryPreserveTheRefreshOutcome() async throws { 106 let backend = try TeraScopeBackend() 107 await backend.setPage(page([TeraScopeFixtures.card("one")], next: "two")) 108 await backend.setPage(page([TeraScopeFixtures.card("two")]), cursor: "two") 109 let (client, store) = try await makeStore(backend) 110 await store.reload() 111 let pause = await backend.pause(.page, fails: true) 112 let task = Task { await store.loadNextPage() } 113 await pause.entered.wait() 114 await pause.resume.open() 115 await task.value 116 XCTAssertEqual(store.cards.map(\.id), ["one"]) 117 XCTAssertEqual(store.presentation.content, .available) 118 XCTAssertEqual(store.presentation.refresh, .completed) 119 XCTAssertNotNil(store.presentation.readFailure) 120 XCTAssertEqual(store.presentation.freshness, .unconfirmed) 121 XCTAssertTrue(store.canLoadNextPage) 122 XCTAssertFalse(store.presentation.readFailure?.requiresRefresh == true) 123 await store.loadNextPage() 124 XCTAssertEqual(store.cards.map(\.id), ["one", "two"]) 125 XCTAssertNil(store.presentation.readFailure) 126 XCTAssertEqual(store.presentation.refresh, .completed) 127 _ = try await client.stop() 128 } 129 130 func testFailedRefreshAndFailedLocalReadHaveIndependentAccessibleOutcomes() async throws { 131 let backend = try TeraScopeBackend() 132 let (client, store) = try await makeStore(backend) 133 let refreshing = await backend.pause(.refresh, fails: true) 134 let reading = await backend.pause(.page, fails: true) 135 let rereading = await backend.pause(.page, fails: true) 136 let task = Task { await store.reload() } 137 await reading.entered.wait() 138 await reading.resume.open() 139 await refreshing.entered.wait() 140 await refreshing.resume.open() 141 await rereading.entered.wait() 142 XCTAssertTrue(store.presentation.isReading) 143 await rereading.resume.open() 144 await task.value 145 XCTAssertEqual(store.presentation.content, .notLoaded) 146 XCTAssertNotNil(store.presentation.readFailure) 147 XCTAssertTrue(store.presentation.accessibilityStatus.contains("Refresh failed.")) 148 XCTAssertTrue(store.presentation.accessibilityStatus.contains("Saved posts could not be read.")) 149 XCTAssertFalse(store.presentation.accessibilityStatus.contains("Reading saved posts.")) 150 _ = try await client.stop() 151 } 152 153 private func assertRefresh(cached: [TeraTodayCard]?, fails: Bool) async throws { 154 let backend = try TeraScopeBackend() 155 await backend.setPage(page(cached ?? [])) 156 let (client, store) = try await makeStore(backend) 157 if cached != nil { 158 await store.reload(refreshProjection: false) 159 } 160 let expected: TeraTodayContentAvailability = (cached ?? []).isEmpty ? .empty : .available 161 let pause = await backend.pause(.refresh, fails: fails) 162 let task = Task { await store.reload() } 163 await pause.entered.wait() 164 XCTAssertEqual(store.presentation.content, expected) 165 XCTAssertEqual(store.presentation.refresh, .refreshing) 166 XCTAssertEqual(store.presentation.freshness, .unconfirmed) 167 XCTAssertFalse(store.presentation.isReading) 168 XCTAssertTrue(store.presentation.accessibilityStatus.contains("Checking for updates.")) 169 XCTAssertEqual(store.cards, cached ?? []) 170 await pause.resume.open() 171 await task.value 172 XCTAssertEqual(store.presentation.content, (cached ?? []).isEmpty ? .empty : .available) 173 XCTAssertNil(store.presentation.readFailure) 174 XCTAssertFalse(store.presentation.isReading) 175 if fails { 176 XCTAssertEqual(store.presentation.refresh, .failed(TeraTodayFailure(TeraScopeFixtures.failure()))) 177 XCTAssertEqual(store.presentation.freshness, .unconfirmed) 178 XCTAssertTrue(store.presentation.accessibilityStatus.contains("Refresh failed.")) 179 XCTAssertTrue(store.presentation.accessibilityStatus.contains("Showing saved posts.")) 180 } else { 181 XCTAssertEqual(store.presentation.refresh, .completed) 182 XCTAssertEqual(store.presentation.freshness, .refreshed(contentGeneration: 1)) 183 XCTAssertEqual(store.presentation.accessibilityStatus, "Saved posts checked.") 184 } 185 _ = try await client.stop() 186 } 187 188 private func makeStore(_ backend: TeraScopeBackend) async throws -> (TeraRuntimeClient, TeraTodayStore) { 189 let client = try await TeraScopeFixtures.client(backend) 190 let store = TeraTodayStore(runtimeClient: client, clock: .fixed(unixSeconds: 1)) 191 store.configure(snapshot: TeraScopeFixtures.snapshot()) 192 return (client, store) 193 } 194 195 private func page(_ cards: [TeraTodayCard], next: String? = nil) -> TeraTodayPage { 196 TeraTodayPage(asOfUnixSeconds: 1, items: cards, nextCursor: next, calendar: TeraScopeFixtures.viewerCalendar(asOf: 1)) 197 } 198 }