From 06ce99b8c6f5036b580541b0d41279fb43ef4f12 Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Sat, 14 Feb 2026 15:50:07 +0900 Subject: [PATCH 01/12] =?UTF-8?q?refactor:=20HometeDomain=E3=83=A2?= =?UTF-8?q?=E3=82=B8=E3=83=A5=E3=83=BC=E3=83=AB=E3=81=AE=E9=9B=9B=E5=BD=A2?= =?UTF-8?q?=E3=82=92=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LocalPackage/Package.swift | 10 +++++----- .../LocalPackage.swift | 0 .../LocalPackageTests.swift | 0 homete.xcodeproj/project.pbxproj | 15 +++++++++++++++ 4 files changed, 20 insertions(+), 5 deletions(-) rename LocalPackage/Sources/{LocalPackage => HometeDomain}/LocalPackage.swift (100%) rename LocalPackage/Tests/{LocalPackageTests => HometeDomainTests}/LocalPackageTests.swift (100%) diff --git a/LocalPackage/Package.swift b/LocalPackage/Package.swift index 51ae1520..33e32bb3 100644 --- a/LocalPackage/Package.swift +++ b/LocalPackage/Package.swift @@ -7,17 +7,17 @@ let package = Package( name: "LocalPackage", products: [ .library( - name: "LocalPackage", - targets: ["LocalPackage"] + name: "HometeDomain", + targets: ["HometeDomain"] ), ], targets: [ .target( - name: "LocalPackage" + name: "HometeDomain" ), .testTarget( - name: "LocalPackageTests", - dependencies: ["LocalPackage"] + name: "HometeDomainTests", + dependencies: ["HometeDomain"] ), ] ) diff --git a/LocalPackage/Sources/LocalPackage/LocalPackage.swift b/LocalPackage/Sources/HometeDomain/LocalPackage.swift similarity index 100% rename from LocalPackage/Sources/LocalPackage/LocalPackage.swift rename to LocalPackage/Sources/HometeDomain/LocalPackage.swift diff --git a/LocalPackage/Tests/LocalPackageTests/LocalPackageTests.swift b/LocalPackage/Tests/HometeDomainTests/LocalPackageTests.swift similarity index 100% rename from LocalPackage/Tests/LocalPackageTests/LocalPackageTests.swift rename to LocalPackage/Tests/HometeDomainTests/LocalPackageTests.swift diff --git a/homete.xcodeproj/project.pbxproj b/homete.xcodeproj/project.pbxproj index 08c52b64..4e5b361c 100644 --- a/homete.xcodeproj/project.pbxproj +++ b/homete.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 04B371992F40526F00820C62 /* HometeDomain in Frameworks */ = {isa = PBXBuildFile; productRef = 04B371982F40526F00820C62 /* HometeDomain */; }; BC0CF70D2E77B3A500DC31CA /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = BC0CF70C2E77B3A500DC31CA /* FirebaseMessaging */; }; BC0CF7122E7863B500DC31CA /* FirebaseFunctions in Frameworks */ = {isa = PBXBuildFile; productRef = BC0CF7112E7863B500DC31CA /* FirebaseFunctions */; }; BC1BB2722E909B99001D168F /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = BC1BB2712E909B99001D168F /* SnapshotTesting */; }; @@ -100,6 +101,7 @@ BC0CF70D2E77B3A500DC31CA /* FirebaseMessaging in Frameworks */, BC0CF7122E7863B500DC31CA /* FirebaseFunctions in Frameworks */, BC2E372C2E3DE189005BD910 /* FirebaseAuth in Frameworks */, + 04B371992F40526F00820C62 /* HometeDomain in Frameworks */, BCB8DB182E37B3F900FFB4E1 /* FirebaseCrashlytics in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -202,6 +204,7 @@ BC0CF70C2E77B3A500DC31CA /* FirebaseMessaging */, BC0CF7112E7863B500DC31CA /* FirebaseFunctions */, BCADFE1C2EACB62E00DF4AAA /* Prefire */, + 04B371982F40526F00820C62 /* HometeDomain */, ); productName = homete; productReference = BCC853F52DB74BBC00C9A44B /* homete.app */; @@ -268,6 +271,7 @@ BCB8DAFC2E36FC7100FFB4E1 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, BC1BB2612E909B50001D168F /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, BC4701F92E9A0F3D006CC530 /* XCRemoteSwiftPackageReference "Prefire" */, + 04B371972F40526F00820C62 /* XCLocalSwiftPackageReference "LocalPackage" */, ); preferredProjectObjectVersion = 77; productRefGroup = BCC853F62DB74BBC00C9A44B /* Products */; @@ -760,6 +764,13 @@ }; /* End XCConfigurationList section */ +/* Begin XCLocalSwiftPackageReference section */ + 04B371972F40526F00820C62 /* XCLocalSwiftPackageReference "LocalPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = LocalPackage; + }; +/* End XCLocalSwiftPackageReference section */ + /* Begin XCRemoteSwiftPackageReference section */ BC1BB2612E909B50001D168F /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { isa = XCRemoteSwiftPackageReference; @@ -788,6 +799,10 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 04B371982F40526F00820C62 /* HometeDomain */ = { + isa = XCSwiftPackageProductDependency; + productName = HometeDomain; + }; BC0CF70C2E77B3A500DC31CA /* FirebaseMessaging */ = { isa = XCSwiftPackageProductDependency; package = BCB8DAFC2E36FC7100FFB4E1 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; From 0b0c20e6d53d5ccf6ea2b6dcc11f43789bdf860b Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Sat, 14 Feb 2026 16:52:28 +0900 Subject: [PATCH 02/12] =?UTF-8?q?refactor:=20Domain=E3=82=92HometeDomain?= =?UTF-8?q?=E3=81=AB=E5=BC=95=E8=B6=8A=E3=81=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LocalPackage/Package.swift | 1 + .../HometeDomain/Account/Account.swift | 29 ++++++ .../HometeDomain}/Account/AccountStore.swift | 58 ++++++------ .../HometeDomain}/Account/LoginContext.swift | 18 ++-- .../HometeDomain}/Account/UserName.swift | 22 +++-- .../AnalyticsLog/AnalyticsEvent.swift | 27 +++--- .../Authentification/AccountAuthInfo.swift | 18 ++++ .../Authentification/AccountAuthResult.swift | 15 +++ .../Authentification/AccountAuthStore.swift | 75 ++++++++------- .../AccountListenerStream.swift | 20 ++-- .../Authentification/LaunchState.swift | 10 +- .../SignInWithAppleNonce.swift | 17 ++++ .../SignInWithAppleResult.swift | 19 ++++ .../Cohabitant/CohabitantData.swift | 21 +++++ .../Cohabitant/CohabitantMember.swift | 19 ++++ .../Cohabitant/CohabitantMemberList.swift | 28 +++--- .../CohabitantRegistrationMessage.swift | 62 ++++++------ .../CohabitantRegistrationRole.swift | 18 ++-- .../CohabitantRegistrationState.swift | 4 +- .../ConfirmedRegistrationPeers.swift | 22 ++--- .../Cohabitant/CohabitantStore.swift | 56 +++++------ .../Housework/DailyHouseworkList.swift | 29 +++--- .../Housework/DailyHouseworkMetaData.swift | 19 ++-- .../Housework/HouseworkBoardList.swift | 20 ++-- .../Housework/HouseworkHistoryList.swift | 54 ++++++----- .../Housework/HouseworkIndexedDate.swift | 24 +++-- .../Cohabitant/Housework/HouseworkItem.swift | 94 ++++++++++++------- .../Housework/HouseworkListStore.swift | 64 ++++++------- .../Cohabitant/Housework/HouseworkState.swift | 8 +- .../Housework/StoredAllHouseworkList.swift | 28 +++--- .../Dependencies/AccountAuthClient.swift | 45 +++++++++ .../Dependencies/AccountInfoClient.swift | 25 +++++ .../Dependencies/AnalyticsClient.swift | 26 +++++ .../Dependencies/CohabitantClient.swift | 35 +++++++ .../CohabitantPushNotificationClient.swift | 19 ++++ .../Dependencies/DependencyClient.swift | 4 +- .../Dependencies/HouseworkClient.swift | 50 ++++++++++ .../Dependencies/NonceGenerationClient.swift | 30 ++++++ .../Dependencies/SignInWithAppleClient.swift | 23 +++++ .../Sources/HometeDomain}/DomainError.swift | 10 +- .../Sources/HometeDomain/LocalPackage.swift | 2 - .../PushNotificationContent.swift | 13 ++- .../Setting/SettingMenuItem.swift | 24 ++--- homete/Model/AppDependencies.swift | 1 + .../Model/Dependencies/AnalyticsClient.swift | 38 -------- .../Model/Dependencies/CohabitantClient.swift | 54 ----------- .../Model/Dependencies/HouseworkClient.swift | 84 ----------------- .../Dependencies/SignInWithAppleClient.swift | 32 ------- homete/Model/Domain/Account/Account.swift | 22 ----- .../Authentification/AccountAuthInfo.swift | 13 --- .../Authentification/AccountAuthResult.swift | 11 --- .../SignInWithAppleNonce.swift | 12 --- .../SignInWithAppleResult.swift | 13 --- .../Domain/Cohabitant/CohabitantData.swift | 16 ---- .../Domain/Cohabitant/CohabitantMember.swift | 14 --- .../ImplAccountAuthClient.swift} | 40 +------- .../ImplAccountInfoClient.swift} | 19 +--- .../ImplAnalyticsClient.swift | 22 +++++ .../ImplCohabitantClient.swift | 29 ++++++ ...mplCohabitantPushNotificationClient.swift} | 9 +- .../ImplHouseworkClient.swift | 46 +++++++++ .../ImplNonceGenerationClient.swift} | 17 +--- .../ImplSignInWithAppleClient.swift | 19 ++++ .../SignInWithApple/SignInWithApple.swift | 1 + .../SignInWithAppleRequestFactory.swift | 1 + .../SignInWithAppleResultFactory.swift | 1 + homete/Resouces/Localizable.xcstrings | 9 -- homete/Views/Auth/Login/LoginView.swift | 3 +- .../Auth/Login/SignInUpWithAppleButton.swift | 1 + .../RegistrationAccountView.swift | 9 +- .../UserNameInputTextField.swift | 1 + .../PreviewUtil/HouseworkUtil.swift | 1 + homete/Views/HomeView/HomeView.swift | 1 + .../HouseworkItemPropertyListContent.swift | 1 + .../HouseworkApprovalView.swift | 1 + .../HouseworkBoardView.swift | 1 + .../SubViews/HouseBoardListRow.swift | 1 + .../SubViews/HouseworkBoardListContent.swift | 1 + .../HouseworkBoardSegmentedControl.swift | 1 + .../HouseworkDetailView.swift | 1 + .../HouseworkDetailActionContent.swift | 1 + .../HouseworkDetailItemListContent.swift | 1 + .../CohabitantRegistrationView.swift | 1 + .../CohabitantRegistrationSession.swift | 1 + ...ntRegistrationProcessingFollowerView.swift | 1 + ...habitantRegistrationProcessingLeader.swift | 1 + ...CohabitantRegistrationProcessingView.swift | 1 + .../CohabitantRegistrationPeersListView.swift | 1 + ...abitantRegistrationScanningStateView.swift | 1 + .../RegisterHouseworkView.swift | 3 +- homete/Views/RootView/LaunchStateProxy.swift | 1 + homete/Views/RootView/RootView.swift | 15 ++- homete/Views/SettingView/SettingView.swift | 5 +- .../SubViews/SettingMenuItemButton.swift | 1 + homete/Views/TabView/AppTabView.swift | 5 +- .../Alert/DomainErrorAlertContent.swift | 1 + .../Navigation/AppNavigationElement.swift | 2 + 97 files changed, 1020 insertions(+), 773 deletions(-) create mode 100644 LocalPackage/Sources/HometeDomain/Account/Account.swift rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Account/AccountStore.swift (75%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Account/LoginContext.swift (51%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Account/UserName.swift (54%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/AnalyticsLog/AnalyticsEvent.swift (65%) create mode 100644 LocalPackage/Sources/HometeDomain/Authentification/AccountAuthInfo.swift create mode 100644 LocalPackage/Sources/HometeDomain/Authentification/AccountAuthResult.swift rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Authentification/AccountAuthStore.swift (73%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Authentification/AccountListenerStream.swift (72%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Authentification/LaunchState.swift (84%) create mode 100644 LocalPackage/Sources/HometeDomain/Authentification/SignInWithAppleNonce.swift create mode 100644 LocalPackage/Sources/HometeDomain/Authentification/SignInWithAppleResult.swift create mode 100644 LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantData.swift create mode 100644 LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantMember.swift rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Cohabitant/CohabitantMemberList.swift (66%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Cohabitant/CohabitantRegistration/CohabitantRegistrationMessage.swift (74%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Cohabitant/CohabitantRegistration/CohabitantRegistrationRole.swift (73%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Cohabitant/CohabitantRegistration/CohabitantRegistrationState.swift (86%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Cohabitant/CohabitantRegistration/ConfirmedRegistrationPeers.swift (64%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Cohabitant/CohabitantStore.swift (73%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Cohabitant/Housework/DailyHouseworkList.swift (55%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Cohabitant/Housework/DailyHouseworkMetaData.swift (55%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Cohabitant/Housework/HouseworkBoardList.swift (64%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Cohabitant/Housework/HouseworkHistoryList.swift (76%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Cohabitant/Housework/HouseworkIndexedDate.swift (81%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Cohabitant/Housework/HouseworkItem.swift (63%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Cohabitant/Housework/HouseworkListStore.swift (81%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Cohabitant/Housework/HouseworkState.swift (58%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Cohabitant/Housework/StoredAllHouseworkList.swift (67%) create mode 100644 LocalPackage/Sources/HometeDomain/Dependencies/AccountAuthClient.swift create mode 100644 LocalPackage/Sources/HometeDomain/Dependencies/AccountInfoClient.swift create mode 100644 LocalPackage/Sources/HometeDomain/Dependencies/AnalyticsClient.swift create mode 100644 LocalPackage/Sources/HometeDomain/Dependencies/CohabitantClient.swift create mode 100644 LocalPackage/Sources/HometeDomain/Dependencies/CohabitantPushNotificationClient.swift rename {homete/Model => LocalPackage/Sources/HometeDomain}/Dependencies/DependencyClient.swift (79%) create mode 100644 LocalPackage/Sources/HometeDomain/Dependencies/HouseworkClient.swift create mode 100644 LocalPackage/Sources/HometeDomain/Dependencies/NonceGenerationClient.swift create mode 100644 LocalPackage/Sources/HometeDomain/Dependencies/SignInWithAppleClient.swift rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/DomainError.swift (77%) delete mode 100644 LocalPackage/Sources/HometeDomain/LocalPackage.swift rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/PushNotificationContent.swift (80%) rename {homete/Model/Domain => LocalPackage/Sources/HometeDomain}/Setting/SettingMenuItem.swift (76%) delete mode 100644 homete/Model/Dependencies/AnalyticsClient.swift delete mode 100644 homete/Model/Dependencies/CohabitantClient.swift delete mode 100644 homete/Model/Dependencies/HouseworkClient.swift delete mode 100644 homete/Model/Dependencies/SignInWithAppleClient.swift delete mode 100644 homete/Model/Domain/Account/Account.swift delete mode 100644 homete/Model/Domain/Authentification/AccountAuthInfo.swift delete mode 100644 homete/Model/Domain/Authentification/AccountAuthResult.swift delete mode 100644 homete/Model/Domain/Authentification/SignInWithAppleNonce.swift delete mode 100644 homete/Model/Domain/Authentification/SignInWithAppleResult.swift delete mode 100644 homete/Model/Domain/Cohabitant/CohabitantData.swift delete mode 100644 homete/Model/Domain/Cohabitant/CohabitantMember.swift rename homete/Model/{Dependencies/AccountAuthClient.swift => ImplDependencies/ImplAccountAuthClient.swift} (56%) rename homete/Model/{Dependencies/AccountInfoClient.swift => ImplDependencies/ImplAccountInfoClient.swift} (52%) create mode 100644 homete/Model/ImplDependencies/ImplAnalyticsClient.swift create mode 100644 homete/Model/ImplDependencies/ImplCohabitantClient.swift rename homete/Model/{Dependencies/CohabitantPushNotificationClient.swift => ImplDependencies/ImplCohabitantPushNotificationClient.swift} (62%) create mode 100644 homete/Model/ImplDependencies/ImplHouseworkClient.swift rename homete/Model/{Dependencies/NonceGenerationClient.swift => ImplDependencies/ImplNonceGenerationClient.swift} (76%) create mode 100644 homete/Model/ImplDependencies/ImplSignInWithAppleClient.swift diff --git a/LocalPackage/Package.swift b/LocalPackage/Package.swift index 33e32bb3..51e6fa9a 100644 --- a/LocalPackage/Package.swift +++ b/LocalPackage/Package.swift @@ -5,6 +5,7 @@ import PackageDescription let package = Package( name: "LocalPackage", + platforms: [.iOS(.v17)], products: [ .library( name: "HometeDomain", diff --git a/LocalPackage/Sources/HometeDomain/Account/Account.swift b/LocalPackage/Sources/HometeDomain/Account/Account.swift new file mode 100644 index 00000000..e7bfc52b --- /dev/null +++ b/LocalPackage/Sources/HometeDomain/Account/Account.swift @@ -0,0 +1,29 @@ +// +// Account.swift +// homete +// +// Created by 佐藤汰一 on 2025/08/03. +// + +public struct Account: Equatable, Codable, Sendable { + + public let id: String + public let userName: String + public let fcmToken: String? + public let cohabitantId: String? + + public init(id: String, userName: String, fcmToken: String?, cohabitantId: String?) { + self.id = id + self.userName = userName + self.fcmToken = fcmToken + self.cohabitantId = cohabitantId + } +} + +public extension Account { + + static func initial(auth: AccountAuthResult, userName: UserName, fcmToken: String?) -> Self { + + return .init(id: auth.id, userName: userName.value, fcmToken: fcmToken, cohabitantId: nil) + } +} diff --git a/homete/Model/Domain/Account/AccountStore.swift b/LocalPackage/Sources/HometeDomain/Account/AccountStore.swift similarity index 75% rename from homete/Model/Domain/Account/AccountStore.swift rename to LocalPackage/Sources/HometeDomain/Account/AccountStore.swift index 46de8488..758530f8 100644 --- a/homete/Model/Domain/Account/AccountStore.swift +++ b/LocalPackage/Sources/HometeDomain/Account/AccountStore.swift @@ -9,54 +9,54 @@ import SwiftUI @MainActor @Observable -final class AccountStore { - - private(set) var account: Account? - +public final class AccountStore { + + public private(set) var account: Account? + private let accountInfoClient: AccountInfoClient - - init( - appDependencies: AppDependencies, + + public init( + accountInfoClient: AccountInfoClient = .previewValue, account: Account? = nil ) { - - accountInfoClient = appDependencies.accountInfoClient + + self.accountInfoClient = accountInfoClient self.account = account } - + /// アカウント情報をロードし、オンメモリにキャッシュする /// - Returns: ロードしたアカウント情報を返す(アカウントがない場合はnilを返す) @discardableResult - func load(_ auth: AccountAuthResult) async -> Account? { - + public func load(_ auth: AccountAuthResult) async -> Account? { + do { - + account = try await accountInfoClient.fetch(auth.id) } catch { - + print("failed to fetch account info: \(error)") } - + return account } - - func registerAccount(auth: AccountAuthResult, userName: UserName) async throws -> Account { - + + public func registerAccount(auth: AccountAuthResult, userName: UserName) async throws -> Account { + let newAccount = Account(id: auth.id, userName: userName.value, fcmToken: nil, cohabitantId: nil) try await accountInfoClient.insertOrUpdate(newAccount) account = newAccount return newAccount } - - func updateFcmTokenIfNeeded(_ fcmToken: String) async { - + + public func updateFcmTokenIfNeeded(_ fcmToken: String) async { + // 保持しているFCMトークンと異なるFCMトークンに変わった場合は、アカウント情報も新しいトークンに更新する guard let account, account.fcmToken != fcmToken else { return } - + do { - + let updatedAccount = Account( id: account.id, userName: account.userName, @@ -67,18 +67,18 @@ final class AccountStore { self.account = updatedAccount } catch { - + print("failed to update fcmToken: \(error)") } } - - func registerCohabitantId(_ cohabitantId: String) async throws { - + + public func registerCohabitantId(_ cohabitantId: String) async throws { + guard let account else { - + preconditionFailure("Not found account.") } - + let updatedAccount = Account( id: account.id, userName: account.userName, diff --git a/homete/Model/Domain/Account/LoginContext.swift b/LocalPackage/Sources/HometeDomain/Account/LoginContext.swift similarity index 51% rename from homete/Model/Domain/Account/LoginContext.swift rename to LocalPackage/Sources/HometeDomain/Account/LoginContext.swift index 7b03dc58..57c64f0c 100644 --- a/homete/Model/Domain/Account/LoginContext.swift +++ b/LocalPackage/Sources/HometeDomain/Account/LoginContext.swift @@ -7,15 +7,19 @@ import SwiftUI -struct LoginContext: Equatable { - - let account: Account - +public struct LoginContext: Equatable { + + public let account: Account + /// パートナー登録済みかどうか - var hasCohabitant: Bool { account.cohabitantId != nil } + public var hasCohabitant: Bool { account.cohabitantId != nil } + + public init(account: Account) { + self.account = account + } } -extension EnvironmentValues { - +public extension EnvironmentValues { + @Entry var loginContext = LoginContext(account: .init(id: "", userName: "", fcmToken: nil, cohabitantId: nil)) } diff --git a/homete/Model/Domain/Account/UserName.swift b/LocalPackage/Sources/HometeDomain/Account/UserName.swift similarity index 54% rename from homete/Model/Domain/Account/UserName.swift rename to LocalPackage/Sources/HometeDomain/Account/UserName.swift index efa58f02..0c6ce812 100644 --- a/homete/Model/Domain/Account/UserName.swift +++ b/LocalPackage/Sources/HometeDomain/Account/UserName.swift @@ -5,20 +5,24 @@ // Created by 佐藤汰一 on 2025/12/27. // -struct UserName { - var value = "" - +public struct UserName { + public var value = "" + private static let limitCharacters = 10 - - var remainingCharacters: Int { + + public var remainingCharacters: Int { return Self.limitCharacters - value.count } - - var isOverLimitCharacters: Bool { + + public var isOverLimitCharacters: Bool { return Self.limitCharacters < value.count } - - var canRegistration: Bool { + + public var canRegistration: Bool { return !value.isEmpty && !isOverLimitCharacters } + + public init(value: String = "") { + self.value = value + } } diff --git a/homete/Model/Domain/AnalyticsLog/AnalyticsEvent.swift b/LocalPackage/Sources/HometeDomain/AnalyticsLog/AnalyticsEvent.swift similarity index 65% rename from homete/Model/Domain/AnalyticsLog/AnalyticsEvent.swift rename to LocalPackage/Sources/HometeDomain/AnalyticsLog/AnalyticsEvent.swift index ec4d480b..03d6b11a 100644 --- a/homete/Model/Domain/AnalyticsLog/AnalyticsEvent.swift +++ b/LocalPackage/Sources/HometeDomain/AnalyticsLog/AnalyticsEvent.swift @@ -5,32 +5,37 @@ // Created by 佐藤汰一 on 2025/08/09. // -struct AnalyticsEvent: Equatable { - - let name: String - let parameters: [String: String] +public struct AnalyticsEvent: Equatable { + + public let name: String + public let parameters: [String: String] + + public init(name: String, parameters: [String: String]) { + self.name = name + self.parameters = parameters + } } -extension AnalyticsEvent { - +public extension AnalyticsEvent { + static func login(isSuccess: Bool) -> Self { - + return .init( name: "login", parameters: ["isSuccess": "\(isSuccess)"] ) } - + static func logout() -> Self { - + return .init( name: "logout", parameters: [:] ) } - + static func deleteAccount() -> Self { - + return .init( name: "delete_account", parameters: [:] diff --git a/LocalPackage/Sources/HometeDomain/Authentification/AccountAuthInfo.swift b/LocalPackage/Sources/HometeDomain/Authentification/AccountAuthInfo.swift new file mode 100644 index 00000000..a8a47220 --- /dev/null +++ b/LocalPackage/Sources/HometeDomain/Authentification/AccountAuthInfo.swift @@ -0,0 +1,18 @@ +// +// AccountAuthInfo.swift +// homete +// +// Created by 佐藤汰一 on 2025/12/31. +// + +public struct AccountAuthInfo: Equatable, Sendable { + public let result: AccountAuthResult? + public let alreadyLoadedAtInitiate: Bool + + public static let initial = AccountAuthInfo(result: nil, alreadyLoadedAtInitiate: false) + + public init(result: AccountAuthResult?, alreadyLoadedAtInitiate: Bool) { + self.result = result + self.alreadyLoadedAtInitiate = alreadyLoadedAtInitiate + } +} diff --git a/LocalPackage/Sources/HometeDomain/Authentification/AccountAuthResult.swift b/LocalPackage/Sources/HometeDomain/Authentification/AccountAuthResult.swift new file mode 100644 index 00000000..d6504897 --- /dev/null +++ b/LocalPackage/Sources/HometeDomain/Authentification/AccountAuthResult.swift @@ -0,0 +1,15 @@ +// +// AccountAuthResult.swift +// homete +// +// Created by 佐藤汰一 on 2025/08/09. +// + +public struct AccountAuthResult: Equatable, Sendable { + + public let id: String + + public init(id: String) { + self.id = id + } +} diff --git a/homete/Model/Domain/Authentification/AccountAuthStore.swift b/LocalPackage/Sources/HometeDomain/Authentification/AccountAuthStore.swift similarity index 73% rename from homete/Model/Domain/Authentification/AccountAuthStore.swift rename to LocalPackage/Sources/HometeDomain/Authentification/AccountAuthStore.swift index 8b7b4659..607d7ff0 100644 --- a/homete/Model/Domain/Authentification/AccountAuthStore.swift +++ b/LocalPackage/Sources/HometeDomain/Authentification/AccountAuthStore.swift @@ -9,92 +9,95 @@ import SwiftUI @MainActor @Observable -final class AccountAuthStore { - - private(set) var currentAuth: AccountAuthInfo - +public final class AccountAuthStore { + + public private(set) var currentAuth: AccountAuthInfo + private let accountAuthClient: AccountAuthClient private let analyticsClient: AnalyticsClient private let signInWithAppleClient: SignInWithAppleClient private let nonceGenerationClient: NonceGenerationClient private let listener: AccountListenerStream - - init( - appDependencies: AppDependencies, + + public init( + accountAuthClient: AccountAuthClient = .previewValue, + analyticsClient: AnalyticsClient = .previewValue, + signInWithAppleClient: SignInWithAppleClient = .previewValue, + nonceGenerationClient: NonceGenerationClient = .previewValue, currentAuth: AccountAuthInfo = .initial ) { - - accountAuthClient = appDependencies.accountAuthClient - analyticsClient = appDependencies.analyticsClient - signInWithAppleClient = appDependencies.signInWithAppleClient - nonceGenerationClient = appDependencies.nonceGeneratorClient + + self.accountAuthClient = accountAuthClient + self.analyticsClient = analyticsClient + self.signInWithAppleClient = signInWithAppleClient + self.nonceGenerationClient = nonceGenerationClient self.currentAuth = currentAuth - + listener = accountAuthClient.makeListener() - + Task { - + await listen() } } - - func login(_ signInResult: SignInWithAppleResult) async throws { - + + public func login(_ signInResult: SignInWithAppleResult) async throws { + do { - + let authInfo = try await accountAuthClient.signIn(signInResult.tokenId, signInResult.nonce) analyticsClient.setId(authInfo.id) analyticsClient.log(.login(isSuccess: true)) } catch { - + analyticsClient.log(.login(isSuccess: false)) throw error } } - - func logOut() { - + + public func logOut() { + currentAuth = .init(result: nil, alreadyLoadedAtInitiate: true) analyticsClient.log(.logout()) - + do { - + try accountAuthClient.signOut() } catch { - + print("occurred error: \(error)") } } - - func deleteAccount() async throws { - + + public func deleteAccount() async throws { + // 1. 再認証 let nonce = nonceGenerationClient() let signInWithAppleResult = try await signInWithAppleClient.reauthentication(nonce) try await accountAuthClient.reauthenticateWithApple(signInWithAppleResult) - + // 2. アカウント削除 try await accountAuthClient.deleteAccount() - + // 3. トークンRevoke try await accountAuthClient.revokeAppleToken(signInWithAppleResult.authorizationCode) - + // 4. ログ記録 analyticsClient.log(.deleteAccount()) - + // 5. 状態更新 currentAuth = .init(result: nil, alreadyLoadedAtInitiate: true) } } private extension AccountAuthStore { - + func listen() async { - + for await value in listener.values { - + currentAuth = .init(result: value, alreadyLoadedAtInitiate: true) print("currentAuth snapshot: \(String(describing: currentAuth))") } diff --git a/homete/Model/Domain/Authentification/AccountListenerStream.swift b/LocalPackage/Sources/HometeDomain/Authentification/AccountListenerStream.swift similarity index 72% rename from homete/Model/Domain/Authentification/AccountListenerStream.swift rename to LocalPackage/Sources/HometeDomain/Authentification/AccountListenerStream.swift index 743b3f71..3db9c180 100644 --- a/homete/Model/Domain/Authentification/AccountListenerStream.swift +++ b/LocalPackage/Sources/HometeDomain/Authentification/AccountListenerStream.swift @@ -7,25 +7,25 @@ import Foundation -struct AccountListenerStream { - - let values: AsyncStream - let listenerToken: any NSObjectProtocol +public struct AccountListenerStream { + + public let values: AsyncStream + public let listenerToken: any NSObjectProtocol private let continuation: AsyncStream.Continuation - - init( + + public init( values: AsyncStream, listenerToken: any NSObjectProtocol, continuation: AsyncStream.Continuation ) { - + self.values = values self.listenerToken = listenerToken self.continuation = continuation } - - func stopListening() { - + + public func stopListening() { + continuation.finish() } } diff --git a/homete/Model/Domain/Authentification/LaunchState.swift b/LocalPackage/Sources/HometeDomain/Authentification/LaunchState.swift similarity index 84% rename from homete/Model/Domain/Authentification/LaunchState.swift rename to LocalPackage/Sources/HometeDomain/Authentification/LaunchState.swift index 73625e46..c88f346f 100644 --- a/homete/Model/Domain/Authentification/LaunchState.swift +++ b/LocalPackage/Sources/HometeDomain/Authentification/LaunchState.swift @@ -5,8 +5,8 @@ // Created by 佐藤汰一 on 2025/09/02. // -enum LaunchState: Equatable { - +public enum LaunchState: Equatable { + /// 起動中 case launching /// 仮ログイン(アカウント未作成) @@ -15,9 +15,9 @@ enum LaunchState: Equatable { case loggedIn(context: LoginContext) /// 未ログイン case notLoggedIn - - var isLoggedIn: Bool { - + + public var isLoggedIn: Bool { + if case .loggedIn = self { return true } return false } diff --git a/LocalPackage/Sources/HometeDomain/Authentification/SignInWithAppleNonce.swift b/LocalPackage/Sources/HometeDomain/Authentification/SignInWithAppleNonce.swift new file mode 100644 index 00000000..da647668 --- /dev/null +++ b/LocalPackage/Sources/HometeDomain/Authentification/SignInWithAppleNonce.swift @@ -0,0 +1,17 @@ +// +// SignInWithAppleNonce.swift +// homete +// +// Created by 佐藤汰一 on 2025/08/04. +// + +public struct SignInWithAppleNonce: Equatable, Sendable { + + public let original: String + public let sha256: String + + public init(original: String, sha256: String) { + self.original = original + self.sha256 = sha256 + } +} diff --git a/LocalPackage/Sources/HometeDomain/Authentification/SignInWithAppleResult.swift b/LocalPackage/Sources/HometeDomain/Authentification/SignInWithAppleResult.swift new file mode 100644 index 00000000..94fd6670 --- /dev/null +++ b/LocalPackage/Sources/HometeDomain/Authentification/SignInWithAppleResult.swift @@ -0,0 +1,19 @@ +// +// SignInWithAppleResult.swift +// homete +// +// Created by 佐藤汰一 on 2025/08/09. +// + +public struct SignInWithAppleResult: Equatable, Sendable { + + public let tokenId: String + public let nonce: String + public let authorizationCode: String + + public init(tokenId: String, nonce: String, authorizationCode: String) { + self.tokenId = tokenId + self.nonce = nonce + self.authorizationCode = authorizationCode + } +} diff --git a/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantData.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantData.swift new file mode 100644 index 00000000..c035c421 --- /dev/null +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantData.swift @@ -0,0 +1,21 @@ +// +// CohabitantData.swift +// homete +// +// Created by 佐藤汰一 on 2026/01/04. +// + +public struct CohabitantData: Codable, Sendable { + + public static let idField = "id" + + /// 家族グループのID + public let id: String + /// 参加しているメンバーのユーザーID + public let members: [String] + + public init(id: String, members: [String]) { + self.id = id + self.members = members + } +} diff --git a/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantMember.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantMember.swift new file mode 100644 index 00000000..d44c7522 --- /dev/null +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantMember.swift @@ -0,0 +1,19 @@ +// +// CohabitantMember.swift +// homete +// +// Created by 佐藤汰一 on 2026/01/04. +// + +public struct CohabitantMember: Equatable, Hashable { + + /// メンバーのユーザーID + public let id: String + /// メンバーのユーザー名 + public let userName: String + + public init(id: String, userName: String) { + self.id = id + self.userName = userName + } +} diff --git a/homete/Model/Domain/Cohabitant/CohabitantMemberList.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantMemberList.swift similarity index 66% rename from homete/Model/Domain/Cohabitant/CohabitantMemberList.swift rename to LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantMemberList.swift index 6f1591b4..28cd4b8b 100644 --- a/homete/Model/Domain/Cohabitant/CohabitantMemberList.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantMemberList.swift @@ -5,29 +5,33 @@ // Created by 佐藤汰一 on 2026/01/04. // -struct CohabitantMemberList { - - private(set) var value: Set - - mutating func insert(_ element: CohabitantMember) { - +public struct CohabitantMemberList { + + public private(set) var value: Set + + public init(value: Set) { + self.value = value + } + + public mutating func insert(_ element: CohabitantMember) { + value.insert(element) } - + /// 与えられたユーザーID配列の中から、まだvalueに存在しないユーザーIDのみを返します。 /// - Parameter userIds: 追加するユーザーIDの候補の配列 /// - Returns: 追加が必要なユーザーIDの配列 - func missingMemberIds(from userIds: Set) -> Set { - + public func missingMemberIds(from userIds: Set) -> Set { + let existingIds = value.map(\.id) return userIds.filter { !existingIds.contains($0) } } - + /// 現在の家族グループの中から指定のユーザーIDの名前を取得する /// - Parameter id: ユーザーID /// - Returns: ユーザー名 - func userName(_ id: String) -> String? { - + public func userName(_ id: String) -> String? { + return value.first { $0.id == id }?.userName } } diff --git a/homete/Model/Domain/Cohabitant/CohabitantRegistration/CohabitantRegistrationMessage.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/CohabitantRegistrationMessage.swift similarity index 74% rename from homete/Model/Domain/Cohabitant/CohabitantRegistration/CohabitantRegistrationMessage.swift rename to LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/CohabitantRegistrationMessage.swift index 8f99b3e7..0d2507d0 100644 --- a/homete/Model/Domain/Cohabitant/CohabitantRegistration/CohabitantRegistrationMessage.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/CohabitantRegistrationMessage.swift @@ -7,12 +7,12 @@ import Foundation -struct CohabitantRegistrationMessage: Codable, Equatable { - - let type: CommunicateType - - enum CommunicateType: Codable, Equatable { - +public struct CohabitantRegistrationMessage: Codable, Equatable { + + public let type: CommunicateType + + public enum CommunicateType: Codable, Equatable { + /// 登録を行うメンバーが確定したかどうかの確認 case fixedMember(isOK: Bool) /// アカウントIDの共有 @@ -22,62 +22,66 @@ struct CohabitantRegistrationMessage: Codable, Equatable { /// 登録完了したかどうかの確認 case complete } - + /// 登録メンバーが確定したかどうか - var isFixedMember: Bool? { - + public var isFixedMember: Bool? { + guard case .fixedMember(let isOK) = type else { return nil } return isOK } - + /// メンバーの役割 - var memberRole: CohabitantRegistrationRole? { - + public var memberRole: CohabitantRegistrationRole? { + guard case .preRegistration(let role) = type else { - + return nil } return role } - + /// 同居人ID - var cohabitantId: String? { - + public var cohabitantId: String? { + guard case .shareCohabitantId(let id) = type else { - + return nil } return id } - + /// 登録処理が完了したかどうか - var isComplete: Bool? { - + public var isComplete: Bool? { + guard case .complete = type else { - + return nil } return true } - - func encodedData() -> Data { - + + public func encodedData() -> Data { + guard let encodedData = try? JSONEncoder().encode(self) else { - + preconditionFailure("Invalid message structure(\(self)).") } return encodedData } + + public init(type: CommunicateType) { + self.type = type + } } -extension CohabitantRegistrationMessage { - +public extension CohabitantRegistrationMessage { + init(_ data: Data) { - + guard let message = try? JSONDecoder().decode(CohabitantRegistrationMessage.self, from: data) else { - + preconditionFailure("Invalid data(\(data)).") } self = message diff --git a/homete/Model/Domain/Cohabitant/CohabitantRegistration/CohabitantRegistrationRole.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/CohabitantRegistrationRole.swift similarity index 73% rename from homete/Model/Domain/Cohabitant/CohabitantRegistration/CohabitantRegistrationRole.swift rename to LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/CohabitantRegistrationRole.swift index fd95989f..8c8f71d0 100644 --- a/homete/Model/Domain/Cohabitant/CohabitantRegistration/CohabitantRegistrationRole.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/CohabitantRegistrationRole.swift @@ -5,21 +5,21 @@ // Created by 佐藤汰一 on 2025/08/30. // -enum CohabitantRegistrationRole: Codable, Equatable { - +public enum CohabitantRegistrationRole: Codable, Equatable { + /// フォロワーはアカウントIDを渡す case follower(accountId: String) case lead - - var isLeader: Bool { - + + public var isLeader: Bool { + return self == .lead } - - var accountId: String { - + + public var accountId: String { + guard case let .follower(accountId) = self else { - + preconditionFailure("Please pre checking role is follower.") } return accountId diff --git a/homete/Model/Domain/Cohabitant/CohabitantRegistration/CohabitantRegistrationState.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/CohabitantRegistrationState.swift similarity index 86% rename from homete/Model/Domain/Cohabitant/CohabitantRegistration/CohabitantRegistrationState.swift rename to LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/CohabitantRegistrationState.swift index c14cf40c..16d813d9 100644 --- a/homete/Model/Domain/Cohabitant/CohabitantRegistration/CohabitantRegistrationState.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/CohabitantRegistrationState.swift @@ -5,8 +5,8 @@ // Created by 佐藤汰一 on 2025/08/26. // -enum CohabitantRegistrationState: Equatable { - +public enum CohabitantRegistrationState: Equatable { + /// 同居人となるメンバーを探している状態 case scanning /// 同居人を登録する処理を行っている状態 diff --git a/homete/Model/Domain/Cohabitant/CohabitantRegistration/ConfirmedRegistrationPeers.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/ConfirmedRegistrationPeers.swift similarity index 64% rename from homete/Model/Domain/Cohabitant/CohabitantRegistration/ConfirmedRegistrationPeers.swift rename to LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/ConfirmedRegistrationPeers.swift index fbec8f12..247bab9c 100644 --- a/homete/Model/Domain/Cohabitant/CohabitantRegistration/ConfirmedRegistrationPeers.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/ConfirmedRegistrationPeers.swift @@ -7,22 +7,22 @@ import MultipeerConnectivity -struct ConfirmedRegistrationPeers: Equatable { - +public struct ConfirmedRegistrationPeers: Equatable { + private var peers: Set - - init(peers: Set) { - + + public init(peers: Set) { + self.peers = peers } - - mutating func addPeer(_ peer: MCPeerID) { - + + public mutating func addPeer(_ peer: MCPeerID) { + peers.insert(peer) } - - func isLeadPeer(connectedPeers: Set, myPeerID: MCPeerID) -> Bool? { - + + public func isLeadPeer(connectedPeers: Set, myPeerID: MCPeerID) -> Bool? { + guard peers == connectedPeers else { return nil } let firstPeerID = ([myPeerID] + connectedPeers) .sorted { $0.displayName < $1.displayName }.first diff --git a/homete/Model/Domain/Cohabitant/CohabitantStore.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantStore.swift similarity index 73% rename from homete/Model/Domain/Cohabitant/CohabitantStore.swift rename to LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantStore.swift index 08d56c7a..1d541150 100644 --- a/homete/Model/Domain/Cohabitant/CohabitantStore.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantStore.swift @@ -9,66 +9,68 @@ import SwiftUI @MainActor @Observable -final class CohabitantStore { - - private(set) var members: CohabitantMemberList +public final class CohabitantStore { + + public private(set) var members: CohabitantMemberList private var listenerTask: Task? - + private let cohabitantListenerKey = "cohabitantListenerKey" - + // MARK: Dependencies - + private let cohabitantClient: CohabitantClient private let accountInfoClient: AccountInfoClient - - init( + + public init( members: CohabitantMemberList = .init(value: []), - appDependencies: AppDependencies = .previewValue + cohabitantClient: CohabitantClient = .previewValue, + accountInfoClient: AccountInfoClient = .previewValue ) { + self.members = members - cohabitantClient = appDependencies.cohabitantClient - accountInfoClient = appDependencies.accountInfoClient + self.cohabitantClient = cohabitantClient + self.accountInfoClient = accountInfoClient } - - func addSnapshotListenerIfNeeded(_ cohabitantId: String) async { - + + public func addSnapshotListenerIfNeeded(_ cohabitantId: String) async { + // すでに監視中の場合は何もしない if listenerTask != nil { return } - + let stream = await cohabitantClient.addSnapshotListener( cohabitantListenerKey, cohabitantId ) - + listenerTask = Task { - + for await cohabitantDataList in stream { - + guard let cohabitantData = cohabitantDataList.first else { continue } - + for member in self.members.missingMemberIds(from: .init(cohabitantData.members)) { - + do { - + guard let account = try await accountInfoClient.fetch(member) else { - + print("Not found account(cohabitantId: \(cohabitantId), userId: \(member))") continue } members.insert(.init(id: member, userName: account.userName)) } catch { - + print("error occurred: \(error)") } } } - + print("finish listening cohabitant snapshot.") } } - - func removeSnapshotListener() async { - + + public func removeSnapshotListener() async { + listenerTask = nil await cohabitantClient.removeSnapshotListener(cohabitantListenerKey) } diff --git a/homete/Model/Domain/Cohabitant/Housework/DailyHouseworkList.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/DailyHouseworkList.swift similarity index 55% rename from homete/Model/Domain/Cohabitant/Housework/DailyHouseworkList.swift rename to LocalPackage/Sources/HometeDomain/Cohabitant/Housework/DailyHouseworkList.swift index c4a5d194..ef459b50 100644 --- a/homete/Model/Domain/Cohabitant/Housework/DailyHouseworkList.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/DailyHouseworkList.swift @@ -7,29 +7,34 @@ import Foundation -struct DailyHouseworkList: Equatable, Sendable { - - let items: [HouseworkItem] - let metaData: DailyHouseworkMetaData - - static func makeInitialValue( +public struct DailyHouseworkList: Equatable, Sendable { + + public let items: [HouseworkItem] + public let metaData: DailyHouseworkMetaData + + public static func makeInitialValue( selectedDate: Date, items: [HouseworkItem], calendar: Calendar ) -> Self { - + return .init( items: items, metaData: .init(selectedDate: selectedDate, calendar: calendar) ) } - + /// この日付の家事情報がすでに登録済みであること - var isRegistered: Bool { !items.isEmpty } - + public var isRegistered: Bool { !items.isEmpty } + /// すでに同じ家事が登録されているかどうか - func isAlreadyRegistered(_ item: HouseworkItem) -> Bool { - + public func isAlreadyRegistered(_ item: HouseworkItem) -> Bool { + return items.contains { $0.title == item.title } } + + public init(items: [HouseworkItem], metaData: DailyHouseworkMetaData) { + self.items = items + self.metaData = metaData + } } diff --git a/homete/Model/Domain/Cohabitant/Housework/DailyHouseworkMetaData.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/DailyHouseworkMetaData.swift similarity index 55% rename from homete/Model/Domain/Cohabitant/Housework/DailyHouseworkMetaData.swift rename to LocalPackage/Sources/HometeDomain/Cohabitant/Housework/DailyHouseworkMetaData.swift index 54d59c20..89936bb5 100644 --- a/homete/Model/Domain/Cohabitant/Housework/DailyHouseworkMetaData.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/DailyHouseworkMetaData.swift @@ -7,16 +7,21 @@ import Foundation -struct DailyHouseworkMetaData: Equatable { - - let indexedDate: HouseworkIndexedDate - let expiredAt: Date +public struct DailyHouseworkMetaData: Equatable, Sendable { + + public let indexedDate: HouseworkIndexedDate + public let expiredAt: Date + + public init(indexedDate: HouseworkIndexedDate, expiredAt: Date) { + self.indexedDate = indexedDate + self.expiredAt = expiredAt + } } -extension DailyHouseworkMetaData { - +public extension DailyHouseworkMetaData { + init(selectedDate: Date, calendar: Calendar) { - + let indexedDate = HouseworkIndexedDate(selectedDate, calendar: calendar) let expiredAt = calendar.date(byAdding: .month, value: 1, to: selectedDate) ?? selectedDate self.init(indexedDate: indexedDate, expiredAt: expiredAt) diff --git a/homete/Model/Domain/Cohabitant/Housework/HouseworkBoardList.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkBoardList.swift similarity index 64% rename from homete/Model/Domain/Cohabitant/Housework/HouseworkBoardList.swift rename to LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkBoardList.swift index abbf5571..7910eb9b 100644 --- a/homete/Model/Domain/Cohabitant/Housework/HouseworkBoardList.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkBoardList.swift @@ -7,24 +7,28 @@ import Foundation -struct HouseworkBoardList: Equatable { - - private(set) var items: [HouseworkItem] - - func items(matching state: HouseworkState) -> [HouseworkItem] { +public struct HouseworkBoardList: Equatable { + + public private(set) var items: [HouseworkItem] + + public func items(matching state: HouseworkState) -> [HouseworkItem] { print("HouseworkBoardList filtering(state: \(state), items: \(items))") return items.filter { $0.state == state } } + + public init(items: [HouseworkItem]) { + self.items = items + } } -extension HouseworkBoardList { - +public extension HouseworkBoardList { + init( dailyList: [DailyHouseworkList], selectedDate: Date, calendar: Calendar ) { - + items = dailyList .first { $0.metaData.indexedDate == .init(selectedDate, calendar: calendar) diff --git a/homete/Model/Domain/Cohabitant/Housework/HouseworkHistoryList.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkHistoryList.swift similarity index 76% rename from homete/Model/Domain/Cohabitant/Housework/HouseworkHistoryList.swift rename to LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkHistoryList.swift index 567d0d50..485dd5e9 100644 --- a/homete/Model/Domain/Cohabitant/Housework/HouseworkHistoryList.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkHistoryList.swift @@ -7,30 +7,34 @@ import Foundation -struct HouseworkHistoryList: Equatable { - - private(set) var items: [String] - +public struct HouseworkHistoryList: Equatable { + + public private(set) var items: [String] + + public init(items: [String]) { + self.items = items + } + /// 履歴があるかどうか - var hasHistory: Bool { !items.isEmpty } - + public var hasHistory: Bool { !items.isEmpty } + /// 引数に受け取った文字列が `items` に存在する場合、その要素を先頭へ移動します。 /// - Parameter value: 先頭へ移動したい要素の文字列 - mutating func moveToFrontIfExists(_ value: String) { - + public mutating func moveToFrontIfExists(_ value: String) { + guard let index = items.firstIndex(of: value) else { return } // 既に先頭なら何もしない if index == items.startIndex { return } let element = items.remove(at: index) items.insert(element, at: 0) } - + /// 引数に受け取った文字列を `items`の先頭に追加する /// - Parameter value: 新しい履歴文字 - mutating func addNewHistory(_ value: String) { - + public mutating func addNewHistory(_ value: String) { + guard items.contains(value) else { - + items.insert(value, at: 0) return } @@ -39,40 +43,40 @@ struct HouseworkHistoryList: Equatable { } extension HouseworkHistoryList: Codable { - + enum CodingKeys: String, CodingKey { case items } - - func encode(to encoder: any Encoder) throws { + + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(items, forKey: .items) } - - init(from decoder: any Decoder) throws { + + public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) items = try container.decode(Array.self, forKey: .items) } } extension HouseworkHistoryList: RawRepresentable { - - init?(rawValue: String) { - + + public init?(rawValue: String) { + guard let data = rawValue.data(using: .utf8), let decoded = try? JSONDecoder().decode(HouseworkHistoryList.self, from: data) else { - + return nil } self = decoded } - - var rawValue: String { - + + public var rawValue: String { + guard let data = try? JSONEncoder().encode(self), let jsonString = String(data: data, encoding: .utf8) else { - + return "" } return jsonString diff --git a/homete/Model/Domain/Cohabitant/Housework/HouseworkIndexedDate.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkIndexedDate.swift similarity index 81% rename from homete/Model/Domain/Cohabitant/Housework/HouseworkIndexedDate.swift rename to LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkIndexedDate.swift index 89756700..05fd06a3 100644 --- a/homete/Model/Domain/Cohabitant/Housework/HouseworkIndexedDate.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkIndexedDate.swift @@ -7,32 +7,36 @@ import Foundation -struct HouseworkIndexedDate: Equatable, Codable, Hashable { - - let value: String - - static func calcTargetPeriod( +public struct HouseworkIndexedDate: Equatable, Codable, Hashable, Sendable { + + public let value: String + + public init(value: String) { + self.value = value + } + + public static func calcTargetPeriod( anchorDate: Date, offsetDays: Int, calendar: Calendar ) -> [[String: String]] { - + let base = calendar.startOfDay(for: anchorDate) guard offsetDays >= 0 else { - + return [["value": HouseworkIndexedDate(base, calendar: calendar).value]] } // -offset ... +offset の範囲を列挙 return (-offsetDays...offsetDays).compactMap { delta in - + guard let date = calendar.date(byAdding: .day, value: delta, to: base) else { return nil } return ["value": HouseworkIndexedDate(date, calendar: calendar).value] } } } -extension HouseworkIndexedDate { - +public extension HouseworkIndexedDate { + init(_ date: Date, calendar: Calendar) { let formatStyle = Date.FormatStyle( date: .numeric, diff --git a/homete/Model/Domain/Cohabitant/Housework/HouseworkItem.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkItem.swift similarity index 63% rename from homete/Model/Domain/Cohabitant/Housework/HouseworkItem.swift rename to LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkItem.swift index 81310b69..c2d09522 100644 --- a/homete/Model/Domain/Cohabitant/Housework/HouseworkItem.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkItem.swift @@ -7,43 +7,69 @@ import Foundation -struct HouseworkItem: Identifiable, Equatable, Sendable, Hashable, Codable { - - let id: String +public struct HouseworkItem: Identifiable, Equatable, Sendable, Hashable, Codable { + + public let id: String /// 家事の日付情報 - let indexedDate: HouseworkIndexedDate + public let indexedDate: HouseworkIndexedDate /// 家事のタイトル - let title: String + public let title: String /// 家事ポイント - let point: Int + public let point: Int /// 家事ステータス - let state: HouseworkState + public let state: HouseworkState /// 実行者のユーザID - let executorId: String? + public let executorId: String? /// 実行日時 - let executedAt: Date? + public let executedAt: Date? /// 確認者のユーザID - let reviewerId: String? + public let reviewerId: String? /// 承認日時 - let approvedAt: Date? + public let approvedAt: Date? /// 確認コメント - let reviewerComment: String? + public let reviewerComment: String? /// 有効期限 - let expiredAt: Date - - var formattedIndexedDate: String { - + public let expiredAt: Date + + public init( + id: String, + indexedDate: HouseworkIndexedDate, + title: String, + point: Int, + state: HouseworkState, + executorId: String?, + executedAt: Date?, + reviewerId: String?, + approvedAt: Date?, + reviewerComment: String?, + expiredAt: Date + ) { + self.id = id + self.indexedDate = indexedDate + self.title = title + self.point = point + self.state = state + self.executorId = executorId + self.executedAt = executedAt + self.reviewerId = reviewerId + self.approvedAt = approvedAt + self.reviewerComment = reviewerComment + self.expiredAt = expiredAt + } + + public var formattedIndexedDate: String { + return indexedDate.value } - + /// レビュー可能かどうか - func canReview(ownUserId: String) -> Bool { - + public func canReview(ownUserId: String) -> Bool { + return executorId != ownUserId && state != .completed } - - func updatePendingApproval(at now: Date, changer: String) -> Self { - + + public func updatePendingApproval(at now: Date, changer: String) -> Self { + return .init( id: id, indexedDate: indexedDate, @@ -58,9 +84,9 @@ struct HouseworkItem: Identifiable, Equatable, Sendable, Hashable, Codable { expiredAt: expiredAt ) } - - func updateApproved(at now: Date, reviewer: String, comment: String) -> Self { - + + public func updateApproved(at now: Date, reviewer: String, comment: String) -> Self { + return .init( id: id, indexedDate: indexedDate, @@ -75,9 +101,9 @@ struct HouseworkItem: Identifiable, Equatable, Sendable, Hashable, Codable { expiredAt: expiredAt ) } - - func updateRejected(at now: Date, reviewer: String, comment: String) -> Self { - + + public func updateRejected(at now: Date, reviewer: String, comment: String) -> Self { + return .init( id: id, indexedDate: indexedDate, @@ -92,9 +118,9 @@ struct HouseworkItem: Identifiable, Equatable, Sendable, Hashable, Codable { expiredAt: expiredAt ) } - - func updateIncomplete() -> Self { - + + public func updateIncomplete() -> Self { + return .init( id: id, indexedDate: indexedDate, @@ -111,8 +137,8 @@ struct HouseworkItem: Identifiable, Equatable, Sendable, Hashable, Codable { } } -extension HouseworkItem { - +public extension HouseworkItem { + init( id: String, title: String, @@ -125,7 +151,7 @@ extension HouseworkItem { approvedAt: Date? = nil, reviewerComment: String? = nil ) { - + self.init( id: id, indexedDate: metaData.indexedDate, diff --git a/homete/Model/Domain/Cohabitant/Housework/HouseworkListStore.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkListStore.swift similarity index 81% rename from homete/Model/Domain/Cohabitant/Housework/HouseworkListStore.swift rename to LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkListStore.swift index 0675b857..ff173988 100644 --- a/homete/Model/Domain/Cohabitant/Housework/HouseworkListStore.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkListStore.swift @@ -9,52 +9,52 @@ import SwiftUI @MainActor @Observable -final class HouseworkListStore { - - private(set) var items: StoredAllHouseworkList +public final class HouseworkListStore { + + public private(set) var items: StoredAllHouseworkList private var cohabitantId: String - + private let houseworkClient: HouseworkClient private let cohabitantPushNotificationClient: CohabitantPushNotificationClient - + private let houseworkObserveKey = "houseworkObserveKey" - - init( + + public init( houseworkClient: HouseworkClient = .previewValue, cohabitantPushNotificationClient: CohabitantPushNotificationClient = .previewValue, items: [DailyHouseworkList] = [], cohabitantId: String = "" ) { - + self.houseworkClient = houseworkClient self.cohabitantPushNotificationClient = cohabitantPushNotificationClient self.items = .init(value: items) self.cohabitantId = cohabitantId } - - func loadHouseworkList(currentTime: Date, cohabitantId: String, calendar: Calendar) async { - + + public func loadHouseworkList(currentTime: Date, cohabitantId: String, calendar: Calendar) async { + self.cohabitantId = cohabitantId - + guard !cohabitantId.isEmpty else { - + await clear() return } - + await houseworkClient.removeListener(houseworkObserveKey) - + let houseworkListStream = await houseworkClient.snapshotListener( houseworkObserveKey, cohabitantId, currentTime, 3 ) - + Task { - + for await currentItems in houseworkListStream { - + items = StoredAllHouseworkList.makeMultiDateList( items: currentItems, calendar: calendar @@ -62,19 +62,19 @@ final class HouseworkListStore { } } } - - func register(_ newItem: HouseworkItem) async throws { - + + public func register(_ newItem: HouseworkItem) async throws { + try await houseworkClient.insertOrUpdateItem(newItem, cohabitantId) - + Task.detached { - + let notificationContent = PushNotificationContent.addNewHouseworkItem(newItem.title) try await self.cohabitantPushNotificationClient.send(self.cohabitantId, notificationContent) } } - - func requestReview(target: HouseworkItem, now: Date, executor: String) async throws { + + public func requestReview(target: HouseworkItem, now: Date, executor: String) async throws { try await updateAndSave(target: target) { $0.updatePendingApproval(at: now, changer: executor) @@ -82,8 +82,8 @@ final class HouseworkListStore { .requestReviewMessage(houseworkTitle: target.title) } } - - func approved(target: HouseworkItem, now: Date, reviwer: Account, comment: String) async throws { + + public func approved(target: HouseworkItem, now: Date, reviwer: Account, comment: String) async throws { try await updateAndSave(target: target) { $0.updateApproved(at: now, reviewer: reviwer.id, comment: comment) @@ -92,7 +92,7 @@ final class HouseworkListStore { } } - func rejected(target: HouseworkItem, now: Date, reviwer: Account, comment: String) async throws { + public func rejected(target: HouseworkItem, now: Date, reviwer: Account, comment: String) async throws { try await updateAndSave(target: target) { $0.updateRejected(at: now, reviewer: reviwer.id, comment: comment) @@ -101,15 +101,15 @@ final class HouseworkListStore { } } - func returnToIncomplete(target: HouseworkItem) async throws { + public func returnToIncomplete(target: HouseworkItem) async throws { try await updateAndSave(target: target) { $0.updateIncomplete() } } - - func remove(_ target: HouseworkItem) async throws { - + + public func remove(_ target: HouseworkItem) async throws { + try await houseworkClient.removeItem(target, cohabitantId) } } diff --git a/homete/Model/Domain/Cohabitant/Housework/HouseworkState.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkState.swift similarity index 58% rename from homete/Model/Domain/Cohabitant/Housework/HouseworkState.swift rename to LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkState.swift index 3ed3278d..176b1b1f 100644 --- a/homete/Model/Domain/Cohabitant/Housework/HouseworkState.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkState.swift @@ -5,11 +5,11 @@ // Created by 佐藤汰一 on 2025/09/06. // -enum HouseworkState: CaseIterable, Identifiable, Codable, Sendable { - +public enum HouseworkState: CaseIterable, Identifiable, Codable, Sendable { + case incomplete case pendingApproval case completed - - var id: Self { self } + + public var id: Self { self } } diff --git a/homete/Model/Domain/Cohabitant/Housework/StoredAllHouseworkList.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/StoredAllHouseworkList.swift similarity index 67% rename from homete/Model/Domain/Cohabitant/Housework/StoredAllHouseworkList.swift rename to LocalPackage/Sources/HometeDomain/Cohabitant/Housework/StoredAllHouseworkList.swift index 0758c3bc..50c7a737 100644 --- a/homete/Model/Domain/Cohabitant/Housework/StoredAllHouseworkList.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/StoredAllHouseworkList.swift @@ -7,15 +7,19 @@ import Foundation -struct StoredAllHouseworkList: Equatable, Sendable { - - private(set) var value: [DailyHouseworkList] - - static func makeMultiDateList(items: [HouseworkItem], calendar: Calendar) -> Self { - +public struct StoredAllHouseworkList: Equatable, Sendable { + + public private(set) var value: [DailyHouseworkList] + + public init(value: [DailyHouseworkList]) { + self.value = value + } + + public static func makeMultiDateList(items: [HouseworkItem], calendar: Calendar) -> Self { + let items: [DailyHouseworkList] = Dictionary(grouping: items) { $0.formattedIndexedDate } .compactMap { - + guard let firstItem = $1.first else { return nil } return .init( items: $1, @@ -24,17 +28,17 @@ struct StoredAllHouseworkList: Equatable, Sendable { } return .init(value: items) } - - func item(_ item: HouseworkItem) -> HouseworkItem? { - + + public func item(_ item: HouseworkItem) -> HouseworkItem? { + guard let targetDayList = value.first( where: { $0.metaData.indexedDate == item.indexedDate } ), let targetItem = targetDayList.items.first(where: { $0.id == item.id }) else { return nil } return targetItem } - - mutating func removeAll() { + + public mutating func removeAll() { value = [] } } diff --git a/LocalPackage/Sources/HometeDomain/Dependencies/AccountAuthClient.swift b/LocalPackage/Sources/HometeDomain/Dependencies/AccountAuthClient.swift new file mode 100644 index 00000000..3ea5fc8d --- /dev/null +++ b/LocalPackage/Sources/HometeDomain/Dependencies/AccountAuthClient.swift @@ -0,0 +1,45 @@ +// +// AccountListener.swift +// homete +// +// Created by 佐藤汰一 on 2025/08/03. +// + +import Foundation + +public struct AccountAuthClient: Sendable { + + public let signIn: @Sendable (String, String) async throws -> AccountAuthResult + public let signOut: @Sendable () throws -> Void + public let makeListener: @Sendable () -> AccountListenerStream + public let reauthenticateWithApple: @Sendable (_ signInWithAppleResult: SignInWithAppleResult) async throws -> Void + public let revokeAppleToken: @Sendable (_ authorizationCode: String) async throws -> Void + public let deleteAccount: @Sendable () async throws -> Void + + public init( + signIn: @Sendable @escaping (String, String) async throws -> AccountAuthResult = { _, _ in .init(id: "id") }, + signOut: @Sendable @escaping () throws -> Void = {}, + makeListener: @Sendable @escaping () -> AccountListenerStream = { + + let (stream, continuation) = AsyncStream.makeStream() + return AccountListenerStream(values: stream, + listenerToken: NSObject(), + continuation: continuation) + }, + reauthenticateWithApple: @Sendable @escaping (_: SignInWithAppleResult) async throws -> Void = { _ in }, + revokeAppleToken: @Sendable @escaping (_: String) async throws -> Void = { _ in }, + deleteAccount: @Sendable @escaping () async throws -> Void = {} + ) { + + self.signIn = signIn + self.signOut = signOut + self.makeListener = makeListener + self.reauthenticateWithApple = reauthenticateWithApple + self.revokeAppleToken = revokeAppleToken + self.deleteAccount = deleteAccount + } +} + +public extension AccountAuthClient { + static let previewValue = AccountAuthClient() +} diff --git a/LocalPackage/Sources/HometeDomain/Dependencies/AccountInfoClient.swift b/LocalPackage/Sources/HometeDomain/Dependencies/AccountInfoClient.swift new file mode 100644 index 00000000..e55da1d3 --- /dev/null +++ b/LocalPackage/Sources/HometeDomain/Dependencies/AccountInfoClient.swift @@ -0,0 +1,25 @@ +// +// AccountInfoClient.swift +// homete +// +// Created by 佐藤汰一 on 2025/08/09. +// + +public struct AccountInfoClient: Sendable { + + public let insertOrUpdate: @Sendable (Account) async throws -> Void + public let fetch: @Sendable (String) async throws -> Account? + + public init( + insertOrUpdate: @Sendable @escaping (Account) async throws -> Void = { _ in }, + fetch: @Sendable @escaping (String) async throws -> Account? = { _ in nil } + ) { + self.insertOrUpdate = insertOrUpdate + self.fetch = fetch + } +} + +public extension AccountInfoClient { + + static let previewValue: AccountInfoClient = .init() +} diff --git a/LocalPackage/Sources/HometeDomain/Dependencies/AnalyticsClient.swift b/LocalPackage/Sources/HometeDomain/Dependencies/AnalyticsClient.swift new file mode 100644 index 00000000..78e63773 --- /dev/null +++ b/LocalPackage/Sources/HometeDomain/Dependencies/AnalyticsClient.swift @@ -0,0 +1,26 @@ +// +// AnalyticsClient.swift +// homete +// +// Created by 佐藤汰一 on 2025/08/09. +// + +public struct AnalyticsClient: Sendable { + + public let setId: @Sendable (String) -> Void + public let log: @Sendable (AnalyticsEvent) -> Void + + public init( + setId: @Sendable @escaping (String) -> Void = { _ in }, + log: @Sendable @escaping (AnalyticsEvent) -> Void = { _ in } + ) { + + self.setId = setId + self.log = log + } +} + +public extension AnalyticsClient { + + static let previewValue = AnalyticsClient() +} diff --git a/LocalPackage/Sources/HometeDomain/Dependencies/CohabitantClient.swift b/LocalPackage/Sources/HometeDomain/Dependencies/CohabitantClient.swift new file mode 100644 index 00000000..f35d8629 --- /dev/null +++ b/LocalPackage/Sources/HometeDomain/Dependencies/CohabitantClient.swift @@ -0,0 +1,35 @@ +// +// CohabitantClient.swift +// homete +// +// Created by 佐藤汰一 on 2025/08/18. +// + +public struct CohabitantClient: Sendable { + + public let register: @Sendable (CohabitantData) async throws -> Void + public let addSnapshotListener: @Sendable ( + _ listenerId: String, + _ cohabitantId: String + ) async -> AsyncStream<[CohabitantData]> + public let removeSnapshotListener: @Sendable (_ listenerId: String) async -> Void + + public init( + register: @Sendable @escaping (CohabitantData) async throws -> Void = { _ in }, + addSnapshotListener: @Sendable @escaping ( + _: String, + _: String + ) async -> AsyncStream<[CohabitantData]> = { _, _ in .init { nil } }, + removeSnapshotListener: @Sendable @escaping (_ listenerId: String) async -> Void = { _ in } + ) { + + self.register = register + self.addSnapshotListener = addSnapshotListener + self.removeSnapshotListener = removeSnapshotListener + } +} + +public extension CohabitantClient { + + static let previewValue: CohabitantClient = .init() +} diff --git a/LocalPackage/Sources/HometeDomain/Dependencies/CohabitantPushNotificationClient.swift b/LocalPackage/Sources/HometeDomain/Dependencies/CohabitantPushNotificationClient.swift new file mode 100644 index 00000000..45418c88 --- /dev/null +++ b/LocalPackage/Sources/HometeDomain/Dependencies/CohabitantPushNotificationClient.swift @@ -0,0 +1,19 @@ +// +// CohabitantPushNotificationClient.swift +// homete +// +// Created by 佐藤汰一 on 2025/11/12. +// + +public struct CohabitantPushNotificationClient: Sendable { + public let send: @Sendable (_ id: String, _ content: PushNotificationContent) async throws -> Void + + public init(send: @Sendable @escaping (_ id: String, _ content: PushNotificationContent) async throws -> Void) { + self.send = send + } +} + +public extension CohabitantPushNotificationClient { + + static let previewValue: CohabitantPushNotificationClient = .init { _, _ in } +} diff --git a/homete/Model/Dependencies/DependencyClient.swift b/LocalPackage/Sources/HometeDomain/Dependencies/DependencyClient.swift similarity index 79% rename from homete/Model/Dependencies/DependencyClient.swift rename to LocalPackage/Sources/HometeDomain/Dependencies/DependencyClient.swift index d5205b35..1145595a 100644 --- a/homete/Model/Dependencies/DependencyClient.swift +++ b/LocalPackage/Sources/HometeDomain/Dependencies/DependencyClient.swift @@ -5,8 +5,8 @@ // Created by 佐藤汰一 on 2025/08/04. // -protocol DependencyClient: Sendable { - +public protocol DependencyClient: Sendable { + static var liveValue: Self { get } static var previewValue: Self { get } } diff --git a/LocalPackage/Sources/HometeDomain/Dependencies/HouseworkClient.swift b/LocalPackage/Sources/HometeDomain/Dependencies/HouseworkClient.swift new file mode 100644 index 00000000..9251b46e --- /dev/null +++ b/LocalPackage/Sources/HometeDomain/Dependencies/HouseworkClient.swift @@ -0,0 +1,50 @@ +// +// HouseworkClient.swift +// homete +// +// Created by 佐藤汰一 on 2025/09/07. +// + +import Foundation + +public struct HouseworkClient: Sendable { + + public let insertOrUpdateItem: @Sendable (_ item: HouseworkItem, _ cohabitantId: String) async throws -> Void + public let removeItem: @Sendable (_ item: HouseworkItem, _ cohabitantId: String) async throws -> Void + public let snapshotListener: @Sendable ( + _ id: String, + _ cohabitantId: String, + _ anchorDate: Date, + _ offset: Int + ) async -> AsyncStream<[HouseworkItem]> + public let removeListener: @Sendable (_ id: String) async -> Void +} + +public extension HouseworkClient { + + init( + insertOrUpdateItemHandler: @escaping @Sendable ( + _ item: HouseworkItem, + _ cohabitantId: String + ) async throws -> Void = { _, _ in }, + removeItemHandler: @escaping @Sendable ( + _ item: HouseworkItem, + _ cohabitantId: String + ) async throws -> Void = { _, _ in }, + snapshotListenerHandler: @escaping @Sendable ( + _ id: String, + _ cohabitantId: String, + _ anchorDate: Date, + _ offset: Int + ) async -> AsyncStream<[HouseworkItem]> = { _, _, _, _ in .makeStream().stream }, + removeListenerHandler: @escaping @Sendable (_ id: String) async -> Void = { _ in } + ) { + + insertOrUpdateItem = insertOrUpdateItemHandler + removeItem = removeItemHandler + snapshotListener = snapshotListenerHandler + removeListener = removeListenerHandler + } + + static let previewValue = HouseworkClient() +} diff --git a/LocalPackage/Sources/HometeDomain/Dependencies/NonceGenerationClient.swift b/LocalPackage/Sources/HometeDomain/Dependencies/NonceGenerationClient.swift new file mode 100644 index 00000000..c56633d3 --- /dev/null +++ b/LocalPackage/Sources/HometeDomain/Dependencies/NonceGenerationClient.swift @@ -0,0 +1,30 @@ +// +// NonceGenerationClient.swift +// homete +// +// Created by 佐藤汰一 on 2025/08/03. +// + +import CryptoKit +import Foundation + +public struct NonceGenerationClient: Sendable { + + public let value: @Sendable () -> SignInWithAppleNonce + public func callAsFunction() -> SignInWithAppleNonce { + + return value() + } + + public init(value: @Sendable @escaping () -> SignInWithAppleNonce) { + self.value = value + } +} + +public extension NonceGenerationClient { + + static let previewValue: NonceGenerationClient = .init { + + return .init(original: "preview", sha256: "preview sha256") + } +} diff --git a/LocalPackage/Sources/HometeDomain/Dependencies/SignInWithAppleClient.swift b/LocalPackage/Sources/HometeDomain/Dependencies/SignInWithAppleClient.swift new file mode 100644 index 00000000..c5db031d --- /dev/null +++ b/LocalPackage/Sources/HometeDomain/Dependencies/SignInWithAppleClient.swift @@ -0,0 +1,23 @@ +// +// SignInWithAppleClient.swift +// homete +// +// Created by 佐藤汰一 on 2025/12/31. +// + +public struct SignInWithAppleClient: Sendable { + public let reauthentication: @MainActor (_ nonce: SignInWithAppleNonce) async throws -> SignInWithAppleResult + + public init( + reauthentication: @MainActor @escaping (_ nonce: SignInWithAppleNonce) + async throws -> SignInWithAppleResult = { _ in preconditionFailure() } + ) { + + self.reauthentication = reauthentication + } +} + +public extension SignInWithAppleClient { + + static let previewValue = SignInWithAppleClient() +} diff --git a/homete/Model/Domain/DomainError.swift b/LocalPackage/Sources/HometeDomain/DomainError.swift similarity index 77% rename from homete/Model/Domain/DomainError.swift rename to LocalPackage/Sources/HometeDomain/DomainError.swift index b8668556..5430daaf 100644 --- a/homete/Model/Domain/DomainError.swift +++ b/LocalPackage/Sources/HometeDomain/DomainError.swift @@ -5,17 +5,17 @@ // Created by 佐藤汰一 on 2025/08/09. // -enum DomainError: Error, Equatable { - +public enum DomainError: Error, Equatable, Sendable { + case failAuth case noNetwork case other } -extension DomainError { - +public extension DomainError { + static func make(_ error: (any Error)?) -> Self? { - + guard let error else { return nil } return error as? DomainError ?? .other } diff --git a/LocalPackage/Sources/HometeDomain/LocalPackage.swift b/LocalPackage/Sources/HometeDomain/LocalPackage.swift deleted file mode 100644 index 08b22b80..00000000 --- a/LocalPackage/Sources/HometeDomain/LocalPackage.swift +++ /dev/null @@ -1,2 +0,0 @@ -// The Swift Programming Language -// https://docs.swift.org/swift-book diff --git a/homete/Model/Domain/PushNotificationContent.swift b/LocalPackage/Sources/HometeDomain/PushNotificationContent.swift similarity index 80% rename from homete/Model/Domain/PushNotificationContent.swift rename to LocalPackage/Sources/HometeDomain/PushNotificationContent.swift index d8a01041..603c1653 100644 --- a/homete/Model/Domain/PushNotificationContent.swift +++ b/LocalPackage/Sources/HometeDomain/PushNotificationContent.swift @@ -5,12 +5,17 @@ // Created by 佐藤汰一 on 2025/11/12. // -struct PushNotificationContent: Equatable { - let title: String - let message: String +public struct PushNotificationContent: Equatable { + public let title: String + public let message: String + + public init(title: String, message: String) { + self.title = title + self.message = message + } } -extension PushNotificationContent { +public extension PushNotificationContent { static func addNewHouseworkItem(_ houseworkTitle: String) -> Self { return .init( diff --git a/homete/Model/Domain/Setting/SettingMenuItem.swift b/LocalPackage/Sources/HometeDomain/Setting/SettingMenuItem.swift similarity index 76% rename from homete/Model/Domain/Setting/SettingMenuItem.swift rename to LocalPackage/Sources/HometeDomain/Setting/SettingMenuItem.swift index a1c7f035..009a8b72 100644 --- a/homete/Model/Domain/Setting/SettingMenuItem.swift +++ b/LocalPackage/Sources/HometeDomain/Setting/SettingMenuItem.swift @@ -7,35 +7,35 @@ import SwiftUI -enum SettingMenuItem: Equatable, CaseIterable { - +public enum SettingMenuItem: Equatable, CaseIterable { + case taskTemplate case privacyPolicy case license - - var title: LocalizedStringKey { - + + public var title: LocalizedStringKey { + switch self { case .taskTemplate: return "家事テンプレート" - + case .privacyPolicy: return "プライバシーポリシー" - + case .license: return "ライセンス" } } - - var iconName: String { - + + public var iconName: String { + switch self { case .taskTemplate: return "house" - + case .privacyPolicy: return "hand.raised" - + case .license: return "cube.box" } diff --git a/homete/Model/AppDependencies.swift b/homete/Model/AppDependencies.swift index dc46e4de..f28d7a8d 100644 --- a/homete/Model/AppDependencies.swift +++ b/homete/Model/AppDependencies.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/08/03. // +import HometeDomain import SwiftUI struct AppDependencies { diff --git a/homete/Model/Dependencies/AnalyticsClient.swift b/homete/Model/Dependencies/AnalyticsClient.swift deleted file mode 100644 index c7ff90d3..00000000 --- a/homete/Model/Dependencies/AnalyticsClient.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// AnalyticsClient.swift -// homete -// -// Created by 佐藤汰一 on 2025/08/09. -// - -import FirebaseAnalytics -import FirebaseCrashlytics - -struct AnalyticsClient { - - let setId: @Sendable (String) -> Void - let log: @Sendable (AnalyticsEvent) -> Void - - init( - setId: @Sendable @escaping (String) -> Void = { _ in }, - log: @Sendable @escaping (AnalyticsEvent) -> Void = { _ in } - ) { - - self.setId = setId - self.log = log - } -} - -extension AnalyticsClient: DependencyClient { - - static let liveValue: AnalyticsClient = .init { userId in - - Analytics.setUserID(userId) - Crashlytics.crashlytics().setUserID(userId) - } log: { event in - - Analytics.logEvent(event.name, parameters: event.parameters) - } - - static let previewValue = AnalyticsClient() -} diff --git a/homete/Model/Dependencies/CohabitantClient.swift b/homete/Model/Dependencies/CohabitantClient.swift deleted file mode 100644 index b467c297..00000000 --- a/homete/Model/Dependencies/CohabitantClient.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// CohabitantClient.swift -// homete -// -// Created by 佐藤汰一 on 2025/08/18. -// - -import FirebaseFirestore - -struct CohabitantClient { - - let register: @Sendable (CohabitantData) async throws -> Void - let addSnapshotListener: @Sendable ( - _ listenerId: String, - _ cohabitantId: String - ) async -> AsyncStream<[CohabitantData]> - let removeSnapshotListener: @Sendable (_ listenerId: String) async -> Void - - init( - register: @Sendable @escaping (CohabitantData) async throws -> Void = { _ in }, - addSnapshotListener: @Sendable @escaping ( - _: String, - _: String - ) async -> AsyncStream<[CohabitantData]> = { _, _ in .init { nil } }, - removeSnapshotListener: @Sendable @escaping (_ listenerId: String) async -> Void = { _ in } - ) { - - self.register = register - self.addSnapshotListener = addSnapshotListener - self.removeSnapshotListener = removeSnapshotListener - } -} - -extension CohabitantClient: DependencyClient { - - static let liveValue: CohabitantClient = .init { data in - - try await FirestoreService.shared.insertOrUpdate(data: data) { - - return $0.cohabitantRef(id: data.id) - } - } addSnapshotListener: { listenerId, cohabitantId in - - return await FirestoreService.shared.addSnapshotListener(id: listenerId) { - $0.collection(path: .cohabitant) - .whereField(CohabitantData.idField, isEqualTo: cohabitantId) - } - } removeSnapshotListener: { listenerId in - - await FirestoreService.shared.removeSnapshotListener(id: listenerId) - } - - static let previewValue: CohabitantClient = .init() -} diff --git a/homete/Model/Dependencies/HouseworkClient.swift b/homete/Model/Dependencies/HouseworkClient.swift deleted file mode 100644 index af3f77d1..00000000 --- a/homete/Model/Dependencies/HouseworkClient.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// HouseworkClient.swift -// homete -// -// Created by 佐藤汰一 on 2025/09/07. -// - -import FirebaseFirestore - -struct HouseworkClient { - - let insertOrUpdateItem: @Sendable (_ item: HouseworkItem, _ cohabitantId: String) async throws -> Void - let removeItem: @Sendable (_ item: HouseworkItem, _ cohabitantId: String) async throws -> Void - let snapshotListener: @Sendable ( - _ id: String, - _ cohabitantId: String, - _ anchorDate: Date, - _ offset: Int - ) async -> AsyncStream<[HouseworkItem]> - let removeListener: @Sendable (_ id: String) async -> Void -} - -extension HouseworkClient: DependencyClient { - - init( - insertOrUpdateItemHandler: @escaping @Sendable ( - _ item: HouseworkItem, - _ cohabitantId: String - ) async throws -> Void = { _, _ in }, - removeItemHandler: @escaping @Sendable ( - _ item: HouseworkItem, - _ cohabitantId: String - ) async throws -> Void = { _, _ in }, - snapshotListenerHandler: @escaping @Sendable ( - _ id: String, - _ cohabitantId: String, - _ anchorDate: Date, - _ offset: Int - ) async -> AsyncStream<[HouseworkItem]> = { _, _, _, _ in .makeStream().stream }, - removeListenerHandler: @escaping @Sendable (_ id: String) async -> Void = { _ in } - ) { - - insertOrUpdateItem = insertOrUpdateItemHandler - removeItem = removeItemHandler - snapshotListener = snapshotListenerHandler - removeListener = removeListenerHandler - } - - static let liveValue = HouseworkClient { item, cohabitantId in - - try await FirestoreService.shared.insertOrUpdate(data: item) { - - return $0 - .houseworkListRef(id: cohabitantId) - .document(item.id) - } - } removeItem: { item, cohabitantId in - - try await FirestoreService.shared.delete { - - return $0 - .houseworkListRef(id: cohabitantId) - .document(item.id) - } - } snapshotListener: { id, cohabitantId, anchorDate, offset in - - let targetDateList = HouseworkIndexedDate.calcTargetPeriod( - anchorDate: anchorDate, - offsetDays: offset, - calendar: .autoupdatingCurrent - ) - - return await FirestoreService.shared.addSnapshotListener(id: id) { - - return $0.houseworkListRef(id: cohabitantId) - .whereField("indexedDate", in: targetDateList) - } - } removeListener: { id in - - await FirestoreService.shared.removeSnapshotListener(id: id) - } - - static let previewValue = HouseworkClient() -} diff --git a/homete/Model/Dependencies/SignInWithAppleClient.swift b/homete/Model/Dependencies/SignInWithAppleClient.swift deleted file mode 100644 index 08ade17f..00000000 --- a/homete/Model/Dependencies/SignInWithAppleClient.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// SignInWithAppleClient.swift -// homete -// -// Created by 佐藤汰一 on 2025/12/31. -// - -import AuthenticationServices - -struct SignInWithAppleClient { - let reauthentication: @MainActor (_ nonce: SignInWithAppleNonce) async throws -> SignInWithAppleResult - - init( - reauthentication: @MainActor @escaping (_ nonce: SignInWithAppleNonce) - async throws -> SignInWithAppleResult = { _ in preconditionFailure() } - ) { - - self.reauthentication = reauthentication - } -} - -extension SignInWithAppleClient: DependencyClient { - - static let liveValue = SignInWithAppleClient { nonce in - - let signInWithApple = SignInWithApple() - let appleIDCredential = try await signInWithApple(nonce) - return try SignInWithAppleResultFactory.make(appleIDCredential, nonce) - } - - static let previewValue = SignInWithAppleClient() -} diff --git a/homete/Model/Domain/Account/Account.swift b/homete/Model/Domain/Account/Account.swift deleted file mode 100644 index d5032127..00000000 --- a/homete/Model/Domain/Account/Account.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// Account.swift -// homete -// -// Created by 佐藤汰一 on 2025/08/03. -// - -struct Account: Equatable, Codable { - - let id: String - let userName: String - let fcmToken: String? - let cohabitantId: String? -} - -extension Account { - - static func initial(auth: AccountAuthResult, userName: UserName, fcmToken: String?) -> Self { - - return .init(id: auth.id, userName: userName.value, fcmToken: fcmToken, cohabitantId: nil) - } -} diff --git a/homete/Model/Domain/Authentification/AccountAuthInfo.swift b/homete/Model/Domain/Authentification/AccountAuthInfo.swift deleted file mode 100644 index 5e52d3ac..00000000 --- a/homete/Model/Domain/Authentification/AccountAuthInfo.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// AccountAuthInfo.swift -// homete -// -// Created by 佐藤汰一 on 2025/12/31. -// - -struct AccountAuthInfo: Equatable { - let result: AccountAuthResult? - let alreadyLoadedAtInitiate: Bool - - static let initial = AccountAuthInfo(result: nil, alreadyLoadedAtInitiate: false) -} diff --git a/homete/Model/Domain/Authentification/AccountAuthResult.swift b/homete/Model/Domain/Authentification/AccountAuthResult.swift deleted file mode 100644 index 693873ec..00000000 --- a/homete/Model/Domain/Authentification/AccountAuthResult.swift +++ /dev/null @@ -1,11 +0,0 @@ -// -// AccountAuthResult.swift -// homete -// -// Created by 佐藤汰一 on 2025/08/09. -// - -struct AccountAuthResult: Equatable { - - let id: String -} diff --git a/homete/Model/Domain/Authentification/SignInWithAppleNonce.swift b/homete/Model/Domain/Authentification/SignInWithAppleNonce.swift deleted file mode 100644 index a605680d..00000000 --- a/homete/Model/Domain/Authentification/SignInWithAppleNonce.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// SignInWithAppleNonce.swift -// homete -// -// Created by 佐藤汰一 on 2025/08/04. -// - -struct SignInWithAppleNonce: Equatable { - - let original: String - let sha256: String -} diff --git a/homete/Model/Domain/Authentification/SignInWithAppleResult.swift b/homete/Model/Domain/Authentification/SignInWithAppleResult.swift deleted file mode 100644 index f26ffc2e..00000000 --- a/homete/Model/Domain/Authentification/SignInWithAppleResult.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// SignInWithAppleResult.swift -// homete -// -// Created by 佐藤汰一 on 2025/08/09. -// - -struct SignInWithAppleResult: Equatable { - - let tokenId: String - let nonce: String - let authorizationCode: String -} diff --git a/homete/Model/Domain/Cohabitant/CohabitantData.swift b/homete/Model/Domain/Cohabitant/CohabitantData.swift deleted file mode 100644 index f929ef27..00000000 --- a/homete/Model/Domain/Cohabitant/CohabitantData.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// CohabitantData.swift -// homete -// -// Created by 佐藤汰一 on 2026/01/04. -// - -struct CohabitantData: Codable { - - static let idField = "id" - - /// 家族グループのID - let id: String - /// 参加しているメンバーのユーザーID - let members: [String] -} diff --git a/homete/Model/Domain/Cohabitant/CohabitantMember.swift b/homete/Model/Domain/Cohabitant/CohabitantMember.swift deleted file mode 100644 index a7e7107d..00000000 --- a/homete/Model/Domain/Cohabitant/CohabitantMember.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// CohabitantMember.swift -// homete -// -// Created by 佐藤汰一 on 2026/01/04. -// - -struct CohabitantMember: Equatable, Hashable { - - /// メンバーのユーザーID - let id: String - /// メンバーのユーザー名 - let userName: String -} diff --git a/homete/Model/Dependencies/AccountAuthClient.swift b/homete/Model/ImplDependencies/ImplAccountAuthClient.swift similarity index 56% rename from homete/Model/Dependencies/AccountAuthClient.swift rename to homete/Model/ImplDependencies/ImplAccountAuthClient.swift index 57ee5646..2d8253ac 100644 --- a/homete/Model/Dependencies/AccountAuthClient.swift +++ b/homete/Model/ImplDependencies/ImplAccountAuthClient.swift @@ -5,42 +5,10 @@ // Created by 佐藤汰一 on 2025/08/03. // +import HometeDomain import FirebaseAuth -struct AccountAuthClient { - - let signIn: @Sendable (String, String) async throws -> AccountAuthResult - let signOut: @Sendable () throws -> Void - let makeListener: @Sendable () -> AccountListenerStream - let reauthenticateWithApple: @Sendable (_ signInWithAppleResult: SignInWithAppleResult) async throws -> Void - let revokeAppleToken: @Sendable (_ authorizationCode: String) async throws -> Void - let deleteAccount: @Sendable () async throws -> Void - - init( - signIn: @Sendable @escaping (String, String) async throws -> AccountAuthResult = { _, _ in .init(id: "id") }, - signOut: @Sendable @escaping () throws -> Void = {}, - makeListener: @Sendable @escaping () -> AccountListenerStream = { - - let (stream, continuation) = AsyncStream.makeStream() - return AccountListenerStream(values: stream, - listenerToken: NSObject(), - continuation: continuation) - }, - reauthenticateWithApple: @Sendable @escaping (_: SignInWithAppleResult) async throws -> Void = { _ in }, - revokeAppleToken: @Sendable @escaping (_: String) async throws -> Void = { _ in }, - deleteAccount: @Sendable @escaping () async throws -> Void = {} - ) { - - self.signIn = signIn - self.signOut = signOut - self.makeListener = makeListener - self.reauthenticateWithApple = reauthenticateWithApple - self.revokeAppleToken = revokeAppleToken - self.deleteAccount = deleteAccount - } -} - -extension AccountAuthClient: DependencyClient { +extension AccountAuthClient { static let liveValue: AccountAuthClient = .init( signIn: { tokenId, nonce in @@ -79,9 +47,7 @@ extension AccountAuthClient: DependencyClient { } try await user.delete() } - ) - - static let previewValue = AccountAuthClient() + ) } private extension AccountAuthClient { diff --git a/homete/Model/Dependencies/AccountInfoClient.swift b/homete/Model/ImplDependencies/ImplAccountInfoClient.swift similarity index 52% rename from homete/Model/Dependencies/AccountInfoClient.swift rename to homete/Model/ImplDependencies/ImplAccountInfoClient.swift index 89e71b89..9d74b283 100644 --- a/homete/Model/Dependencies/AccountInfoClient.swift +++ b/homete/Model/ImplDependencies/ImplAccountInfoClient.swift @@ -6,22 +6,9 @@ // import FirebaseFirestore +import HometeDomain -struct AccountInfoClient { - - let insertOrUpdate: @Sendable (Account) async throws -> Void - let fetch: @Sendable (String) async throws -> Account? - - init( - insertOrUpdate: @Sendable @escaping (Account) async throws -> Void = { _ in }, - fetch: @Sendable @escaping (String) async throws -> Account? = { _ in nil } - ) { - self.insertOrUpdate = insertOrUpdate - self.fetch = fetch - } -} - -extension AccountInfoClient: DependencyClient { +extension AccountInfoClient { private static let collectionPath = "Account" private static let primaryKey = "id" @@ -39,6 +26,4 @@ extension AccountInfoClient: DependencyClient { return $0.accountRef(id: id) } } - - static let previewValue: AccountInfoClient = .init() } diff --git a/homete/Model/ImplDependencies/ImplAnalyticsClient.swift b/homete/Model/ImplDependencies/ImplAnalyticsClient.swift new file mode 100644 index 00000000..9f1af32d --- /dev/null +++ b/homete/Model/ImplDependencies/ImplAnalyticsClient.swift @@ -0,0 +1,22 @@ +// +// AnalyticsClient.swift +// homete +// +// Created by 佐藤汰一 on 2025/08/09. +// + +import FirebaseAnalytics +import FirebaseCrashlytics +import HometeDomain + +extension AnalyticsClient { + + static let liveValue: AnalyticsClient = .init { userId in + + Analytics.setUserID(userId) + Crashlytics.crashlytics().setUserID(userId) + } log: { event in + + Analytics.logEvent(event.name, parameters: event.parameters) + } +} diff --git a/homete/Model/ImplDependencies/ImplCohabitantClient.swift b/homete/Model/ImplDependencies/ImplCohabitantClient.swift new file mode 100644 index 00000000..34c07770 --- /dev/null +++ b/homete/Model/ImplDependencies/ImplCohabitantClient.swift @@ -0,0 +1,29 @@ +// +// CohabitantClient.swift +// homete +// +// Created by 佐藤汰一 on 2025/08/18. +// + +import FirebaseFirestore +import HometeDomain + +extension CohabitantClient { + + static let liveValue: CohabitantClient = .init { data in + + try await FirestoreService.shared.insertOrUpdate(data: data) { + + return $0.cohabitantRef(id: data.id) + } + } addSnapshotListener: { listenerId, cohabitantId in + + return await FirestoreService.shared.addSnapshotListener(id: listenerId) { + $0.collection(path: .cohabitant) + .whereField(CohabitantData.idField, isEqualTo: cohabitantId) + } + } removeSnapshotListener: { listenerId in + + await FirestoreService.shared.removeSnapshotListener(id: listenerId) + } +} diff --git a/homete/Model/Dependencies/CohabitantPushNotificationClient.swift b/homete/Model/ImplDependencies/ImplCohabitantPushNotificationClient.swift similarity index 62% rename from homete/Model/Dependencies/CohabitantPushNotificationClient.swift rename to homete/Model/ImplDependencies/ImplCohabitantPushNotificationClient.swift index 0c536610..bb6a650e 100644 --- a/homete/Model/Dependencies/CohabitantPushNotificationClient.swift +++ b/homete/Model/ImplDependencies/ImplCohabitantPushNotificationClient.swift @@ -6,12 +6,9 @@ // import FirebaseFunctions +import HometeDomain -struct CohabitantPushNotificationClient { - let send: @Sendable (_ id: String, _ content: PushNotificationContent) async throws -> Void -} - -extension CohabitantPushNotificationClient: DependencyClient { +extension CohabitantPushNotificationClient { static let liveValue: CohabitantPushNotificationClient = .init { id, content in _ = try await Functions.functions() @@ -22,6 +19,4 @@ extension CohabitantPushNotificationClient: DependencyClient { "body": content.message ]) } - - static let previewValue: CohabitantPushNotificationClient = .init { _, _ in } } diff --git a/homete/Model/ImplDependencies/ImplHouseworkClient.swift b/homete/Model/ImplDependencies/ImplHouseworkClient.swift new file mode 100644 index 00000000..40a0e590 --- /dev/null +++ b/homete/Model/ImplDependencies/ImplHouseworkClient.swift @@ -0,0 +1,46 @@ +// +// HouseworkClient.swift +// homete +// +// Created by 佐藤汰一 on 2025/09/07. +// + +import FirebaseFirestore +import HometeDomain + +extension HouseworkClient { + + static let liveValue = HometeDomain.HouseworkClient { item, cohabitantId in + + try await FirestoreService.shared.insertOrUpdate(data: item) { + + return $0 + .houseworkListRef(id: cohabitantId) + .document(item.id) + } + } removeItemHandler: { item, cohabitantId in + + try await FirestoreService.shared.delete { + + return $0 + .houseworkListRef(id: cohabitantId) + .document(item.id) + } + } snapshotListenerHandler: { id, cohabitantId, anchorDate, offset in + + let targetDateList = HouseworkIndexedDate.calcTargetPeriod( + anchorDate: anchorDate, + offsetDays: offset, + calendar: .autoupdatingCurrent + ) + + return await FirestoreService.shared.addSnapshotListener(id: id) { + + return $0.houseworkListRef(id: cohabitantId) + .whereField("indexedDate", in: targetDateList) + } + } removeListenerHandler: { id in + + await FirestoreService.shared.removeSnapshotListener(id: id) + } +} diff --git a/homete/Model/Dependencies/NonceGenerationClient.swift b/homete/Model/ImplDependencies/ImplNonceGenerationClient.swift similarity index 76% rename from homete/Model/Dependencies/NonceGenerationClient.swift rename to homete/Model/ImplDependencies/ImplNonceGenerationClient.swift index c99bf688..c49fb7cd 100644 --- a/homete/Model/Dependencies/NonceGenerationClient.swift +++ b/homete/Model/ImplDependencies/ImplNonceGenerationClient.swift @@ -7,17 +7,9 @@ import CryptoKit import Foundation +import HometeDomain -struct NonceGenerationClient { - - let value: @Sendable () -> SignInWithAppleNonce - func callAsFunction() -> SignInWithAppleNonce { - - return value() - } -} - -extension NonceGenerationClient: DependencyClient { +extension NonceGenerationClient { static let liveValue: NonceGenerationClient = .init { @@ -49,9 +41,4 @@ extension NonceGenerationClient: DependencyClient { let hashString = hashedData.compactMap { String(format: "%02x", $0) }.joined() return hashString } - - static let previewValue: NonceGenerationClient = .init { - - return .init(original: "preview", sha256: "preview sha256") - } } diff --git a/homete/Model/ImplDependencies/ImplSignInWithAppleClient.swift b/homete/Model/ImplDependencies/ImplSignInWithAppleClient.swift new file mode 100644 index 00000000..7bf66c38 --- /dev/null +++ b/homete/Model/ImplDependencies/ImplSignInWithAppleClient.swift @@ -0,0 +1,19 @@ +// +// SignInWithAppleClient.swift +// homete +// +// Created by 佐藤汰一 on 2025/12/31. +// + +import AuthenticationServices +import HometeDomain + +extension SignInWithAppleClient { + + static let liveValue = SignInWithAppleClient { nonce in + + let signInWithApple = SignInWithApple() + let appleIDCredential = try await signInWithApple(nonce) + return try SignInWithAppleResultFactory.make(appleIDCredential, nonce) + } +} diff --git a/homete/Model/Service/SignInWithApple/SignInWithApple.swift b/homete/Model/Service/SignInWithApple/SignInWithApple.swift index 537bb297..13f9c9b2 100644 --- a/homete/Model/Service/SignInWithApple/SignInWithApple.swift +++ b/homete/Model/Service/SignInWithApple/SignInWithApple.swift @@ -1,4 +1,5 @@ import AuthenticationServices +import HometeDomain final class SignInWithApple: NSObject, ASAuthorizationControllerDelegate { diff --git a/homete/Model/Service/SignInWithApple/SignInWithAppleRequestFactory.swift b/homete/Model/Service/SignInWithApple/SignInWithAppleRequestFactory.swift index 12bb1e4e..b51362a0 100644 --- a/homete/Model/Service/SignInWithApple/SignInWithAppleRequestFactory.swift +++ b/homete/Model/Service/SignInWithApple/SignInWithAppleRequestFactory.swift @@ -6,6 +6,7 @@ // import AuthenticationServices +import HometeDomain enum SignInWithAppleRequestFactory { diff --git a/homete/Model/Service/SignInWithApple/SignInWithAppleResultFactory.swift b/homete/Model/Service/SignInWithApple/SignInWithAppleResultFactory.swift index 834c8ac2..07450f2b 100644 --- a/homete/Model/Service/SignInWithApple/SignInWithAppleResultFactory.swift +++ b/homete/Model/Service/SignInWithApple/SignInWithAppleResultFactory.swift @@ -6,6 +6,7 @@ // import AuthenticationServices +import HometeDomain enum SignInWithAppleResultFactory { static func make( diff --git a/homete/Resouces/Localizable.xcstrings b/homete/Resouces/Localizable.xcstrings index 864de677..5b5903c4 100644 --- a/homete/Resouces/Localizable.xcstrings +++ b/homete/Resouces/Localizable.xcstrings @@ -95,9 +95,6 @@ }, "はじめまして!" : { - }, - "プライバシーポリシー" : { - }, "ポイント" : { @@ -129,9 +126,6 @@ } } } - }, - "ライセンス" : { - }, "ログアウト" : { @@ -196,9 +190,6 @@ }, "家事がありません" : { - }, - "家事テンプレート" : { - }, "家事のテンプレートが設定されていません" : { diff --git a/homete/Views/Auth/Login/LoginView.swift b/homete/Views/Auth/Login/LoginView.swift index f50c7a32..71b0d023 100644 --- a/homete/Views/Auth/Login/LoginView.swift +++ b/homete/Views/Auth/Login/LoginView.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/08/03. // +import HometeDomain import SwiftUI struct LoginView: View { @@ -65,5 +66,5 @@ private extension LoginView { #Preview { LoginView() - .environment(AccountAuthStore(appDependencies: .previewValue)) + .environment(AccountAuthStore()) } diff --git a/homete/Views/Auth/Login/SignInUpWithAppleButton.swift b/homete/Views/Auth/Login/SignInUpWithAppleButton.swift index 18ec72dc..0eee7a88 100644 --- a/homete/Views/Auth/Login/SignInUpWithAppleButton.swift +++ b/homete/Views/Auth/Login/SignInUpWithAppleButton.swift @@ -7,6 +7,7 @@ import AuthenticationServices import SwiftUI +import HometeDomain struct SignInUpWithAppleButton: View { @Environment(\.colorScheme) var colorScheme diff --git a/homete/Views/Auth/RegistrationAccount/RegistrationAccountView.swift b/homete/Views/Auth/RegistrationAccount/RegistrationAccountView.swift index af22a728..e64fcb7c 100644 --- a/homete/Views/Auth/RegistrationAccount/RegistrationAccountView.swift +++ b/homete/Views/Auth/RegistrationAccount/RegistrationAccountView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import HometeDomain struct RegistrationAccountView: View { @Environment(AccountStore.self) var accountStore @@ -86,14 +87,14 @@ private extension RegistrationAccountView { } #Preview("RegistrationAccountView_未入力") { - RegistrationAccountView(authInfo: .init(id: "")) - .environment(AccountStore(appDependencies: .previewValue)) + RegistrationAccountView(authInfo: AccountAuthResult(id: "")) + .environment(AccountStore()) } #Preview("RegistrationAccountView_入力済み") { RegistrationAccountView( loadingState: .init(store: .init(isLoading: true)), - authInfo: .init(id: "Test") + authInfo: AccountAuthResult(id: "Test") ) - .environment(AccountStore(appDependencies: .previewValue)) + .environment(AccountStore()) } diff --git a/homete/Views/Auth/RegistrationAccount/UserNameInputTextField.swift b/homete/Views/Auth/RegistrationAccount/UserNameInputTextField.swift index 06c46ea2..9813c9b3 100644 --- a/homete/Views/Auth/RegistrationAccount/UserNameInputTextField.swift +++ b/homete/Views/Auth/RegistrationAccount/UserNameInputTextField.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/12/27. // +import HometeDomain import SwiftUI struct UserNameInputTextField: View { diff --git a/homete/Views/Components/PreviewUtil/HouseworkUtil.swift b/homete/Views/Components/PreviewUtil/HouseworkUtil.swift index 00efe0c0..8a368869 100644 --- a/homete/Views/Components/PreviewUtil/HouseworkUtil.swift +++ b/homete/Views/Components/PreviewUtil/HouseworkUtil.swift @@ -6,6 +6,7 @@ // import Foundation +import HometeDomain extension HouseworkItem { diff --git a/homete/Views/HomeView/HomeView.swift b/homete/Views/HomeView/HomeView.swift index b7cf8745..74c293bd 100644 --- a/homete/Views/HomeView/HomeView.swift +++ b/homete/Views/HomeView/HomeView.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/08/11. // +import HometeDomain import SwiftUI struct HomeView: View { diff --git a/homete/Views/HouseworkApproval/Components/HouseworkItemPropertyListContent.swift b/homete/Views/HouseworkApproval/Components/HouseworkItemPropertyListContent.swift index 5914718b..1ad27f2f 100644 --- a/homete/Views/HouseworkApproval/Components/HouseworkItemPropertyListContent.swift +++ b/homete/Views/HouseworkApproval/Components/HouseworkItemPropertyListContent.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/12/06. // +import HometeDomain import SwiftUI struct HouseworkItemPropertyListContent: View { diff --git a/homete/Views/HouseworkApproval/HouseworkApprovalView.swift b/homete/Views/HouseworkApproval/HouseworkApprovalView.swift index e2306f32..02b240cd 100644 --- a/homete/Views/HouseworkApproval/HouseworkApprovalView.swift +++ b/homete/Views/HouseworkApproval/HouseworkApprovalView.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/11/23. // +import HometeDomain import SwiftUI struct HouseworkApprovalView: View { diff --git a/homete/Views/HouseworkBoardView/HouseworkBoardView.swift b/homete/Views/HouseworkBoardView/HouseworkBoardView.swift index 27f8d523..f97eb93d 100644 --- a/homete/Views/HouseworkBoardView/HouseworkBoardView.swift +++ b/homete/Views/HouseworkBoardView/HouseworkBoardView.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/09/06. // +import HometeDomain import SwiftUI struct HouseworkBoardView: View { diff --git a/homete/Views/HouseworkBoardView/SubViews/HouseBoardListRow.swift b/homete/Views/HouseworkBoardView/SubViews/HouseBoardListRow.swift index 3e6fac6c..50826103 100644 --- a/homete/Views/HouseworkBoardView/SubViews/HouseBoardListRow.swift +++ b/homete/Views/HouseworkBoardView/SubViews/HouseBoardListRow.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/10/28. // +import HometeDomain import SwiftUI struct HouseBoardListRow: View { diff --git a/homete/Views/HouseworkBoardView/SubViews/HouseworkBoardListContent.swift b/homete/Views/HouseworkBoardView/SubViews/HouseworkBoardListContent.swift index a2b67e79..45467497 100644 --- a/homete/Views/HouseworkBoardView/SubViews/HouseworkBoardListContent.swift +++ b/homete/Views/HouseworkBoardView/SubViews/HouseworkBoardListContent.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/09/06. // +import HometeDomain import SwiftUI struct HouseworkBoardListContent: View { diff --git a/homete/Views/HouseworkBoardView/SubViews/HouseworkBoardSegmentedControl.swift b/homete/Views/HouseworkBoardView/SubViews/HouseworkBoardSegmentedControl.swift index 8c8bbfd4..46f5b67b 100644 --- a/homete/Views/HouseworkBoardView/SubViews/HouseworkBoardSegmentedControl.swift +++ b/homete/Views/HouseworkBoardView/SubViews/HouseworkBoardSegmentedControl.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/09/06. // +import HometeDomain import SwiftUI struct HouseworkBoardSegmentedControl: View { diff --git a/homete/Views/HouseworkDetailView/HouseworkDetailView.swift b/homete/Views/HouseworkDetailView/HouseworkDetailView.swift index 72eac2e3..cae30be7 100644 --- a/homete/Views/HouseworkDetailView/HouseworkDetailView.swift +++ b/homete/Views/HouseworkDetailView/HouseworkDetailView.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/11/08. // +import HometeDomain import SwiftUI struct HouseworkDetailView: View { diff --git a/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailActionContent.swift b/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailActionContent.swift index 2c4ce35f..a6c2df50 100644 --- a/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailActionContent.swift +++ b/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailActionContent.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/11/16. // +import HometeDomain import SwiftUI struct HouseworkDetailActionContent: View { diff --git a/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailItemListContent.swift b/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailItemListContent.swift index 2952ee00..a737a7df 100644 --- a/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailItemListContent.swift +++ b/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailItemListContent.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2026/01/04. // +import HometeDomain import SwiftUI struct HouseworkDetailItemListContent: View { diff --git a/homete/Views/RegisterCohabitantView/CohabitantRegistrationView.swift b/homete/Views/RegisterCohabitantView/CohabitantRegistrationView.swift index 840c42f1..69c3e361 100644 --- a/homete/Views/RegisterCohabitantView/CohabitantRegistrationView.swift +++ b/homete/Views/RegisterCohabitantView/CohabitantRegistrationView.swift @@ -6,6 +6,7 @@ // import MultipeerConnectivity +import HometeDomain import SwiftUI struct CohabitantRegistrationView: View { diff --git a/homete/Views/RegisterCohabitantView/SubViews/CohabitantRegistrationSession.swift b/homete/Views/RegisterCohabitantView/SubViews/CohabitantRegistrationSession.swift index 2f6a907c..e15f9a03 100644 --- a/homete/Views/RegisterCohabitantView/SubViews/CohabitantRegistrationSession.swift +++ b/homete/Views/RegisterCohabitantView/SubViews/CohabitantRegistrationSession.swift @@ -6,6 +6,7 @@ // import MultipeerConnectivity +import HometeDomain import SwiftUI struct CohabitantRegistrationSession: View { diff --git a/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingFollowerView.swift b/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingFollowerView.swift index 5f1c6547..20c7b850 100644 --- a/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingFollowerView.swift +++ b/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingFollowerView.swift @@ -6,6 +6,7 @@ // import MultipeerConnectivity +import HometeDomain import SwiftUI struct CohabitantRegistrationProcessingFollower: View { diff --git a/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingLeader.swift b/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingLeader.swift index 6c2a8041..c3eaf924 100644 --- a/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingLeader.swift +++ b/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingLeader.swift @@ -6,6 +6,7 @@ // import MultipeerConnectivity +import HometeDomain import SwiftUI struct CohabitantRegistrationProcessingLeader: View { diff --git a/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingView.swift b/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingView.swift index f183305c..7e1ea426 100644 --- a/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingView.swift +++ b/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingView.swift @@ -7,6 +7,7 @@ import Combine import MultipeerConnectivity +import HometeDomain import SwiftUI struct CohabitantRegistrationProcessingView: View { diff --git a/homete/Views/RegisterCohabitantView/SubViews/ScanningState/CohabitantRegistrationPeersListView.swift b/homete/Views/RegisterCohabitantView/SubViews/ScanningState/CohabitantRegistrationPeersListView.swift index ef8c09d3..4d87b2f2 100644 --- a/homete/Views/RegisterCohabitantView/SubViews/ScanningState/CohabitantRegistrationPeersListView.swift +++ b/homete/Views/RegisterCohabitantView/SubViews/ScanningState/CohabitantRegistrationPeersListView.swift @@ -6,6 +6,7 @@ // import MultipeerConnectivity +import HometeDomain import SwiftUI struct CohabitantRegistrationPeersListView: View { diff --git a/homete/Views/RegisterCohabitantView/SubViews/ScanningState/CohabitantRegistrationScanningStateView.swift b/homete/Views/RegisterCohabitantView/SubViews/ScanningState/CohabitantRegistrationScanningStateView.swift index a5858295..7917e64e 100644 --- a/homete/Views/RegisterCohabitantView/SubViews/ScanningState/CohabitantRegistrationScanningStateView.swift +++ b/homete/Views/RegisterCohabitantView/SubViews/ScanningState/CohabitantRegistrationScanningStateView.swift @@ -6,6 +6,7 @@ // import MultipeerConnectivity +import HometeDomain import SwiftUI struct CohabitantRegistrationScanningStateView: View { diff --git a/homete/Views/RegisterHouseworkView/RegisterHouseworkView.swift b/homete/Views/RegisterHouseworkView/RegisterHouseworkView.swift index cca38464..a664ac48 100644 --- a/homete/Views/RegisterHouseworkView/RegisterHouseworkView.swift +++ b/homete/Views/RegisterHouseworkView/RegisterHouseworkView.swift @@ -5,8 +5,9 @@ // Created by 佐藤汰一 on 2025/09/07. // -import Prefire import FirebaseFunctions +import Prefire +import HometeDomain import SwiftUI struct RegisterHouseworkView: View { diff --git a/homete/Views/RootView/LaunchStateProxy.swift b/homete/Views/RootView/LaunchStateProxy.swift index 3ca23f13..770d5da4 100644 --- a/homete/Views/RootView/LaunchStateProxy.swift +++ b/homete/Views/RootView/LaunchStateProxy.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/12/27. // +import HometeDomain import SwiftUI struct LaunchStateProxy { diff --git a/homete/Views/RootView/RootView.swift b/homete/Views/RootView/RootView.swift index ec996313..423f9620 100644 --- a/homete/Views/RootView/RootView.swift +++ b/homete/Views/RootView/RootView.swift @@ -6,6 +6,7 @@ // import FirebaseMessaging +import HometeDomain import SwiftUI struct RootView: View { @@ -60,13 +61,21 @@ extension RootView { static func make() -> some View { DependenciesInjectLayer { RootView() - .environment(AccountStore(appDependencies: $0)) - .environment(AccountAuthStore(appDependencies: $0)) + .environment(AccountStore(accountInfoClient: $0.accountInfoClient)) + .environment(AccountAuthStore( + accountAuthClient: $0.accountAuthClient, + analyticsClient: $0.analyticsClient, + signInWithAppleClient: $0.signInWithAppleClient, + nonceGenerationClient: $0.nonceGeneratorClient + )) .environment(HouseworkListStore( houseworkClient: $0.houseworkClient, cohabitantPushNotificationClient: $0.cohabitantPushNotificationClient )) - .environment(CohabitantStore(appDependencies: $0)) + .environment(CohabitantStore( + cohabitantClient: $0.cohabitantClient, + accountInfoClient: $0.accountInfoClient + )) } } } diff --git a/homete/Views/SettingView/SettingView.swift b/homete/Views/SettingView/SettingView.swift index 835a0869..3ca47d3e 100644 --- a/homete/Views/SettingView/SettingView.swift +++ b/homete/Views/SettingView/SettingView.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/08/11. // +import HometeDomain import SwiftUI struct SettingView: View { @@ -123,6 +124,6 @@ private extension SettingView { #Preview { SettingView() - .environment(AccountAuthStore(appDependencies: .previewValue)) - .environment(AccountStore(appDependencies: .previewValue)) + .environment(AccountAuthStore()) + .environment(AccountStore()) } diff --git a/homete/Views/SettingView/SubViews/SettingMenuItemButton.swift b/homete/Views/SettingView/SubViews/SettingMenuItemButton.swift index c94ef0df..2f0a061a 100644 --- a/homete/Views/SettingView/SubViews/SettingMenuItemButton.swift +++ b/homete/Views/SettingView/SubViews/SettingMenuItemButton.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/08/11. // +import HometeDomain import SwiftUI struct SettingMenuItemButton: View { diff --git a/homete/Views/TabView/AppTabView.swift b/homete/Views/TabView/AppTabView.swift index bdcda430..354c1fd6 100644 --- a/homete/Views/TabView/AppTabView.swift +++ b/homete/Views/TabView/AppTabView.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/09/06. // +import HometeDomain import SwiftUI import UserNotifications @@ -116,8 +117,8 @@ extension AppTabView { #Preview { AppTabView() - .environment(AccountStore(appDependencies: .previewValue)) - .environment(AccountAuthStore(appDependencies: .previewValue)) + .environment(AccountStore()) + .environment(AccountAuthStore()) .environment(HouseworkListStore( houseworkClient: .previewValue, cohabitantPushNotificationClient: .previewValue diff --git a/homete/Views/ViewUtilities/Alert/DomainErrorAlertContent.swift b/homete/Views/ViewUtilities/Alert/DomainErrorAlertContent.swift index 40dd2386..0861c2d4 100644 --- a/homete/Views/ViewUtilities/Alert/DomainErrorAlertContent.swift +++ b/homete/Views/ViewUtilities/Alert/DomainErrorAlertContent.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/11/11. // +import HometeDomain import SwiftUI struct DomainErrorAlertContent { diff --git a/homete/Views/ViewUtilities/Navigation/AppNavigationElement.swift b/homete/Views/ViewUtilities/Navigation/AppNavigationElement.swift index 68f1c069..d09a5d15 100644 --- a/homete/Views/ViewUtilities/Navigation/AppNavigationElement.swift +++ b/homete/Views/ViewUtilities/Navigation/AppNavigationElement.swift @@ -5,6 +5,8 @@ // Created by 佐藤汰一 on 2025/11/08. // +import HometeDomain + enum AppNavigationElement: Hashable { /// 家事詳細画面 case houseworkDetail(item: HouseworkItem) From 58f967bc965389a285ed43b9f0cd32a7e520cc2d Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Tue, 17 Feb 2026 22:53:05 +0900 Subject: [PATCH 03/12] =?UTF-8?q?refactor:=20LocalPackage=E3=81=A7?= =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=81=8C=E5=8B=95=E4=BD=9C=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcschemes/HometeDomain.xcscheme | 79 ++++ LocalPackage/Package.swift | 2 +- .../Cohabitant/CohabitantMember.swift | 2 +- .../Cohabitant/CohabitantMemberList.swift | 2 +- .../CohabitantRegistrationMessage.swift | 4 +- .../CohabitantRegistrationRole.swift | 2 +- .../Cohabitant/CohabitantStore.swift | 2 + .../Housework/HouseworkListStore.swift | 13 +- .../PushNotificationContent.swift | 2 +- .../AccountAuthStoreTest.swift | 152 +++++++ .../HometeDomainTests/AccountStoreTest.swift | 113 +++++ .../CohabitantRegistrationMessageTests.swift | 183 ++++++++ .../ConfirmedRegistrationPeersTest.swift | 71 +++ .../CohabitantStoreTest.swift | 78 ++++ .../Housework/DailyHouseworkListTest.swift | 103 +++++ .../Housework/HouseworkBoardListTest.swift | 85 ++++ .../Housework/HouseworkHistoryListTest.swift | 104 +++++ .../Housework/HouseworkIndexedDate.swift | 87 ++++ .../Housework/HouseworkItemTest.swift | 197 +++++++++ .../Housework/HouseworkListStoreTest.swift | 414 ++++++++++++++++++ .../StoredAllHouseworkListTest.swift | 78 ++++ .../HometeDomainTests/LocalPackageTests.swift | 6 - .../AccountListenerStreamCreater.swift | 18 + .../TestHelper/CalendarHelper.swift | 17 + .../TestHelper/DailyHouseworkListHelper.swift | 18 + .../TestHelper/DateHelper.swift | 31 ++ .../TestHelper/HouseworkItemHelper.swift | 80 ++++ .../TestHelper/LocaleHelper.swift | 5 + .../TestHelper/ObservationHelper.swift | 30 ++ .../TestHelper/TimeZoneHelper.swift | 13 + 30 files changed, 1977 insertions(+), 14 deletions(-) create mode 100644 LocalPackage/.swiftpm/xcode/xcshareddata/xcschemes/HometeDomain.xcscheme create mode 100644 LocalPackage/Tests/HometeDomainTests/AccountAuthStoreTest.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/AccountStoreTest.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/CohabitantRegistration/CohabitantRegistrationMessageTests.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/CohabitantRegistration/ConfirmedRegistrationPeersTest.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/CohabitantStoreTest.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/Housework/DailyHouseworkListTest.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/Housework/HouseworkBoardListTest.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/Housework/HouseworkHistoryListTest.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/Housework/HouseworkIndexedDate.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/Housework/HouseworkItemTest.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/Housework/HouseworkListStoreTest.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/Housework/StoredAllHouseworkListTest.swift delete mode 100644 LocalPackage/Tests/HometeDomainTests/LocalPackageTests.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/TestHelper/AccountListenerStreamCreater.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/TestHelper/CalendarHelper.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/TestHelper/DailyHouseworkListHelper.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/TestHelper/DateHelper.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/TestHelper/HouseworkItemHelper.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/TestHelper/LocaleHelper.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/TestHelper/ObservationHelper.swift create mode 100644 LocalPackage/Tests/HometeDomainTests/TestHelper/TimeZoneHelper.swift diff --git a/LocalPackage/.swiftpm/xcode/xcshareddata/xcschemes/HometeDomain.xcscheme b/LocalPackage/.swiftpm/xcode/xcshareddata/xcschemes/HometeDomain.xcscheme new file mode 100644 index 00000000..969323f0 --- /dev/null +++ b/LocalPackage/.swiftpm/xcode/xcshareddata/xcschemes/HometeDomain.xcscheme @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LocalPackage/Package.swift b/LocalPackage/Package.swift index 51e6fa9a..27ef927d 100644 --- a/LocalPackage/Package.swift +++ b/LocalPackage/Package.swift @@ -5,7 +5,7 @@ import PackageDescription let package = Package( name: "LocalPackage", - platforms: [.iOS(.v17)], + platforms: [.iOS(.v17), .macOS(.v15)], products: [ .library( name: "HometeDomain", diff --git a/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantMember.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantMember.swift index d44c7522..0ad8d2f8 100644 --- a/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantMember.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantMember.swift @@ -5,7 +5,7 @@ // Created by 佐藤汰一 on 2026/01/04. // -public struct CohabitantMember: Equatable, Hashable { +public struct CohabitantMember: Equatable, Hashable, Sendable { /// メンバーのユーザーID public let id: String diff --git a/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantMemberList.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantMemberList.swift index 28cd4b8b..7f2afcc0 100644 --- a/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantMemberList.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantMemberList.swift @@ -5,7 +5,7 @@ // Created by 佐藤汰一 on 2026/01/04. // -public struct CohabitantMemberList { +public struct CohabitantMemberList: Sendable { public private(set) var value: Set diff --git a/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/CohabitantRegistrationMessage.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/CohabitantRegistrationMessage.swift index 0d2507d0..ac9983be 100644 --- a/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/CohabitantRegistrationMessage.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/CohabitantRegistrationMessage.swift @@ -7,11 +7,11 @@ import Foundation -public struct CohabitantRegistrationMessage: Codable, Equatable { +public struct CohabitantRegistrationMessage: Codable, Equatable, Sendable { public let type: CommunicateType - public enum CommunicateType: Codable, Equatable { + public enum CommunicateType: Codable, Equatable, Sendable { /// 登録を行うメンバーが確定したかどうかの確認 case fixedMember(isOK: Bool) diff --git a/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/CohabitantRegistrationRole.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/CohabitantRegistrationRole.swift index 8c8f71d0..497580d9 100644 --- a/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/CohabitantRegistrationRole.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/CohabitantRegistrationRole.swift @@ -5,7 +5,7 @@ // Created by 佐藤汰一 on 2025/08/30. // -public enum CohabitantRegistrationRole: Codable, Equatable { +public enum CohabitantRegistrationRole: Codable, Equatable, Sendable { /// フォロワーはアカウントIDを渡す case follower(accountId: String) diff --git a/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantStore.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantStore.swift index 1d541150..7d7bd85d 100644 --- a/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantStore.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantStore.swift @@ -71,6 +71,8 @@ public final class CohabitantStore { public func removeSnapshotListener() async { + listenerTask?.cancel() + await listenerTask?.value listenerTask = nil await cohabitantClient.removeSnapshotListener(cohabitantListenerKey) } diff --git a/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkListStore.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkListStore.swift index ff173988..70e5521d 100644 --- a/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkListStore.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkListStore.swift @@ -14,6 +14,8 @@ public final class HouseworkListStore { public private(set) var items: StoredAllHouseworkList private var cohabitantId: String + private var observeTask: Task? + private let houseworkClient: HouseworkClient private let cohabitantPushNotificationClient: CohabitantPushNotificationClient @@ -42,6 +44,7 @@ public final class HouseworkListStore { return } + observeTask?.cancel() await houseworkClient.removeListener(houseworkObserveKey) let houseworkListStream = await houseworkClient.snapshotListener( @@ -51,7 +54,7 @@ public final class HouseworkListStore { 3 ) - Task { + observeTask = Task { for await currentItems in houseworkListStream { @@ -112,12 +115,20 @@ public final class HouseworkListStore { try await houseworkClient.removeItem(target, cohabitantId) } + + public func stopObserving() async { + + observeTask?.cancel() + await observeTask?.value + observeTask = nil + } } private extension HouseworkListStore { func clear() async { + await stopObserving() await houseworkClient.removeListener(houseworkObserveKey) items.removeAll() } diff --git a/LocalPackage/Sources/HometeDomain/PushNotificationContent.swift b/LocalPackage/Sources/HometeDomain/PushNotificationContent.swift index 603c1653..c6d6e3be 100644 --- a/LocalPackage/Sources/HometeDomain/PushNotificationContent.swift +++ b/LocalPackage/Sources/HometeDomain/PushNotificationContent.swift @@ -5,7 +5,7 @@ // Created by 佐藤汰一 on 2025/11/12. // -public struct PushNotificationContent: Equatable { +public struct PushNotificationContent: Equatable, Sendable { public let title: String public let message: String diff --git a/LocalPackage/Tests/HometeDomainTests/AccountAuthStoreTest.swift b/LocalPackage/Tests/HometeDomainTests/AccountAuthStoreTest.swift new file mode 100644 index 00000000..505af937 --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/AccountAuthStoreTest.swift @@ -0,0 +1,152 @@ +// +// AccountAuthStoreTest.swift +// hometeTests +// +// Created by 佐藤汰一 on 2025/08/09. +// + +import os +import Testing +@testable import HometeDomain + +@MainActor +struct AccountAuthStoreTest { + + @Test("ログイン処理ではサーバー側でサインイン後、成功したらログを送信する") + func test_login() async throws { + + let inputTokenId = "testId" + let inputNonce = "testNonce" + let outputAccount = AccountAuthResult(id: "testAccountId") + + try await confirmation(expectedCount: 4) { confirmation in + + let store = AccountAuthStore( + accountAuthClient: .init( + signIn: { tokenId, nonce in + + confirmation() + #expect(tokenId == inputTokenId) + #expect(nonce == inputNonce) + return outputAccount + }, + signOut: { confirmation() }, + makeListener: { + + confirmation() + return .defaultValue() + } + ), + analyticsClient: .init( + setId: { id in + + confirmation() + #expect(id == outputAccount.id) + }, log: { event in + + confirmation() + #expect(event == .login(isSuccess: true)) + } + ) + ) + + try await store.login(.init(tokenId: inputTokenId, nonce: inputNonce, authorizationCode: "code")) + } + } + + @Test("ログアウト時はローカルのログイン状態をログアウトにしてログアウト処理を行い、イベントログを送信する") + func test_logout() throws { + + let isCallSignOut = OSAllocatedUnfairLock(initialState: false) + let isCallAnalyticsLog = OSAllocatedUnfairLock(initialState: false) + + let store = AccountAuthStore( + accountAuthClient: .init( + signIn: { _, _ in + + Issue.record() + return .init(id: "") + }, + signOut: { isCallSignOut.withLock { $0 = true } }, + makeListener: { .defaultValue() } + ), + analyticsClient: .init( + setId: { _ in + + Issue.record() + }, + log: { event in + + isCallAnalyticsLog.withLock { $0 = true } + #expect(event == .logout()) + } + ), + currentAuth: .init(result: .init(id: "test"), alreadyLoadedAtInitiate: true) + ) + + store.logOut() + + #expect(isCallSignOut.withLock { $0 }) + #expect(isCallAnalyticsLog.withLock { $0 }) + #expect(store.currentAuth == .init(result: nil, alreadyLoadedAtInitiate: true)) + } + + @Test("再認証を行った後にアカウントを削除し認証トークンの無効化を行いログアウト状態にすることで、退会処理を完了させる") + func deleteAccount() async throws { + + // Arrange + let inputNonce = SignInWithAppleNonce(original: "originalTestNonce", sha256: "sha256TestNonce") + let inputSignInWithAppleResult = SignInWithAppleResult( + tokenId: "testToken", + nonce: inputNonce.sha256, + authorizationCode: "testCode" + ) + + try await confirmation(expectedCount: 6) { confirmation in + + let store = AccountAuthStore( + accountAuthClient: .init( + reauthenticateWithApple: { result in + + confirmation() + #expect(result == inputSignInWithAppleResult) + }, + revokeAppleToken: { code in + + confirmation() + #expect(code == inputSignInWithAppleResult.authorizationCode) + }, + deleteAccount: { + + confirmation() + }, + ), + analyticsClient: .init( + log: { event in + + confirmation() + #expect(event == .deleteAccount()) + } + ), + signInWithAppleClient: .init { nonce in + + confirmation() + #expect(nonce == inputNonce) + return inputSignInWithAppleResult + }, + nonceGenerationClient: .init { + + confirmation() + return inputNonce + }, + currentAuth: .init(result: .init(id: "test"), alreadyLoadedAtInitiate: true) + ) + + // Act + try await store.deleteAccount() + + // Assert + #expect(store.currentAuth == .init(result: nil, alreadyLoadedAtInitiate: true)) + } + } +} diff --git a/LocalPackage/Tests/HometeDomainTests/AccountStoreTest.swift b/LocalPackage/Tests/HometeDomainTests/AccountStoreTest.swift new file mode 100644 index 00000000..81cb2beb --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/AccountStoreTest.swift @@ -0,0 +1,113 @@ +// +// AccountStoreTest.swift +// hometeTests +// +// Created by 佐藤汰一 on 2025/08/09. +// + +import Testing +@testable import HometeDomain + +@MainActor +struct AccountStoreTest { + + @Test("アカウント情報をロードし、アカウントがある場合はアカウント情報を返す") + func load() async throws { + + // Arrange + let inputAccountId = "test" + let inputAccount = Account( + id: inputAccountId, + userName: "testUserName", + fcmToken: "testToken", + cohabitantId: nil + ) + + await confirmation(expectedCount: 1) { confirmation in + + let inputAuthResult = AccountAuthResult(id: "test") + let accountInfoClient = AccountInfoClient(fetch: { + + confirmation() + #expect($0 == inputAuthResult.id) + return inputAccount + }) + let store = AccountStore(accountInfoClient: accountInfoClient) + + // Act + let actual = await store.load(inputAuthResult) + + // Assert + #expect(actual == inputAccount) + } + } + + @Test("サーバーにログイン情報がありFCMトークンが更新されている場合アカウントに紐づくFCMトークンを更新") + func updateFcmTokenIfNeeded() async { + + await confirmation(expectedCount: 1) { confirmation in + + // Arrange + let inputFcmToken = "token" + let initialAccount = Account(id: "testId", userName: "testUser", fcmToken: nil, cohabitantId: nil) + let expectedAccount = Account( + id: initialAccount.id, + userName: initialAccount.userName, + fcmToken: inputFcmToken, + cohabitantId: nil + ) + let accountInfoClient = AccountInfoClient(insertOrUpdate: { + + confirmation() + #expect($0 == expectedAccount) + }) + let store = AccountStore( + accountInfoClient: accountInfoClient, + account: initialAccount + ) + + // Act + await store.updateFcmTokenIfNeeded(inputFcmToken) + + // Assert + #expect(store.account == expectedAccount) + } + } + + @Test("パートナーの登録で保持しているアカウントにパートナーグループIDの情報を更新する") + func registerCohabitantId() async throws { + + try await confirmation(expectedCount: 1) { confirmation in + + // Arrange + let inputCohabitantId = "testCohabitantId" + let initialAccount = Account( + id: "testId", + userName: "testUser", + fcmToken: nil, + cohabitantId: nil + ) + let expectedAccount = Account( + id: initialAccount.id, + userName: initialAccount.userName, + fcmToken: nil, + cohabitantId: inputCohabitantId + ) + let accountInfoClient = AccountInfoClient(insertOrUpdate: { + + confirmation() + #expect($0 == expectedAccount) + }) + let store = AccountStore( + accountInfoClient: accountInfoClient, + account: initialAccount + ) + + // Act + try await store.registerCohabitantId(inputCohabitantId) + + // Assert + #expect(store.account == expectedAccount) + } + } +} diff --git a/LocalPackage/Tests/HometeDomainTests/CohabitantRegistration/CohabitantRegistrationMessageTests.swift b/LocalPackage/Tests/HometeDomainTests/CohabitantRegistration/CohabitantRegistrationMessageTests.swift new file mode 100644 index 00000000..79a37684 --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/CohabitantRegistration/CohabitantRegistrationMessageTests.swift @@ -0,0 +1,183 @@ +// +// CohabitantRegistrationMessageTests.swift +// hometeTests +// +// Created by 佐藤汰一 on 2025/08/31. +// + +import Foundation +import Testing +@testable import HometeDomain + +struct CohabitantRegistrationMessageTests { + + @Test( + "メンバー確認のメッセージの場合、メンバー確定するかどうかが取得できること", + arguments: [true, false] + ) + func isFixedMember_typeIsFixedMember(input: Bool) { + + let message = CohabitantRegistrationMessage(type: .fixedMember(isOK: input)) + + let actual = message.isFixedMember + + #expect(actual == input) + } + + @Test( + "メンバー確認のメッセージ以外の場合、nilを返す", + arguments: [ + CohabitantRegistrationMessage.CommunicateType.complete, + .preRegistration(role: .lead), + .preRegistration(role: .follower(accountId: "id")), + .shareCohabitantId(id: "id") + ] + ) + func isFixedMember_typeIsNotFixedMember( + inputMessageType: CohabitantRegistrationMessage.CommunicateType + ) { + + let message = CohabitantRegistrationMessage(type: inputMessageType) + + let actual = message.isFixedMember + + #expect(actual == nil) + } + + @Test( + "登録処理開始前のメッセージの場合、送信者の役割を取得できる", + arguments: [ + CohabitantRegistrationRole.lead, + .follower(accountId: "id") + ] + ) + func memberRole_typeIsPreRegistration(inputRole: CohabitantRegistrationRole) { + + let message = CohabitantRegistrationMessage(type: .preRegistration(role: inputRole)) + + let actual = message.memberRole + + #expect(actual == inputRole) + } + + @Test( + "登録処理開始前のメッセージ以外の場合、nilを返す", + arguments: [ + CohabitantRegistrationMessage.CommunicateType.complete, + .fixedMember(isOK: true), + .shareCohabitantId(id: "id") + ] + ) + func memberRole_typeIsNotPreRegistration( + inputMessageType: CohabitantRegistrationMessage.CommunicateType + ) { + + let message = CohabitantRegistrationMessage(type: inputMessageType) + + let actual = message.memberRole + + #expect(actual == nil) + } + + @Test("同居人ID共有メッセージの場合、共有された同居人IDを取得できる") + func cohabitantId_typeIsShareCohabitantId() { + + let inputId = "test_id" + let message = CohabitantRegistrationMessage(type: .shareCohabitantId(id: inputId)) + + let actual = message.cohabitantId + + #expect(actual == inputId) + } + + @Test( + "同居人ID共有メッセージ以外の場合、nilを返す", + arguments: [ + CohabitantRegistrationMessage.CommunicateType.complete, + .preRegistration(role: .lead), + .preRegistration(role: .follower(accountId: "id")), + .fixedMember(isOK: true) + ] + ) + func cohabitantId_typeIsNotShareCohabitantId( + inputMessageType: CohabitantRegistrationMessage.CommunicateType + ) { + + let message = CohabitantRegistrationMessage(type: inputMessageType) + + let actual = message.cohabitantId + + #expect(actual == nil) + } + + @Test("登録処理完了メッセージの場合、完了かどうかを取得する") + func isComplete_typeIsComplete() { + let message = CohabitantRegistrationMessage(type: .complete) + + let actual = message.isComplete + + #expect(actual == true) + } + + @Test( + "登録処理完了メッセージ以外の場合、nilを返す", + arguments: [ + CohabitantRegistrationMessage.CommunicateType.preRegistration(role: .lead), + .preRegistration(role: .follower(accountId: "id")), + .fixedMember(isOK: true), + .shareCohabitantId(id: "id") + ] + ) + func isComplete_typeIsNotComplete_returnsNil( + inputMessageType: CohabitantRegistrationMessage.CommunicateType + ) { + + let message = CohabitantRegistrationMessage(type: inputMessageType) + + let actual = message.isComplete + + #expect(actual == nil) + } + + @Test( + "同居人登録のメッセージはJSONエンコードされたDataが送信されること", + arguments: [ + CohabitantRegistrationMessage.CommunicateType.complete, + .fixedMember(isOK: true), + .fixedMember(isOK: false), + .preRegistration(role: .lead), + .preRegistration(role: .follower(accountId: "id")), + .shareCohabitantId(id: "id") + ] + ) + func encodeDecode(type: CohabitantRegistrationMessage.CommunicateType) throws { + + let message = CohabitantRegistrationMessage(type: type) + + let actual = message.encodedData() + + let expected = try JSONEncoder().encode(message) + #expect(actual == expected) + } + + @Test( + "JSONエンコードされた同居人登録のメッセージを元の形式のでコードできること", + arguments: [ + CohabitantRegistrationMessage.CommunicateType.complete, + .fixedMember(isOK: true), + .fixedMember(isOK: false), + .preRegistration(role: .lead), + .preRegistration(role: .follower(accountId: "id")), + .shareCohabitantId(id: "id") + ] + ) + func init_withValidData(type: CohabitantRegistrationMessage.CommunicateType) throws { + + let message = CohabitantRegistrationMessage(type: type) + let encodedData = try JSONEncoder().encode(message) + + let actual = CohabitantRegistrationMessage(encodedData) + + #expect(actual == message) + } +} diff --git a/LocalPackage/Tests/HometeDomainTests/CohabitantRegistration/ConfirmedRegistrationPeersTest.swift b/LocalPackage/Tests/HometeDomainTests/CohabitantRegistration/ConfirmedRegistrationPeersTest.swift new file mode 100644 index 00000000..ed3c64e3 --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/CohabitantRegistration/ConfirmedRegistrationPeersTest.swift @@ -0,0 +1,71 @@ +// +// ConfirmedRegistrationPeersTest.swift +// hometeTests +// +// Created by 佐藤汰一 on 2025/08/31. +// + +import MultipeerConnectivity +import Testing +@testable import HometeDomain + +struct ConfirmedRegistrationPeersTest { + + @Test("PeerIDの表示名の降順で一番最初のPeerIDになるデバイスがリードデバイスになる") + func isLeadPeer_lead_case() throws { + + let connectedPeers: Set = [ + .init(displayName: "BBB"), + .init(displayName: "CCC"), + .init(displayName: "EEE") + ] + let peers = ConfirmedRegistrationPeers(peers: connectedPeers) + + let actual = peers.isLeadPeer( + connectedPeers: connectedPeers, + myPeerID: .init(displayName: "AAA") + ) + + #expect(actual == true) + } + + @Test( + "PeerIDの表示名の降順で一番最初ではない場合は、フォロワーデバイスになる", + arguments: [ + "DDD", + "FFF", + "CCC", + "ZZZ" + ] + ) + func isLeadPeer_follower_case(myPeerIDString: String) throws { + + let connectedPeers: Set = [ + .init(displayName: "BBB"), + .init(displayName: "CCC"), + .init(displayName: "EEE") + ] + let peers = ConfirmedRegistrationPeers(peers: connectedPeers) + + let actual = peers.isLeadPeer(connectedPeers: connectedPeers, myPeerID: .init(displayName: myPeerIDString)) + + #expect(actual == false) + } + + @Test( + "接続中の全てのデバイスが確認済みでない場合は、まだ登録メンバーが揃っていないのでリードデバイスかどうかを返さない" + ) + func isLeadPeer_not_match_case() throws { + + let confirmedPeers: Set = [ + .init(displayName: "BBB"), + .init(displayName: "CCC") + ] + let connectedPeers: Set = .init(confirmedPeers + [.init(displayName: "EEE")]) + let peers = ConfirmedRegistrationPeers(peers: confirmedPeers) + + let actual = peers.isLeadPeer(connectedPeers: connectedPeers, myPeerID: .init(displayName: "AAA")) + + #expect(actual == nil) + } +} diff --git a/LocalPackage/Tests/HometeDomainTests/CohabitantStoreTest.swift b/LocalPackage/Tests/HometeDomainTests/CohabitantStoreTest.swift new file mode 100644 index 00000000..88919205 --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/CohabitantStoreTest.swift @@ -0,0 +1,78 @@ +// +// CohabitantStoreTest.swift +// hometeTests +// +// Created by Taichi Sato on 2026/01/12. +// + +import Testing +import Observation +@testable import HometeDomain + +@MainActor +struct CohabitantStoreTest { + + private let inputCohabitantId = "testCohabitantId" + private let inputListenerId = "cohabitantListenerKey" + + @Test("パートナーの監視中に、まだキャッシュしていないメンバーの場合はパートナーのリストにキャッシュとして追加する") + func addSnapshotListenerIfNeeded_add_member_case() async throws { + + // Arrange + + let newMemberId = "newMemberId" + let newMemberUserName = "新しいメンバー" + let expectedAccount = Account( + id: newMemberId, + userName: newMemberUserName, + fcmToken: nil, + cohabitantId: inputCohabitantId + ) + let inputCohabitantData = CohabitantData( + id: inputCohabitantId, + members: [newMemberId] + ) + + let (stream, continuation) = AsyncStream<[CohabitantData]>.makeStream() + + let store = CohabitantStore( + cohabitantClient: .init( + addSnapshotListener: { listenerId, cohabitantId in + + #expect(listenerId == inputListenerId) + #expect(cohabitantId == inputCohabitantId) + return stream + } + ), + accountInfoClient: .init(fetch: { userId in + + // Assert + + #expect(userId == newMemberId) + return expectedAccount + }) + ) + + // Act + + await store.addSnapshotListenerIfNeeded(inputCohabitantId) + + // Assert + + let waiterForUpdateMembers = Task { + await withCheckedContinuation { continuation in + ObservationHelper.continuousObservationTracking({ store.members }) { + continuation.resume(returning: ()) + } + } + } + + continuation.yield([inputCohabitantData]) + await waiterForUpdateMembers.value + continuation.finish() + await store.removeSnapshotListener() + + #expect(store.members.value.count == 1) + #expect(store.members.value.contains(.init(id: newMemberId, userName: newMemberUserName))) + } +} diff --git a/LocalPackage/Tests/HometeDomainTests/Housework/DailyHouseworkListTest.swift b/LocalPackage/Tests/HometeDomainTests/Housework/DailyHouseworkListTest.swift new file mode 100644 index 00000000..654a7879 --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/Housework/DailyHouseworkListTest.swift @@ -0,0 +1,103 @@ +// +// DailyHouseworkListTest.swift +// hometeTests +// +// Created by 佐藤汰一 on 2025/09/08. +// + +import Foundation +import Testing + +@testable import HometeDomain + +// swiftlint:disable:next convenience_type +struct DailyHouseworkListTest { + + struct MakeInitialValueCase {} + struct IsRegisteredCase {} + struct IsAlreadyRegisteredCase {} +} + +extension DailyHouseworkListTest.MakeInitialValueCase { + + @Test("一日の家事情報の保持期限は3か月後になる") + func makeInitialValue() throws { + + // Arrange + let calendar = Calendar.japanese + let selectedDate = Date() + let expectedIndexedDate = HouseworkIndexedDate(selectedDate, calendar: calendar) + let expectedExpiredAt = try #require(calendar.date(byAdding: .month, value: 1, to: selectedDate)) + + let expectedList = DailyHouseworkList( + items: [], + metaData: .init(indexedDate: expectedIndexedDate, expiredAt: expectedExpiredAt) + ) + + // Act + let list = DailyHouseworkList.makeInitialValue( + selectedDate: selectedDate, + items: [], + calendar: calendar + ) + + // Assert + #expect(list == expectedList) + } +} + +extension DailyHouseworkListTest.IsRegisteredCase { + + @Test( + "登録されている家事がない場合は、その日付の家事レコードが登録されていない", + arguments: [ + [HouseworkItem.makeForTest(id: 1, title: "洗濯", point: 1)], + [] + ] + ) + func isRegistered(inputItems: [HouseworkItem]) { + + // Arrange + let list = DailyHouseworkList( + items: inputItems, + metaData: .init(indexedDate: .init(.now, calendar: .japanese), expiredAt: .now) + ) + + // Act + let result = list.isRegistered + + // Assert + #expect(result == !inputItems.isEmpty) + } +} + +extension DailyHouseworkListTest.IsAlreadyRegisteredCase { + + @Test( + "同じタイトルの家事が含まれていれば登録済みの家事とみなす", + arguments: [ + HouseworkItem.makeForTest(id: 1, title: "洗濯", point: 1), + .makeForTest(id: 3, title: "洗濯", point: 1), + .makeForTest(id: 1, title: "掃除", point: 1) + ] + ) + func alreadyRegistered_trueWhenSameTitleExists(inputItem: HouseworkItem) { + + // Arrange + let items: [HouseworkItem] = [ + .makeForTest(id: 1, title: "洗濯", point: 1), + .makeForTest(id: 2, title: "ゴミ捨て", point: 1, state: .completed) + ] + let list = DailyHouseworkList( + items: items, + metaData: .init(indexedDate: .init(.now, calendar: .japanese), expiredAt: .now) + ) + + // Act + let result = list.isAlreadyRegistered(inputItem) + + // Assert + let expected = items.contains { $0.title == inputItem.title } + #expect(result == expected) + } +} diff --git a/LocalPackage/Tests/HometeDomainTests/Housework/HouseworkBoardListTest.swift b/LocalPackage/Tests/HometeDomainTests/Housework/HouseworkBoardListTest.swift new file mode 100644 index 00000000..cc2fb36d --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/Housework/HouseworkBoardListTest.swift @@ -0,0 +1,85 @@ +// +// HouseworkBoardListTest.swift +// hometeTests +// +// Created by 佐藤汰一 on 2025/10/02. +// + +import Foundation +import Testing + +@testable import HometeDomain + +struct HouseworkBoardListTest { + + private static let calendar = Calendar.autoupdatingCurrent + + @Test( + "選択している日付のみを家事ボードに表示する", + arguments: [ + Date.now, + Date.distantFuture, + Date.distantPast + ] + ) + func initialize(selectTime: Date) async throws { + + // Arrange + let inputSelectedDateItemList: [HouseworkItem] = [ + .makeForTest(id: 1, indexedDate: selectTime), + .makeForTest(id: 2, indexedDate: selectTime) + ] + let inputUnselectedDateItemList: [HouseworkItem] = [ + .makeForTest( + id: 1, + indexedDate: Self.calendar.date(bySetting: .day, value: -3, of: .now) ?? .now + ) + ] + let inputList: [DailyHouseworkList] = [ + .makeForTest(items: inputSelectedDateItemList), + .makeForTest(items: inputUnselectedDateItemList) + ] + + // Act + let actual = HouseworkBoardList( + dailyList: inputList, + selectedDate: selectTime, + calendar: .japanese + ) + + // Assert + let expected = HouseworkBoardList(items: inputSelectedDateItemList) + #expect(actual == expected) + } + + @Test( + "指定した状態にマッチする家事を取得する", + arguments: HouseworkState.allCases + ) + func items_match_state(expectedState: HouseworkState) async throws { + + // Arrange + let inputHouseworkItem = makeHouseworkItemListWithAllState() + let houseworkBoardList = HouseworkBoardList( + dailyList: [.makeForTest(items: inputHouseworkItem)], + selectedDate: .now, + calendar: .japanese + ) + + // Act + let actual = houseworkBoardList.items(matching: expectedState) + + // Assert + let expected = inputHouseworkItem.filter { $0.state == expectedState } + #expect(actual == expected) + } +} + +private extension HouseworkBoardListTest { + + func makeHouseworkItemListWithAllState() -> [HouseworkItem] { + HouseworkState.allCases.enumerated().flatMap { index, state in + (1...3).map { HouseworkItem.makeForTest(id: index + 1 + $0, state: state) } + } + } +} diff --git a/LocalPackage/Tests/HometeDomainTests/Housework/HouseworkHistoryListTest.swift b/LocalPackage/Tests/HometeDomainTests/Housework/HouseworkHistoryListTest.swift new file mode 100644 index 00000000..1c73ded5 --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/Housework/HouseworkHistoryListTest.swift @@ -0,0 +1,104 @@ +// +// HouseworkHistoryListTest.swift +// hometeTests +// +// Created by 佐藤汰一 on 2025/09/07. +// + +import Testing +@testable import HometeDomain + +// swiftlint:disable:next convenience_type +struct HouseworkHistoryListTest { + + struct MoveToFrontIfExistsCase {} + struct AddNewHistoryCase {} +} + +extension HouseworkHistoryListTest.MoveToFrontIfExistsCase { + + @Test("存在する要素を指定すると先頭へ移動する") + func moveExistingItemToFront() { + + // Arrange + var list = HouseworkHistoryList(items: ["1", "2", "3"]) + let target = "2" + let expected = HouseworkHistoryList(items: ["2", "1", "3"]) + + // Act + list.moveToFrontIfExists(target) + + // Assert + #expect(list == expected) + } + + @Test("既に先頭の要素を指定しても変更されない") + func noChangeWhenItemAlreadyAtFront() { + + // Arrange + let initial = HouseworkHistoryList(items: ["a", "b", "c"]) + var list = initial + let target = "a" + + // Act + list.moveToFrontIfExists(target) + + // Assert + #expect(list == initial) + } + + @Test("存在しない要素を指定しても変更されない") + func noChangeWhenItemDoesNotExist() { + + // Arrange + let initial = HouseworkHistoryList(items: ["x", "y", "z"]) + var list = initial + let target = "w" + + // Act + list.moveToFrontIfExists(target) + + // Assert + #expect(list == initial) + } +} + +extension HouseworkHistoryListTest.AddNewHistoryCase { + + @Test( + "履歴に存在しない要素を追加する場合、その要素が先頭に追加される", + arguments: [ + ["洗濯", "皿洗い"], + [] + ] + ) + func addNewItem(initialList: [String]) { + + // Arrange + let value = "掃除" + var list = HouseworkHistoryList(items: initialList) + let expected = HouseworkHistoryList(items: ["掃除"] + initialList) + + // Act + list.addNewHistory(value) + + // Assert + #expect(list == expected) + } + + @Test("既に存在する要素を追加する場合、リストは変更されない") + func addValueAlreadyAtFrontDoesNotChange() { + + // Arrange + let initial = HouseworkHistoryList(items: ["洗濯", "掃除", "皿洗い"]) + var list = initial + let value = "掃除" + + // Act + list.addNewHistory(value) + + // Assert + let expected = HouseworkHistoryList(items: ["掃除", "洗濯", "皿洗い"]) + #expect(list == expected) + } +} diff --git a/LocalPackage/Tests/HometeDomainTests/Housework/HouseworkIndexedDate.swift b/LocalPackage/Tests/HometeDomainTests/Housework/HouseworkIndexedDate.swift new file mode 100644 index 00000000..7a89f321 --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/Housework/HouseworkIndexedDate.swift @@ -0,0 +1,87 @@ +// +// HouseworkIndexedDate.swift +// hometeTests +// +// Created by 佐藤汰一 on 2025/12/06. +// + +import Foundation +import Testing +@testable import HometeDomain + +enum HouseworkIndexedDateTest { + + struct InitCase {} + struct CalcTargetPeriodCase {} +} + +// MARK: - InitCase + +extension HouseworkIndexedDateTest.InitCase { + + @Test(arguments: [ + Date.distantPast, + .init(timeIntervalSince1970: .zero), + .dateComponents(year: 2025, month: 1, day: 1), + .distantFuture + ]) + func init_parse_date(inputDate: Date) async throws { + let indexedDate = HouseworkIndexedDate(inputDate, calendar: .japanese) + + let expected = inputDate.formatted( + Date.FormatStyle(date: .numeric, time: .omitted, calendar: .japanese, timeZone: .tokyo) + .year(.extended(minimumLength: 4)) + .month(.twoDigits) + .day(.twoDigits) + .locale(.jp) + ) + #expect(indexedDate.value == expected) + } +} + +// MARK: - CalcTargetPeriodCase + +extension HouseworkIndexedDateTest.CalcTargetPeriodCase { + + @Test("指定の日付から指定の期間の日付情報を返す") + func calcTargetPeriod() { + + // Arrange + let calendar = Calendar.japanese + + // Act + let result = HouseworkIndexedDate.calcTargetPeriod( + anchorDate: .dateComponents(year: 2026, month: 2, day: 1), + offsetDays: 2, + calendar: calendar + ) + + // Assert + let expected = [ + ["value": "2026/01/30"], + ["value": "2026/01/31"], + ["value": "2026/02/01"], + ["value": "2026/02/02"], + ["value": "2026/02/03"] + ] + #expect(result == expected) + } + + @Test("指定の期間が0以下の場合、基準日付のみ返す") + func calcTargetPeriod_zero_offset() { + + // Arrange + let calendar = Calendar.japanese + + // Act + let result = HouseworkIndexedDate.calcTargetPeriod( + anchorDate: .dateComponents(year: 2026, month: 1, day: 15), + offsetDays: 0, + calendar: calendar + ) + + // Assert + let expected = [["value": "2026/01/15"]] + #expect(result == expected) + } +} diff --git a/LocalPackage/Tests/HometeDomainTests/Housework/HouseworkItemTest.swift b/LocalPackage/Tests/HometeDomainTests/Housework/HouseworkItemTest.swift new file mode 100644 index 00000000..6a446531 --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/Housework/HouseworkItemTest.swift @@ -0,0 +1,197 @@ +// +// HouseworkItemTest.swift +// hometeTests +// +// Created by 佐藤汰一 on 2025/09/08. +// + +import Foundation +import Testing + +@testable import HometeDomain + +enum HouseworkItemTest { + + struct CanReviewCase {} + struct UpdateStateCase {} +} + +extension HouseworkItemTest.CanReviewCase { + + @Test( + "担当者が自分以外かつ未完了の場合、レビュー可能", + arguments: [HouseworkState.incomplete, .pendingApproval] + ) + func canReview_notOwnUserAndNotCompleted_returnsTrue(state: HouseworkState) { + + // Arrange + let item = HouseworkItem.makeForTest( + id: 1, + state: state, + executorId: "otherUserId" + ) + + // Act + let result = item.canReview(ownUserId: "ownUserId") + + // Assert + #expect(result == true) + } + + @Test( + "担当者が自分以外でも完了済みの場合、レビュー不可", + arguments: ["otherUserId", nil] + ) + func canReview_completedState_returnsFalse(executorId: String?) { + + // Arrange + let item = HouseworkItem.makeForTest( + id: 1, + state: .completed, + executorId: executorId + ) + + // Act + let result = item.canReview(ownUserId: "ownUserId") + + // Assert + #expect(result == false) + } + + @Test( + "担当者が自分の場合、未完了でもレビュー不可", + arguments: HouseworkState.allCases + ) + func canReview_ownUser_returnsFalse(state: HouseworkState) { + + // Arrange + let ownUserId = "ownUserId" + let item = HouseworkItem.makeForTest( + id: 1, + state: state, + executorId: ownUserId + ) + + // Act + let result = item.canReview(ownUserId: ownUserId) + + // Assert + #expect(result == false) + } +} + +// MARK: - UpdateStateCase + +extension HouseworkItemTest.UpdateStateCase { + + @Test("承認待ち状態に更新すると、state・executorId・executedAtが更新される") + func updatePendingApproval_updatesStateAndExecutorInfo() { + + // Arrange + let indexedDate = Date() + let expiredAt = Date().addingTimeInterval(3600) + let item = HouseworkItem.makeForTest( + id: 1, + indexedDate: indexedDate, + title: "洗濯", + point: 100, + state: .incomplete, + expiredAt: expiredAt + ) + let now = Date() + let changerId = "changerId" + + // Act + let result = item.updatePendingApproval(at: now, changer: changerId) + + // Assert + let expected = HouseworkItem.makeForTest( + id: 1, + indexedDate: indexedDate, + title: "洗濯", + point: 100, + state: .pendingApproval, + executorId: changerId, + executedAt: now, + expiredAt: expiredAt + ) + #expect(result == expected) + } + + @Test("承認すると、state・reviewerId・approvedAt・reviewerCommentが更新される") + func updateApproved_updatesStateAndReviewerInfo() { + + // Arrange + let indexedDate = Date() + let expiredAt = Date().addingTimeInterval(3600) + let executorId = "executorId" + let executedAt = Date().addingTimeInterval(-3600) + let item = HouseworkItem.makeForTest( + id: 1, + indexedDate: indexedDate, + title: "洗濯", + point: 100, + state: .pendingApproval, + executorId: executorId, + executedAt: executedAt, + expiredAt: expiredAt + ) + let now = Date() + let reviewerId = "reviewerId" + let comment = "よくできました" + + // Act + let result = item.updateApproved(at: now, reviewer: reviewerId, comment: comment) + + // Assert + let expected = HouseworkItem.makeForTest( + id: 1, + indexedDate: indexedDate, + title: "洗濯", + point: 100, + state: .completed, + executorId: executorId, + executedAt: executedAt, + reviewerId: reviewerId, + approvedAt: now, + reviewerComment: comment, + expiredAt: expiredAt + ) + #expect(result == expected) + } + + @Test("未完了状態に戻すと、stateがincompleteになり実行者・承認者情報がクリアされる") + func updateIncomplete_clearsExecutorAndReviewerInfo() { + + // Arrange + let indexedDate = Date() + let expiredAt = Date().addingTimeInterval(3600) + let item = HouseworkItem.makeForTest( + id: 1, + indexedDate: indexedDate, + title: "洗濯", + point: 100, + state: .completed, + executorId: "executorId", + executedAt: Date(), + reviewerId: "reviewerId", + approvedAt: Date(), + reviewerComment: "コメント", + expiredAt: expiredAt + ) + + // Act + let result = item.updateIncomplete() + + // Assert + let expected = HouseworkItem.makeForTest( + id: 1, + indexedDate: indexedDate, + title: "洗濯", + point: 100, + state: .incomplete, + expiredAt: expiredAt + ) + #expect(result == expected) + } +} diff --git a/LocalPackage/Tests/HometeDomainTests/Housework/HouseworkListStoreTest.swift b/LocalPackage/Tests/HometeDomainTests/Housework/HouseworkListStoreTest.swift new file mode 100644 index 00000000..52ae4fdb --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/Housework/HouseworkListStoreTest.swift @@ -0,0 +1,414 @@ +// +// HouseworkListStoreTest.swift +// hometeTests +// +// Created by 佐藤汰一 on 2025/09/29. +// + +import Foundation +import Testing + +@testable import HometeDomain + +@MainActor +struct HouseworkListStoreTest { + + private let inputId = "houseworkObserveKey" + private let inputCohabitantId = "cohabitantId" + + @MainActor + struct UpdateStatusCase { + + private let inputId = "houseworkObserveKey" + private let inputCohabitantId = "cohabitantId" + } + + @Test("家事リストのロードを行うと最新の家事リストを常に監視し続ける") + func loadHouseworkList() async throws { + + // Arrange + + let now = Date() + let calendar = Calendar.autoupdatingCurrent + let (stream, continuation) = AsyncStream<[HouseworkItem]>.makeStream() + let store = HouseworkListStore( + houseworkClient: .init(snapshotListenerHandler: { id, cohabitantId, anchorDate, offset in + + #expect(id == inputId) + #expect(cohabitantId == inputCohabitantId) + #expect(anchorDate == now) + #expect(offset == 3) + return stream + }), + cohabitantPushNotificationClient: .previewValue + ) + + // Act + + await store.loadHouseworkList( + currentTime: now, + cohabitantId: inputCohabitantId, + calendar: calendar + ) + + // Assert + + let waiterForUpdateItems = Task { + await withCheckedContinuation { continuation in + ObservationHelper.continuousObservationTracking({ store.items }) { + continuation.resume(returning: ()) + } + } + } + + let inputHouseworkList: [HouseworkItem] = [ + .makeForTest(id: 1, indexedDate: now, expiredAt: now) + ] + continuation.yield(inputHouseworkList) + await waiterForUpdateItems.value + continuation.finish() + await store.stopObserving() + + #expect( + store.items == .init(value: [ + .init( + items: inputHouseworkList, + metaData: .init( + indexedDate: .init(.now, calendar: .japanese), + expiredAt: now + ) + ) + ]) + ) + } + + @Test("新しい家事の登録すると、パートナーに通知を送信する") + func register() async throws { + + // Arrange + + let inputHouseworkItem = HouseworkItem.makeForTest(id: 1) + let expectedNotificationContent = PushNotificationContent( + title: "新しい家事が登録されました", + message: inputHouseworkItem.title + ) + + await confirmation(expectedCount: 2) { confirmation in + + let _: Void = await withCheckedContinuation { continuation in + + let store = HouseworkListStore( + houseworkClient: .init(insertOrUpdateItemHandler: { item, cohabitantId in + + // Assert + + #expect(item == inputHouseworkItem) + #expect(cohabitantId == inputCohabitantId) + confirmation() + }), + cohabitantPushNotificationClient: .init { id, content in + + // Assert + + #expect(id == inputCohabitantId) + #expect(content == expectedNotificationContent) + confirmation() + continuation.resume() + }, + cohabitantId: inputCohabitantId + ) + + // Act + + Task { + + try await store.register(inputHouseworkItem) + } + } + } + } +} + +extension HouseworkListStoreTest.UpdateStatusCase { + + @Test("家事の完了確認を依頼すると、パートナーにその旨Push通知を送信する") + func requestReview() async throws { + + // Arrange + + let inputHouseworkItem = HouseworkItem.makeForTest(id: 1) + let expectedNotificationContent = PushNotificationContent( + title: "確認が必要な家事があります", + message: "問題なければ「\(inputHouseworkItem.title)」の完了に感謝を伝えましょう!" + ) + let requestedAt = Date() + let inputExecutor = "dummyExecutor" + let updatedHouseworkItem = inputHouseworkItem.updateProperties( + state: .pendingApproval, + executorId: inputExecutor, + executedAt: requestedAt + ) + + await confirmation(expectedCount: 2) { confirmation in + + let _: Void = await withCheckedContinuation { continuation in + + let store = HouseworkListStore( + houseworkClient: .init( + insertOrUpdateItemHandler: { item, cohabitantId in + + // Assert + + #expect(item == updatedHouseworkItem) + #expect(cohabitantId == inputCohabitantId) + confirmation() + } + ), + cohabitantPushNotificationClient: .init { id, content in + + // Assert + + #expect(id == inputCohabitantId) + #expect(content == expectedNotificationContent) + confirmation() + continuation.resume() + }, + items: [.makeForTest(items: [inputHouseworkItem])], + cohabitantId: inputCohabitantId + ) + + // Act + + Task { + + try await store.requestReview( + target: inputHouseworkItem, + now: requestedAt, + executor: inputExecutor + ) + } + } + } + } + + @Test("実施者、実施日をクリアして家事のステータスを未完了に戻す") + func returnToIncomplete() async throws { + + // Arrange + + let inputHouseworkItem = HouseworkItem.makeForTest( + id: 1, + state: .pendingApproval, + executorId: "dummyExecutor", + executedAt: .distantPast + ) + let updatedHouseworkItem = inputHouseworkItem.updateProperties( + state: .incomplete, + executorId: nil, + executedAt: nil + ) + + try await confirmation(expectedCount: 1) { confirmation in + + let store = HouseworkListStore( + houseworkClient: .init( + insertOrUpdateItemHandler: { item, cohabitantId in + + // Assert + + #expect(item == updatedHouseworkItem) + #expect(cohabitantId == inputCohabitantId) + confirmation() + } + ), + cohabitantPushNotificationClient: .init { _, _ in + + Issue.record() + }, + items: [.makeForTest(items: [inputHouseworkItem])], + cohabitantId: inputCohabitantId + ) + + // Act + + try await store.returnToIncomplete(target: inputHouseworkItem) + } + } + + @Test("家事削除時は家事を削除するAPIを実行する") + func remove() async throws { + + // Arrange + + let inputHouseworkItem = HouseworkItem.makeForTest(id: 1) + + try await confirmation { confirmation in + + let store = HouseworkListStore( + houseworkClient: .init(removeItemHandler: { item, cohabitantId in + + // Assert + + #expect(item == inputHouseworkItem) + #expect(cohabitantId == inputCohabitantId) + confirmation() + }), + cohabitantPushNotificationClient: .previewValue, + items: [.makeForTest(items: [inputHouseworkItem])], + cohabitantId: inputCohabitantId + ) + + // Act + + try await store.remove(inputHouseworkItem) + } + } + + @Test("家事を承認すると、承認情報を更新しパートナーに通知を送信する") + // swiftlint:disable:next function_body_length + func approved() async throws { + + // Arrange + + let inputHouseworkItem = HouseworkItem.makeForTest( + id: 1, + state: .pendingApproval, + executorId: "executorId", + executedAt: .distantPast + ) + let approvedAt = Date() + let inputReviewer = Account( + id: "reviewerId", + userName: "レビュアー", + fcmToken: nil, + cohabitantId: inputCohabitantId + ) + let inputComment = "お疲れ様でした!" + let updatedHouseworkItem = inputHouseworkItem.updateApproved( + at: approvedAt, + reviewer: inputReviewer.id, + comment: inputComment + ) + let expectedNotificationContent = PushNotificationContent.approvedMessage( + reviwerName: inputReviewer.userName, + houseworkTitle: inputHouseworkItem.title, + comment: inputComment + ) + + await confirmation(expectedCount: 2) { confirmation in + + let _: Void = await withCheckedContinuation { continuation in + + let store = HouseworkListStore( + houseworkClient: .init( + insertOrUpdateItemHandler: { item, cohabitantId in + + // Assert + + #expect(item == updatedHouseworkItem) + #expect(cohabitantId == inputCohabitantId) + confirmation() + } + ), + cohabitantPushNotificationClient: .init { id, content in + + // Assert + + #expect(id == inputCohabitantId) + #expect(content == expectedNotificationContent) + confirmation() + continuation.resume() + }, + items: [.makeForTest(items: [inputHouseworkItem])], + cohabitantId: inputCohabitantId + ) + + // Act + + Task { + + try await store.approved( + target: inputHouseworkItem, + now: approvedAt, + reviwer: inputReviewer, + comment: inputComment + ) + } + } + } + } + + @Test("家事を却下すると、却下情報を更新しパートナーに通知を送信する") + // swiftlint:disable:next function_body_length + func rejected() async throws { + + // Arrange + + let inputHouseworkItem = HouseworkItem.makeForTest( + id: 1, + state: .pendingApproval, + executorId: "executorId", + executedAt: .distantPast + ) + let rejectedAt = Date() + let inputReviewer = Account( + id: "reviewerId", + userName: "レビュアー", + fcmToken: nil, + cohabitantId: inputCohabitantId + ) + let inputComment = "もう一度確認してください" + let updatedHouseworkItem = inputHouseworkItem.updateRejected( + at: rejectedAt, + reviewer: inputReviewer.id, + comment: inputComment + ) + let expectedNotificationContent = PushNotificationContent.rejectedMessage( + reviwerName: inputReviewer.userName, + houseworkTitle: inputHouseworkItem.title, + comment: inputComment + ) + + await confirmation(expectedCount: 2) { confirmation in + + let _: Void = await withCheckedContinuation { continuation in + + let store = HouseworkListStore( + houseworkClient: .init( + insertOrUpdateItemHandler: { item, cohabitantId in + + // Assert + + #expect(item == updatedHouseworkItem) + #expect(cohabitantId == inputCohabitantId) + confirmation() + } + ), + cohabitantPushNotificationClient: .init { id, content in + + // Assert + + #expect(id == inputCohabitantId) + #expect(content == expectedNotificationContent) + confirmation() + continuation.resume() + }, + items: [.makeForTest(items: [inputHouseworkItem])], + cohabitantId: inputCohabitantId + ) + + // Act + + Task { + + try await store.rejected( + target: inputHouseworkItem, + now: rejectedAt, + reviwer: inputReviewer, + comment: inputComment + ) + } + } + } + } +} diff --git a/LocalPackage/Tests/HometeDomainTests/Housework/StoredAllHouseworkListTest.swift b/LocalPackage/Tests/HometeDomainTests/Housework/StoredAllHouseworkListTest.swift new file mode 100644 index 00000000..b05fc108 --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/Housework/StoredAllHouseworkListTest.swift @@ -0,0 +1,78 @@ +// +// StoredAllHouseworkListTest.swift +// hometeTests +// +// Created by 佐藤汰一 on 2025/11/22. +// + +import Foundation +import Testing +@testable import HometeDomain + +struct StoredAllHouseworkListTest { + + private static let inputFirstDate = Date.dateComponents(year: 2025, month: 1, day: 1) + private static let inputSecondDate = Date.dateComponents(year: 2025, month: 1, day: 2) + + @Test("家事リストは家事の日付毎に保持される") + func makeMultiDateList() { + + let inputFirstHouseworkGroup = makeAllCasesItems( + at: Self.inputFirstDate, + offset: 0 + ) + let inputSecondHouseworkGroup = makeAllCasesItems( + at: Self.inputSecondDate, + offset: inputFirstHouseworkGroup.count + ) + let inputHouseworkItem = inputFirstHouseworkGroup + inputSecondHouseworkGroup + + let actual = StoredAllHouseworkList.makeMultiDateList( + items: inputHouseworkItem, + calendar: .current + ) + + let sortedActualValue = actual.value.sorted { $0.metaData.indexedDate.value < $1.metaData.indexedDate.value } + let sortedActual = StoredAllHouseworkList(value: sortedActualValue) + let expected = StoredAllHouseworkList(value: [ + .makeForTest(items: inputFirstHouseworkGroup), + .makeForTest(items: inputSecondHouseworkGroup) + ]) + #expect(sortedActual == expected) + } + + @Test( + "保持している家事リストからidと対象日に合致する単一の家事を取得する", + arguments: [ + HouseworkItem.makeForTest(id: 999, indexedDate: Self.inputFirstDate), + .makeForTest(id: 999, indexedDate: Date.now) + ] + ) + func items(expectedItem: HouseworkItem) { + + let inputFirstHouseworkGroup = makeAllCasesItems( + at: Self.inputFirstDate, + offset: 0 + ) + let list = StoredAllHouseworkList.makeMultiDateList( + items: (inputFirstHouseworkGroup + [expectedItem]).shuffled(), + calendar: .current + ) + + let actual = list.item(expectedItem) + + #expect(actual == expectedItem) + } +} + +private extension StoredAllHouseworkListTest { + + func makeAllCasesItems(at indexedDate: Date, offset: Int) -> [HouseworkItem] { + return [ + .makeForTest(id: offset + 1, indexedDate: indexedDate), + .makeForTest(id: offset + 2, indexedDate: indexedDate, state: .pendingApproval), + .makeForTest(id: offset + 3, indexedDate: indexedDate, state: .completed), + .makeForTest(id: offset + 4, indexedDate: indexedDate, executorId: "dummy", executedAt: .now) + ] + } +} diff --git a/LocalPackage/Tests/HometeDomainTests/LocalPackageTests.swift b/LocalPackage/Tests/HometeDomainTests/LocalPackageTests.swift deleted file mode 100644 index f1376e7e..00000000 --- a/LocalPackage/Tests/HometeDomainTests/LocalPackageTests.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Testing -@testable import LocalPackage - -@Test func example() async throws { - // Write your test here and use APIs like `#expect(...)` to check expected conditions. -} diff --git a/LocalPackage/Tests/HometeDomainTests/TestHelper/AccountListenerStreamCreater.swift b/LocalPackage/Tests/HometeDomainTests/TestHelper/AccountListenerStreamCreater.swift new file mode 100644 index 00000000..858e2ddc --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/TestHelper/AccountListenerStreamCreater.swift @@ -0,0 +1,18 @@ +// +// Account.swift +// homete +// +// Created by 佐藤汰一 on 2025/08/09. +// + +import Foundation +@testable import HometeDomain + +extension AccountListenerStream { + + static func defaultValue() -> Self { + + let (stream, continuation) = AsyncStream.makeStream() + return .init(values: stream, listenerToken: NSObject(), continuation: continuation) + } +} diff --git a/LocalPackage/Tests/HometeDomainTests/TestHelper/CalendarHelper.swift b/LocalPackage/Tests/HometeDomainTests/TestHelper/CalendarHelper.swift new file mode 100644 index 00000000..fca998a6 --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/TestHelper/CalendarHelper.swift @@ -0,0 +1,17 @@ +// +// CalendarHelper.swift +// homete +// +// Created by Taichi Sato on 2026/01/17. +// + +import Foundation + +extension Calendar { + static var japanese: Self { + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = .tokyo + calendar.locale = .jp + return calendar + } +} diff --git a/LocalPackage/Tests/HometeDomainTests/TestHelper/DailyHouseworkListHelper.swift b/LocalPackage/Tests/HometeDomainTests/TestHelper/DailyHouseworkListHelper.swift new file mode 100644 index 00000000..445c2ffa --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/TestHelper/DailyHouseworkListHelper.swift @@ -0,0 +1,18 @@ +// +// DailyHouseworkListHelper.swift +// homete +// +// Created by 佐藤汰一 on 2025/10/02. +// + +@testable import HometeDomain + +extension DailyHouseworkList { + + static func makeForTest(items: [HouseworkItem]) -> Self { + + let indexedDate = items[0].indexedDate + let expiredAt = items[0].expiredAt + return .init(items: items, metaData: .init(indexedDate: indexedDate, expiredAt: expiredAt)) + } +} diff --git a/LocalPackage/Tests/HometeDomainTests/TestHelper/DateHelper.swift b/LocalPackage/Tests/HometeDomainTests/TestHelper/DateHelper.swift new file mode 100644 index 00000000..42060991 --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/TestHelper/DateHelper.swift @@ -0,0 +1,31 @@ +// +// DateHelper.swift +// homete +// +// Created by 佐藤汰一 on 2025/11/22. +// + +import Foundation + +extension Date { + static func dateComponents( + year: Int, + month: Int, + day: Int, + hour: Int = .zero, + minute: Int = .zero, + second: Int = .zero + ) -> Date { + DateComponents( + calendar: Calendar.init(identifier: .gregorian), + timeZone: .init(identifier: "Asia/Tokyo"), + year: year, + month: month, + day: day, + hour: hour, + minute: minute, + second: second + ) + .date! // swiftlint:disable:this force_unwrapping + } +} diff --git a/LocalPackage/Tests/HometeDomainTests/TestHelper/HouseworkItemHelper.swift b/LocalPackage/Tests/HometeDomainTests/TestHelper/HouseworkItemHelper.swift new file mode 100644 index 00000000..3e0d3b0b --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/TestHelper/HouseworkItemHelper.swift @@ -0,0 +1,80 @@ +// +// HouseworkItemHelper.swift +// homete +// +// Created by 佐藤汰一 on 2025/10/02. +// + +import Foundation +@testable import HometeDomain + +extension HouseworkItem { + + static func makeForTest( + id: Int, + indexedDate: Date = .now, + title: String = "title", + point: Int = 100, + state: HouseworkState = .incomplete, + executorId: String? = nil, + executedAt: Date? = nil, + reviewerId: String? = nil, + approvedAt: Date? = nil, + reviewerComment: String? = nil, + expiredAt: Date = .now + ) -> Self { + + return .init( + id: "id\(id.formatted())", + indexedDate: .init(indexedDate, calendar: .japanese), + title: title, + point: point, + state: state, + executorId: executorId, + executedAt: executedAt, + reviewerId: reviewerId, + approvedAt: approvedAt, + reviewerComment: reviewerComment, + expiredAt: expiredAt + ) + } + + func updateProperties( + indexedDate: HouseworkIndexedDate? = nil, + title: String? = nil, + point: Int? = nil, + state: HouseworkState? = nil, + executorId: String? = nil, + executedAt: Date? = nil, + reviewerId: String? = nil, + approvedAt: Date? = nil, + reviewerComment: String? = nil, + expiredAt: Date? = nil + ) -> HouseworkItem { + + let inputIndexedDate = indexedDate ?? self.indexedDate + let inputTitle = title ?? self.title + let inputPoint = point ?? self.point + let inputState = state ?? self.state + let inputExecutorId = executorId + let inputExecutedAt = executedAt + let inputReviewerId = reviewerId + let inputApprovedAt = approvedAt + let inputReviewerComment = reviewerComment + let inputExpiredAt = expiredAt ?? self.expiredAt + + return .init( + id: id, + indexedDate: inputIndexedDate, + title: inputTitle, + point: inputPoint, + state: inputState, + executorId: inputExecutorId, + executedAt: inputExecutedAt, + reviewerId: inputReviewerId, + approvedAt: inputApprovedAt, + reviewerComment: inputReviewerComment, + expiredAt: inputExpiredAt + ) + } +} diff --git a/LocalPackage/Tests/HometeDomainTests/TestHelper/LocaleHelper.swift b/LocalPackage/Tests/HometeDomainTests/TestHelper/LocaleHelper.swift new file mode 100644 index 00000000..f8ffa711 --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/TestHelper/LocaleHelper.swift @@ -0,0 +1,5 @@ +import Foundation + +extension Locale { + static let jp = Self.init(identifier: "ja_JP") +} diff --git a/LocalPackage/Tests/HometeDomainTests/TestHelper/ObservationHelper.swift b/LocalPackage/Tests/HometeDomainTests/TestHelper/ObservationHelper.swift new file mode 100644 index 00000000..d4059a64 --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/TestHelper/ObservationHelper.swift @@ -0,0 +1,30 @@ +// +// ObservationHelper.swift +// homete +// +// Created by Taichi Sato on 2026/01/12. +// + +import Observation + +enum ObservationHelper { + + /// Observableなオブジェクトのプロパティが変更を検知する + /// - Parameters: + /// - apply: 変更を検知したいプロパティを返す + /// - onChange: 変更検知時に発火するクロージャ + @MainActor + static func continuousObservationTracking( + _ apply: @escaping @MainActor @Sendable () -> T, + onChange: @escaping @Sendable () -> Void + ) { + + _ = withObservationTracking({ apply() }) { + + onChange() + Task { @MainActor in + continuousObservationTracking(apply, onChange: onChange) + } + } + } +} diff --git a/LocalPackage/Tests/HometeDomainTests/TestHelper/TimeZoneHelper.swift b/LocalPackage/Tests/HometeDomainTests/TestHelper/TimeZoneHelper.swift new file mode 100644 index 00000000..55f9f65d --- /dev/null +++ b/LocalPackage/Tests/HometeDomainTests/TestHelper/TimeZoneHelper.swift @@ -0,0 +1,13 @@ +// +// TimeZoneHelper.swift +// homete +// +// Created by Taichi Sato on 2026/01/17. +// + +import Foundation + +extension TimeZone { + // swiftlint:disable:next force_unwrapping + static let tokyo = Self.init(identifier: "Asia/Tokyo")! +} From 353e6728d3ae2808a075f377666b350c493ce908 Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Tue, 17 Feb 2026 22:54:56 +0900 Subject: [PATCH 04/12] =?UTF-8?q?refactor:=20=E4=B8=8D=E8=A6=81=E3=81=AATa?= =?UTF-8?q?rget=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homete.xcodeproj/project.pbxproj | 130 ------ hometeTests/Domain/AccountAuthStoreTest.swift | 156 ------- hometeTests/Domain/AccountStoreTest.swift | 113 ----- .../CohabitantRegistrationMessageTests.swift | 183 -------- .../ConfirmedRegistrationPeersTest.swift | 71 --- hometeTests/Domain/CohabitantStoreTest.swift | 81 ---- .../Housework/DailyHouseworkListTest.swift | 103 ----- .../Housework/HouseworkBoardListTest.swift | 85 ---- .../Housework/HouseworkHistoryListTest.swift | 104 ----- .../Housework/HouseworkIndexedDate.swift | 87 ---- .../Domain/Housework/HouseworkItemTest.swift | 197 --------- .../Housework/HouseworkListStoreTest.swift | 415 ------------------ .../StoredAllHouseworkListTest.swift | 78 ---- .../AccountListenerStreamCreater.swift | 18 - hometeTests/TestHelper/CalendarHelper.swift | 17 - .../TestHelper/DailyHouseworkListHelper.swift | 18 - hometeTests/TestHelper/DateHelper.swift | 31 -- .../TestHelper/HouseworkItemHelper.swift | 80 ---- hometeTests/TestHelper/LocaleHelper.swift | 5 - .../TestHelper/ObservationHelper.swift | 27 -- hometeTests/TestHelper/TimeZoneHelper.swift | 13 - 21 files changed, 2012 deletions(-) delete mode 100644 hometeTests/Domain/AccountAuthStoreTest.swift delete mode 100644 hometeTests/Domain/AccountStoreTest.swift delete mode 100644 hometeTests/Domain/CohabitantRegistration/CohabitantRegistrationMessageTests.swift delete mode 100644 hometeTests/Domain/CohabitantRegistration/ConfirmedRegistrationPeersTest.swift delete mode 100644 hometeTests/Domain/CohabitantStoreTest.swift delete mode 100644 hometeTests/Domain/Housework/DailyHouseworkListTest.swift delete mode 100644 hometeTests/Domain/Housework/HouseworkBoardListTest.swift delete mode 100644 hometeTests/Domain/Housework/HouseworkHistoryListTest.swift delete mode 100644 hometeTests/Domain/Housework/HouseworkIndexedDate.swift delete mode 100644 hometeTests/Domain/Housework/HouseworkItemTest.swift delete mode 100644 hometeTests/Domain/Housework/HouseworkListStoreTest.swift delete mode 100644 hometeTests/Domain/Housework/StoredAllHouseworkListTest.swift delete mode 100644 hometeTests/TestHelper/AccountListenerStreamCreater.swift delete mode 100644 hometeTests/TestHelper/CalendarHelper.swift delete mode 100644 hometeTests/TestHelper/DailyHouseworkListHelper.swift delete mode 100644 hometeTests/TestHelper/DateHelper.swift delete mode 100644 hometeTests/TestHelper/HouseworkItemHelper.swift delete mode 100644 hometeTests/TestHelper/LocaleHelper.swift delete mode 100644 hometeTests/TestHelper/ObservationHelper.swift delete mode 100644 hometeTests/TestHelper/TimeZoneHelper.swift diff --git a/homete.xcodeproj/project.pbxproj b/homete.xcodeproj/project.pbxproj index 4e5b361c..71dd7397 100644 --- a/homete.xcodeproj/project.pbxproj +++ b/homete.xcodeproj/project.pbxproj @@ -30,13 +30,6 @@ remoteGlobalIDString = BCC853F42DB74BBC00C9A44B; remoteInfo = homete; }; - BCC854032DB74BBF00C9A44B /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BCC853ED2DB74BBC00C9A44B /* Project object */; - proxyType = 1; - remoteGlobalIDString = BCC853F42DB74BBC00C9A44B; - remoteInfo = homete; - }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -47,7 +40,6 @@ BC5204662DB7BEFD0097472D /* homete.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = homete.xctestplan; sourceTree = ""; }; BC7469462DBCBE71007E4222 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; BCC853F52DB74BBC00C9A44B /* homete.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = homete.app; sourceTree = BUILT_PRODUCTS_DIR; }; - BCC854022DB74BBF00C9A44B /* hometeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = hometeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -74,11 +66,6 @@ path = homete; sourceTree = ""; }; - BCC854052DB74BBF00C9A44B /* hometeTests */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = hometeTests; - sourceTree = ""; - }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -106,13 +93,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - BCC853FF2DB74BBF00C9A44B /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -132,7 +112,6 @@ BC7469462DBCBE71007E4222 /* PrivacyInfo.xcprivacy */, BC5204662DB7BEFD0097472D /* homete.xctestplan */, BCC853F72DB74BBC00C9A44B /* homete */, - BCC854052DB74BBF00C9A44B /* hometeTests */, BC1BB2692E909B8C001D168F /* hometeSnapshotTests */, BCB8DB162E37B3F900FFB4E1 /* Frameworks */, BCC853F62DB74BBC00C9A44B /* Products */, @@ -143,7 +122,6 @@ isa = PBXGroup; children = ( BCC853F52DB74BBC00C9A44B /* homete.app */, - BCC854022DB74BBF00C9A44B /* hometeTests.xctest */, BC1BB2682E909B8C001D168F /* hometeSnapshotTests.xctest */, ); name = Products; @@ -210,29 +188,6 @@ productReference = BCC853F52DB74BBC00C9A44B /* homete.app */; productType = "com.apple.product-type.application"; }; - BCC854012DB74BBF00C9A44B /* hometeTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = BCC854192DB74BBF00C9A44B /* Build configuration list for PBXNativeTarget "hometeTests" */; - buildPhases = ( - BCC853FE2DB74BBF00C9A44B /* Sources */, - BCC853FF2DB74BBF00C9A44B /* Frameworks */, - BCC854002DB74BBF00C9A44B /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - BCC854042DB74BBF00C9A44B /* PBXTargetDependency */, - ); - fileSystemSynchronizedGroups = ( - BCC854052DB74BBF00C9A44B /* hometeTests */, - ); - name = hometeTests; - packageProductDependencies = ( - ); - productName = hometeTests; - productReference = BCC854022DB74BBF00C9A44B /* hometeTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -250,11 +205,6 @@ BCC853F42DB74BBC00C9A44B = { CreatedOnToolsVersion = 16.3; }; - BCC854012DB74BBF00C9A44B = { - CreatedOnToolsVersion = 16.3; - LastSwiftMigration = 1640; - TestTargetID = BCC853F42DB74BBC00C9A44B; - }; }; }; buildConfigurationList = BCC853F02DB74BBC00C9A44B /* Build configuration list for PBXProject "homete" */; @@ -279,7 +229,6 @@ projectRoot = ""; targets = ( BCC853F42DB74BBC00C9A44B /* homete */, - BCC854012DB74BBF00C9A44B /* hometeTests */, BC1BB2672E909B8C001D168F /* hometeSnapshotTests */, ); }; @@ -303,13 +252,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - BCC854002DB74BBF00C9A44B /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -373,13 +315,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - BCC853FE2DB74BBF00C9A44B /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -392,11 +327,6 @@ isa = PBXTargetDependency; productRef = BC4701FC2E9A0F49006CC530 /* PrefireTestsPlugin */; }; - BCC854042DB74BBF00C9A44B /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = BCC853F42DB74BBC00C9A44B /* homete */; - targetProxy = BCC854032DB74BBF00C9A44B /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -672,57 +602,6 @@ }; name = Release; }; - BCC8541A2DB74BBF00C9A44B /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 56LYVN6DMF; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = taichi.satou.hometeTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/homete.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/homete"; - }; - name = Debug; - }; - BCC8541B2DB74BBF00C9A44B /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 56LYVN6DMF; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = taichi.satou.hometeTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/homete.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/homete"; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -753,15 +632,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - BCC854192DB74BBF00C9A44B /* Build configuration list for PBXNativeTarget "hometeTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - BCC8541A2DB74BBF00C9A44B /* Debug */, - BCC8541B2DB74BBF00C9A44B /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ diff --git a/hometeTests/Domain/AccountAuthStoreTest.swift b/hometeTests/Domain/AccountAuthStoreTest.swift deleted file mode 100644 index 231b6c23..00000000 --- a/hometeTests/Domain/AccountAuthStoreTest.swift +++ /dev/null @@ -1,156 +0,0 @@ -// -// AccountAuthStoreTest.swift -// hometeTests -// -// Created by 佐藤汰一 on 2025/08/09. -// - -import os -import Testing -@testable import homete - -@MainActor -struct AccountAuthStoreTest { - - @Test("ログイン処理ではサーバー側でサインイン後、成功したらログを送信する") - func test_login() async throws { - - let inputTokenId = "testId" - let inputNonce = "testNonce" - let outputAccount = AccountAuthResult(id: "testAccountId") - - try await confirmation(expectedCount: 4) { confirmation in - - let store = AccountAuthStore(appDependencies: .init( - accountAuthClient: .init( - signIn: { tokenId, nonce in - - confirmation() - #expect(tokenId == inputTokenId) - #expect(nonce == inputNonce) - return outputAccount - }, - signOut: { confirmation() }, - makeListener: { - - confirmation() - return .defaultValue() - } - ), - analyticsClient: .init( - setId: { id in - - confirmation() - #expect(id == outputAccount.id) - }, log: { event in - - confirmation() - #expect(event == .login(isSuccess: true)) - } - ) - )) - - try await store.login(.init(tokenId: inputTokenId, nonce: inputNonce, authorizationCode: "code")) - } - } - - @Test("ログアウト時はローカルのログイン状態をログアウトにしてログアウト処理を行い、イベントログを送信する") - func test_logout() throws { - - let isCallSignOut = OSAllocatedUnfairLock(initialState: false) - let isCallAnalyticsLog = OSAllocatedUnfairLock(initialState: false) - - let store = AccountAuthStore( - appDependencies: .init( - accountAuthClient: .init( - signIn: { _, _ in - - Issue.record() - return .init(id: "") - }, - signOut: { isCallSignOut.withLock { $0 = true } }, - makeListener: { .defaultValue() } - ), - analyticsClient: .init( - setId: { _ in - - Issue.record() - }, - log: { event in - - isCallAnalyticsLog.withLock { $0 = true } - #expect(event == .logout()) - } - ) - ), - currentAuth: .init(result: .init(id: "test"), alreadyLoadedAtInitiate: true) - ) - - store.logOut() - - #expect(isCallSignOut.withLock { $0 }) - #expect(isCallAnalyticsLog.withLock { $0 }) - #expect(store.currentAuth == .init(result: nil, alreadyLoadedAtInitiate: true)) - } - - @Test("再認証を行った後にアカウントを削除し認証トークンの無効化を行いログアウト状態にすることで、退会処理を完了させる") - func deleteAccount() async throws { - - // Arrange - let inputNonce = SignInWithAppleNonce(original: "originalTestNonce", sha256: "sha256TestNonce") - let inputSignInWithAppleResult = SignInWithAppleResult( - tokenId: "testToken", - nonce: inputNonce.sha256, - authorizationCode: "testCode" - ) - - try await confirmation(expectedCount: 6) { confirmation in - - let store = AccountAuthStore( - appDependencies: .init( - nonceGeneratorClient: .init { - - confirmation() - return inputNonce - }, - accountAuthClient: .init( - reauthenticateWithApple: { result in - - confirmation() - #expect(result == inputSignInWithAppleResult) - }, - revokeAppleToken: { code in - - confirmation() - #expect(code == inputSignInWithAppleResult.authorizationCode) - }, - deleteAccount: { - - confirmation() - }, - ), - analyticsClient: .init( - log: { event in - - confirmation() - #expect(event == .deleteAccount()) - } - ), - signInWithAppleClient: .init { nonce in - - confirmation() - #expect(nonce == inputNonce) - return inputSignInWithAppleResult - } - ), - currentAuth: .init(result: .init(id: "test"), alreadyLoadedAtInitiate: true) - ) - - // Act - try await store.deleteAccount() - - // Assert - #expect(store.currentAuth == .init(result: nil, alreadyLoadedAtInitiate: true)) - } - } -} diff --git a/hometeTests/Domain/AccountStoreTest.swift b/hometeTests/Domain/AccountStoreTest.swift deleted file mode 100644 index e5038694..00000000 --- a/hometeTests/Domain/AccountStoreTest.swift +++ /dev/null @@ -1,113 +0,0 @@ -// -// AccountStoreTest.swift -// hometeTests -// -// Created by 佐藤汰一 on 2025/08/09. -// - -import Testing -@testable import homete - -@MainActor -struct AccountStoreTest { - - @Test("アカウント情報をロードし、アカウントがある場合はアカウント情報を返す") - func load() async throws { - - // Arrange - let inputAccountId = "test" - let inputAccount = Account( - id: inputAccountId, - userName: "testUserName", - fcmToken: "testToken", - cohabitantId: nil - ) - - await confirmation(expectedCount: 1) { confirmation in - - let inputAuthResult = AccountAuthResult(id: "test") - let accountInfoClient = AccountInfoClient(fetch: { - - confirmation() - #expect($0 == inputAuthResult.id) - return inputAccount - }) - let store = AccountStore(appDependencies: .init(accountInfoClient: accountInfoClient)) - - // Act - let actual = await store.load(inputAuthResult) - - // Assert - #expect(actual == inputAccount) - } - } - - @Test("サーバーにログイン情報がありFCMトークンが更新されている場合アカウントに紐づくFCMトークンを更新") - func updateFcmTokenIfNeeded() async { - - await confirmation(expectedCount: 1) { confirmation in - - // Arrange - let inputFcmToken = "token" - let initialAccount = Account(id: "testId", userName: "testUser", fcmToken: nil, cohabitantId: nil) - let expectedAccount = Account( - id: initialAccount.id, - userName: initialAccount.userName, - fcmToken: inputFcmToken, - cohabitantId: nil - ) - let accountInfoClient = AccountInfoClient(insertOrUpdate: { - - confirmation() - #expect($0 == expectedAccount) - }) - let store = AccountStore( - appDependencies: .init(accountInfoClient: accountInfoClient), - account: initialAccount - ) - - // Act - await store.updateFcmTokenIfNeeded(inputFcmToken) - - // Assert - #expect(store.account == expectedAccount) - } - } - - @Test("パートナーの登録で保持しているアカウントにパートナーグループIDの情報を更新する") - func registerCohabitantId() async throws { - - try await confirmation(expectedCount: 1) { confirmation in - - // Arrange - let inputCohabitantId = "testCohabitantId" - let initialAccount = Account( - id: "testId", - userName: "testUser", - fcmToken: nil, - cohabitantId: nil - ) - let expectedAccount = Account( - id: initialAccount.id, - userName: initialAccount.userName, - fcmToken: nil, - cohabitantId: inputCohabitantId - ) - let accountInfoClient = AccountInfoClient(insertOrUpdate: { - - confirmation() - #expect($0 == expectedAccount) - }) - let store = AccountStore( - appDependencies: .init(accountInfoClient: accountInfoClient), - account: initialAccount - ) - - // Act - try await store.registerCohabitantId(inputCohabitantId) - - // Assert - #expect(store.account == expectedAccount) - } - } -} diff --git a/hometeTests/Domain/CohabitantRegistration/CohabitantRegistrationMessageTests.swift b/hometeTests/Domain/CohabitantRegistration/CohabitantRegistrationMessageTests.swift deleted file mode 100644 index d55600f2..00000000 --- a/hometeTests/Domain/CohabitantRegistration/CohabitantRegistrationMessageTests.swift +++ /dev/null @@ -1,183 +0,0 @@ -// -// CohabitantRegistrationMessageTests.swift -// hometeTests -// -// Created by 佐藤汰一 on 2025/08/31. -// - -import Foundation -import Testing -@testable import homete - -struct CohabitantRegistrationMessageTests { - - @Test( - "メンバー確認のメッセージの場合、メンバー確定するかどうかが取得できること", - arguments: [true, false] - ) - func isFixedMember_typeIsFixedMember(input: Bool) { - - let message = CohabitantRegistrationMessage(type: .fixedMember(isOK: input)) - - let actual = message.isFixedMember - - #expect(actual == input) - } - - @Test( - "メンバー確認のメッセージ以外の場合、nilを返す", - arguments: [ - CohabitantRegistrationMessage.CommunicateType.complete, - .preRegistration(role: .lead), - .preRegistration(role: .follower(accountId: "id")), - .shareCohabitantId(id: "id") - ] - ) - func isFixedMember_typeIsNotFixedMember( - inputMessageType: CohabitantRegistrationMessage.CommunicateType - ) { - - let message = CohabitantRegistrationMessage(type: inputMessageType) - - let actual = message.isFixedMember - - #expect(actual == nil) - } - - @Test( - "登録処理開始前のメッセージの場合、送信者の役割を取得できる", - arguments: [ - CohabitantRegistrationRole.lead, - .follower(accountId: "id") - ] - ) - func memberRole_typeIsPreRegistration(inputRole: CohabitantRegistrationRole) { - - let message = CohabitantRegistrationMessage(type: .preRegistration(role: inputRole)) - - let actual = message.memberRole - - #expect(actual == inputRole) - } - - @Test( - "登録処理開始前のメッセージ以外の場合、nilを返す", - arguments: [ - CohabitantRegistrationMessage.CommunicateType.complete, - .fixedMember(isOK: true), - .shareCohabitantId(id: "id") - ] - ) - func memberRole_typeIsNotPreRegistration( - inputMessageType: CohabitantRegistrationMessage.CommunicateType - ) { - - let message = CohabitantRegistrationMessage(type: inputMessageType) - - let actual = message.memberRole - - #expect(actual == nil) - } - - @Test("同居人ID共有メッセージの場合、共有された同居人IDを取得できる") - func cohabitantId_typeIsShareCohabitantId() { - - let inputId = "test_id" - let message = CohabitantRegistrationMessage(type: .shareCohabitantId(id: inputId)) - - let actual = message.cohabitantId - - #expect(actual == inputId) - } - - @Test( - "同居人ID共有メッセージ以外の場合、nilを返す", - arguments: [ - CohabitantRegistrationMessage.CommunicateType.complete, - .preRegistration(role: .lead), - .preRegistration(role: .follower(accountId: "id")), - .fixedMember(isOK: true) - ] - ) - func cohabitantId_typeIsNotShareCohabitantId( - inputMessageType: CohabitantRegistrationMessage.CommunicateType - ) { - - let message = CohabitantRegistrationMessage(type: inputMessageType) - - let actual = message.cohabitantId - - #expect(actual == nil) - } - - @Test("登録処理完了メッセージの場合、完了かどうかを取得する") - func isComplete_typeIsComplete() { - let message = CohabitantRegistrationMessage(type: .complete) - - let actual = message.isComplete - - #expect(actual == true) - } - - @Test( - "登録処理完了メッセージ以外の場合、nilを返す", - arguments: [ - CohabitantRegistrationMessage.CommunicateType.preRegistration(role: .lead), - .preRegistration(role: .follower(accountId: "id")), - .fixedMember(isOK: true), - .shareCohabitantId(id: "id") - ] - ) - func isComplete_typeIsNotComplete_returnsNil( - inputMessageType: CohabitantRegistrationMessage.CommunicateType - ) { - - let message = CohabitantRegistrationMessage(type: inputMessageType) - - let actual = message.isComplete - - #expect(actual == nil) - } - - @Test( - "同居人登録のメッセージはJSONエンコードされたDataが送信されること", - arguments: [ - CohabitantRegistrationMessage.CommunicateType.complete, - .fixedMember(isOK: true), - .fixedMember(isOK: false), - .preRegistration(role: .lead), - .preRegistration(role: .follower(accountId: "id")), - .shareCohabitantId(id: "id") - ] - ) - func encodeDecode(type: CohabitantRegistrationMessage.CommunicateType) throws { - - let message = CohabitantRegistrationMessage(type: type) - - let actual = message.encodedData() - - let expected = try JSONEncoder().encode(message) - #expect(actual == expected) - } - - @Test( - "JSONエンコードされた同居人登録のメッセージを元の形式のでコードできること", - arguments: [ - CohabitantRegistrationMessage.CommunicateType.complete, - .fixedMember(isOK: true), - .fixedMember(isOK: false), - .preRegistration(role: .lead), - .preRegistration(role: .follower(accountId: "id")), - .shareCohabitantId(id: "id") - ] - ) - func init_withValidData(type: CohabitantRegistrationMessage.CommunicateType) throws { - - let message = CohabitantRegistrationMessage(type: type) - let encodedData = try JSONEncoder().encode(message) - - let actual = CohabitantRegistrationMessage(encodedData) - - #expect(actual == message) - } -} diff --git a/hometeTests/Domain/CohabitantRegistration/ConfirmedRegistrationPeersTest.swift b/hometeTests/Domain/CohabitantRegistration/ConfirmedRegistrationPeersTest.swift deleted file mode 100644 index 476dcb52..00000000 --- a/hometeTests/Domain/CohabitantRegistration/ConfirmedRegistrationPeersTest.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// ConfirmedRegistrationPeersTest.swift -// hometeTests -// -// Created by 佐藤汰一 on 2025/08/31. -// - -import MultipeerConnectivity -import Testing -@testable import homete - -struct ConfirmedRegistrationPeersTest { - - @Test("PeerIDの表示名の降順で一番最初のPeerIDになるデバイスがリードデバイスになる") - func isLeadPeer_lead_case() throws { - - let connectedPeers: Set = [ - .init(displayName: "BBB"), - .init(displayName: "CCC"), - .init(displayName: "EEE") - ] - let peers = ConfirmedRegistrationPeers(peers: connectedPeers) - - let actual = peers.isLeadPeer( - connectedPeers: connectedPeers, - myPeerID: .init(displayName: "AAA") - ) - - #expect(actual == true) - } - - @Test( - "PeerIDの表示名の降順で一番最初ではない場合は、フォロワーデバイスになる", - arguments: [ - MCPeerID(displayName: "DDD"), - .init(displayName: "FFF"), - .init(displayName: "CCC"), - .init(displayName: "ZZZ") - ] - ) - func isLeadPeer_follower_case(myPeerID: MCPeerID) throws { - - let connectedPeers: Set = [ - .init(displayName: "BBB"), - .init(displayName: "CCC"), - .init(displayName: "EEE") - ] - let peers = ConfirmedRegistrationPeers(peers: connectedPeers) - - let actual = peers.isLeadPeer(connectedPeers: connectedPeers, myPeerID: myPeerID) - - #expect(actual == false) - } - - @Test( - "接続中の全てのデバイスが確認済みでない場合は、まだ登録メンバーが揃っていないのでリードデバイスかどうかを返さない" - ) - func isLeadPeer_not_match_case() throws { - - let confirmedPeers: Set = [ - .init(displayName: "BBB"), - .init(displayName: "CCC") - ] - let connectedPeers: Set = .init(confirmedPeers + [.init(displayName: "EEE")]) - let peers = ConfirmedRegistrationPeers(peers: confirmedPeers) - - let actual = peers.isLeadPeer(connectedPeers: connectedPeers, myPeerID: .init(displayName: "AAA")) - - #expect(actual == nil) - } -} diff --git a/hometeTests/Domain/CohabitantStoreTest.swift b/hometeTests/Domain/CohabitantStoreTest.swift deleted file mode 100644 index a94bea94..00000000 --- a/hometeTests/Domain/CohabitantStoreTest.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// CohabitantStoreTest.swift -// hometeTests -// -// Created by Taichi Sato on 2026/01/12. -// - -import Testing -import Observation -@testable import homete - -@MainActor -struct CohabitantStoreTest { - - private let inputCohabitantId = "testCohabitantId" - private let inputListenerId = "cohabitantListenerKey" - - @Test("パートナーの監視中に、まだキャッシュしていないメンバーの場合はパートナーのリストにキャッシュとして追加する") - func addSnapshotListenerIfNeeded_add_member_case() async throws { - - // Arrange - - let newMemberId = "newMemberId" - let newMemberUserName = "新しいメンバー" - let expectedAccount = Account( - id: newMemberId, - userName: newMemberUserName, - fcmToken: nil, - cohabitantId: inputCohabitantId - ) - let inputCohabitantData = CohabitantData( - id: inputCohabitantId, - members: [newMemberId] - ) - - let (stream, continuation) = AsyncStream<[CohabitantData]>.makeStream() - - let store = CohabitantStore( - appDependencies: .init( - accountInfoClient: .init(fetch: { userId in - - // Assert - - #expect(userId == newMemberId) - return expectedAccount - }), - cohabitantClient: .init( - addSnapshotListener: { listenerId, cohabitantId in - - #expect(listenerId == inputListenerId) - #expect(cohabitantId == inputCohabitantId) - return stream - } - ) - ) - ) - - // Act - - await store.addSnapshotListenerIfNeeded(inputCohabitantId) - - // Assert - - let waiterForUpdateMembers = Task { - await withCheckedContinuation { continuation in - ObservationHelper.continuousObservationTracking { - store.members - } onChange: { - continuation.resume(returning: ()) - } - } - } - - continuation.yield([inputCohabitantData]) - await waiterForUpdateMembers.value - continuation.finish() - - #expect(store.members.value.count == 1) - #expect(store.members.value.contains(.init(id: newMemberId, userName: newMemberUserName))) - } -} diff --git a/hometeTests/Domain/Housework/DailyHouseworkListTest.swift b/hometeTests/Domain/Housework/DailyHouseworkListTest.swift deleted file mode 100644 index e2e9736a..00000000 --- a/hometeTests/Domain/Housework/DailyHouseworkListTest.swift +++ /dev/null @@ -1,103 +0,0 @@ -// -// DailyHouseworkListTest.swift -// hometeTests -// -// Created by 佐藤汰一 on 2025/09/08. -// - -import Foundation -import Testing - -@testable import homete - -// swiftlint:disable:next convenience_type -struct DailyHouseworkListTest { - - struct MakeInitialValueCase {} - struct IsRegisteredCase {} - struct IsAlreadyRegisteredCase {} -} - -extension DailyHouseworkListTest.MakeInitialValueCase { - - @Test("一日の家事情報の保持期限は3か月後になる") - func makeInitialValue() throws { - - // Arrange - let calendar = Calendar.japanese - let selectedDate = Date() - let expectedIndexedDate = HouseworkIndexedDate(selectedDate, calendar: calendar) - let expectedExpiredAt = try #require(calendar.date(byAdding: .month, value: 1, to: selectedDate)) - - let expectedList = DailyHouseworkList( - items: [], - metaData: .init(indexedDate: expectedIndexedDate, expiredAt: expectedExpiredAt) - ) - - // Act - let list = DailyHouseworkList.makeInitialValue( - selectedDate: selectedDate, - items: [], - calendar: calendar - ) - - // Assert - #expect(list == expectedList) - } -} - -extension DailyHouseworkListTest.IsRegisteredCase { - - @Test( - "登録されている家事がない場合は、その日付の家事レコードが登録されていない", - arguments: [ - [HouseworkItem.makeForTest(id: 1, title: "洗濯", point: 1)], - [] - ] - ) - func isRegistered(inputItems: [HouseworkItem]) { - - // Arrange - let list = DailyHouseworkList( - items: inputItems, - metaData: .init(indexedDate: .init(.now, calendar: .japanese), expiredAt: .now) - ) - - // Act - let result = list.isRegistered - - // Assert - #expect(result == !inputItems.isEmpty) - } -} - -extension DailyHouseworkListTest.IsAlreadyRegisteredCase { - - @Test( - "同じタイトルの家事が含まれていれば登録済みの家事とみなす", - arguments: [ - HouseworkItem.makeForTest(id: 1, title: "洗濯", point: 1), - .makeForTest(id: 3, title: "洗濯", point: 1), - .makeForTest(id: 1, title: "掃除", point: 1) - ] - ) - func alreadyRegistered_trueWhenSameTitleExists(inputItem: HouseworkItem) { - - // Arrange - let items: [HouseworkItem] = [ - .makeForTest(id: 1, title: "洗濯", point: 1), - .makeForTest(id: 2, title: "ゴミ捨て", point: 1, state: .completed) - ] - let list = DailyHouseworkList( - items: items, - metaData: .init(indexedDate: .init(.now, calendar: .japanese), expiredAt: .now) - ) - - // Act - let result = list.isAlreadyRegistered(inputItem) - - // Assert - let expected = items.contains { $0.title == inputItem.title } - #expect(result == expected) - } -} diff --git a/hometeTests/Domain/Housework/HouseworkBoardListTest.swift b/hometeTests/Domain/Housework/HouseworkBoardListTest.swift deleted file mode 100644 index bda8aede..00000000 --- a/hometeTests/Domain/Housework/HouseworkBoardListTest.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// HouseworkBoardListTest.swift -// hometeTests -// -// Created by 佐藤汰一 on 2025/10/02. -// - -import Foundation -import Testing - -@testable import homete - -struct HouseworkBoardListTest { - - private static let calendar = Calendar.autoupdatingCurrent - - @Test( - "選択している日付のみを家事ボードに表示する", - arguments: [ - Date.now, - Date.distantFuture, - Date.distantPast - ] - ) - func initialize(selectTime: Date) async throws { - - // Arrange - let inputSelectedDateItemList: [HouseworkItem] = [ - .makeForTest(id: 1, indexedDate: selectTime), - .makeForTest(id: 2, indexedDate: selectTime) - ] - let inputUnselectedDateItemList: [HouseworkItem] = [ - .makeForTest( - id: 1, - indexedDate: Self.calendar.date(bySetting: .day, value: -3, of: .now) ?? .now - ) - ] - let inputList: [DailyHouseworkList] = [ - .makeForTest(items: inputSelectedDateItemList), - .makeForTest(items: inputUnselectedDateItemList) - ] - - // Act - let actual = HouseworkBoardList( - dailyList: inputList, - selectedDate: selectTime, - calendar: .japanese - ) - - // Assert - let expected = HouseworkBoardList(items: inputSelectedDateItemList) - #expect(actual == expected) - } - - @Test( - "指定した状態にマッチする家事を取得する", - arguments: HouseworkState.allCases - ) - func items_match_state(expectedState: HouseworkState) async throws { - - // Arrange - let inputHouseworkItem = makeHouseworkItemListWithAllState() - let houseworkBoardList = HouseworkBoardList( - dailyList: [.makeForTest(items: inputHouseworkItem)], - selectedDate: .now, - calendar: .japanese - ) - - // Act - let actual = houseworkBoardList.items(matching: expectedState) - - // Assert - let expected = inputHouseworkItem.filter { $0.state == expectedState } - #expect(actual == expected) - } -} - -private extension HouseworkBoardListTest { - - func makeHouseworkItemListWithAllState() -> [HouseworkItem] { - HouseworkState.allCases.enumerated().flatMap { index, state in - (1...3).map { HouseworkItem.makeForTest(id: index + 1 + $0, state: state) } - } - } -} diff --git a/hometeTests/Domain/Housework/HouseworkHistoryListTest.swift b/hometeTests/Domain/Housework/HouseworkHistoryListTest.swift deleted file mode 100644 index 64485e54..00000000 --- a/hometeTests/Domain/Housework/HouseworkHistoryListTest.swift +++ /dev/null @@ -1,104 +0,0 @@ -// -// HouseworkHistoryListTest.swift -// hometeTests -// -// Created by 佐藤汰一 on 2025/09/07. -// - -import Testing -@testable import homete - -// swiftlint:disable:next convenience_type -struct HouseworkHistoryListTest { - - struct MoveToFrontIfExistsCase {} - struct AddNewHistoryCase {} -} - -extension HouseworkHistoryListTest.MoveToFrontIfExistsCase { - - @Test("存在する要素を指定すると先頭へ移動する") - func moveExistingItemToFront() { - - // Arrange - var list = HouseworkHistoryList(items: ["1", "2", "3"]) - let target = "2" - let expected = HouseworkHistoryList(items: ["2", "1", "3"]) - - // Act - list.moveToFrontIfExists(target) - - // Assert - #expect(list == expected) - } - - @Test("既に先頭の要素を指定しても変更されない") - func noChangeWhenItemAlreadyAtFront() { - - // Arrange - let initial = HouseworkHistoryList(items: ["a", "b", "c"]) - var list = initial - let target = "a" - - // Act - list.moveToFrontIfExists(target) - - // Assert - #expect(list == initial) - } - - @Test("存在しない要素を指定しても変更されない") - func noChangeWhenItemDoesNotExist() { - - // Arrange - let initial = HouseworkHistoryList(items: ["x", "y", "z"]) - var list = initial - let target = "w" - - // Act - list.moveToFrontIfExists(target) - - // Assert - #expect(list == initial) - } -} - -extension HouseworkHistoryListTest.AddNewHistoryCase { - - @Test( - "履歴に存在しない要素を追加する場合、その要素が先頭に追加される", - arguments: [ - ["洗濯", "皿洗い"], - [] - ] - ) - func addNewItem(initialList: [String]) { - - // Arrange - let value = "掃除" - var list = HouseworkHistoryList(items: initialList) - let expected = HouseworkHistoryList(items: ["掃除"] + initialList) - - // Act - list.addNewHistory(value) - - // Assert - #expect(list == expected) - } - - @Test("既に存在する要素を追加する場合、リストは変更されない") - func addValueAlreadyAtFrontDoesNotChange() { - - // Arrange - let initial = HouseworkHistoryList(items: ["洗濯", "掃除", "皿洗い"]) - var list = initial - let value = "掃除" - - // Act - list.addNewHistory(value) - - // Assert - let expected = HouseworkHistoryList(items: ["掃除", "洗濯", "皿洗い"]) - #expect(list == expected) - } -} diff --git a/hometeTests/Domain/Housework/HouseworkIndexedDate.swift b/hometeTests/Domain/Housework/HouseworkIndexedDate.swift deleted file mode 100644 index c71084b1..00000000 --- a/hometeTests/Domain/Housework/HouseworkIndexedDate.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// HouseworkIndexedDate.swift -// hometeTests -// -// Created by 佐藤汰一 on 2025/12/06. -// - -import Foundation -import Testing -@testable import homete - -enum HouseworkIndexedDateTest { - - struct InitCase {} - struct CalcTargetPeriodCase {} -} - -// MARK: - InitCase - -extension HouseworkIndexedDateTest.InitCase { - - @Test(arguments: [ - Date.distantPast, - .init(timeIntervalSince1970: .zero), - .dateComponents(year: 2025, month: 1, day: 1), - .distantFuture - ]) - func init_parse_date(inputDate: Date) async throws { - let indexedDate = HouseworkIndexedDate(inputDate, calendar: .japanese) - - let expected = inputDate.formatted( - Date.FormatStyle(date: .numeric, time: .omitted, calendar: .japanese, timeZone: .tokyo) - .year(.extended(minimumLength: 4)) - .month(.twoDigits) - .day(.twoDigits) - .locale(.jp) - ) - #expect(indexedDate.value == expected) - } -} - -// MARK: - CalcTargetPeriodCase - -extension HouseworkIndexedDateTest.CalcTargetPeriodCase { - - @Test("指定の日付から指定の期間の日付情報を返す") - func calcTargetPeriod() { - - // Arrange - let calendar = Calendar.japanese - - // Act - let result = HouseworkIndexedDate.calcTargetPeriod( - anchorDate: .dateComponents(year: 2026, month: 2, day: 1), - offsetDays: 2, - calendar: calendar - ) - - // Assert - let expected = [ - ["value": "2026/01/30"], - ["value": "2026/01/31"], - ["value": "2026/02/01"], - ["value": "2026/02/02"], - ["value": "2026/02/03"] - ] - #expect(result == expected) - } - - @Test("指定の期間が0以下の場合、基準日付のみ返す") - func calcTargetPeriod_zero_offset() { - - // Arrange - let calendar = Calendar.japanese - - // Act - let result = HouseworkIndexedDate.calcTargetPeriod( - anchorDate: .dateComponents(year: 2026, month: 1, day: 15), - offsetDays: 0, - calendar: calendar - ) - - // Assert - let expected = [["value": "2026/01/15"]] - #expect(result == expected) - } -} diff --git a/hometeTests/Domain/Housework/HouseworkItemTest.swift b/hometeTests/Domain/Housework/HouseworkItemTest.swift deleted file mode 100644 index 4ec1eb20..00000000 --- a/hometeTests/Domain/Housework/HouseworkItemTest.swift +++ /dev/null @@ -1,197 +0,0 @@ -// -// HouseworkItemTest.swift -// hometeTests -// -// Created by 佐藤汰一 on 2025/09/08. -// - -import Foundation -import Testing - -@testable import homete - -enum HouseworkItemTest { - - struct CanReviewCase {} - struct UpdateStateCase {} -} - -extension HouseworkItemTest.CanReviewCase { - - @Test( - "担当者が自分以外かつ未完了の場合、レビュー可能", - arguments: [HouseworkState.incomplete, .pendingApproval] - ) - func canReview_notOwnUserAndNotCompleted_returnsTrue(state: HouseworkState) { - - // Arrange - let item = HouseworkItem.makeForTest( - id: 1, - state: state, - executorId: "otherUserId" - ) - - // Act - let result = item.canReview(ownUserId: "ownUserId") - - // Assert - #expect(result == true) - } - - @Test( - "担当者が自分以外でも完了済みの場合、レビュー不可", - arguments: ["otherUserId", nil] - ) - func canReview_completedState_returnsFalse(executorId: String?) { - - // Arrange - let item = HouseworkItem.makeForTest( - id: 1, - state: .completed, - executorId: executorId - ) - - // Act - let result = item.canReview(ownUserId: "ownUserId") - - // Assert - #expect(result == false) - } - - @Test( - "担当者が自分の場合、未完了でもレビュー不可", - arguments: HouseworkState.allCases - ) - func canReview_ownUser_returnsFalse(state: HouseworkState) { - - // Arrange - let ownUserId = "ownUserId" - let item = HouseworkItem.makeForTest( - id: 1, - state: state, - executorId: ownUserId - ) - - // Act - let result = item.canReview(ownUserId: ownUserId) - - // Assert - #expect(result == false) - } -} - -// MARK: - UpdateStateCase - -extension HouseworkItemTest.UpdateStateCase { - - @Test("承認待ち状態に更新すると、state・executorId・executedAtが更新される") - func updatePendingApproval_updatesStateAndExecutorInfo() { - - // Arrange - let indexedDate = Date() - let expiredAt = Date().addingTimeInterval(3600) - let item = HouseworkItem.makeForTest( - id: 1, - indexedDate: indexedDate, - title: "洗濯", - point: 100, - state: .incomplete, - expiredAt: expiredAt - ) - let now = Date() - let changerId = "changerId" - - // Act - let result = item.updatePendingApproval(at: now, changer: changerId) - - // Assert - let expected = HouseworkItem.makeForTest( - id: 1, - indexedDate: indexedDate, - title: "洗濯", - point: 100, - state: .pendingApproval, - executorId: changerId, - executedAt: now, - expiredAt: expiredAt - ) - #expect(result == expected) - } - - @Test("承認すると、state・reviewerId・approvedAt・reviewerCommentが更新される") - func updateApproved_updatesStateAndReviewerInfo() { - - // Arrange - let indexedDate = Date() - let expiredAt = Date().addingTimeInterval(3600) - let executorId = "executorId" - let executedAt = Date().addingTimeInterval(-3600) - let item = HouseworkItem.makeForTest( - id: 1, - indexedDate: indexedDate, - title: "洗濯", - point: 100, - state: .pendingApproval, - executorId: executorId, - executedAt: executedAt, - expiredAt: expiredAt - ) - let now = Date() - let reviewerId = "reviewerId" - let comment = "よくできました" - - // Act - let result = item.updateApproved(at: now, reviewer: reviewerId, comment: comment) - - // Assert - let expected = HouseworkItem.makeForTest( - id: 1, - indexedDate: indexedDate, - title: "洗濯", - point: 100, - state: .completed, - executorId: executorId, - executedAt: executedAt, - reviewerId: reviewerId, - approvedAt: now, - reviewerComment: comment, - expiredAt: expiredAt - ) - #expect(result == expected) - } - - @Test("未完了状態に戻すと、stateがincompleteになり実行者・承認者情報がクリアされる") - func updateIncomplete_clearsExecutorAndReviewerInfo() { - - // Arrange - let indexedDate = Date() - let expiredAt = Date().addingTimeInterval(3600) - let item = HouseworkItem.makeForTest( - id: 1, - indexedDate: indexedDate, - title: "洗濯", - point: 100, - state: .completed, - executorId: "executorId", - executedAt: Date(), - reviewerId: "reviewerId", - approvedAt: Date(), - reviewerComment: "コメント", - expiredAt: expiredAt - ) - - // Act - let result = item.updateIncomplete() - - // Assert - let expected = HouseworkItem.makeForTest( - id: 1, - indexedDate: indexedDate, - title: "洗濯", - point: 100, - state: .incomplete, - expiredAt: expiredAt - ) - #expect(result == expected) - } -} diff --git a/hometeTests/Domain/Housework/HouseworkListStoreTest.swift b/hometeTests/Domain/Housework/HouseworkListStoreTest.swift deleted file mode 100644 index 101fdbf0..00000000 --- a/hometeTests/Domain/Housework/HouseworkListStoreTest.swift +++ /dev/null @@ -1,415 +0,0 @@ -// -// HouseworkListStoreTest.swift -// hometeTests -// -// Created by 佐藤汰一 on 2025/09/29. -// - -import Foundation -import Testing - -@testable import homete - -@MainActor -struct HouseworkListStoreTest { - - private let inputId = "houseworkObserveKey" - private let inputCohabitantId = "cohabitantId" - - @MainActor - struct UpdateStatusCase { - - private let inputId = "houseworkObserveKey" - private let inputCohabitantId = "cohabitantId" - } - - @Test("家事リストのロードを行うと最新の家事リストを常に監視し続ける") - func loadHouseworkList() async throws { - - // Arrange - - let now = Date() - let calendar = Calendar.autoupdatingCurrent - let (stream, continuation) = AsyncStream<[HouseworkItem]>.makeStream() - let store = HouseworkListStore( - houseworkClient: .init(snapshotListenerHandler: { id, cohabitantId, anchorDate, offset in - - #expect(id == inputId) - #expect(cohabitantId == inputCohabitantId) - #expect(anchorDate == now) - #expect(offset == 3) - return stream - }), - cohabitantPushNotificationClient: .previewValue - ) - - // Act - - await store.loadHouseworkList( - currentTime: now, - cohabitantId: inputCohabitantId, - calendar: calendar - ) - - // Assert - - let waiterForUpdateItems = Task { - await withCheckedContinuation { continuation in - ObservationHelper.continuousObservationTracking { - store.items - } onChange: { - continuation.resume(returning: ()) - } - } - } - - let inputHouseworkList: [HouseworkItem] = [ - .makeForTest(id: 1, indexedDate: now, expiredAt: now) - ] - continuation.yield(inputHouseworkList) - await waiterForUpdateItems.value - continuation.finish() - - #expect( - store.items == .init(value: [ - .init( - items: inputHouseworkList, - metaData: .init( - indexedDate: .init(.now, calendar: .japanese), - expiredAt: now - ) - ) - ]) - ) - } - - @Test("新しい家事の登録すると、パートナーに通知を送信する") - func register() async throws { - - // Arrange - - let inputHouseworkItem = HouseworkItem.makeForTest(id: 1) - let expectedNotificationContent = PushNotificationContent( - title: "新しい家事が登録されました", - message: inputHouseworkItem.title - ) - - await confirmation(expectedCount: 2) { confirmation in - - let _: Void = await withCheckedContinuation { continuation in - - let store = HouseworkListStore( - houseworkClient: .init(insertOrUpdateItemHandler: { item, cohabitantId in - - // Assert - - #expect(item == inputHouseworkItem) - #expect(cohabitantId == inputCohabitantId) - confirmation() - }), - cohabitantPushNotificationClient: .init { id, content in - - // Assert - - #expect(id == inputCohabitantId) - #expect(content == expectedNotificationContent) - confirmation() - continuation.resume() - }, - cohabitantId: inputCohabitantId - ) - - // Act - - Task { - - try await store.register(inputHouseworkItem) - } - } - } - } -} - -extension HouseworkListStoreTest.UpdateStatusCase { - - @Test("家事の完了確認を依頼すると、パートナーにその旨Push通知を送信する") - func requestReview() async throws { - - // Arrange - - let inputHouseworkItem = HouseworkItem.makeForTest(id: 1) - let expectedNotificationContent = PushNotificationContent( - title: "確認が必要な家事があります", - message: "問題なければ「\(inputHouseworkItem.title)」の完了に感謝を伝えましょう!" - ) - let requestedAt = Date() - let inputExecutor = "dummyExecutor" - let updatedHouseworkItem = inputHouseworkItem.updateProperties( - state: .pendingApproval, - executorId: inputExecutor, - executedAt: requestedAt - ) - - await confirmation(expectedCount: 2) { confirmation in - - let _: Void = await withCheckedContinuation { continuation in - - let store = HouseworkListStore( - houseworkClient: .init( - insertOrUpdateItemHandler: { item, cohabitantId in - - // Assert - - #expect(item == updatedHouseworkItem) - #expect(cohabitantId == inputCohabitantId) - confirmation() - } - ), - cohabitantPushNotificationClient: .init { id, content in - - // Assert - - #expect(id == inputCohabitantId) - #expect(content == expectedNotificationContent) - confirmation() - continuation.resume() - }, - items: [.makeForTest(items: [inputHouseworkItem])], - cohabitantId: inputCohabitantId - ) - - // Act - - Task { - - try await store.requestReview( - target: inputHouseworkItem, - now: requestedAt, - executor: inputExecutor - ) - } - } - } - } - - @Test("実施者、実施日をクリアして家事のステータスを未完了に戻す") - func returnToIncomplete() async throws { - - // Arrange - - let inputHouseworkItem = HouseworkItem.makeForTest( - id: 1, - state: .pendingApproval, - executorId: "dummyExecutor", - executedAt: .distantPast - ) - let updatedHouseworkItem = inputHouseworkItem.updateProperties( - state: .incomplete, - executorId: nil, - executedAt: nil - ) - - try await confirmation(expectedCount: 1) { confirmation in - - let store = HouseworkListStore( - houseworkClient: .init( - insertOrUpdateItemHandler: { item, cohabitantId in - - // Assert - - #expect(item == updatedHouseworkItem) - #expect(cohabitantId == inputCohabitantId) - confirmation() - } - ), - cohabitantPushNotificationClient: .init { _, _ in - - Issue.record() - }, - items: [.makeForTest(items: [inputHouseworkItem])], - cohabitantId: inputCohabitantId - ) - - // Act - - try await store.returnToIncomplete(target: inputHouseworkItem) - } - } - - @Test("家事削除時は家事を削除するAPIを実行する") - func remove() async throws { - - // Arrange - - let inputHouseworkItem = HouseworkItem.makeForTest(id: 1) - - try await confirmation { confirmation in - - let store = HouseworkListStore( - houseworkClient: .init(removeItemHandler: { item, cohabitantId in - - // Assert - - #expect(item == inputHouseworkItem) - #expect(cohabitantId == inputCohabitantId) - confirmation() - }), - cohabitantPushNotificationClient: .previewValue, - items: [.makeForTest(items: [inputHouseworkItem])], - cohabitantId: inputCohabitantId - ) - - // Act - - try await store.remove(inputHouseworkItem) - } - } - - @Test("家事を承認すると、承認情報を更新しパートナーに通知を送信する") - // swiftlint:disable:next function_body_length - func approved() async throws { - - // Arrange - - let inputHouseworkItem = HouseworkItem.makeForTest( - id: 1, - state: .pendingApproval, - executorId: "executorId", - executedAt: .distantPast - ) - let approvedAt = Date() - let inputReviewer = Account( - id: "reviewerId", - userName: "レビュアー", - fcmToken: nil, - cohabitantId: inputCohabitantId - ) - let inputComment = "お疲れ様でした!" - let updatedHouseworkItem = inputHouseworkItem.updateApproved( - at: approvedAt, - reviewer: inputReviewer.id, - comment: inputComment - ) - let expectedNotificationContent = PushNotificationContent.approvedMessage( - reviwerName: inputReviewer.userName, - houseworkTitle: inputHouseworkItem.title, - comment: inputComment - ) - - await confirmation(expectedCount: 2) { confirmation in - - let _: Void = await withCheckedContinuation { continuation in - - let store = HouseworkListStore( - houseworkClient: .init( - insertOrUpdateItemHandler: { item, cohabitantId in - - // Assert - - #expect(item == updatedHouseworkItem) - #expect(cohabitantId == inputCohabitantId) - confirmation() - } - ), - cohabitantPushNotificationClient: .init { id, content in - - // Assert - - #expect(id == inputCohabitantId) - #expect(content == expectedNotificationContent) - confirmation() - continuation.resume() - }, - items: [.makeForTest(items: [inputHouseworkItem])], - cohabitantId: inputCohabitantId - ) - - // Act - - Task { - - try await store.approved( - target: inputHouseworkItem, - now: approvedAt, - reviwer: inputReviewer, - comment: inputComment - ) - } - } - } - } - - @Test("家事を却下すると、却下情報を更新しパートナーに通知を送信する") - // swiftlint:disable:next function_body_length - func rejected() async throws { - - // Arrange - - let inputHouseworkItem = HouseworkItem.makeForTest( - id: 1, - state: .pendingApproval, - executorId: "executorId", - executedAt: .distantPast - ) - let rejectedAt = Date() - let inputReviewer = Account( - id: "reviewerId", - userName: "レビュアー", - fcmToken: nil, - cohabitantId: inputCohabitantId - ) - let inputComment = "もう一度確認してください" - let updatedHouseworkItem = inputHouseworkItem.updateRejected( - at: rejectedAt, - reviewer: inputReviewer.id, - comment: inputComment - ) - let expectedNotificationContent = PushNotificationContent.rejectedMessage( - reviwerName: inputReviewer.userName, - houseworkTitle: inputHouseworkItem.title, - comment: inputComment - ) - - await confirmation(expectedCount: 2) { confirmation in - - let _: Void = await withCheckedContinuation { continuation in - - let store = HouseworkListStore( - houseworkClient: .init( - insertOrUpdateItemHandler: { item, cohabitantId in - - // Assert - - #expect(item == updatedHouseworkItem) - #expect(cohabitantId == inputCohabitantId) - confirmation() - } - ), - cohabitantPushNotificationClient: .init { id, content in - - // Assert - - #expect(id == inputCohabitantId) - #expect(content == expectedNotificationContent) - confirmation() - continuation.resume() - }, - items: [.makeForTest(items: [inputHouseworkItem])], - cohabitantId: inputCohabitantId - ) - - // Act - - Task { - - try await store.rejected( - target: inputHouseworkItem, - now: rejectedAt, - reviwer: inputReviewer, - comment: inputComment - ) - } - } - } - } -} diff --git a/hometeTests/Domain/Housework/StoredAllHouseworkListTest.swift b/hometeTests/Domain/Housework/StoredAllHouseworkListTest.swift deleted file mode 100644 index 7c58f494..00000000 --- a/hometeTests/Domain/Housework/StoredAllHouseworkListTest.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// StoredAllHouseworkListTest.swift -// hometeTests -// -// Created by 佐藤汰一 on 2025/11/22. -// - -import Foundation -import Testing -@testable import homete - -struct StoredAllHouseworkListTest { - - private static let inputFirstDate = Date.dateComponents(year: 2025, month: 1, day: 1) - private static let inputSecondDate = Date.dateComponents(year: 2025, month: 1, day: 2) - - @Test("家事リストは家事の日付毎に保持される") - func makeMultiDateList() { - - let inputFirstHouseworkGroup = makeAllCasesItems( - at: Self.inputFirstDate, - offset: 0 - ) - let inputSecondHouseworkGroup = makeAllCasesItems( - at: Self.inputSecondDate, - offset: inputFirstHouseworkGroup.count - ) - let inputHouseworkItem = inputFirstHouseworkGroup + inputSecondHouseworkGroup - - let actual = StoredAllHouseworkList.makeMultiDateList( - items: inputHouseworkItem, - calendar: .current - ) - - let sortedActualValue = actual.value.sorted { $0.metaData.indexedDate.value < $1.metaData.indexedDate.value } - let sortedActual = StoredAllHouseworkList(value: sortedActualValue) - let expected = StoredAllHouseworkList(value: [ - .makeForTest(items: inputFirstHouseworkGroup), - .makeForTest(items: inputSecondHouseworkGroup) - ]) - #expect(sortedActual == expected) - } - - @Test( - "保持している家事リストからidと対象日に合致する単一の家事を取得する", - arguments: [ - HouseworkItem.makeForTest(id: 999, indexedDate: Self.inputFirstDate), - .makeForTest(id: 999, indexedDate: Date.now) - ] - ) - func items(expectedItem: HouseworkItem) { - - let inputFirstHouseworkGroup = makeAllCasesItems( - at: Self.inputFirstDate, - offset: 0 - ) - let list = StoredAllHouseworkList.makeMultiDateList( - items: (inputFirstHouseworkGroup + [expectedItem]).shuffled(), - calendar: .current - ) - - let actual = list.item(expectedItem) - - #expect(actual == expectedItem) - } -} - -private extension StoredAllHouseworkListTest { - - func makeAllCasesItems(at indexedDate: Date, offset: Int) -> [HouseworkItem] { - return [ - .makeForTest(id: offset + 1, indexedDate: indexedDate), - .makeForTest(id: offset + 2, indexedDate: indexedDate, state: .pendingApproval), - .makeForTest(id: offset + 3, indexedDate: indexedDate, state: .completed), - .makeForTest(id: offset + 4, indexedDate: indexedDate, executorId: "dummy", executedAt: .now) - ] - } -} diff --git a/hometeTests/TestHelper/AccountListenerStreamCreater.swift b/hometeTests/TestHelper/AccountListenerStreamCreater.swift deleted file mode 100644 index 0f07c944..00000000 --- a/hometeTests/TestHelper/AccountListenerStreamCreater.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// Account.swift -// homete -// -// Created by 佐藤汰一 on 2025/08/09. -// - -import Foundation -@testable import homete - -extension AccountListenerStream { - - static func defaultValue() -> Self { - - let (stream, continuation) = AsyncStream.makeStream() - return .init(values: stream, listenerToken: NSObject(), continuation: continuation) - } -} diff --git a/hometeTests/TestHelper/CalendarHelper.swift b/hometeTests/TestHelper/CalendarHelper.swift deleted file mode 100644 index fca998a6..00000000 --- a/hometeTests/TestHelper/CalendarHelper.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// CalendarHelper.swift -// homete -// -// Created by Taichi Sato on 2026/01/17. -// - -import Foundation - -extension Calendar { - static var japanese: Self { - var calendar = Calendar(identifier: .gregorian) - calendar.timeZone = .tokyo - calendar.locale = .jp - return calendar - } -} diff --git a/hometeTests/TestHelper/DailyHouseworkListHelper.swift b/hometeTests/TestHelper/DailyHouseworkListHelper.swift deleted file mode 100644 index 34a03a70..00000000 --- a/hometeTests/TestHelper/DailyHouseworkListHelper.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// DailyHouseworkListHelper.swift -// homete -// -// Created by 佐藤汰一 on 2025/10/02. -// - -@testable import homete - -extension DailyHouseworkList { - - static func makeForTest(items: [HouseworkItem]) -> Self { - - let indexedDate = items[0].indexedDate - let expiredAt = items[0].expiredAt - return .init(items: items, metaData: .init(indexedDate: indexedDate, expiredAt: expiredAt)) - } -} diff --git a/hometeTests/TestHelper/DateHelper.swift b/hometeTests/TestHelper/DateHelper.swift deleted file mode 100644 index 42060991..00000000 --- a/hometeTests/TestHelper/DateHelper.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// DateHelper.swift -// homete -// -// Created by 佐藤汰一 on 2025/11/22. -// - -import Foundation - -extension Date { - static func dateComponents( - year: Int, - month: Int, - day: Int, - hour: Int = .zero, - minute: Int = .zero, - second: Int = .zero - ) -> Date { - DateComponents( - calendar: Calendar.init(identifier: .gregorian), - timeZone: .init(identifier: "Asia/Tokyo"), - year: year, - month: month, - day: day, - hour: hour, - minute: minute, - second: second - ) - .date! // swiftlint:disable:this force_unwrapping - } -} diff --git a/hometeTests/TestHelper/HouseworkItemHelper.swift b/hometeTests/TestHelper/HouseworkItemHelper.swift deleted file mode 100644 index 7517c4d3..00000000 --- a/hometeTests/TestHelper/HouseworkItemHelper.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// HouseworkItemHelper.swift -// homete -// -// Created by 佐藤汰一 on 2025/10/02. -// - -import Foundation -@testable import homete - -extension HouseworkItem { - - static func makeForTest( - id: Int, - indexedDate: Date = .now, - title: String = "title", - point: Int = 100, - state: HouseworkState = .incomplete, - executorId: String? = nil, - executedAt: Date? = nil, - reviewerId: String? = nil, - approvedAt: Date? = nil, - reviewerComment: String? = nil, - expiredAt: Date = .now - ) -> Self { - - return .init( - id: "id\(id.formatted())", - indexedDate: .init(indexedDate, calendar: .japanese), - title: title, - point: point, - state: state, - executorId: executorId, - executedAt: executedAt, - reviewerId: reviewerId, - approvedAt: approvedAt, - reviewerComment: reviewerComment, - expiredAt: expiredAt - ) - } - - func updateProperties( - indexedDate: HouseworkIndexedDate? = nil, - title: String? = nil, - point: Int? = nil, - state: HouseworkState? = nil, - executorId: String? = nil, - executedAt: Date? = nil, - reviewerId: String? = nil, - approvedAt: Date? = nil, - reviewerComment: String? = nil, - expiredAt: Date? = nil - ) -> HouseworkItem { - - let inputIndexedDate = indexedDate ?? self.indexedDate - let inputTitle = title ?? self.title - let inputPoint = point ?? self.point - let inputState = state ?? self.state - let inputExecutorId = executorId - let inputExecutedAt = executedAt - let inputReviewerId = reviewerId - let inputApprovedAt = approvedAt - let inputReviewerComment = reviewerComment - let inputExpiredAt = expiredAt ?? self.expiredAt - - return .init( - id: id, - indexedDate: inputIndexedDate, - title: inputTitle, - point: inputPoint, - state: inputState, - executorId: inputExecutorId, - executedAt: inputExecutedAt, - reviewerId: inputReviewerId, - approvedAt: inputApprovedAt, - reviewerComment: inputReviewerComment, - expiredAt: inputExpiredAt - ) - } -} diff --git a/hometeTests/TestHelper/LocaleHelper.swift b/hometeTests/TestHelper/LocaleHelper.swift deleted file mode 100644 index f8ffa711..00000000 --- a/hometeTests/TestHelper/LocaleHelper.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -extension Locale { - static let jp = Self.init(identifier: "ja_JP") -} diff --git a/hometeTests/TestHelper/ObservationHelper.swift b/hometeTests/TestHelper/ObservationHelper.swift deleted file mode 100644 index 086e4c1a..00000000 --- a/hometeTests/TestHelper/ObservationHelper.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// ObservationHelper.swift -// homete -// -// Created by Taichi Sato on 2026/01/12. -// - -import Observation - -enum ObservationHelper { - - /// Observableなオブジェクトのプロパティが変更を検知する - /// - Parameters: - /// - apply: 変更を検知したいプロパティを返す - /// - onChange: 変更検知時に発火するクロージャ - static func continuousObservationTracking( - _ apply: @escaping () -> T, - onChange: @escaping (@Sendable () -> Void) - ) { - - _ = withObservationTracking(apply) { - - onChange() - continuousObservationTracking(apply, onChange: onChange) - } - } -} diff --git a/hometeTests/TestHelper/TimeZoneHelper.swift b/hometeTests/TestHelper/TimeZoneHelper.swift deleted file mode 100644 index 55f9f65d..00000000 --- a/hometeTests/TestHelper/TimeZoneHelper.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// TimeZoneHelper.swift -// homete -// -// Created by Taichi Sato on 2026/01/17. -// - -import Foundation - -extension TimeZone { - // swiftlint:disable:next force_unwrapping - static let tokyo = Self.init(identifier: "Asia/Tokyo")! -} From 25626e038d3d832f5fc368a4a1a040a9ec2a1f14 Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Tue, 17 Feb 2026 22:56:01 +0900 Subject: [PATCH 05/12] =?UTF-8?q?refactor:=20testplan=E3=81=AE=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CI.xctestplan | 7 ------- homete.xctestplan | 7 +++---- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/CI.xctestplan b/CI.xctestplan index d3e9b3f6..410271cc 100644 --- a/CI.xctestplan +++ b/CI.xctestplan @@ -31,13 +31,6 @@ "identifier" : "BC1BB2672E909B8C001D168F", "name" : "hometeSnapshotTests" } - }, - { - "target" : { - "containerPath" : "container:homete.xcodeproj", - "identifier" : "BCC854012DB74BBF00C9A44B", - "name" : "hometeTests" - } } ], "version" : 1 diff --git a/homete.xctestplan b/homete.xctestplan index d9dcc27e..0a4ab649 100644 --- a/homete.xctestplan +++ b/homete.xctestplan @@ -31,11 +31,10 @@ }, "testTargets" : [ { - "parallelizable" : true, "target" : { - "containerPath" : "container:homete.xcodeproj", - "identifier" : "BCC854012DB74BBF00C9A44B", - "name" : "hometeTests" + "containerPath" : "container:LocalPackage", + "identifier" : "HometeDomainTests", + "name" : "HometeDomainTests" } } ], From 8f2635a54f730f46398d1d28eb184e56a361f910 Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Tue, 17 Feb 2026 23:11:20 +0900 Subject: [PATCH 06/12] =?UTF-8?q?refactor:=20linux=E7=92=B0=E5=A2=83?= =?UTF-8?q?=E3=81=A7localpackage=E3=81=AE=E3=83=93=E3=83=AB=E3=83=89?= =?UTF-8?q?=E3=82=92=E8=A1=8C=E3=81=86=E3=81=9F=E3=82=81=E3=81=AE=E6=BA=96?= =?UTF-8?q?=E5=82=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci_local_package.yml | 29 ++++++++ .../HometeDomain/Account/AccountStore.swift | 2 +- .../HometeDomain/Account/LoginContext.swift | 7 -- .../Authentification/AccountAuthStore.swift | 2 +- .../ConfirmedRegistrationPeers.swift | 2 + .../Cohabitant/CohabitantStore.swift | 2 +- .../Housework/HouseworkListStore.swift | 3 +- .../Dependencies/NonceGenerationClient.swift | 1 - .../Setting/SettingMenuItem.swift | 4 ++ .../ConfirmedRegistrationPeersTest.swift | 71 ------------------- .../P2P/ConfirmedRegistrationPeers.swift | 24 +++++++ .../LoginContext+Environment.swift | 14 ++++ 12 files changed, 78 insertions(+), 83 deletions(-) create mode 100644 .github/workflows/ci_local_package.yml delete mode 100644 LocalPackage/Tests/HometeDomainTests/CohabitantRegistration/ConfirmedRegistrationPeersTest.swift create mode 100644 homete/Model/Service/P2P/ConfirmedRegistrationPeers.swift create mode 100644 homete/Views/Components/Environment/LoginContext+Environment.swift diff --git a/.github/workflows/ci_local_package.yml b/.github/workflows/ci_local_package.yml new file mode 100644 index 00000000..9b8b25ec --- /dev/null +++ b/.github/workflows/ci_local_package.yml @@ -0,0 +1,29 @@ +name: local_package_test + +on: + push: + branches: ["main"] + paths: + - "LocalPackage/**" + pull_request: + branches: ["**"] + paths: + - "LocalPackage/**" + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + + - name: Install Swift via swiftly + uses: swiftlang/swiftly-action@v1 + with: + toolchain: 6.2-snapshot + + - name: Show Swift version + run: swift --version + + - name: Run tests + run: swift test --package-path LocalPackage diff --git a/LocalPackage/Sources/HometeDomain/Account/AccountStore.swift b/LocalPackage/Sources/HometeDomain/Account/AccountStore.swift index 758530f8..e981f6dd 100644 --- a/LocalPackage/Sources/HometeDomain/Account/AccountStore.swift +++ b/LocalPackage/Sources/HometeDomain/Account/AccountStore.swift @@ -5,7 +5,7 @@ // Created by 佐藤汰一 on 2025/08/03. // -import SwiftUI +import Observation @MainActor @Observable diff --git a/LocalPackage/Sources/HometeDomain/Account/LoginContext.swift b/LocalPackage/Sources/HometeDomain/Account/LoginContext.swift index 57c64f0c..0b2149b5 100644 --- a/LocalPackage/Sources/HometeDomain/Account/LoginContext.swift +++ b/LocalPackage/Sources/HometeDomain/Account/LoginContext.swift @@ -5,8 +5,6 @@ // Created by 佐藤汰一 on 2025/12/27. // -import SwiftUI - public struct LoginContext: Equatable { public let account: Account @@ -18,8 +16,3 @@ public struct LoginContext: Equatable { self.account = account } } - -public extension EnvironmentValues { - - @Entry var loginContext = LoginContext(account: .init(id: "", userName: "", fcmToken: nil, cohabitantId: nil)) -} diff --git a/LocalPackage/Sources/HometeDomain/Authentification/AccountAuthStore.swift b/LocalPackage/Sources/HometeDomain/Authentification/AccountAuthStore.swift index 607d7ff0..86e290d2 100644 --- a/LocalPackage/Sources/HometeDomain/Authentification/AccountAuthStore.swift +++ b/LocalPackage/Sources/HometeDomain/Authentification/AccountAuthStore.swift @@ -5,7 +5,7 @@ // Created by 佐藤汰一 on 2025/08/09. // -import SwiftUI +import Observation @MainActor @Observable diff --git a/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/ConfirmedRegistrationPeers.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/ConfirmedRegistrationPeers.swift index 247bab9c..03d5c768 100644 --- a/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/ConfirmedRegistrationPeers.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantRegistration/ConfirmedRegistrationPeers.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/08/30. // +#if canImport(MultipeerConnectivity) import MultipeerConnectivity public struct ConfirmedRegistrationPeers: Equatable { @@ -29,3 +30,4 @@ public struct ConfirmedRegistrationPeers: Equatable { return firstPeerID == myPeerID } } +#endif diff --git a/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantStore.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantStore.swift index 7d7bd85d..250234f3 100644 --- a/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantStore.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/CohabitantStore.swift @@ -5,7 +5,7 @@ // Created by 佐藤汰一 on 2026/01/04. // -import SwiftUI +import Observation @MainActor @Observable diff --git a/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkListStore.swift b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkListStore.swift index 70e5521d..d564c152 100644 --- a/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkListStore.swift +++ b/LocalPackage/Sources/HometeDomain/Cohabitant/Housework/HouseworkListStore.swift @@ -5,7 +5,8 @@ // Created by 佐藤汰一 on 2025/09/27. // -import SwiftUI +import Foundation +import Observation @MainActor @Observable diff --git a/LocalPackage/Sources/HometeDomain/Dependencies/NonceGenerationClient.swift b/LocalPackage/Sources/HometeDomain/Dependencies/NonceGenerationClient.swift index c56633d3..43594936 100644 --- a/LocalPackage/Sources/HometeDomain/Dependencies/NonceGenerationClient.swift +++ b/LocalPackage/Sources/HometeDomain/Dependencies/NonceGenerationClient.swift @@ -5,7 +5,6 @@ // Created by 佐藤汰一 on 2025/08/03. // -import CryptoKit import Foundation public struct NonceGenerationClient: Sendable { diff --git a/LocalPackage/Sources/HometeDomain/Setting/SettingMenuItem.swift b/LocalPackage/Sources/HometeDomain/Setting/SettingMenuItem.swift index 009a8b72..6fc12f43 100644 --- a/LocalPackage/Sources/HometeDomain/Setting/SettingMenuItem.swift +++ b/LocalPackage/Sources/HometeDomain/Setting/SettingMenuItem.swift @@ -5,7 +5,9 @@ // Created by 佐藤汰一 on 2025/08/11. // +#if canImport(SwiftUI) import SwiftUI +#endif public enum SettingMenuItem: Equatable, CaseIterable { @@ -13,6 +15,7 @@ public enum SettingMenuItem: Equatable, CaseIterable { case privacyPolicy case license + #if canImport(SwiftUI) public var title: LocalizedStringKey { switch self { @@ -26,6 +29,7 @@ public enum SettingMenuItem: Equatable, CaseIterable { return "ライセンス" } } + #endif public var iconName: String { diff --git a/LocalPackage/Tests/HometeDomainTests/CohabitantRegistration/ConfirmedRegistrationPeersTest.swift b/LocalPackage/Tests/HometeDomainTests/CohabitantRegistration/ConfirmedRegistrationPeersTest.swift deleted file mode 100644 index ed3c64e3..00000000 --- a/LocalPackage/Tests/HometeDomainTests/CohabitantRegistration/ConfirmedRegistrationPeersTest.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// ConfirmedRegistrationPeersTest.swift -// hometeTests -// -// Created by 佐藤汰一 on 2025/08/31. -// - -import MultipeerConnectivity -import Testing -@testable import HometeDomain - -struct ConfirmedRegistrationPeersTest { - - @Test("PeerIDの表示名の降順で一番最初のPeerIDになるデバイスがリードデバイスになる") - func isLeadPeer_lead_case() throws { - - let connectedPeers: Set = [ - .init(displayName: "BBB"), - .init(displayName: "CCC"), - .init(displayName: "EEE") - ] - let peers = ConfirmedRegistrationPeers(peers: connectedPeers) - - let actual = peers.isLeadPeer( - connectedPeers: connectedPeers, - myPeerID: .init(displayName: "AAA") - ) - - #expect(actual == true) - } - - @Test( - "PeerIDの表示名の降順で一番最初ではない場合は、フォロワーデバイスになる", - arguments: [ - "DDD", - "FFF", - "CCC", - "ZZZ" - ] - ) - func isLeadPeer_follower_case(myPeerIDString: String) throws { - - let connectedPeers: Set = [ - .init(displayName: "BBB"), - .init(displayName: "CCC"), - .init(displayName: "EEE") - ] - let peers = ConfirmedRegistrationPeers(peers: connectedPeers) - - let actual = peers.isLeadPeer(connectedPeers: connectedPeers, myPeerID: .init(displayName: myPeerIDString)) - - #expect(actual == false) - } - - @Test( - "接続中の全てのデバイスが確認済みでない場合は、まだ登録メンバーが揃っていないのでリードデバイスかどうかを返さない" - ) - func isLeadPeer_not_match_case() throws { - - let confirmedPeers: Set = [ - .init(displayName: "BBB"), - .init(displayName: "CCC") - ] - let connectedPeers: Set = .init(confirmedPeers + [.init(displayName: "EEE")]) - let peers = ConfirmedRegistrationPeers(peers: confirmedPeers) - - let actual = peers.isLeadPeer(connectedPeers: connectedPeers, myPeerID: .init(displayName: "AAA")) - - #expect(actual == nil) - } -} diff --git a/homete/Model/Service/P2P/ConfirmedRegistrationPeers.swift b/homete/Model/Service/P2P/ConfirmedRegistrationPeers.swift new file mode 100644 index 00000000..013835ff --- /dev/null +++ b/homete/Model/Service/P2P/ConfirmedRegistrationPeers.swift @@ -0,0 +1,24 @@ +import MultipeerConnectivity + +struct ConfirmedRegistrationPeers: Equatable { + + private var peers: Set + + init(peers: Set) { + + self.peers = peers + } + + mutating func addPeer(_ peer: MCPeerID) { + + peers.insert(peer) + } + + func isLeadPeer(connectedPeers: Set, myPeerID: MCPeerID) -> Bool? { + + guard peers == connectedPeers else { return nil } + let firstPeerID = ([myPeerID] + connectedPeers) + .sorted { $0.displayName < $1.displayName }.first + return firstPeerID == myPeerID + } +} diff --git a/homete/Views/Components/Environment/LoginContext+Environment.swift b/homete/Views/Components/Environment/LoginContext+Environment.swift new file mode 100644 index 00000000..6bf24d9b --- /dev/null +++ b/homete/Views/Components/Environment/LoginContext+Environment.swift @@ -0,0 +1,14 @@ +// +// LoginContext+Environment.swift +// homete +// +// Created by 佐藤汰一 on 2025/12/27. +// + +import HometeDomain +import SwiftUI + +extension EnvironmentValues { + + @Entry var loginContext = LoginContext(account: .init(id: "", userName: "", fcmToken: nil, cohabitantId: nil)) +} From 274befa025dcc0c29c078a3fe26c5caffd8201a9 Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Tue, 17 Feb 2026 23:18:04 +0900 Subject: [PATCH 07/12] =?UTF-8?q?fix:=20CI=E3=81=AESwift=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=83=BC=E3=83=ABAction=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit swiftlang/swiftly-actionが存在しないため、swift-actions/setup-swift@v2に差し替え。 Swift 6.2正式版を使用。 Co-Authored-By: Claude Opus 4.6 --- .github/workflows/ci_local_package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_local_package.yml b/.github/workflows/ci_local_package.yml index 9b8b25ec..0d820e51 100644 --- a/.github/workflows/ci_local_package.yml +++ b/.github/workflows/ci_local_package.yml @@ -17,10 +17,10 @@ jobs: steps: - uses: actions/checkout@v4.2.2 - - name: Install Swift via swiftly - uses: swiftlang/swiftly-action@v1 + - name: Setup Swift + uses: swift-actions/setup-swift@v2 with: - toolchain: 6.2-snapshot + swift-version: "6.2" - name: Show Swift version run: swift --version From 574d6346ec660ff30dcba8af0590c10499268b07 Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Tue, 17 Feb 2026 23:21:48 +0900 Subject: [PATCH 08/12] =?UTF-8?q?fix:=20AccountAuthStoreTest=E3=81=8B?= =?UTF-8?q?=E3=82=89import=20os=E3=82=92=E9=99=A4=E5=8E=BB=E3=81=97Linux?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OSAllocatedUnfairLockをconfirmationパターンに置き換え。 @MainActorスコープの同期テストなのでスレッドセーフティの保証は維持される。 Co-Authored-By: Claude Opus 4.6 --- .../AccountAuthStoreTest.swift | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/LocalPackage/Tests/HometeDomainTests/AccountAuthStoreTest.swift b/LocalPackage/Tests/HometeDomainTests/AccountAuthStoreTest.swift index 505af937..4e832b53 100644 --- a/LocalPackage/Tests/HometeDomainTests/AccountAuthStoreTest.swift +++ b/LocalPackage/Tests/HometeDomainTests/AccountAuthStoreTest.swift @@ -5,7 +5,6 @@ // Created by 佐藤汰一 on 2025/08/09. // -import os import Testing @testable import HometeDomain @@ -55,40 +54,42 @@ struct AccountAuthStoreTest { } @Test("ログアウト時はローカルのログイン状態をログアウトにしてログアウト処理を行い、イベントログを送信する") - func test_logout() throws { - - let isCallSignOut = OSAllocatedUnfairLock(initialState: false) - let isCallAnalyticsLog = OSAllocatedUnfairLock(initialState: false) - - let store = AccountAuthStore( - accountAuthClient: .init( - signIn: { _, _ in - - Issue.record() - return .init(id: "") - }, - signOut: { isCallSignOut.withLock { $0 = true } }, - makeListener: { .defaultValue() } - ), - analyticsClient: .init( - setId: { _ in - - Issue.record() - }, - log: { event in - - isCallAnalyticsLog.withLock { $0 = true } - #expect(event == .logout()) - } - ), - currentAuth: .init(result: .init(id: "test"), alreadyLoadedAtInitiate: true) - ) - - store.logOut() - - #expect(isCallSignOut.withLock { $0 }) - #expect(isCallAnalyticsLog.withLock { $0 }) - #expect(store.currentAuth == .init(result: nil, alreadyLoadedAtInitiate: true)) + func test_logout() async throws { + + try await confirmation(expectedCount: 3) { confirmation in + + let store = AccountAuthStore( + accountAuthClient: .init( + signIn: { _, _ in + + Issue.record() + return .init(id: "") + }, + signOut: { confirmation() }, + makeListener: { + + confirmation() + return .defaultValue() + } + ), + analyticsClient: .init( + setId: { _ in + + Issue.record() + }, + log: { event in + + confirmation() + #expect(event == .logout()) + } + ), + currentAuth: .init(result: .init(id: "test"), alreadyLoadedAtInitiate: true) + ) + + store.logOut() + + #expect(store.currentAuth == .init(result: nil, alreadyLoadedAtInitiate: true)) + } } @Test("再認証を行った後にアカウントを削除し認証トークンの無効化を行いログアウト状態にすることで、退会処理を完了させる") From 443d7a1d7414b8ada89c52cb98f1c30a2f1c9a83 Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Wed, 18 Feb 2026 18:54:54 +0900 Subject: [PATCH 09/12] =?UTF-8?q?refactor:=20prefire=E3=81=AE=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=81=A7=E3=82=82HometeDomain=E3=81=ABr?= =?UTF-8?q?=E4=BE=9D=E5=AD=98=E3=81=95=E3=81=9B=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prefire.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.prefire.yml b/.prefire.yml index 99b43a4a..c65d76b6 100644 --- a/.prefire.yml +++ b/.prefire.yml @@ -15,3 +15,5 @@ test_configuration: - UIKit - SwiftUI - MultipeerConnectivity + - HometeDomain + From 4cb65074d0e1e55a3b2a151e112c42d5eef1d88a Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Wed, 18 Feb 2026 19:13:07 +0900 Subject: [PATCH 10/12] =?UTF-8?q?perf:=20Swift=E3=83=84=E3=83=BC=E3=83=AB?= =?UTF-8?q?=E3=83=81=E3=82=A7=E3=83=BC=E3=83=B3=E3=81=AE=E3=82=AD=E3=83=A3?= =?UTF-8?q?=E3=83=83=E3=82=B7=E3=83=A5=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=97?= =?UTF-8?q?=E3=81=A6CI=E9=AB=98=E9=80=9F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- .github/workflows/ci_local_package.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci_local_package.yml b/.github/workflows/ci_local_package.yml index 0d820e51..62f44cf5 100644 --- a/.github/workflows/ci_local_package.yml +++ b/.github/workflows/ci_local_package.yml @@ -14,13 +14,29 @@ on: jobs: test: runs-on: ubuntu-latest + env: + SWIFT_VERSION: "6.2" + SWIFTLY_HOME_DIR: ${{ github.workspace }}/.swiftly + SWIFTLY_BIN_DIR: ${{ github.workspace }}/.swiftly/bin steps: - uses: actions/checkout@v4.2.2 + - name: Cache Swift toolchain + id: cache-swift + uses: actions/cache@v4 + with: + path: ${{ env.SWIFTLY_HOME_DIR }} + key: swift-toolchain-${{ runner.os }}-${{ env.SWIFT_VERSION }} + - name: Setup Swift + if: steps.cache-swift.outputs.cache-hit != 'true' uses: swift-actions/setup-swift@v2 with: - swift-version: "6.2" + swift-version: ${{ env.SWIFT_VERSION }} + + - name: Restore Swift PATH from cache + if: steps.cache-swift.outputs.cache-hit == 'true' + run: echo "${{ env.SWIFTLY_BIN_DIR }}" >> $GITHUB_PATH - name: Show Swift version run: swift --version From 67f6d19d237e870812576aa5aa078e49ec74c060 Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Wed, 18 Feb 2026 19:16:17 +0900 Subject: [PATCH 11/12] =?UTF-8?q?fix:=20=E8=AD=A6=E5=91=8A=E3=81=AE?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LocalPackage/Sources/HometeDomain/Account/Account.swift | 1 + homete/Model/ImplDependencies/ImplAccountAuthClient.swift | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/LocalPackage/Sources/HometeDomain/Account/Account.swift b/LocalPackage/Sources/HometeDomain/Account/Account.swift index e7bfc52b..d8b5eef0 100644 --- a/LocalPackage/Sources/HometeDomain/Account/Account.swift +++ b/LocalPackage/Sources/HometeDomain/Account/Account.swift @@ -5,6 +5,7 @@ // Created by 佐藤汰一 on 2025/08/03. // +// TODO: ダミー public struct Account: Equatable, Codable, Sendable { public let id: String diff --git a/homete/Model/ImplDependencies/ImplAccountAuthClient.swift b/homete/Model/ImplDependencies/ImplAccountAuthClient.swift index 2d8253ac..00c1035e 100644 --- a/homete/Model/ImplDependencies/ImplAccountAuthClient.swift +++ b/homete/Model/ImplDependencies/ImplAccountAuthClient.swift @@ -47,7 +47,7 @@ extension AccountAuthClient { } try await user.delete() } - ) + ) } private extension AccountAuthClient { From a169a09e51ecda4e02a30a04daac099918ef5baf Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Wed, 18 Feb 2026 19:16:49 +0900 Subject: [PATCH 12/12] =?UTF-8?q?fix:=20Swift=E3=83=84=E3=83=BC=E3=83=AB?= =?UTF-8?q?=E3=83=81=E3=82=A7=E3=83=BC=E3=83=B3=E3=81=AE=E3=82=AD=E3=83=A3?= =?UTF-8?q?=E3=83=83=E3=82=B7=E3=83=A5=E3=83=91=E3=82=B9=E3=82=92=E5=AE=9F?= =?UTF-8?q?=E9=9A=9B=E3=81=AE=E3=82=A4=E3=83=B3=E3=82=B9=E3=83=88=E3=83=BC?= =?UTF-8?q?=E3=83=AB=E5=85=88=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- .github/workflows/ci_local_package.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci_local_package.yml b/.github/workflows/ci_local_package.yml index 62f44cf5..eb4bf418 100644 --- a/.github/workflows/ci_local_package.yml +++ b/.github/workflows/ci_local_package.yml @@ -16,8 +16,7 @@ jobs: runs-on: ubuntu-latest env: SWIFT_VERSION: "6.2" - SWIFTLY_HOME_DIR: ${{ github.workspace }}/.swiftly - SWIFTLY_BIN_DIR: ${{ github.workspace }}/.swiftly/bin + SWIFT_TOOLCHAIN_DIR: /opt/hostedtoolcache/swift-Ubuntu steps: - uses: actions/checkout@v4.2.2 @@ -25,7 +24,7 @@ jobs: id: cache-swift uses: actions/cache@v4 with: - path: ${{ env.SWIFTLY_HOME_DIR }} + path: ${{ env.SWIFT_TOOLCHAIN_DIR }} key: swift-toolchain-${{ runner.os }}-${{ env.SWIFT_VERSION }} - name: Setup Swift @@ -36,7 +35,9 @@ jobs: - name: Restore Swift PATH from cache if: steps.cache-swift.outputs.cache-hit == 'true' - run: echo "${{ env.SWIFTLY_BIN_DIR }}" >> $GITHUB_PATH + run: | + SWIFT_BIN=$(find ${{ env.SWIFT_TOOLCHAIN_DIR }} -name swift -type f -path "*/usr/bin/swift" | head -1) + echo "$(dirname "$SWIFT_BIN")" >> $GITHUB_PATH - name: Show Swift version run: swift --version