diff --git a/Sources/SpeziAccount/AccountValue/Configuration/AccountKeyConfigurationImpl.swift b/Sources/SpeziAccount/AccountValue/Configuration/AccountKeyConfigurationImpl.swift index de62e0e2..941ef467 100644 --- a/Sources/SpeziAccount/AccountValue/Configuration/AccountKeyConfigurationImpl.swift +++ b/Sources/SpeziAccount/AccountValue/Configuration/AccountKeyConfigurationImpl.swift @@ -30,9 +30,9 @@ struct AccountKeyConfigurationImpl: AccountKeyConfiguration { let keyPathDescription: String - init(_ keyPath: KeyPath, type: AccountKeyRequirement) { + init(_ keyPath: KeyPath, requirement: AccountKeyRequirement) { self.key = Key.self - self.requirement = type + self.requirement = requirement self.keyPathDescription = keyPath.shortDescription } } @@ -58,6 +58,8 @@ extension AccountKeyConfigurationImpl { return ".collects(\(keyPathDescription))" case .supported: return ".supports(\(keyPathDescription))" + case .manual: + return ".manual(\(keyPathDescription))" } } diff --git a/Sources/SpeziAccount/AccountValue/Configuration/AccountKeyRequirement.swift b/Sources/SpeziAccount/AccountValue/Configuration/AccountKeyRequirement.swift index 414c5334..3dcbb319 100644 --- a/Sources/SpeziAccount/AccountValue/Configuration/AccountKeyRequirement.swift +++ b/Sources/SpeziAccount/AccountValue/Configuration/AccountKeyRequirement.swift @@ -23,6 +23,10 @@ public enum AccountKeyRequirement { /// The account value is **not** collected at signup. However, it is displayed in the account overview /// and a user can supply a value by editing their account details. case supported + /// The associated account value **can** be provided by the user at a later point in time. + /// + /// The account value is **not** collected at signup. It is also not displayed in the account overview. + case manual } diff --git a/Sources/SpeziAccount/AccountValue/Configuration/ConfiguredAccountKey.swift b/Sources/SpeziAccount/AccountValue/Configuration/ConfiguredAccountKey.swift index df89756d..74ff236c 100644 --- a/Sources/SpeziAccount/AccountValue/Configuration/ConfiguredAccountKey.swift +++ b/Sources/SpeziAccount/AccountValue/Configuration/ConfiguredAccountKey.swift @@ -38,7 +38,7 @@ public struct ConfiguredAccountKey { /// - Parameter keyPath: The `KeyPath` referencing the ``AccountKey``. /// - Returns: Returns the ``AccountKey`` configuration. public static func requires(_ keyPath: KeyPath) -> ConfiguredAccountKey { - .init(configuration: AccountKeyConfigurationImpl(keyPath, type: .required)) + .init(configuration: AccountKeyConfigurationImpl(keyPath, requirement: .required)) } /// Configure an ``AccountKey`` as ``AccountKeyRequirement/collected``. @@ -46,7 +46,7 @@ public struct ConfiguredAccountKey { /// - Returns: Returns the ``AccountKey`` configuration. @_disfavoredOverload public static func collects(_ keyPath: KeyPath) -> ConfiguredAccountKey { - .init(configuration: AccountKeyConfigurationImpl(keyPath, type: .collected)) + .init(configuration: AccountKeyConfigurationImpl(keyPath, requirement: .collected)) } /// Configure an ``AccountKey`` as ``AccountKeyRequirement/supported``. @@ -54,7 +54,15 @@ public struct ConfiguredAccountKey { /// - Returns: Returns the ``AccountKey`` configuration. @_disfavoredOverload public static func supports(_ keyPath: KeyPath) -> ConfiguredAccountKey { - .init(configuration: AccountKeyConfigurationImpl(keyPath, type: .supported)) + .init(configuration: AccountKeyConfigurationImpl(keyPath, requirement: .supported)) + } + + /// Configure an ``AccountKey`` as ``AccountKeyRequirement/manual``. + /// - Parameter keyPath: The `KeyPath` referencing the ``AccountKey``. + /// - Returns: Returns the ``AccountKey`` configuration. + @_disfavoredOverload + public static func manual(_ keyPath: KeyPath) -> ConfiguredAccountKey { + .init(configuration: AccountKeyConfigurationImpl(keyPath, requirement: .manual)) } /// Configure an ``AccountKey`` as ``AccountKeyRequirement/required`` as ``RequiredAccountKey`` can only be configured as required. @@ -73,6 +81,14 @@ public struct ConfiguredAccountKey { public static func supports(_ keyPath: KeyPath) -> ConfiguredAccountKey { requires(keyPath) } + + /// Configure an ``AccountKey`` as ``AccountKeyRequirement/required`` as ``RequiredAccountKey`` can only be configured as required. + /// - Parameter keyPath: The `KeyPath` referencing the ``AccountKey``. + /// - Returns: Returns the ``AccountKey`` configuration. + @available(*, deprecated, renamed: "requires", message: "A 'RequiredAccountKey' must always be supplied as required using requires(_:)") + public static func manual(_ keyPath: KeyPath) -> ConfiguredAccountKey { + requires(keyPath) + } } diff --git a/Sources/SpeziAccount/ViewModel/AccountOverviewFormViewModel.swift b/Sources/SpeziAccount/ViewModel/AccountOverviewFormViewModel.swift index 5d9d5ed8..b80987fc 100644 --- a/Sources/SpeziAccount/ViewModel/AccountOverviewFormViewModel.swift +++ b/Sources/SpeziAccount/ViewModel/AccountOverviewFormViewModel.swift @@ -46,7 +46,7 @@ class AccountOverviewFormViewModel { init(_ valueConfiguration: AccountValueConfiguration, _ serviceConfiguration: AccountServiceConfiguration) { - self.categorizedAccountKeys = valueConfiguration.allCategorized() + self.categorizedAccountKeys = valueConfiguration.allCategorized(filteredBy: [.required, .collected, .supported]) self.accountServiceConfiguration = serviceConfiguration } diff --git a/Sources/SpeziAccount/ViewModifier/AccountRequiredModifier.swift b/Sources/SpeziAccount/ViewModifier/AccountRequiredModifier.swift index 2fde38e4..ef251ce6 100644 --- a/Sources/SpeziAccount/ViewModifier/AccountRequiredModifier.swift +++ b/Sources/SpeziAccount/ViewModifier/AccountRequiredModifier.swift @@ -10,7 +10,7 @@ import OSLog import SwiftUI -private let logger = Logger(subsystem: "edu.stanford.sepzi.SepziAccount", category: "AccountRequiredModifier") +private let logger = Logger(subsystem: "edu.stanford.spezi.SpeziAccount", category: "AccountRequiredModifier") struct AccountRequiredModifier: ViewModifier { diff --git a/Sources/SpeziAccount/ViewModifier/DisableFieldAssitantsModifier.swift b/Sources/SpeziAccount/ViewModifier/DisableFieldAssistantsModifier.swift similarity index 100% rename from Sources/SpeziAccount/ViewModifier/DisableFieldAssitantsModifier.swift rename to Sources/SpeziAccount/ViewModifier/DisableFieldAssistantsModifier.swift diff --git a/Tests/UITests/TestApp/AccountTestsView.swift b/Tests/UITests/TestApp/AccountTestsView.swift index a3071efd..979b12ab 100644 --- a/Tests/UITests/TestApp/AccountTestsView.swift +++ b/Tests/UITests/TestApp/AccountTestsView.swift @@ -92,6 +92,11 @@ struct AccountTestsView: View { } label: { Text(verbatim: "License Information") } + if let invitationCode = account.details?.invitationCode { + LabeledContent("Invitation Code") { + Text(invitationCode) + } + } } } } diff --git a/Tests/UITests/TestApp/TestAppDelegate.swift b/Tests/UITests/TestApp/TestAppDelegate.swift index 37dcc966..d6b7cecf 100644 --- a/Tests/UITests/TestApp/TestAppDelegate.swift +++ b/Tests/UITests/TestApp/TestAppDelegate.swift @@ -32,7 +32,8 @@ class TestAppDelegate: SpeziAppDelegate { .collects(\.name), .collects(\.genderIdentity), .collects(\.dateOfBirth), - .supports(\.biography) + .supports(\.biography), + .manual(\.invitationCode) ] case .allRequired: #if os(visionOS) diff --git a/Tests/UITests/TestApp/Utils/InvitationCodeKey.swift b/Tests/UITests/TestApp/Utils/InvitationCodeKey.swift new file mode 100644 index 00000000..9a11e5a9 --- /dev/null +++ b/Tests/UITests/TestApp/Utils/InvitationCodeKey.swift @@ -0,0 +1,19 @@ +// +// This source file is part of the Spezi open-source project +// +// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) +// +// SPDX-License-Identifier: MIT +// + +import SpeziAccount +import SwiftUI + +extension AccountDetails { + @AccountKey(name: "Invitation Code", category: .other, as: String.self) + var invitationCode: String? +} + + +@KeyEntry(\.invitationCode) +extension AccountKeys {} diff --git a/Tests/UITests/TestAppUITests/DocumentationHintsTests.swift b/Tests/UITests/TestAppUITests/DocumentationHintsTests.swift index c8926ba8..6e3ae336 100644 --- a/Tests/UITests/TestAppUITests/DocumentationHintsTests.swift +++ b/Tests/UITests/TestAppUITests/DocumentationHintsTests.swift @@ -16,7 +16,7 @@ final class DocumentationHintsTests: XCTestCase { } @MainActor - func testDocumentationHint(type: ServiceType, button: String, hint: String) { + func testDocumentationHint(type: ServiceType, button: String, hint: String) throws { let app = XCUIApplication() app.launch(serviceType: type) @@ -37,8 +37,7 @@ final class DocumentationHintsTests: XCTestCase { sleep(3) #endif XCTAssert(safari.wait(for: .runningForeground, timeout: 5)) - XCTAssertTrue(safari.staticTexts["Swift Package Index"].waitForExistence(timeout: 10.0)) - XCTAssertTrue(safari.staticTexts["Initial Setup"].waitForExistence(timeout: 2.0)) // The initial setup article + safari.terminate() app.activate() @@ -46,8 +45,8 @@ final class DocumentationHintsTests: XCTestCase { } @MainActor - func testEmptyAccountServices() { - testDocumentationHint( + func testEmptyAccountServices() throws { + try testDocumentationHint( type: .empty, button: "Account Setup", hint: """ @@ -58,8 +57,8 @@ final class DocumentationHintsTests: XCTestCase { } @MainActor - func testMissingAccount() { - testDocumentationHint( + func testMissingAccount() throws { + try testDocumentationHint( type: .mail, button: "Account Overview", hint: """ diff --git a/Tests/UITests/UITests.xcodeproj/project.pbxproj b/Tests/UITests/UITests.xcodeproj/project.pbxproj index 018d8a92..00983c87 100644 --- a/Tests/UITests/UITests.xcodeproj/project.pbxproj +++ b/Tests/UITests/UITests.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 2F6D139A28F5F386007C25D6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2F6D139928F5F386007C25D6 /* Assets.xcassets */; }; 2FA7382C290ADFAA007ACEB9 /* TestApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA7382B290ADFAA007ACEB9 /* TestApp.swift */; }; 2FAD38C02A455FC200E79ED1 /* SpeziAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 2FAD38BF2A455FC200E79ED1 /* SpeziAccount */; }; + 9B345FB82C94BF470067C977 /* InvitationCodeKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B345FB72C94BF470067C977 /* InvitationCodeKey.swift */; }; A969240F2A9A198800E2128B /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = A969240E2A9A198800E2128B /* ArgumentParser */; }; A98739032C64EE6000E17A42 /* EntryViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98739022C64EE5F00E17A42 /* EntryViewTests.swift */; }; A98739052C64F1BB00E17A42 /* EntryViewsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98739042C64F1B300E17A42 /* EntryViewsTests.swift */; }; @@ -54,6 +55,7 @@ 2FAD38BE2A455F7D00E79ED1 /* SpeziAccount */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = SpeziAccount; path = ../..; sourceTree = ""; }; 2FE750C92A8720CE00723EAE /* TestApp.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TestApp.xctestplan; sourceTree = ""; }; 636D985F2AF188E00020B8BC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 9B345FB72C94BF470067C977 /* InvitationCodeKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InvitationCodeKey.swift; sourceTree = ""; }; A98739022C64EE5F00E17A42 /* EntryViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryViewTests.swift; sourceTree = ""; }; A98739042C64F1B300E17A42 /* EntryViewsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryViewsTests.swift; sourceTree = ""; }; A9B6E3F62A9B6F5B0008B232 /* AccountSetupTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSetupTests.swift; sourceTree = ""; }; @@ -94,6 +96,7 @@ isa = PBXGroup; children = ( A9B6E3FC2A9B74830008B232 /* BiographyKey.swift */, + 9B345FB72C94BF470067C977 /* InvitationCodeKey.swift */, A9B6E3FE2A9B795C0008B232 /* TestStandard.swift */, 2F027C7F29D6C29B00234098 /* AccountDetails+Default.swift */, ); @@ -284,6 +287,7 @@ 2FA7382C290ADFAA007ACEB9 /* TestApp.swift in Sources */, A98739032C64EE6000E17A42 /* EntryViewTests.swift in Sources */, A9EE7D2A2A3359E800C2B9A9 /* Features.swift in Sources */, + 9B345FB82C94BF470067C977 /* InvitationCodeKey.swift in Sources */, 2F027C9529D6C63100234098 /* TestAppDelegate.swift in Sources */, 2F027C8929D6C2AD00234098 /* AccountDetails+Default.swift in Sources */, A9B6E3FF2A9B795C0008B232 /* TestStandard.swift in Sources */,