Add stream tests

This commit is contained in:
Thomas Ricouard 2023-12-30 09:08:19 +01:00
parent 176e4feaf8
commit 8a2861b37f
8 changed files with 65 additions and 105 deletions

View File

@ -7,8 +7,6 @@
objects = {
/* Begin PBXBuildFile section */
065FA1FE29866CD600012EA0 /* LRUCache in Frameworks */ = {isa = PBXBuildFile; productRef = 065FA1FD29866CD600012EA0 /* LRUCache */; };
065FA20A298675BA00012EA0 /* LRUCache in Frameworks */ = {isa = PBXBuildFile; productRef = 065FA209298675BA00012EA0 /* LRUCache */; };
069709A5298C8545006E4CB5 /* Atkinson-Hyperlegible-Regular-102.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 069709A3298C8545006E4CB5 /* Atkinson-Hyperlegible-Regular-102.ttf */; };
069709A8298C87B5006E4CB5 /* OpenDyslexic-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 069709A7298C87B5006E4CB5 /* OpenDyslexic-Regular.otf */; };
069709AA298C9AD7006E4CB5 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 069709A9298C9AD7006E4CB5 /* AboutView.swift */; };
@ -93,13 +91,10 @@
9FD542E72962D2FF0045321A /* Lists in Frameworks */ = {isa = PBXBuildFile; productRef = 9FD542E62962D2FF0045321A /* Lists */; };
9FE151A6293C90F900E9683D /* IconSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FE151A5293C90F900E9683D /* IconSelectorView.swift */; };
9FE3DB57296FEFCA00628CB0 /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9FE3DB56296FEFCA00628CB0 /* AppAccount */; };
9FF614472B2EDCE500F7B0E6 /* GiphyUISDK in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, maccatalyst, ); productRef = 9FF614462B2EDCE500F7B0E6 /* GiphyUISDK */; };
9FF614492B2EDCEC00F7B0E6 /* GiphyUISDK in Frameworks */ = {isa = PBXBuildFile; productRef = 9FF614482B2EDCEC00F7B0E6 /* GiphyUISDK */; };
9FFF677C299B7B2C00FE700A /* Notifications in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677B299B7B2C00FE700A /* Notifications */; };
9FFF677E299B7D2800FE700A /* Status in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677D299B7D2800FE700A /* Status */; };
9FFF6780299B7D2B00FE700A /* DesignSystem in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677F299B7D2B00FE700A /* DesignSystem */; };
9FFF6782299B7D3A00FE700A /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF6781299B7D3A00FE700A /* Account */; };
9FFF6784299B7D4400FE700A /* LRUCache in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF6783299B7D4400FE700A /* LRUCache */; };
C9B22677297F6C2E001F9EFE /* ContentSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B22676297F6C2E001F9EFE /* ContentSettingsView.swift */; };
D08A9C3529956CFA00204A4A /* SwipeActionsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08A9C3429956CFA00204A4A /* SwipeActionsSettingsView.swift */; };
DA0B24FB2A6876D50045BDD7 /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = DA0B24FA2A6876D50045BDD7 /* SFSafeSymbols */; };
@ -265,7 +260,6 @@
9FFF677C299B7B2C00FE700A /* Notifications in Frameworks */,
9F7D93942980063100EE6B7A /* AppAccount in Frameworks */,
9FFF6780299B7D2B00FE700A /* DesignSystem in Frameworks */,
9FFF6784299B7D4400FE700A /* LRUCache in Frameworks */,
9F2A5428296AB683009B2D7C /* Models in Frameworks */,
9FFF6782299B7D3A00FE700A /* Account in Frameworks */,
9F2A5426296AB67E009B2D7C /* KeychainSwift in Frameworks */,
@ -281,12 +275,10 @@
9FAD85A2297456A400496AB1 /* Env in Frameworks */,
9FAD85A0297456A100496AB1 /* Models in Frameworks */,
9FAD85A4297456A800496AB1 /* DesignSystem in Frameworks */,
9FF614492B2EDCEC00F7B0E6 /* GiphyUISDK in Frameworks */,
9FAD859E2974569B00496AB1 /* Account in Frameworks */,
9FAD859C2974422700496AB1 /* AppAccount in Frameworks */,
9FAD859A297440CB00496AB1 /* KeychainSwift in Frameworks */,
9FAD85982974405D00496AB1 /* Status in Frameworks */,
065FA20A298675BA00012EA0 /* LRUCache in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -297,7 +289,6 @@
9F7335EF29674F7100AFF0BA /* QuickLook.framework in Frameworks */,
9F7335ED2967463400AFF0BA /* AVKit.framework in Frameworks */,
9F2A540C29699705009B2D7C /* RevenueCat in Frameworks */,
065FA1FE29866CD600012EA0 /* LRUCache in Frameworks */,
9F2A540E2969A0B0009B2D7C /* StoreKit.framework in Frameworks */,
9F55C6902955993C00F94077 /* Explore in Frameworks */,
9FAE4ACE29379A5A00772766 /* KeychainSwift in Frameworks */,
@ -309,7 +300,6 @@
9F398AAB2935FFDB00A889F2 /* Models in Frameworks */,
9F5E581929545BE700A53960 /* Env in Frameworks */,
9F2A540A29699705009B2D7C /* ReceiptParser in Frameworks */,
9FF614472B2EDCE500F7B0E6 /* GiphyUISDK in Frameworks */,
9F35DB44294F9A7D00B3281A /* Status in Frameworks */,
DA0B24FB2A6876D50045BDD7 /* SFSafeSymbols in Frameworks */,
9F295540292B6C3400E0E81B /* Timeline in Frameworks */,
@ -578,7 +568,6 @@
9FFF677D299B7D2800FE700A /* Status */,
9FFF677F299B7D2B00FE700A /* DesignSystem */,
9FFF6781299B7D3A00FE700A /* Account */,
9FFF6783299B7D4400FE700A /* LRUCache */,
);
productName = IceCubesNotifications;
productReference = 9F2A5416296AB631009B2D7C /* IceCubesNotifications.appex */;
@ -605,8 +594,6 @@
9FAD859F297456A100496AB1 /* Models */,
9FAD85A1297456A400496AB1 /* Env */,
9FAD85A3297456A800496AB1 /* DesignSystem */,
065FA209298675BA00012EA0 /* LRUCache */,
9FF614482B2EDCEC00F7B0E6 /* GiphyUISDK */,
);
productName = IceCubesShareExtension;
productReference = 9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */;
@ -644,9 +631,7 @@
9F2A540929699705009B2D7C /* ReceiptParser */,
9F2A540B29699705009B2D7C /* RevenueCat */,
9FE3DB56296FEFCA00628CB0 /* AppAccount */,
065FA1FD29866CD600012EA0 /* LRUCache */,
DA0B24FA2A6876D50045BDD7 /* SFSafeSymbols */,
9FF614462B2EDCE500F7B0E6 /* GiphyUISDK */,
);
productName = IceCubesApp;
productReference = 9FBFE639292A715500C250E9 /* Ice Cubes.app */;
@ -727,9 +712,7 @@
packageReferences = (
9FAE4ACC29379A5A00772766 /* XCRemoteSwiftPackageReference "keychain-swift" */,
9F2A540829699705009B2D7C /* XCRemoteSwiftPackageReference "purchases-ios" */,
065FA1FC29866CD600012EA0 /* XCRemoteSwiftPackageReference "LRUCache" */,
DA0B24F92A6876D40045BDD7 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */,
9FF614452B2EDCE500F7B0E6 /* XCRemoteSwiftPackageReference "giphy-ios-sdk" */,
);
productRefGroup = 9FBFE63A292A715500C250E9 /* Products */;
projectDirPath = "";
@ -1410,14 +1393,6 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
065FA1FC29866CD600012EA0 /* XCRemoteSwiftPackageReference "LRUCache" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/nicklockwood/LRUCache";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.0.0;
};
};
9F2A540829699705009B2D7C /* XCRemoteSwiftPackageReference "purchases-ios" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/RevenueCat/purchases-ios.git";
@ -1434,14 +1409,6 @@
kind = branch;
};
};
9FF614452B2EDCE500F7B0E6 /* XCRemoteSwiftPackageReference "giphy-ios-sdk" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/Giphy/giphy-ios-sdk";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.2.7;
};
};
DA0B24F92A6876D40045BDD7 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/SFSafeSymbols/SFSafeSymbols";
@ -1453,16 +1420,6 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
065FA1FD29866CD600012EA0 /* LRUCache */ = {
isa = XCSwiftPackageProductDependency;
package = 065FA1FC29866CD600012EA0 /* XCRemoteSwiftPackageReference "LRUCache" */;
productName = LRUCache;
};
065FA209298675BA00012EA0 /* LRUCache */ = {
isa = XCSwiftPackageProductDependency;
package = 065FA1FC29866CD600012EA0 /* XCRemoteSwiftPackageReference "LRUCache" */;
productName = LRUCache;
};
9F29553F292B6C3400E0E81B /* Timeline */ = {
isa = XCSwiftPackageProductDependency;
productName = Timeline;
@ -1568,16 +1525,6 @@
isa = XCSwiftPackageProductDependency;
productName = AppAccount;
};
9FF614462B2EDCE500F7B0E6 /* GiphyUISDK */ = {
isa = XCSwiftPackageProductDependency;
package = 9FF614452B2EDCE500F7B0E6 /* XCRemoteSwiftPackageReference "giphy-ios-sdk" */;
productName = GiphyUISDK;
};
9FF614482B2EDCEC00F7B0E6 /* GiphyUISDK */ = {
isa = XCSwiftPackageProductDependency;
package = 9FF614452B2EDCE500F7B0E6 /* XCRemoteSwiftPackageReference "giphy-ios-sdk" */;
productName = GiphyUISDK;
};
9FFF677B299B7B2C00FE700A /* Notifications */ = {
isa = XCSwiftPackageProductDependency;
productName = Notifications;
@ -1594,11 +1541,6 @@
isa = XCSwiftPackageProductDependency;
productName = Account;
};
9FFF6783299B7D4400FE700A /* LRUCache */ = {
isa = XCSwiftPackageProductDependency;
package = 065FA1FC29866CD600012EA0 /* XCRemoteSwiftPackageReference "LRUCache" */;
productName = LRUCache;
};
DA0B24FA2A6876D50045BDD7 /* SFSafeSymbols */ = {
isa = XCSwiftPackageProductDependency;
package = DA0B24F92A6876D40045BDD7 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */;

View File

@ -30558,6 +30558,7 @@
}
},
"iPhone" : {
"extractionState" : "stale",
"localizations" : {
"be" : {
"stringUnit" : {
@ -73626,4 +73627,4 @@
}
},
"version" : "1.0"
}
}

View File

@ -23,6 +23,8 @@ let package = Package(
.package(name: "Network", path: "../Network"),
.package(name: "Env", path: "../Env"),
.package(name: "DesignSystem", path: "../DesignSystem"),
.package(url: "https://github.com/Giphy/giphy-ios-sdk", from: "2.2.7"),
.package(url: "https://github.com/nicklockwood/LRUCache", from: "1.0.4"),
],
targets: [
.target(
@ -34,6 +36,8 @@ let package = Package(
.product(name: "Network", package: "Network"),
.product(name: "Env", package: "Env"),
.product(name: "DesignSystem", package: "DesignSystem"),
.product(name: "GiphyUISDK", package: "giphy-ios-sdk"),
.product(name: "LRUCache", package: "LRUCache"),
],
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency"),

View File

@ -11,6 +11,10 @@ actor TimelineDatasource {
func get() -> [Status] {
statuses.filter { $0.filtered?.first?.filter.filterAction != .hide }
}
func count() -> Int {
statuses.count
}
func reset() {
statuses = []

View File

@ -122,8 +122,10 @@ public struct TimelineView: View {
SoundEffectManager.shared.playSound(.refresh)
}
.onChange(of: watcher.latestEvent?.id) {
if let latestEvent = watcher.latestEvent {
viewModel.handleEvent(event: latestEvent, currentAccount: account)
Task {
if let latestEvent = watcher.latestEvent {
await viewModel.handleEvent(event: latestEvent)
}
}
}
.onChange(of: timeline) { _, newValue in

View File

@ -54,15 +54,11 @@ import SwiftUI
var tag: Tag?
// Internal source of truth for a timeline.
private var datasource = TimelineDatasource()
private(set) var datasource = TimelineDatasource()
private let cache = TimelineCache()
private var visibileStatusesIds = Set<String>()
private var canStreamEvents: Bool = true
private var accountId: String? {
CurrentAccount.shared.account?.id
}
var client: Client? {
didSet {
if oldValue != client {
@ -108,35 +104,33 @@ import SwiftUI
await datasource.reset()
}
func handleEvent(event: any StreamEvent, currentAccount _: CurrentAccount) {
Task {
if let event = event as? StreamEventUpdate,
timeline == .home,
canStreamEvents,
isTimelineVisible,
await !datasource.contains(statusId: event.status.id)
{
pendingStatusesObserver.pendingStatuses.insert(event.status.id, at: 0)
let newStatus = event.status
await datasource.insert(newStatus, at: 0)
func handleEvent(event: any StreamEvent) async {
if let event = event as? StreamEventUpdate,
timeline == .home,
canStreamEvents,
isTimelineVisible,
await !datasource.contains(statusId: event.status.id)
{
pendingStatusesObserver.pendingStatuses.insert(event.status.id, at: 0)
let newStatus = event.status
await datasource.insert(newStatus, at: 0)
await cacheHome()
let statuses = await datasource.get()
withAnimation {
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
}
} else if let event = event as? StreamEventDelete {
await datasource.remove(event.status)
await cacheHome()
let statuses = await datasource.get()
withAnimation {
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
}
} else if let event = event as? StreamEventStatusUpdate {
if let originalIndex = await datasource.indexOf(statusId: event.status.id) {
await datasource.replace(event.status, at: originalIndex)
await cacheHome()
let statuses = await datasource.get()
withAnimation {
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
}
} else if let event = event as? StreamEventDelete {
await datasource.remove(event.status)
await cacheHome()
let statuses = await datasource.get()
withAnimation {
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
}
} else if let event = event as? StreamEventStatusUpdate {
if let originalIndex = await datasource.indexOf(statusId: event.status.id) {
await datasource.replace(event.status, at: originalIndex)
await cacheHome()
statusesState = await .display(statuses: datasource.get(), nextPageState: .hasNextPage)
}
statusesState = await .display(statuses: datasource.get(), nextPageState: .hasNextPage)
}
}
}

View File

@ -1,11 +0,0 @@
@testable import Timeline
import XCTest
final class TimelineTests: XCTestCase {
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertEqual(Timeline().text, "Hello, World!")
}
}

View File

@ -0,0 +1,24 @@
@testable import Timeline
import XCTest
import Network
import Models
@MainActor
final class TimelineViewModelTests: XCTestCase {
func testStreamEventInsertNewStatus() async throws {
let subject = TimelineViewModel()
let client = Client(server: "localhost")
subject.client = client
subject.timeline = .home
subject.isTimelineVisible = true
let isEmpty = await subject.datasource.isEmpty
XCTAssertTrue(isEmpty)
await subject.datasource.append(.placeholder())
var count = await subject.datasource.count()
XCTAssertTrue(count == 1)
await subject.handleEvent(event: StreamEventUpdate(status: .placeholder()))
count = await subject.datasource.count()
XCTAssertTrue(count == 2)
}
}