diff --git a/xcode/Subconscious/Shared/Components/AppTabView.swift b/xcode/Subconscious/Shared/Components/AppTabView.swift index a8e2809a..504b8f23 100644 --- a/xcode/Subconscious/Shared/Components/AppTabView.swift +++ b/xcode/Subconscious/Shared/Components/AppTabView.swift @@ -9,7 +9,7 @@ import SwiftUI import ObservableStore import Combine -enum AppTab: String { +enum AppTab: String, Hashable { case feed case notebook case profile diff --git a/xcode/Subconscious/Shared/Components/AppView.swift b/xcode/Subconscious/Shared/Components/AppView.swift index 7fd526f9..f4604236 100644 --- a/xcode/Subconscious/Shared/Components/AppView.swift +++ b/xcode/Subconscious/Shared/Components/AppView.swift @@ -90,15 +90,15 @@ typealias InviteCodeFormField = FormField typealias NicknameFormField = FormField typealias GatewayUrlFormField = FormField -struct PeerIndexError: Error { - let error: Error +struct PeerIndexError: Error, Hashable { + let error: String let petname: Petname } typealias PeerIndexResult = Result // MARK: Action -enum AppAction { +enum AppAction: Hashable { // Logger for actions static let logger = Logger( subsystem: Config.default.rdns, @@ -466,7 +466,7 @@ enum AppDatabaseState { case ready } -enum FirstRunStep { +enum FirstRunStep: Hashable { case sphere case recovery case profile diff --git a/xcode/Subconscious/Shared/Components/Detail/UserProfileDetailView.swift b/xcode/Subconscious/Shared/Components/Detail/UserProfileDetailView.swift index fd3a0526..597b5944 100644 --- a/xcode/Subconscious/Shared/Components/Detail/UserProfileDetailView.swift +++ b/xcode/Subconscious/Shared/Components/Detail/UserProfileDetailView.swift @@ -633,13 +633,15 @@ struct UserProfileDetailModel: ModelProtocol { logger.log("Begin loading profile \(address)") return try await Self.refresh(address: address, environment: environment) } - .map { content in - UserProfileDetailAction.populate(content) - } - .catch { error in - Just(UserProfileDetailAction.failedToPopulate(error.localizedDescription)) - } - .eraseToAnyPublisher() + .map { content in + UserProfileDetailAction.populate(content) + } + .recover { error in + UserProfileDetailAction.failedToPopulate( + error.localizedDescription + ) + } + .eraseToAnyPublisher() return Update(state: model, fx: fx) } @@ -656,7 +658,7 @@ struct UserProfileDetailModel: ModelProtocol { model.following = content.following model.loadingState = .loaded - return Update(state: model) + return Update(state: model).animation(.default) } static func failedToPopulate( diff --git a/xcode/Subconscious/Shared/Components/Feed/Feed.swift b/xcode/Subconscious/Shared/Components/Feed/Feed.swift index 3192d822..0cb9b645 100644 --- a/xcode/Subconscious/Shared/Components/Feed/Feed.swift +++ b/xcode/Subconscious/Shared/Components/Feed/Feed.swift @@ -112,28 +112,24 @@ struct FeedView: View { app.actions.compactMap(FeedAction.from), perform: store.send ) - } -} - -extension FeedAction { - static func from(_ action: AppAction) -> Self? { - switch action { - case .succeedIndexOurSphere: - return .refreshAll - case .completeIndexPeers: - return .refreshAll - case .succeedRecoverOurSphere: - return .refreshAll - case .requestFeedRoot: - return .requestFeedRoot - default: - return nil + /// Replay some feed actions on app store + .onReceive( + store.actions.compactMap(AppAction.from), + perform: app.send + ) + .onReceive(store.actions) { action in + FeedAction.logger.debug("\(String(describing: action))") } } } // MARK: Action -enum FeedAction { +enum FeedAction: Hashable { + static let logger = Logger( + subsystem: Config.default.rdns, + category: "FeedAction" + ) + case search(SearchAction) case activatedSuggestion(Suggestion) case detailStack(DetailStackAction) @@ -143,7 +139,10 @@ enum FeedAction { case ready case refreshAll - /// DetailStack notification actions + /// DetailStack-related actions + case requestDeleteMemo(Slashlink?) + case succeedDeleteMemo(Slashlink) + case failDeleteMemo(String) case succeedSaveEntry(slug: Slashlink, modified: Date) case succeedMoveEntry(from: Slashlink, to: Slashlink) case succeedMergeEntry(parent: Slashlink, child: Slashlink) @@ -155,11 +154,43 @@ enum FeedAction { /// Set stories case succeedFetchFeed([StoryEntry]) /// Fetch feed failed - case failFetchFeed(Error) + case failFetchFeed(String) case requestFeedRoot } +extension AppAction { + static func from(_ action: FeedAction) -> Self? { + switch action { + case let .requestDeleteMemo(slashlink): + return .deleteMemo(slashlink) + default: + return nil + } + } +} + +extension FeedAction { + static func from(_ action: AppAction) -> Self? { + switch action { + case .succeedIndexOurSphere: + return .refreshAll + case .completeIndexPeers: + return .refreshAll + case .succeedRecoverOurSphere: + return .refreshAll + case .requestFeedRoot: + return .requestFeedRoot + case let .succeedDeleteMemo(address): + return .succeedDeleteMemo(address) + case let .failDeleteMemo(error): + return .failDeleteMemo(error) + default: + return nil + } + } +} + struct FeedSearchCursor: CursorProtocol { typealias Model = FeedModel typealias ViewModel = SearchModel @@ -202,6 +233,8 @@ struct FeedDetailStackCursor: CursorProtocol { static func tag(_ action: ViewModel.Action) -> Model.Action { switch action { + case let .requestDeleteMemo(slashlink): + return .requestDeleteMemo(slashlink) case let .succeedMergeEntry(parent: parent, child: child): return .succeedMergeEntry(parent: parent, child: child) case let .succeedMoveEntry(from: from, to: to): @@ -229,12 +262,12 @@ struct FeedModel: ModelProtocol { /// Entry detail var detailStack = DetailStackModel() var entries: [StoryEntry]? = nil - + static let logger = Logger( subsystem: Config.default.rdns, category: "Feed" ) - + // MARK: Update static func update( state: FeedModel, @@ -282,7 +315,11 @@ struct FeedModel: ModelProtocol { entries: entries ) case .failFetchFeed(let error): - return failFetchFeed(state: state, environment: environment, error: error) + return failFetchFeed( + state: state, + environment: environment, + error: error + ) case let .activatedSuggestion(suggestion): return FeedDetailStackCursor.update( state: state, @@ -294,6 +331,24 @@ struct FeedModel: ModelProtocol { state: state, environment: environment ) + case .requestDeleteMemo(let address): + return requestDeleteMemo( + state: state, + environment: environment, + address: address + ) + case .failDeleteMemo(let error): + return failDeleteMemo( + state: state, + environment: environment, + error: error + ) + case .succeedDeleteMemo(let address): + return succeedDeleteMemo( + state: state, + environment: environment, + address: address + ) case let .succeedUpdateAudience(receipt): return update( state: state, @@ -342,7 +397,7 @@ struct FeedModel: ModelProtocol { logger.log("\(error.localizedDescription)") return Update(state: state) } - + /// Log error at warning level static func warn( state: FeedModel, @@ -352,7 +407,7 @@ struct FeedModel: ModelProtocol { logger.warning("\(error.localizedDescription)") return Update(state: state) } - + /// Set search presented flag static func setSearchPresented( state: FeedModel, @@ -363,14 +418,14 @@ struct FeedModel: ModelProtocol { model.isSearchPresented = isPresented return Update(state: model) } - + static func ready( state: FeedModel, environment: AppEnvironment ) -> Update { return fetchFeed(state: state, environment: environment) } - + /// Refresh all list views from database static func refreshAll( state: FeedModel, @@ -385,7 +440,7 @@ struct FeedModel: ModelProtocol { environment: environment ) } - + /// Fetch latest from feed static func fetchFeed( state: FeedModel, @@ -395,11 +450,11 @@ struct FeedModel: ModelProtocol { .map({ stories in FeedAction.succeedFetchFeed(stories) }) - .catch({ error in - Just(FeedAction.failFetchFeed(error)) + .recover({ error in + FeedAction.failFetchFeed(error.localizedDescription) }) .eraseToAnyPublisher() - + var model = state // only display loading state if we have no posts to show // if we have stale posts, show them until we load the new ones @@ -409,7 +464,7 @@ struct FeedModel: ModelProtocol { return Update(state: model, fx: fx) } - + /// Set feed response static func succeedFetchFeed( state: FeedModel, @@ -425,24 +480,14 @@ struct FeedModel: ModelProtocol { static func failFetchFeed( state: FeedModel, environment: AppEnvironment, - error: Error + error: String ) -> Update { - logger.error("Failed to fetch feed \(error.localizedDescription)") + logger.error("Failed to fetch feed \(error)") var model = state model.status = .notFound return Update(state: model) } - - /// Handle entry deleted - static func entryDeleted( - state: FeedModel, - environment: AppEnvironment, - slug: Slug - ) -> Update { - logger.warning("Not implemented") - return Update(state: state) - } static func requestFeedRoot( state: FeedModel, @@ -454,4 +499,64 @@ struct FeedModel: ModelProtocol { environment: environment ) } + + /// Entry delete succeeded + static func requestDeleteMemo( + state: Self, + environment: Environment, + address: Slashlink? + ) -> Update { + logger.log( + "Request delete memo", + metadata: [ + "address": address?.description ?? "" + ] + ) + return update( + state: state, + action: .detailStack(.requestDeleteMemo(address)), + environment: environment + ) + } + + /// Entry delete succeeded + static func succeedDeleteMemo( + state: Self, + environment: Environment, + address: Slashlink + ) -> Update { + logger.log( + "Memo was deleted", + metadata: [ + "address": address.description + ] + ) + return update( + state: state, + actions: [ + .detailStack(.succeedDeleteMemo(address)), + .refreshAll + ], + environment: environment + ) + } + + /// Entry delete failed + static func failDeleteMemo( + state: Self, + environment: Environment, + error: String + ) -> Update { + logger.log( + "Failed to delete memo", + metadata: [ + "error": error + ] + ) + return update( + state: state, + action: .detailStack(.failDeleteMemo(error)), + environment: environment + ) + } } diff --git a/xcode/Subconscious/Shared/Components/HomeProfileView.swift b/xcode/Subconscious/Shared/Components/HomeProfileView.swift index d3869930..69b22028 100644 --- a/xcode/Subconscious/Shared/Components/HomeProfileView.swift +++ b/xcode/Subconscious/Shared/Components/HomeProfileView.swift @@ -83,11 +83,16 @@ struct HomeProfileView: View { .onAppear { store.send(.appear) } - /// Replay app actions on local store + /// Replay some app actions on store .onReceive( app.actions.compactMap(HomeProfileAction.from), perform: store.send ) + /// Replay some store actions on app + .onReceive( + store.actions.compactMap(AppAction.from), + perform: app.send + ) .onReceive(store.actions) { action in HomeProfileAction.logger.debug("\(String(describing: action))") } @@ -96,7 +101,7 @@ struct HomeProfileView: View { // MARK: Action -enum HomeProfileAction { +enum HomeProfileAction: Hashable { static let logger = Logger( subsystem: Config.default.rdns, category: "HomeProfileAction" @@ -110,6 +115,11 @@ enum HomeProfileAction { case setSearchPresented(Bool) case requestProfileRoot + + /// DetailStack-related actions + case requestDeleteMemo(Slashlink?) + case succeedDeleteMemo(Slashlink) + case failDeleteMemo(String) } // MARK: Cursors and tagging functions @@ -128,7 +138,12 @@ struct HomeProfileDetailStackCursor: CursorProtocol { } static func tag(_ action: ViewModel.Action) -> Model.Action { - .detailStack(action) + switch action { + case let .requestDeleteMemo(slashlink): + return .requestDeleteMemo(slashlink) + default: + return .detailStack(action) + } } } @@ -139,6 +154,21 @@ extension HomeProfileAction { return .ready case .requestProfileRoot: return .requestProfileRoot + case let .succeedDeleteMemo(address): + return .succeedDeleteMemo(address) + case let .failDeleteMemo(error): + return .failDeleteMemo(error) + default: + return nil + } + } +} + +extension AppAction { + static func from(_ action: HomeProfileAction) -> Self? { + switch action { + case let .requestDeleteMemo(slashlink): + return .deleteMemo(slashlink) default: return nil } @@ -184,7 +214,7 @@ struct HomeProfileModel: ModelProtocol { var details: [MemoDetailDescription] { detailStack.details } - + static func update( state: Self, action: HomeProfileAction, @@ -230,15 +260,33 @@ struct HomeProfileModel: ModelProtocol { action: DetailStackAction.fromSuggestion(suggestion), environment: environment ) + case .requestDeleteMemo(let address): + return requestDeleteMemo( + state: state, + environment: environment, + address: address + ) + case .failDeleteMemo(let error): + return failDeleteMemo( + state: state, + environment: environment, + error: error + ) + case .succeedDeleteMemo(let address): + return succeedDeleteMemo( + state: state, + environment: environment, + address: address + ) } } - + // Logger for actions static let logger = Logger( subsystem: Config.default.rdns, category: "HomeProfileModel" ) - + /// Just before view appears (sent by task) static func appear( state: Self, @@ -246,7 +294,7 @@ struct HomeProfileModel: ModelProtocol { ) -> Update { return Update(state: state) } - + /// View is ready static func ready( state: Self, @@ -254,7 +302,7 @@ struct HomeProfileModel: ModelProtocol { ) -> Update { return Update(state: state) } - + static func requestProfileRoot( state: Self, environment: AppEnvironment @@ -276,4 +324,61 @@ struct HomeProfileModel: ModelProtocol { model.isSearchPresented = isPresented return Update(state: model) } + + /// Entry delete succeeded + static func requestDeleteMemo( + state: Self, + environment: Environment, + address: Slashlink? + ) -> Update { + logger.log( + "Request delete memo", + metadata: [ + "address": address?.description ?? "" + ] + ) + return update( + state: state, + action: .detailStack(.requestDeleteMemo(address)), + environment: environment + ) + } + + /// Entry delete succeeded + static func succeedDeleteMemo( + state: Self, + environment: Environment, + address: Slashlink + ) -> Update { + logger.log( + "Memo was deleted", + metadata: [ + "address": address.description + ] + ) + return update( + state: state, + action: .detailStack(.succeedDeleteMemo(address)), + environment: environment + ) + } + + /// Entry delete failed + static func failDeleteMemo( + state: Self, + environment: Environment, + error: String + ) -> Update { + logger.log( + "Failed to delete memo", + metadata: [ + "error": error + ] + ) + return update( + state: state, + action: .detailStack(.failDeleteMemo(error)), + environment: environment + ) + } } diff --git a/xcode/Subconscious/Shared/Components/Notebook/Notebook.swift b/xcode/Subconscious/Shared/Components/Notebook/Notebook.swift index 6442f5d0..34e155c3 100644 --- a/xcode/Subconscious/Shared/Components/Notebook/Notebook.swift +++ b/xcode/Subconscious/Shared/Components/Notebook/Notebook.swift @@ -85,7 +85,7 @@ struct NotebookView: View { /// Actions for modifying state /// For action naming convention, see /// https://github.com/gordonbrander/subconscious/wiki/action-naming-convention -enum NotebookAction { +enum NotebookAction: Hashable { static let logger = Logger( subsystem: Config.default.rdns, category: "NotebookAction" @@ -115,7 +115,7 @@ enum NotebookAction { /// Set the count of existing entries case setEntryCount(Int) /// Fail to get count of existing entries - case failEntryCount(Error) + case failEntryCount(String) // List entries case listRecent @@ -211,6 +211,8 @@ struct NotebookDetailStackCursor: CursorProtocol { static func tag(_ action: ViewModel.Action) -> NotebookModel.Action { switch action { + case let .requestDeleteMemo(slashlink): + return .requestDeleteMemo(slashlink) case let .succeedMergeEntry(parent: parent, child: child): return .succeedMergeEntry(parent: parent, child: child) case let .succeedMoveEntry(from: from, to: to): @@ -430,8 +432,11 @@ struct NotebookModel: ModelProtocol { address: address ) case .failDeleteMemo(let error): - logger.log("failDeleteMemo: \(error)") - return Update(state: state) + return failDeleteMemo( + state: state, + environment: environment, + error: error + ) case .succeedDeleteMemo(let address): return succeedDeleteMemo( state: state, @@ -575,15 +580,16 @@ struct NotebookModel: ModelProtocol { state: NotebookModel, environment: AppEnvironment ) -> Update { - let fx: Fx = environment.data.countMemosPublisher() + let fx: Fx = environment.data + .countMemosPublisher() .map({ count in NotebookAction.setEntryCount(count) }) - .catch({ error in - Just(NotebookAction.failEntryCount(error)) + .recover({ error in + NotebookAction.failEntryCount(error.localizedDescription) }) - .eraseToAnyPublisher() - return Update(state: state, fx: fx) + .eraseToAnyPublisher() + return Update(state: state, fx: fx) } /// Set entry count @@ -727,7 +733,7 @@ struct NotebookModel: ModelProtocol { ) } - /// Entry delete succeeded + /// Entry delete failed static func failDeleteMemo( state: Self, environment: Environment, diff --git a/xcode/Subconscious/Shared/Components/Settings/AuthorizationSettingsView.swift b/xcode/Subconscious/Shared/Components/Settings/AuthorizationSettingsView.swift index 4b31fccf..d54f8634 100644 --- a/xcode/Subconscious/Shared/Components/Settings/AuthorizationSettingsView.swift +++ b/xcode/Subconscious/Shared/Components/Settings/AuthorizationSettingsView.swift @@ -193,7 +193,7 @@ struct AuthorizationSettingsView_Previews: PreviewProvider { } } -enum AuthorizationSettingsFormAction: Equatable { +enum AuthorizationSettingsFormAction: Hashable, Equatable { case didField(FormFieldAction) case nameField(FormFieldAction) case reset @@ -253,7 +253,7 @@ struct AuthorizationSettingsFormModel: ModelProtocol { } // MARK: Actions -enum AuthorizationSettingsAction { +enum AuthorizationSettingsAction: Hashable { case appear case listAuthorizations case succeedListAuthorizations([Authorization]) diff --git a/xcode/Subconscious/Shared/Services/DataService.swift b/xcode/Subconscious/Shared/Services/DataService.swift index e00c9bf0..7d31a4cd 100644 --- a/xcode/Subconscious/Shared/Services/DataService.swift +++ b/xcode/Subconscious/Shared/Services/DataService.swift @@ -243,7 +243,7 @@ actor DataService { results.append( PeerIndexResult.failure( PeerIndexError( - error: error, + error: error.localizedDescription, petname: petname ) ) diff --git a/xcode/Subconscious/Shared/Services/DatabaseService.swift b/xcode/Subconscious/Shared/Services/DatabaseService.swift index d7f65ab8..2dfda285 100644 --- a/xcode/Subconscious/Shared/Services/DatabaseService.swift +++ b/xcode/Subconscious/Shared/Services/DatabaseService.swift @@ -12,7 +12,7 @@ import Combine import os /// Enum representing the current readiness state of the database service -enum DatabaseServiceState: String { +enum DatabaseServiceState: String, Hashable { case initial = "initial" case broken = "broken" case ready = "ready" diff --git a/xcode/Subconscious/Shared/Services/Noosphere/Noosphere.swift b/xcode/Subconscious/Shared/Services/Noosphere/Noosphere.swift index 65e1056b..bf942e79 100644 --- a/xcode/Subconscious/Shared/Services/Noosphere/Noosphere.swift +++ b/xcode/Subconscious/Shared/Services/Noosphere/Noosphere.swift @@ -35,7 +35,8 @@ public enum NoosphereError: Error, LocalizedError { public struct SphereReceipt: CustomStringConvertible, - CustomDebugStringConvertible + CustomDebugStringConvertible, + Hashable { public var identity: String // !!!: Mnemonic is a secret and should never be logged or persisted diff --git a/xcode/Subconscious/Subconscious.xcodeproj/project.pbxproj b/xcode/Subconscious/Subconscious.xcodeproj/project.pbxproj index f9198810..f7b8cee1 100644 --- a/xcode/Subconscious/Subconscious.xcodeproj/project.pbxproj +++ b/xcode/Subconscious/Subconscious.xcodeproj/project.pbxproj @@ -110,6 +110,10 @@ B5F6ADCC29C1323900690DE4 /* Tests_FormField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F6ADCB29C1323900690DE4 /* Tests_FormField.swift */; }; B5FB9D9429D5176100D64988 /* GhostPillButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5FB9D9329D5176100D64988 /* GhostPillButtonStyle.swift */; }; B5FB9D9529D51D9600D64988 /* UserProfileHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58A46B629D3D09500491E43 /* UserProfileHeaderView.swift */; }; + B80024882AE6BC4700CE6778 /* Tests_FeedAppAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80024872AE6BC4700CE6778 /* Tests_FeedAppAction.swift */; }; + B800248A2AE6BCC900CE6778 /* Tests_FeedAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80024892AE6BCC900CE6778 /* Tests_FeedAction.swift */; }; + B800248C2AE6C47E00CE6778 /* Tests_HomeProfileAppAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B800248B2AE6C47E00CE6778 /* Tests_HomeProfileAppAction.swift */; }; + B800248E2AE6C55F00CE6778 /* Tests_HomeProfileAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B800248D2AE6C55F00CE6778 /* Tests_HomeProfileAction.swift */; }; B80057EB27DC355E002C0129 /* SubconsciousTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80057EA27DC355E002C0129 /* SubconsciousTests.swift */; }; B80057F427DC35BE002C0129 /* Tests_Slug.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80057F327DC35BE002C0129 /* Tests_Slug.swift */; }; B800ABE8297DE7D20024D1FD /* DataService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B800ABE7297DE7D20024D1FD /* DataService.swift */; }; @@ -177,6 +181,9 @@ B831BDB92825A28A00C4CE92 /* Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = B831BDB82825A28A00C4CE92 /* Header.swift */; }; B831BDBA2825A28A00C4CE92 /* Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = B831BDB82825A28A00C4CE92 /* Header.swift */; }; B831BDBC2825A4E700C4CE92 /* Tests_Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = B831BDBB2825A4E700C4CE92 /* Tests_Header.swift */; }; + B83660562AE3225200BA1864 /* Tests_NotebookAppAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83660552AE3225200BA1864 /* Tests_NotebookAppAction.swift */; }; + B83660582AE3231500BA1864 /* Tests_NotebookDetailStackCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83660572AE3231500BA1864 /* Tests_NotebookDetailStackCursor.swift */; }; + B836605A2AE3239A00BA1864 /* Tests_NotebookAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83660592AE3239A00BA1864 /* Tests_NotebookAction.swift */; }; B83B19A32A005C6B007657D9 /* Did+SubconsciousLocal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83B19A22A005C6B007657D9 /* Did+SubconsciousLocal.swift */; }; B83B19A52A015D93007657D9 /* Tests_Did.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83B19A42A015D93007657D9 /* Tests_Did.swift */; }; B83B19A92A0183AA007657D9 /* Tests_Did+SubconsciousLocal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83B19A82A0183AA007657D9 /* Tests_Did+SubconsciousLocal.swift */; }; @@ -207,7 +214,6 @@ B85BF47827BC2B4700F55730 /* DetailToolbarContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85BF47727BC2B4700F55730 /* DetailToolbarContent.swift */; }; B85BF47927BC2B4700F55730 /* DetailToolbarContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85BF47727BC2B4700F55730 /* DetailToolbarContent.swift */; }; B85D5E3D28BE4B2C00EE0078 /* Tests_NotebookUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85D5E3C28BE4B2C00EE0078 /* Tests_NotebookUpdate.swift */; }; - B85D5E3F28BE4B4600EE0078 /* Tests_FeedUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85D5E3E28BE4B4600EE0078 /* Tests_FeedUpdate.swift */; }; B85DF78C29B660C60042D725 /* Prose.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85DF78B29B660C50042D725 /* Prose.swift */; }; B85DF78D29B660C60042D725 /* Prose.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85DF78B29B660C50042D725 /* Prose.swift */; }; B85DF78F29B7B5440042D725 /* Tests_Prose.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85DF78E29B7B5440042D725 /* Tests_Prose.swift */; }; @@ -561,6 +567,10 @@ B5F6ADC829C02F4A00690DE4 /* AddressBookService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressBookService.swift; sourceTree = ""; }; B5F6ADCB29C1323900690DE4 /* Tests_FormField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_FormField.swift; sourceTree = ""; }; B5FB9D9329D5176100D64988 /* GhostPillButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostPillButtonStyle.swift; sourceTree = ""; }; + B80024872AE6BC4700CE6778 /* Tests_FeedAppAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_FeedAppAction.swift; sourceTree = ""; }; + B80024892AE6BCC900CE6778 /* Tests_FeedAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_FeedAction.swift; sourceTree = ""; }; + B800248B2AE6C47E00CE6778 /* Tests_HomeProfileAppAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_HomeProfileAppAction.swift; sourceTree = ""; }; + B800248D2AE6C55F00CE6778 /* Tests_HomeProfileAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_HomeProfileAction.swift; sourceTree = ""; }; B80057E827DC355E002C0129 /* SubconsciousTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SubconsciousTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; B80057EA27DC355E002C0129 /* SubconsciousTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubconsciousTests.swift; sourceTree = ""; }; B80057F327DC35BE002C0129 /* Tests_Slug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_Slug.swift; sourceTree = ""; }; @@ -604,6 +614,9 @@ B831BDB62824DA9700C4CE92 /* Tests_Tape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_Tape.swift; sourceTree = ""; }; B831BDB82825A28A00C4CE92 /* Header.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Header.swift; sourceTree = ""; }; B831BDBB2825A4E700C4CE92 /* Tests_Header.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_Header.swift; sourceTree = ""; }; + B83660552AE3225200BA1864 /* Tests_NotebookAppAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_NotebookAppAction.swift; sourceTree = ""; }; + B83660572AE3231500BA1864 /* Tests_NotebookDetailStackCursor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_NotebookDetailStackCursor.swift; sourceTree = ""; }; + B83660592AE3239A00BA1864 /* Tests_NotebookAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_NotebookAction.swift; sourceTree = ""; }; B83B19A22A005C6B007657D9 /* Did+SubconsciousLocal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Did+SubconsciousLocal.swift"; sourceTree = ""; }; B83B19A42A015D93007657D9 /* Tests_Did.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tests_Did.swift; sourceTree = ""; }; B83B19A82A0183AA007657D9 /* Tests_Did+SubconsciousLocal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tests_Did+SubconsciousLocal.swift"; sourceTree = ""; }; @@ -625,7 +638,6 @@ B85BF47427BB3D6E00F55730 /* ToolbarTitleGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarTitleGroupView.swift; sourceTree = ""; }; B85BF47727BC2B4700F55730 /* DetailToolbarContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailToolbarContent.swift; sourceTree = ""; }; B85D5E3C28BE4B2C00EE0078 /* Tests_NotebookUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_NotebookUpdate.swift; sourceTree = ""; }; - B85D5E3E28BE4B4600EE0078 /* Tests_FeedUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_FeedUpdate.swift; sourceTree = ""; }; B85DF78B29B660C50042D725 /* Prose.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Prose.swift; sourceTree = ""; }; B85DF78E29B7B5440042D725 /* Tests_Prose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_Prose.swift; sourceTree = ""; }; B85EC45F296F099700558761 /* ProfilePic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePic.swift; sourceTree = ""; }; @@ -942,13 +954,16 @@ B83B19A42A015D93007657D9 /* Tests_Did.swift */, B83B19A82A0183AA007657D9 /* Tests_Did+SubconsciousLocal.swift */, B84AD8DE280F3659006B3153 /* Tests_EntryLink.swift */, - B85D5E3E28BE4B4600EE0078 /* Tests_FeedUpdate.swift */, + B80024892AE6BCC900CE6778 /* Tests_FeedAction.swift */, + B80024872AE6BC4700CE6778 /* Tests_FeedAppAction.swift */, B8B3194E2909F36800A1E62A /* Tests_FileFingerprint.swift */, B509612F2A4CEFCF008E9EDB /* Tests_FirstRun.swift */, B5F6ADCB29C1323900690DE4 /* Tests_FormField.swift */, B8925B2A29C0FA43001F9503 /* Tests_Func.swift */, B831BDBB2825A4E700C4CE92 /* Tests_Header.swift */, B80C9E422A2A7CE400E152FB /* Tests_HeaderSubtext.swift */, + B800248D2AE6C55F00CE6778 /* Tests_HomeProfileAction.swift */, + B800248B2AE6C47E00CE6778 /* Tests_HomeProfileAppAction.swift */, B8CAA6CC2A02FF9B00F4A0F6 /* Tests_Link.swift */, B8E1A9482A13F49F00B757A5 /* Tests_LogFmt.swift */, B809AFF328D8E7BC00D0589A /* Tests_MarkupText.swift */, @@ -958,6 +973,9 @@ B8B604E8291476E9006FCB77 /* Tests_Migrations.swift */, B82FF241298C0DAF0097D688 /* Tests_Noosphere.swift */, B8EA3756299EBA5500D98E2B /* Tests_NoosphereService.swift */, + B83660592AE3239A00BA1864 /* Tests_NotebookAction.swift */, + B83660552AE3225200BA1864 /* Tests_NotebookAppAction.swift */, + B83660572AE3231500BA1864 /* Tests_NotebookDetailStackCursor.swift */, B85D5E3C28BE4B2C00EE0078 /* Tests_NotebookUpdate.swift */, B88CC95A284FF64300994928 /* Tests_OrderedCollectionUtilities.swift */, B82BB7FD28243F32000C9FCC /* Tests_Parser.swift */, @@ -1708,12 +1726,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + B800248C2AE6C47E00CE6778 /* Tests_HomeProfileAppAction.swift in Sources */, B88DFEF129E7454100B00DE8 /* Tests_CombineUtilities.swift in Sources */, B8AC58762AC7568A00F1647D /* Tests_RecoveryPhrase.swift in Sources */, B82FF242298C0DAF0097D688 /* Tests_Noosphere.swift in Sources */, B508956E29E7862A0048106B /* Tests_AddressBookService.swift in Sources */, B80CC807299D4DF000C4D7C0 /* Tests_SQLite3Database.swift in Sources */, B83B19A52A015D93007657D9 /* Tests_Did.swift in Sources */, + B836605A2AE3239A00BA1864 /* Tests_NotebookAction.swift in Sources */, B8CAA6CD2A02FF9B00F4A0F6 /* Tests_Link.swift in Sources */, B8AC58742AC74F1600F1647D /* Tests_RecoveryModeView.swift in Sources */, B8E1A9492A13F49F00B757A5 /* Tests_LogFmt.swift in Sources */, @@ -1727,6 +1747,7 @@ B80C9E432A2A7CE400E152FB /* Tests_HeaderSubtext.swift in Sources */, B8F27EE42970CD8F00A33E78 /* Tests_Sphere.swift in Sources */, B80C9E452A2A7CF100E152FB /* Tests_Audience.swift in Sources */, + B83660582AE3231500BA1864 /* Tests_NotebookDetailStackCursor.swift in Sources */, B56C2D4E2A4962D00062DAC0 /* Tests_TranscludeService.swift in Sources */, B82BB7FE28243F32000C9FCC /* Tests_Parser.swift in Sources */, B8099F022A3B6FA50014FC2E /* Tests_MemoRecord.swift in Sources */, @@ -1739,7 +1760,9 @@ B5AD5C902AA7FF1900FC5BC5 /* Tests_DetailStack.swift in Sources */, B88A76D729E0AA44005F3422 /* Tests_MemoViewerDetailMetaSheet.swift in Sources */, B85DF78F29B7B5440042D725 /* Tests_Prose.swift in Sources */, + B800248E2AE6C55F00CE6778 /* Tests_HomeProfileAction.swift in Sources */, B85D5E3D28BE4B2C00EE0078 /* Tests_NotebookUpdate.swift in Sources */, + B83660562AE3225200BA1864 /* Tests_NotebookAppAction.swift in Sources */, B50961302A4CEFCF008E9EDB /* Tests_FirstRun.swift in Sources */, B8682DCD2804C379001CD8DD /* Tests_CollectionUtilities.swift in Sources */, B5908BEB29DAB05B00225B1A /* TestUtilities.swift in Sources */, @@ -1754,9 +1777,9 @@ B8925B2B29C0FA43001F9503 /* Tests_Func.swift in Sources */, B8D328B529A640F200850A37 /* Tests_RecoveryPhraseView.swift in Sources */, B8F832E329B9292C00DFDFA8 /* Tests_SubtextAttributedStringRenderer.swift in Sources */, + B80024882AE6BC4700CE6778 /* Tests_FeedAppAction.swift in Sources */, B831BDBC2825A4E700C4CE92 /* Tests_Header.swift in Sources */, B8B1769D2ACCD02100A8CAAB /* MockErrorLoggingService.swift in Sources */, - B85D5E3F28BE4B4600EE0078 /* Tests_FeedUpdate.swift in Sources */, B8E2B02729AD053D004A78B3 /* Tests_Petname.swift in Sources */, B80057EB27DC355E002C0129 /* SubconsciousTests.swift in Sources */, B81D063D29F1C1E400593BBA /* Tests_SphereFile.swift in Sources */, @@ -1770,6 +1793,7 @@ B8D328BA29A69D2D00850A37 /* Tests_URLUtilities.swift in Sources */, B8F164252AE1B4A300A05CE7 /* Tests_StoryUser.swift in Sources */, B831BDB72824DA9700C4CE92 /* Tests_Tape.swift in Sources */, + B800248A2AE6BCC900CE6778 /* Tests_FeedAction.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/xcode/Subconscious/SubconsciousTests/Tests_FeedAction.swift b/xcode/Subconscious/SubconsciousTests/Tests_FeedAction.swift new file mode 100644 index 00000000..1253887c --- /dev/null +++ b/xcode/Subconscious/SubconsciousTests/Tests_FeedAction.swift @@ -0,0 +1,51 @@ +// +// Tests_FeedAction.swift +// SubconsciousTests +// +// Created by Gordon Brander on 10/23/23. +// + +import XCTest +@testable import Subconscious + +final class Tests_FeedAction: XCTestCase { + func testFromSucceedIndexOurSphere() throws { + let action = FeedAction.from( + .succeedIndexOurSphere( + OurSphereRecord( + identity: Did("did:key:abc123")!, + since: "bafyfakefakefake" + ) + ) + ) + XCTAssertEqual(action, .refreshAll) + } + + func testFromSucceedRecoverOurSphere() throws { + let action = FeedAction.from( + .succeedRecoverOurSphere + ) + XCTAssertEqual(action, .refreshAll) + } + + func testFromSucceedDeleteMemo() throws { + let action = FeedAction.from( + .succeedDeleteMemo(Slashlink("/foo")!) + ) + XCTAssertEqual(action, .succeedDeleteMemo(Slashlink("/foo")!)) + } + + func testFromFailDeleteMemo() throws { + let action = FeedAction.from( + .failDeleteMemo("") + ) + XCTAssertEqual(action, .failDeleteMemo("")) + } + + func testFromRequestFeedRoot() throws { + let action = FeedAction.from( + .requestFeedRoot + ) + XCTAssertEqual(action, .requestFeedRoot) + } +} diff --git a/xcode/Subconscious/SubconsciousTests/Tests_FeedAppAction.swift b/xcode/Subconscious/SubconsciousTests/Tests_FeedAppAction.swift new file mode 100644 index 00000000..5e349bd6 --- /dev/null +++ b/xcode/Subconscious/SubconsciousTests/Tests_FeedAppAction.swift @@ -0,0 +1,21 @@ +// +// Tests_Feed+AppAction.swift +// SubconsciousTests +// +// Created by Gordon Brander on 10/23/23. +// + +import XCTest + +@testable import Subconscious + +final class Tests_Feed_AppAction: XCTestCase { + func testFromRequestDeleteMemo() throws { + let action = AppAction.from( + FeedAction.requestDeleteMemo( + Slashlink("/bob")! + ) + ) + XCTAssertEqual(action, .deleteMemo(Slashlink("/bob")!)) + } +} diff --git a/xcode/Subconscious/SubconsciousTests/Tests_FeedUpdate.swift b/xcode/Subconscious/SubconsciousTests/Tests_FeedUpdate.swift deleted file mode 100644 index 309f0661..00000000 --- a/xcode/Subconscious/SubconsciousTests/Tests_FeedUpdate.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// Tests_FeedUpdate.swift -// SubconsciousTests -// -// Created by Gordon Brander on 8/30/22. -// - -import XCTest - -class Tests_FeedUpdate: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - 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. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/xcode/Subconscious/SubconsciousTests/Tests_HomeProfileAction.swift b/xcode/Subconscious/SubconsciousTests/Tests_HomeProfileAction.swift new file mode 100644 index 00000000..daa7bca3 --- /dev/null +++ b/xcode/Subconscious/SubconsciousTests/Tests_HomeProfileAction.swift @@ -0,0 +1,46 @@ +// +// Tests_HomeProfileAction.swift +// SubconsciousTests +// +// Created by Gordon Brander on 10/23/23. +// + +import XCTest +@testable import Subconscious + +final class Tests_HomeProfileAction: XCTestCase { + typealias Action = HomeProfileAction + + func testFromSucceedIndexOurSphere() throws { + let action = Action.from( + .succeedIndexOurSphere( + OurSphereRecord( + identity: Did("did:key:abc123")!, + since: "bafyfakefakefake" + ) + ) + ) + XCTAssertEqual(action, .ready) + } + + func testFromSucceedDeleteMemo() throws { + let action = Action.from( + .succeedDeleteMemo(Slashlink("/foo")!) + ) + XCTAssertEqual(action, .succeedDeleteMemo(Slashlink("/foo")!)) + } + + func testFromFailDeleteMemo() throws { + let action = Action.from( + .failDeleteMemo("") + ) + XCTAssertEqual(action, .failDeleteMemo("")) + } + + func testFromRequestFeedRoot() throws { + let action = Action.from( + .requestProfileRoot + ) + XCTAssertEqual(action, .requestProfileRoot) + } +} diff --git a/xcode/Subconscious/SubconsciousTests/Tests_HomeProfileAppAction.swift b/xcode/Subconscious/SubconsciousTests/Tests_HomeProfileAppAction.swift new file mode 100644 index 00000000..70cfb637 --- /dev/null +++ b/xcode/Subconscious/SubconsciousTests/Tests_HomeProfileAppAction.swift @@ -0,0 +1,21 @@ +// +// Tests_HomeProfile+AppAction.swift +// SubconsciousTests +// +// Created by Gordon Brander on 10/23/23. +// + +import XCTest + +@testable import Subconscious + +final class Tests_HomeProfile_AppAction: XCTestCase { + func testFromRequestDeleteMemo() throws { + let action = AppAction.from( + HomeProfileAction.requestDeleteMemo( + Slashlink("/bob")! + ) + ) + XCTAssertEqual(action, .deleteMemo(Slashlink("/bob")!)) + } +} diff --git a/xcode/Subconscious/SubconsciousTests/Tests_NotebookAction.swift b/xcode/Subconscious/SubconsciousTests/Tests_NotebookAction.swift new file mode 100644 index 00000000..ca2c1928 --- /dev/null +++ b/xcode/Subconscious/SubconsciousTests/Tests_NotebookAction.swift @@ -0,0 +1,63 @@ +// +// Tests_NotebookAction.swift +// SubconsciousTests +// +// Created by Gordon Brander on 10/20/23. +// + +import XCTest +@testable import Subconscious + +final class Tests_NotebookAction: XCTestCase { + func testFromSucceedMigrateDatabase() throws { + let action = NotebookAction.from(.succeedMigrateDatabase(0)) + XCTAssertEqual(action, .ready) + } + + func testFromSucceedSyncLocalFilesWithDatabase() throws { + let action = NotebookAction.from( + .succeedSyncLocalFilesWithDatabase([]) + ) + XCTAssertEqual(action, .ready) + } + + func testFromSucceedIndexOurSphere() throws { + let action = NotebookAction.from( + .succeedIndexOurSphere( + OurSphereRecord( + identity: Did("did:key:abc123")!, + since: "bafyfakefakefake" + ) + ) + ) + XCTAssertEqual(action, .refreshLists) + } + + func testFromSucceedRecoverOurSphere() throws { + let action = NotebookAction.from( + .succeedRecoverOurSphere + ) + XCTAssertEqual(action, .refreshLists) + } + + func testFromSucceedDeleteMemo() throws { + let action = NotebookAction.from( + .succeedDeleteMemo(Slashlink("/foo")!) + ) + XCTAssertEqual(action, .succeedDeleteMemo(Slashlink("/foo")!)) + } + + func testFromFailDeleteMemo() throws { + let action = NotebookAction.from( + .failDeleteMemo("") + ) + XCTAssertEqual(action, .failDeleteMemo("")) + } + + func testFromRequestNotebookRoot() throws { + let action = NotebookAction.from( + .requestNotebookRoot + ) + XCTAssertEqual(action, .requestNotebookRoot) + } +} diff --git a/xcode/Subconscious/SubconsciousTests/Tests_NotebookAppAction.swift b/xcode/Subconscious/SubconsciousTests/Tests_NotebookAppAction.swift new file mode 100644 index 00000000..b4dd7f9d --- /dev/null +++ b/xcode/Subconscious/SubconsciousTests/Tests_NotebookAppAction.swift @@ -0,0 +1,21 @@ +// +// Tests_Notebook+AppAction.swift +// SubconsciousTests +// +// Created by Gordon Brander on 10/20/23. +// + +import XCTest + +@testable import Subconscious + +final class Tests_Notebook_AppAction: XCTestCase { + func testFromRequestDeleteMemo() throws { + let action = AppAction.from( + NotebookAction.requestDeleteMemo( + Slashlink("/bob")! + ) + ) + XCTAssertEqual(action, .deleteMemo(Slashlink("/bob")!)) + } +} diff --git a/xcode/Subconscious/SubconsciousTests/Tests_NotebookDetailStackCursor.swift b/xcode/Subconscious/SubconsciousTests/Tests_NotebookDetailStackCursor.swift new file mode 100644 index 00000000..d249d79f --- /dev/null +++ b/xcode/Subconscious/SubconsciousTests/Tests_NotebookDetailStackCursor.swift @@ -0,0 +1,90 @@ +// +// Tests_NotebookDetailStackCursor.swift +// SubconsciousTests +// +// Created by Gordon Brander on 10/20/23. +// + +import XCTest +@testable import Subconscious + +final class Tests_NotebookDetailStackCursor: XCTestCase { + func testTagRequestDeleteMemo() throws { + let action = NotebookDetailStackCursor.tag( + .requestDeleteMemo(Slashlink("@bob/foo")!) + ) + XCTAssertEqual( + action, + NotebookAction.requestDeleteMemo(Slashlink("@bob/foo")!) + ) + } + + func testTagSucceedMergeEntry() throws { + let action = NotebookDetailStackCursor.tag( + .succeedMergeEntry( + parent: Slashlink("/foo")!, + child: Slashlink("/bar")! + ) + ) + XCTAssertEqual( + action, + NotebookAction.succeedMergeEntry( + parent: Slashlink("/foo")!, + child: Slashlink("/bar")! + ) + ) + } + + func testTagSucceedMoveEntry() throws { + let action = NotebookDetailStackCursor.tag( + .succeedMoveEntry( + from: Slashlink("/foo")!, + to: Slashlink("/bar")! + ) + ) + XCTAssertEqual( + action, + NotebookAction.succeedMoveEntry( + from: Slashlink("/foo")!, + to: Slashlink("/bar")! + ) + ) + } + + func testTagSucceedUpdateAudience() throws { + let action = NotebookDetailStackCursor.tag( + .succeedUpdateAudience( + MoveReceipt( + from: Slashlink("/foo")!, + to: Slashlink("/bar")! + ) + ) + ) + XCTAssertEqual( + action, + NotebookAction.succeedUpdateAudience( + MoveReceipt( + from: Slashlink("/foo")!, + to: Slashlink("/bar")! + ) + ) + ) + } + + func testTagSucceedSaveEntry() throws { + let date = Date.now + let action = NotebookDetailStackCursor.tag( + .succeedSaveEntry( + address: Slashlink("/bar")!, + modified: date + ) + ) + XCTAssertEqual( + action, + NotebookAction.succeedSaveEntry( + slug: Slashlink("/bar")!, + modified: date + ) + ) + } +} diff --git a/xcode/Subconscious/SubconsciousTests/Tests_NotebookUpdate.swift b/xcode/Subconscious/SubconsciousTests/Tests_NotebookUpdate.swift index 576158ef..41b5e70d 100644 --- a/xcode/Subconscious/SubconsciousTests/Tests_NotebookUpdate.swift +++ b/xcode/Subconscious/SubconsciousTests/Tests_NotebookUpdate.swift @@ -12,7 +12,7 @@ import ObservableStore /// Tests for Notebook.update class Tests_NotebookUpdate: XCTestCase { let environment = AppEnvironment() - + func testEntryCount() throws { let state = NotebookModel() let update = NotebookModel.update( @@ -26,7 +26,7 @@ class Tests_NotebookUpdate: XCTestCase { "Entry count correctly set" ) } - + func testDeleteEntry() throws { let a = Slug(formatting: "A")!.toSlashlink() let b = Slug(formatting: "B")!.toLocalSlashlink() @@ -74,6 +74,4 @@ class Tests_NotebookUpdate: XCTestCase { "Slug C moved up because slug B was removed" ) } - - }