mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-25 01:31:02 +00:00
Refactoring
This commit is contained in:
parent
7161c21807
commit
2dd1f3ebdd
16 changed files with 121 additions and 113 deletions
|
@ -9,8 +9,8 @@ struct AccountResult: Codable, Hashable, FetchableRecord {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension QueryInterfaceRequest where RowDecoder == AccountRecord {
|
extension QueryInterfaceRequest where RowDecoder == AccountRecord {
|
||||||
var accountResultRequest: AnyFetchRequest<AccountResult> {
|
var accountResultRequest: QueryInterfaceRequest<AccountResult> {
|
||||||
AnyFetchRequest(including(optional: AccountRecord.moved))
|
including(optional: AccountRecord.moved)
|
||||||
.asRequest(of: AccountResult.self)
|
.asRequest(of: AccountResult.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,11 +74,11 @@ extension StatusRecord {
|
||||||
through: descendantJoins,
|
through: descendantJoins,
|
||||||
using: StatusContextJoin.status)
|
using: StatusContextJoin.status)
|
||||||
|
|
||||||
var ancestors: AnyFetchRequest<StatusResult> {
|
var ancestors: QueryInterfaceRequest<StatusResult> {
|
||||||
request(for: Self.ancestors).statusResultRequest
|
request(for: Self.ancestors).statusResultRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
var descendants: AnyFetchRequest<StatusResult> {
|
var descendants: QueryInterfaceRequest<StatusResult> {
|
||||||
request(for: Self.descendants).statusResultRequest
|
request(for: Self.descendants).statusResultRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,12 +25,12 @@ extension StatusResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension QueryInterfaceRequest where RowDecoder == StatusRecord {
|
extension QueryInterfaceRequest where RowDecoder == StatusRecord {
|
||||||
var statusResultRequest: AnyFetchRequest<StatusResult> {
|
var statusResultRequest: QueryInterfaceRequest<StatusResult> {
|
||||||
AnyFetchRequest(including(required: StatusRecord.account)
|
including(required: StatusRecord.account)
|
||||||
.including(optional: StatusRecord.accountMoved)
|
.including(optional: StatusRecord.accountMoved)
|
||||||
.including(optional: StatusRecord.reblogAccount)
|
.including(optional: StatusRecord.reblogAccount)
|
||||||
.including(optional: StatusRecord.reblogAccountMoved)
|
.including(optional: StatusRecord.reblogAccountMoved)
|
||||||
.including(optional: StatusRecord.reblog))
|
.including(optional: StatusRecord.reblog)
|
||||||
.asRequest(of: StatusResult.self)
|
.asRequest(of: StatusResult.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ extension Identity {
|
||||||
instance: result.instance,
|
instance: result.instance,
|
||||||
account: result.account,
|
account: result.account,
|
||||||
lastRegisteredDeviceToken: result.identity.lastRegisteredDeviceToken,
|
lastRegisteredDeviceToken: result.identity.lastRegisteredDeviceToken,
|
||||||
pushSubscriptionAlerts: result.pushSubscriptionAlerts)
|
pushSubscriptionAlerts: result.identity.pushSubscriptionAlerts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ extension Timeline {
|
||||||
using: TimelineStatusJoin.status)
|
using: TimelineStatusJoin.status)
|
||||||
.order(Column("createdAt").desc)
|
.order(Column("createdAt").desc)
|
||||||
|
|
||||||
var statuses: AnyFetchRequest<StatusResult> {
|
var statuses: QueryInterfaceRequest<StatusResult> {
|
||||||
request(for: Self.statuses).statusResultRequest
|
request(for: Self.statuses).statusResultRequest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,16 +141,14 @@ public extension IdentityDatabase {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
func identityObservation(id: UUID) -> AnyPublisher<Identity, Error> {
|
func identityObservation(id: UUID, immediate: Bool) -> AnyPublisher<Identity, Error> {
|
||||||
ValueObservation.tracking(
|
ValueObservation.tracking(
|
||||||
IdentityRecord
|
IdentityRecord
|
||||||
.filter(Column("id") == id)
|
.filter(Column("id") == id)
|
||||||
.including(optional: IdentityRecord.instance)
|
.identityResultRequest
|
||||||
.including(optional: IdentityRecord.account)
|
|
||||||
.asRequest(of: IdentityResult.self)
|
|
||||||
.fetchOne)
|
.fetchOne)
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.publisher(in: databaseQueue, scheduling: .immediate)
|
.publisher(in: databaseQueue, scheduling: immediate ? .immediate : .async(onQueue: .main))
|
||||||
.tryMap {
|
.tryMap {
|
||||||
guard let result = $0 else { throw IdentityDatabaseError.identityNotFound }
|
guard let result = $0 else { throw IdentityDatabaseError.identityNotFound }
|
||||||
|
|
||||||
|
@ -160,7 +158,11 @@ public extension IdentityDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func identitiesObservation() -> AnyPublisher<[Identity], Error> {
|
func identitiesObservation() -> AnyPublisher<[Identity], Error> {
|
||||||
ValueObservation.tracking(Self.identitiesRequest().fetchAll)
|
ValueObservation.tracking(
|
||||||
|
IdentityRecord
|
||||||
|
.order(Column("lastUsedAt").desc)
|
||||||
|
.identityResultRequest
|
||||||
|
.fetchAll)
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.publisher(in: databaseQueue)
|
.publisher(in: databaseQueue)
|
||||||
.map { $0.map(Identity.init(result:)) }
|
.map { $0.map(Identity.init(result:)) }
|
||||||
|
@ -169,7 +171,9 @@ public extension IdentityDatabase {
|
||||||
|
|
||||||
func recentIdentitiesObservation(excluding: UUID) -> AnyPublisher<[Identity], Error> {
|
func recentIdentitiesObservation(excluding: UUID) -> AnyPublisher<[Identity], Error> {
|
||||||
ValueObservation.tracking(
|
ValueObservation.tracking(
|
||||||
Self.identitiesRequest()
|
IdentityRecord
|
||||||
|
.order(Column("lastUsedAt").desc)
|
||||||
|
.identityResultRequest
|
||||||
.filter(Column("id") != excluding)
|
.filter(Column("id") != excluding)
|
||||||
.limit(9)
|
.limit(9)
|
||||||
.fetchAll)
|
.fetchAll)
|
||||||
|
@ -179,7 +183,7 @@ public extension IdentityDatabase {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
func mostRecentlyUsedIdentityIDObservation() -> AnyPublisher<UUID?, Error> {
|
func immediateMostRecentlyUsedIdentityIDObservation() -> AnyPublisher<UUID?, Error> {
|
||||||
ValueObservation.tracking(IdentityRecord.select(Column("id")).order(Column("lastUsedAt").desc).fetchOne)
|
ValueObservation.tracking(IdentityRecord.select(Column("id")).order(Column("lastUsedAt").desc).fetchOne)
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.publisher(in: databaseQueue, scheduling: .immediate)
|
.publisher(in: databaseQueue, scheduling: .immediate)
|
||||||
|
@ -188,7 +192,9 @@ public extension IdentityDatabase {
|
||||||
|
|
||||||
func identitiesWithOutdatedDeviceTokens(deviceToken: Data) -> AnyPublisher<[Identity], Error> {
|
func identitiesWithOutdatedDeviceTokens(deviceToken: Data) -> AnyPublisher<[Identity], Error> {
|
||||||
databaseQueue.readPublisher(
|
databaseQueue.readPublisher(
|
||||||
value: Self.identitiesRequest()
|
value: IdentityRecord
|
||||||
|
.order(Column("lastUsedAt").desc)
|
||||||
|
.identityResultRequest
|
||||||
.filter(Column("lastRegisteredDeviceToken") != deviceToken)
|
.filter(Column("lastRegisteredDeviceToken") != deviceToken)
|
||||||
.fetchAll)
|
.fetchAll)
|
||||||
.map { $0.map(Identity.init(result:)) }
|
.map { $0.map(Identity.init(result:)) }
|
||||||
|
@ -199,14 +205,6 @@ public extension IdentityDatabase {
|
||||||
private extension IdentityDatabase {
|
private extension IdentityDatabase {
|
||||||
private static let name = "Identity"
|
private static let name = "Identity"
|
||||||
|
|
||||||
private static func identitiesRequest() -> QueryInterfaceRequest<IdentityResult> {
|
|
||||||
IdentityRecord
|
|
||||||
.order(Column("lastUsedAt").desc)
|
|
||||||
.including(optional: IdentityRecord.instance)
|
|
||||||
.including(optional: IdentityRecord.account)
|
|
||||||
.asRequest(of: IdentityResult.self)
|
|
||||||
}
|
|
||||||
|
|
||||||
private static func writePreferences(_ preferences: Identity.Preferences, id: UUID) -> (Database) throws -> Void {
|
private static func writePreferences(_ preferences: Identity.Preferences, id: UUID) -> (Database) throws -> Void {
|
||||||
{
|
{
|
||||||
let data = try IdentityRecord.databaseJSONEncoder(for: "preferences").encode(preferences)
|
let data = try IdentityRecord.databaseJSONEncoder(for: "preferences").encode(preferences)
|
||||||
|
|
|
@ -8,5 +8,12 @@ struct IdentityResult: Codable, Hashable, FetchableRecord {
|
||||||
let identity: IdentityRecord
|
let identity: IdentityRecord
|
||||||
let instance: Identity.Instance?
|
let instance: Identity.Instance?
|
||||||
let account: Identity.Account?
|
let account: Identity.Account?
|
||||||
let pushSubscriptionAlerts: PushSubscription.Alerts
|
}
|
||||||
|
|
||||||
|
extension QueryInterfaceRequest where RowDecoder == IdentityRecord {
|
||||||
|
var identityResultRequest: QueryInterfaceRequest<IdentityResult> {
|
||||||
|
including(optional: IdentityRecord.instance)
|
||||||
|
.including(optional: IdentityRecord.account)
|
||||||
|
.asRequest(of: IdentityResult.self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,6 @@ import MastodonAPI
|
||||||
import Secrets
|
import Secrets
|
||||||
|
|
||||||
public struct AllIdentitiesService {
|
public struct AllIdentitiesService {
|
||||||
public let mostRecentlyUsedIdentityID: AnyPublisher<UUID?, Never>
|
|
||||||
|
|
||||||
private let environment: AppEnvironment
|
private let environment: AppEnvironment
|
||||||
private let database: IdentityDatabase
|
private let database: IdentityDatabase
|
||||||
|
|
||||||
|
@ -18,10 +16,6 @@ public struct AllIdentitiesService {
|
||||||
self.database = try environment.fixtureDatabase ?? IdentityDatabase(
|
self.database = try environment.fixtureDatabase ?? IdentityDatabase(
|
||||||
inMemory: environment.inMemoryContent,
|
inMemory: environment.inMemoryContent,
|
||||||
keychain: environment.keychain)
|
keychain: environment.keychain)
|
||||||
|
|
||||||
mostRecentlyUsedIdentityID = database.mostRecentlyUsedIdentityIDObservation()
|
|
||||||
.replaceError(with: nil)
|
|
||||||
.eraseToAnyPublisher()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +24,10 @@ public extension AllIdentitiesService {
|
||||||
try IdentityService(id: id, database: database, environment: environment)
|
try IdentityService(id: id, database: database, environment: environment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func immediateMostRecentlyUsedIdentityIDObservation() -> AnyPublisher<UUID?, Error> {
|
||||||
|
database.immediateMostRecentlyUsedIdentityIDObservation()
|
||||||
|
}
|
||||||
|
|
||||||
func createIdentity(id: UUID, url: URL, authenticated: Bool) -> AnyPublisher<Never, Error> {
|
func createIdentity(id: UUID, url: URL, authenticated: Bool) -> AnyPublisher<Never, Error> {
|
||||||
let secrets = Secrets(identityID: id, keychain: environment.keychain)
|
let secrets = Secrets(identityID: id, keychain: environment.keychain)
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ public struct IdentityService {
|
||||||
private let environment: AppEnvironment
|
private let environment: AppEnvironment
|
||||||
private let mastodonAPIClient: MastodonAPIClient
|
private let mastodonAPIClient: MastodonAPIClient
|
||||||
private let secrets: Secrets
|
private let secrets: Secrets
|
||||||
private let observationErrorsInput = PassthroughSubject<Error, Never>()
|
|
||||||
|
|
||||||
init(id: UUID, database: IdentityDatabase, environment: AppEnvironment) throws {
|
init(id: UUID, database: IdentityDatabase, environment: AppEnvironment) throws {
|
||||||
identityID = id
|
identityID = id
|
||||||
|
@ -86,8 +85,8 @@ public extension IdentityService {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
func observation() -> AnyPublisher<Identity, Error> {
|
func observation(immediate: Bool) -> AnyPublisher<Identity, Error> {
|
||||||
identityDatabase.identityObservation(id: identityID)
|
identityDatabase.identityObservation(id: identityID, immediate: immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func listsObservation() -> AnyPublisher<[Timeline], Error> {
|
func listsObservation() -> AnyPublisher<[Timeline], Error> {
|
||||||
|
|
|
@ -4,38 +4,16 @@ import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
enum IdentificationError: Error {
|
|
||||||
case initialIdentityValueAbsent
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class Identification: ObservableObject {
|
public final class Identification: ObservableObject {
|
||||||
@Published private(set) var identity: Identity
|
@Published private(set) var identity: Identity
|
||||||
let service: IdentityService
|
let service: IdentityService
|
||||||
let observationErrors: AnyPublisher<Error, Never>
|
|
||||||
|
|
||||||
init(service: IdentityService) throws {
|
|
||||||
self.service = service
|
|
||||||
// The scheduling on the observation is immediate so an initial value can be extracted
|
|
||||||
let sharedObservation = service.observation().share()
|
|
||||||
var initialIdentity: Identity?
|
|
||||||
|
|
||||||
_ = sharedObservation.first().sink(
|
|
||||||
receiveCompletion: { _ in },
|
|
||||||
receiveValue: { initialIdentity = $0 })
|
|
||||||
|
|
||||||
guard let identity = initialIdentity else { throw IdentificationError.initialIdentityValueAbsent }
|
|
||||||
|
|
||||||
|
init(identity: Identity, observation: AnyPublisher<Identity, Never>, service: IdentityService) {
|
||||||
self.identity = identity
|
self.identity = identity
|
||||||
|
self.service = service
|
||||||
|
|
||||||
let observationErrorsSubject = PassthroughSubject<Error, Never>()
|
DispatchQueue.main.async {
|
||||||
|
observation.dropFirst().assign(to: &self.$identity)
|
||||||
observationErrors = observationErrorsSubject.eraseToAnyPublisher()
|
|
||||||
|
|
||||||
sharedObservation.catch { error -> Empty<Identity, Never> in
|
|
||||||
observationErrorsSubject.send(error)
|
|
||||||
|
|
||||||
return Empty()
|
|
||||||
}
|
}
|
||||||
.assign(to: &$identity)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,24 @@ import Foundation
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
public final class RootViewModel: ObservableObject {
|
public final class RootViewModel: ObservableObject {
|
||||||
@Published public private(set) var identification: Identification?
|
@Published public private(set) var identification: Identification? {
|
||||||
|
didSet {
|
||||||
|
guard let identification = identification else { return }
|
||||||
|
|
||||||
|
identification.service.updateLastUse()
|
||||||
|
.sink { _ in } receiveValue: { _ in }
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
userNotificationService.isAuthorized()
|
||||||
|
.filter { $0 }
|
||||||
|
.zip(registerForRemoteNotifications())
|
||||||
|
.filter { identification.identity.lastRegisteredDeviceToken != $1 }
|
||||||
|
.map { ($1, identification.identity.pushSubscriptionAlerts) }
|
||||||
|
.flatMap(identification.service.createPushSubscription(deviceToken:alerts:))
|
||||||
|
.sink { _ in } receiveValue: { _ in }
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Published private var mostRecentlyUsedIdentityID: UUID?
|
@Published private var mostRecentlyUsedIdentityID: UUID?
|
||||||
private let environment: AppEnvironment
|
private let environment: AppEnvironment
|
||||||
|
@ -21,9 +38,11 @@ public final class RootViewModel: ObservableObject {
|
||||||
userNotificationService = UserNotificationService(environment: environment)
|
userNotificationService = UserNotificationService(environment: environment)
|
||||||
self.registerForRemoteNotifications = registerForRemoteNotifications
|
self.registerForRemoteNotifications = registerForRemoteNotifications
|
||||||
|
|
||||||
allIdentitiesService.mostRecentlyUsedIdentityID.assign(to: &$mostRecentlyUsedIdentityID)
|
allIdentitiesService.immediateMostRecentlyUsedIdentityIDObservation()
|
||||||
|
.replaceError(with: nil)
|
||||||
|
.assign(to: &$mostRecentlyUsedIdentityID)
|
||||||
|
|
||||||
newIdentitySelected(id: mostRecentlyUsedIdentityID)
|
identitySelected(id: mostRecentlyUsedIdentityID, immediate: true)
|
||||||
|
|
||||||
userNotificationService.isAuthorized()
|
userNotificationService.isAuthorized()
|
||||||
.filter { $0 }
|
.filter { $0 }
|
||||||
|
@ -36,39 +55,8 @@ public final class RootViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension RootViewModel {
|
public extension RootViewModel {
|
||||||
func newIdentitySelected(id: UUID?) {
|
func identitySelected(id: UUID?) {
|
||||||
guard let id = id else {
|
identitySelected(id: id, immediate: false)
|
||||||
identification = nil
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let identification: Identification
|
|
||||||
|
|
||||||
do {
|
|
||||||
identification = try Identification(service: allIdentitiesService.identityService(id: id))
|
|
||||||
self.identification = identification
|
|
||||||
} catch {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
identification.observationErrors
|
|
||||||
.receive(on: RunLoop.main)
|
|
||||||
.sink { [weak self] _ in self?.newIdentitySelected(id: self?.mostRecentlyUsedIdentityID ) }
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
identification.service.updateLastUse()
|
|
||||||
.sink { _ in } receiveValue: { _ in }
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
userNotificationService.isAuthorized()
|
|
||||||
.filter { $0 }
|
|
||||||
.zip(registerForRemoteNotifications())
|
|
||||||
.filter { identification.identity.lastRegisteredDeviceToken != $1 }
|
|
||||||
.map { ($1, identification.identity.pushSubscriptionAlerts) }
|
|
||||||
.flatMap(identification.service.createPushSubscription(deviceToken:alerts:))
|
|
||||||
.sink { _ in } receiveValue: { _ in }
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteIdentity(id: UUID) {
|
func deleteIdentity(id: UUID) {
|
||||||
|
@ -83,3 +71,33 @@ public extension RootViewModel {
|
||||||
instanceFilterService: InstanceFilterService(environment: environment))
|
instanceFilterService: InstanceFilterService(environment: environment))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension RootViewModel {
|
||||||
|
func identitySelected(id: UUID?, immediate: Bool) {
|
||||||
|
guard
|
||||||
|
let id = id,
|
||||||
|
let identityService = try? allIdentitiesService.identityService(id: id) else {
|
||||||
|
identification = nil
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let observation = identityService.observation(immediate: immediate)
|
||||||
|
.catch { [weak self] _ -> Empty<Identity, Never> in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self?.identitySelected(id: self?.mostRecentlyUsedIdentityID, immediate: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Empty()
|
||||||
|
}
|
||||||
|
.share()
|
||||||
|
|
||||||
|
observation.map {
|
||||||
|
Identification(
|
||||||
|
identity: $0,
|
||||||
|
observation: observation.eraseToAnyPublisher(),
|
||||||
|
service: identityService)
|
||||||
|
}
|
||||||
|
.assign(to: &$identification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,7 +12,10 @@ import XCTest
|
||||||
|
|
||||||
class AddIdentityViewModelTests: XCTestCase {
|
class AddIdentityViewModelTests: XCTestCase {
|
||||||
func testAddIdentity() throws {
|
func testAddIdentity() throws {
|
||||||
let sut = AddIdentityViewModel(allIdentitiesService: try AllIdentitiesService(environment: .mock()))
|
let environment = AppEnvironment.mock()
|
||||||
|
let sut = AddIdentityViewModel(
|
||||||
|
allIdentitiesService: try AllIdentitiesService(environment: environment),
|
||||||
|
instanceFilterService: InstanceFilterService(environment: environment))
|
||||||
let addedIDRecorder = sut.addedIdentityID.record()
|
let addedIDRecorder = sut.addedIdentityID.record()
|
||||||
|
|
||||||
sut.urlFieldText = "https://mastodon.social"
|
sut.urlFieldText = "https://mastodon.social"
|
||||||
|
@ -22,7 +25,10 @@ class AddIdentityViewModelTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAddIdentityWithoutScheme() throws {
|
func testAddIdentityWithoutScheme() throws {
|
||||||
let sut = AddIdentityViewModel(allIdentitiesService: try AllIdentitiesService(environment: .mock()))
|
let environment = AppEnvironment.mock()
|
||||||
|
let sut = AddIdentityViewModel(
|
||||||
|
allIdentitiesService: try AllIdentitiesService(environment: environment),
|
||||||
|
instanceFilterService: InstanceFilterService(environment: environment))
|
||||||
let addedIDRecorder = sut.addedIdentityID.record()
|
let addedIDRecorder = sut.addedIdentityID.record()
|
||||||
|
|
||||||
sut.urlFieldText = "mastodon.social"
|
sut.urlFieldText = "mastodon.social"
|
||||||
|
@ -32,7 +38,10 @@ class AddIdentityViewModelTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testInvalidURL() throws {
|
func testInvalidURL() throws {
|
||||||
let sut = AddIdentityViewModel(allIdentitiesService: try AllIdentitiesService(environment: .mock()))
|
let environment = AppEnvironment.mock()
|
||||||
|
let sut = AddIdentityViewModel(
|
||||||
|
allIdentitiesService: try AllIdentitiesService(environment: environment),
|
||||||
|
instanceFilterService: InstanceFilterService(environment: environment))
|
||||||
let recorder = sut.$alertItem.record()
|
let recorder = sut.$alertItem.record()
|
||||||
|
|
||||||
XCTAssertNil(try wait(for: recorder.next(), timeout: 1))
|
XCTAssertNil(try wait(for: recorder.next(), timeout: 1))
|
||||||
|
@ -46,9 +55,10 @@ class AddIdentityViewModelTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDoesNotAlertCanceledLogin() throws {
|
func testDoesNotAlertCanceledLogin() throws {
|
||||||
let allIdentitiesService = try AllIdentitiesService(
|
let environment = AppEnvironment.mock(webAuthSessionType: CanceledLoginMockWebAuthSession.self)
|
||||||
environment: .mock(webAuthSessionType: CanceledLoginMockWebAuthSession.self))
|
let sut = AddIdentityViewModel(
|
||||||
let sut = AddIdentityViewModel(allIdentitiesService: allIdentitiesService)
|
allIdentitiesService: try AllIdentitiesService(environment: environment),
|
||||||
|
instanceFilterService: InstanceFilterService(environment: environment))
|
||||||
let recorder = sut.$alertItem.record()
|
let recorder = sut.$alertItem.record()
|
||||||
|
|
||||||
XCTAssertNil(try wait(for: recorder.next(), timeout: 1))
|
XCTAssertNil(try wait(for: recorder.next(), timeout: 1))
|
||||||
|
|
|
@ -21,7 +21,7 @@ class RootViewModelTests: XCTestCase {
|
||||||
let addIdentityViewModel = sut.addIdentityViewModel()
|
let addIdentityViewModel = sut.addIdentityViewModel()
|
||||||
|
|
||||||
addIdentityViewModel.addedIdentityID
|
addIdentityViewModel.addedIdentityID
|
||||||
.sink(receiveValue: sut.newIdentitySelected(id:))
|
.sink(receiveValue: sut.identitySelected(id:))
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
addIdentityViewModel.urlFieldText = "https://mastodon.social"
|
addIdentityViewModel.urlFieldText = "https://mastodon.social"
|
||||||
|
|
|
@ -28,7 +28,7 @@ struct AddIdentityView: View {
|
||||||
.alertItem($viewModel.alertItem)
|
.alertItem($viewModel.alertItem)
|
||||||
.onReceive(viewModel.addedIdentityID) { id in
|
.onReceive(viewModel.addedIdentityID) { id in
|
||||||
withAnimation {
|
withAnimation {
|
||||||
rootViewModel.newIdentitySelected(id: id)
|
rootViewModel.identitySelected(id: id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear(perform: viewModel.refreshFilter)
|
.onAppear(perform: viewModel.refreshFilter)
|
||||||
|
|
|
@ -41,7 +41,7 @@ private extension IdentitiesView {
|
||||||
ForEach(identities) { identity in
|
ForEach(identities) { identity in
|
||||||
Button {
|
Button {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
rootViewModel.newIdentitySelected(id: identity.id)
|
rootViewModel.identitySelected(id: identity.id)
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
row(identity: identity)
|
row(identity: identity)
|
||||||
|
|
|
@ -88,7 +88,7 @@ private extension TabNavigationView {
|
||||||
.contextMenu(ContextMenu {
|
.contextMenu(ContextMenu {
|
||||||
ForEach(viewModel.recentIdentities) { recentIdentity in
|
ForEach(viewModel.recentIdentities) { recentIdentity in
|
||||||
Button {
|
Button {
|
||||||
rootViewModel.newIdentitySelected(id: recentIdentity.id)
|
rootViewModel.identitySelected(id: recentIdentity.id)
|
||||||
} label: {
|
} label: {
|
||||||
Label(
|
Label(
|
||||||
title: { Text(recentIdentity.handle) },
|
title: { Text(recentIdentity.handle) },
|
||||||
|
|
Loading…
Reference in a new issue