diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4762a8ac..48edb744 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,8 +20,17 @@ jobs: runs-on: macos-14 strategy: matrix: - destination: ['platform=iOS\ Simulator,name=iPhone\ 14\ Pro\ Max', 'platform=watchOS\ Simulator,name=Apple\ Watch\ Series\ 5\ \(40mm\)', 'platform=macOS CODE_SIGN_IDENTITY=""', 'platform=visionOS\ Simulator,OS=1.2,name=Apple\ Vision\ Pro CODE_SIGN_IDENTITY=""'] - action: ['test'] + destination: ['platform=iOS\ Simulator,name=iPhone\ 15\ Pro\ Max', 'platform=watchOS\ Simulator,name=Apple\ Watch\ Series\ 9\ \(41mm\)', 'platform=macOS CODE_SIGN_IDENTITY=""', 'platform=visionOS\ Simulator,OS=1.2,name=Apple\ Vision\ Pro CODE_SIGN_IDENTITY=""'] + action: ['test', 'build'] + exclude: + - destination: 'platform=iOS\ Simulator,OS=17.5,name=iPhone\ 15\ Pro\ Max' + action: 'build' + - destination: 'platform=macOS CODE_SIGN_IDENTITY=""' + action: 'build' + - destination: 'platform=visionOS\ Simulator,OS=1.2,name=Apple\ Vision\ Pro CODE_SIGN_IDENTITY=""' + action: 'test' + - destination: 'platform=watchOS\ Simulator,name=Apple\ Watch\ Series\ 9\ \(41mm\)' + action: 'test' steps: - uses: actions/checkout@v4 - name: Install Swiftlint diff --git a/CareKitEssentials.xcodeproj/project.pbxproj b/CareKitEssentials.xcodeproj/project.pbxproj index 8cc3f1cc..203b7159 100644 --- a/CareKitEssentials.xcodeproj/project.pbxproj +++ b/CareKitEssentials.xcodeproj/project.pbxproj @@ -16,6 +16,13 @@ 70BBCB532A12BDAD00759A9C /* Slider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70BBCB522A12BDAC00759A9C /* Slider.swift */; }; 70BBCB552A12BDBD00759A9C /* SliderLogButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70BBCB542A12BDBD00759A9C /* SliderLogButton.swift */; }; 70BBCB572A14164E00759A9C /* SliderLogTaskViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70BBCB562A14164E00759A9C /* SliderLogTaskViewModel.swift */; }; + 70C422CA2C3B681A00E6DC51 /* OCKAnyOutcome+Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70C422C92C3B681A00E6DC51 /* OCKAnyOutcome+Sequence.swift */; }; + 70C422D22C3B6C7600E6DC51 /* TestHostApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70C422D12C3B6C7600E6DC51 /* TestHostApp.swift */; }; + 70C422D42C3B6C7600E6DC51 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70C422D32C3B6C7600E6DC51 /* ContentView.swift */; }; + 70C422D62C3B6C7600E6DC51 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 70C422D52C3B6C7600E6DC51 /* Assets.xcassets */; }; + 70C422DA2C3B6C7600E6DC51 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 70C422D92C3B6C7600E6DC51 /* Preview Assets.xcassets */; }; + 70C422DF2C3B6C8D00E6DC51 /* CareKitEssentials.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "carekitessentials::CareKitEssentials::Product" /* CareKitEssentials.framework */; }; + 70C422E02C3B6C8D00E6DC51 /* CareKitEssentials.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = "carekitessentials::CareKitEssentials::Product" /* CareKitEssentials.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 70FDC6162C387C9F00A32137 /* NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70FDC6152C387C9F00A32137 /* NSImage.swift */; }; 911BDB262A11C437004F8442 /* View+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = 911BDB252A11C437004F8442 /* View+Default.swift */; }; 911BDB282A11C491004F8442 /* CGFloat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 911BDB272A11C491004F8442 /* CGFloat.swift */; }; @@ -49,11 +56,25 @@ OBJ_765 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_39 /* UIImage.swift */; }; OBJ_766 /* OSValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_41 /* OSValue.swift */; }; OBJ_767 /* ScheduleUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_42 /* ScheduleUtility.swift */; }; - OBJ_791 /* CareKitEssentialsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_45 /* CareKitEssentialsTests.swift */; }; + OBJ_791 /* OCKOutcomeExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_45 /* OCKOutcomeExtensionsTests.swift */; }; OBJ_793 /* CareKitEssentials.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "carekitessentials::CareKitEssentials::Product" /* CareKitEssentials.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 70C422E12C3B6C8D00E6DC51 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = OBJ_1 /* Project object */; + proxyType = 1; + remoteGlobalIDString = "carekitessentials::CareKitEssentials"; + remoteInfo = CareKitEssentials; + }; + 70C422E42C3B6CA000E6DC51 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = OBJ_1 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 70C422CE2C3B6C7600E6DC51; + remoteInfo = TestHost; + }; 91F46E0E2A11BF70002EBDDD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = OBJ_1 /* Project object */; @@ -63,6 +84,20 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 70C422E32C3B6C8D00E6DC51 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 70C422E02C3B6C8D00E6DC51 /* CareKitEssentials.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 700DA8F92C29051900435E2C /* CareKitEssentials.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = CareKitEssentials.xctestplan; sourceTree = ""; }; 700DA9042C2A609600435E2C /* CareKitEssentialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CareKitEssentialView.swift; sourceTree = ""; }; @@ -74,6 +109,13 @@ 70BBCB522A12BDAC00759A9C /* Slider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Slider.swift; sourceTree = ""; }; 70BBCB542A12BDBD00759A9C /* SliderLogButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderLogButton.swift; sourceTree = ""; }; 70BBCB562A14164E00759A9C /* SliderLogTaskViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderLogTaskViewModel.swift; sourceTree = ""; }; + 70C422C92C3B681A00E6DC51 /* OCKAnyOutcome+Sequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCKAnyOutcome+Sequence.swift"; sourceTree = ""; }; + 70C422CF2C3B6C7600E6DC51 /* TestHost.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestHost.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 70C422D12C3B6C7600E6DC51 /* TestHostApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestHostApp.swift; sourceTree = ""; }; + 70C422D32C3B6C7600E6DC51 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 70C422D52C3B6C7600E6DC51 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 70C422D72C3B6C7600E6DC51 /* TestHost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TestHost.entitlements; sourceTree = ""; }; + 70C422D92C3B6C7600E6DC51 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 70FDC6152C387C9F00A32137 /* NSImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSImage.swift; sourceTree = ""; }; 911BDB252A11C437004F8442 /* View+Default.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Default.swift"; sourceTree = ""; }; 911BDB272A11C491004F8442 /* CGFloat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGFloat.swift; sourceTree = ""; }; @@ -106,13 +148,21 @@ OBJ_42 /* ScheduleUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleUtility.swift; sourceTree = ""; }; OBJ_420 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; OBJ_421 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - OBJ_45 /* CareKitEssentialsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CareKitEssentialsTests.swift; sourceTree = ""; }; + OBJ_45 /* OCKOutcomeExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OCKOutcomeExtensionsTests.swift; sourceTree = ""; }; OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; "carekitessentials::CareKitEssentials::Product" /* CareKitEssentials.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CareKitEssentials.framework; sourceTree = BUILT_PRODUCTS_DIR; }; "carekitessentials::CareKitEssentialsTests::Product" /* CareKitEssentialsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = CareKitEssentialsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 70C422CC2C3B6C7600E6DC51 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 70C422DF2C3B6C8D00E6DC51 /* CareKitEssentials.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; OBJ_768 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 0; @@ -186,6 +236,33 @@ path = SliderLog; sourceTree = ""; }; + 70C422D02C3B6C7600E6DC51 /* TestHost */ = { + isa = PBXGroup; + children = ( + 70C422D12C3B6C7600E6DC51 /* TestHostApp.swift */, + 70C422D32C3B6C7600E6DC51 /* ContentView.swift */, + 70C422D52C3B6C7600E6DC51 /* Assets.xcassets */, + 70C422D72C3B6C7600E6DC51 /* TestHost.entitlements */, + 70C422D82C3B6C7600E6DC51 /* Preview Content */, + ); + path = TestHost; + sourceTree = ""; + }; + 70C422D82C3B6C7600E6DC51 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 70C422D92C3B6C7600E6DC51 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 70C422DE2C3B6C8D00E6DC51 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; OBJ_14 /* DigitalCrown */ = { isa = PBXGroup; children = ( @@ -209,6 +286,7 @@ OBJ_29 /* OCKAnyEvent.swift */, OBJ_28 /* OCKAnyEvent+CustomStringConvertable.swift */, OBJ_30 /* OCKAnyOutcome.swift */, + 70C422C92C3B681A00E6DC51 /* OCKAnyOutcome+Sequence.swift */, OBJ_31 /* OCKBiologicalSex+Hashable.swift */, OBJ_33 /* OCKOutcome.swift */, OBJ_34 /* OCKOutcomeValue+Identifiable.swift */, @@ -235,6 +313,7 @@ children = ( "carekitessentials::CareKitEssentials::Product" /* CareKitEssentials.framework */, "carekitessentials::CareKitEssentialsTests::Product" /* CareKitEssentialsTests.xctest */, + 70C422CF2C3B6C7600E6DC51 /* TestHost.app */, ); name = Products; sourceTree = BUILT_PRODUCTS_DIR; @@ -250,7 +329,7 @@ OBJ_44 /* CareKitEssentialsTests */ = { isa = PBXGroup; children = ( - OBJ_45 /* CareKitEssentialsTests.swift */, + OBJ_45 /* OCKOutcomeExtensionsTests.swift */, ); name = CareKitEssentialsTests; path = Tests/CareKitEssentialsTests; @@ -263,9 +342,11 @@ OBJ_6 /* Package.swift */, OBJ_7 /* Sources */, OBJ_43 /* Tests */, + 70C422D02C3B6C7600E6DC51 /* TestHost */, OBJ_413 /* Products */, OBJ_420 /* LICENSE */, OBJ_421 /* README.md */, + 70C422DE2C3B6C8D00E6DC51 /* Frameworks */, ); sourceTree = ""; }; @@ -303,6 +384,25 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 70C422CE2C3B6C7600E6DC51 /* TestHost */ = { + isa = PBXNativeTarget; + buildConfigurationList = 70C422DB2C3B6C7600E6DC51 /* Build configuration list for PBXNativeTarget "TestHost" */; + buildPhases = ( + 70C422CB2C3B6C7600E6DC51 /* Sources */, + 70C422CC2C3B6C7600E6DC51 /* Frameworks */, + 70C422CD2C3B6C7600E6DC51 /* Resources */, + 70C422E32C3B6C8D00E6DC51 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 70C422E22C3B6C8D00E6DC51 /* PBXTargetDependency */, + ); + name = TestHost; + productName = TestHost; + productReference = 70C422CF2C3B6C7600E6DC51 /* TestHost.app */; + productType = "com.apple.product-type.application"; + }; "carekitessentials::CareKitEssentials" /* CareKitEssentials */ = { isa = PBXNativeTarget; buildConfigurationList = OBJ_736 /* Build configuration list for PBXNativeTarget "CareKitEssentials" */; @@ -336,6 +436,7 @@ ); dependencies = ( OBJ_797 /* PBXTargetDependency */, + 70C422E52C3B6CA000E6DC51 /* PBXTargetDependency */, ); name = CareKitEssentialsTests; productName = CareKitEssentialsTests; @@ -350,7 +451,16 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftMigration = 9999; + LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1540; + TargetAttributes = { + 70C422CE2C3B6C7600E6DC51 = { + CreatedOnToolsVersion = 15.4; + }; + "carekitessentials::CareKitEssentialsTests" = { + TestTargetID = 70C422CE2C3B6C7600E6DC51; + }; + }; }; buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "CareKitEssentials" */; compatibilityVersion = "Xcode 3.2"; @@ -370,10 +480,23 @@ targets = ( "carekitessentials::CareKitEssentials" /* CareKitEssentials */, "carekitessentials::CareKitEssentialsTests" /* CareKitEssentialsTests */, + 70C422CE2C3B6C7600E6DC51 /* TestHost */, ); }; /* End PBXProject section */ +/* Begin PBXResourcesBuildPhase section */ + 70C422CD2C3B6C7600E6DC51 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 70C422DA2C3B6C7600E6DC51 /* Preview Assets.xcassets in Resources */, + 70C422D62C3B6C7600E6DC51 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + /* Begin PBXShellScriptBuildPhase section */ 911BDB292A11C81C004F8442 /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; @@ -397,6 +520,15 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 70C422CB2C3B6C7600E6DC51 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 70C422D42C3B6C7600E6DC51 /* ContentView.swift in Sources */, + 70C422D22C3B6C7600E6DC51 /* TestHostApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; OBJ_739 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 0; @@ -426,6 +558,7 @@ 70BBCB472A12B8F500759A9C /* CardEnabledEnvironmentKey.swift in Sources */, 70BBCB4E2A12BB0500759A9C /* SliderLogTaskView.swift in Sources */, 91A9E7DE2A19758300F3414D /* NumericProgressTaskView.swift in Sources */, + 70C422CA2C3B681A00E6DC51 /* OCKAnyOutcome+Sequence.swift in Sources */, 911BDB342A130AF9004F8442 /* OCKStore.swift in Sources */, OBJ_757 /* OCKBiologicalSex+Hashable.swift in Sources */, OBJ_758 /* CustomLinearCareTaskProgress.swift in Sources */, @@ -447,13 +580,23 @@ isa = PBXSourcesBuildPhase; buildActionMask = 0; files = ( - OBJ_791 /* CareKitEssentialsTests.swift in Sources */, + OBJ_791 /* OCKOutcomeExtensionsTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 70C422E22C3B6C8D00E6DC51 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = "carekitessentials::CareKitEssentials" /* CareKitEssentials */; + targetProxy = 70C422E12C3B6C8D00E6DC51 /* PBXContainerItemProxy */; + }; + 70C422E52C3B6CA000E6DC51 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 70C422CE2C3B6C7600E6DC51 /* TestHost */; + targetProxy = 70C422E42C3B6CA000E6DC51 /* PBXContainerItemProxy */; + }; OBJ_797 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = "carekitessentials::CareKitEssentials" /* CareKitEssentials */; @@ -462,6 +605,126 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 70C422DC2C3B6C7600E6DC51 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = TestHost/TestHost.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"TestHost/Preview Content\""; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = edu.netrecon.usc.TestHost; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + }; + name = Debug; + }; + 70C422DD2C3B6C7600E6DC51 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = TestHost/TestHost.entitlements; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"TestHost/Preview Content\""; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = edu.netrecon.usc.TestHost; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + }; + name = Release; + }; OBJ_3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -688,6 +951,7 @@ SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,4,7"; TARGET_NAME = CareKitEssentialsTests; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestHost.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/TestHost"; TVOS_DEPLOYMENT_TARGET = 14.0; WATCHOS_DEPLOYMENT_TARGET = 7.0; }; @@ -724,6 +988,7 @@ SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,4,7"; TARGET_NAME = CareKitEssentialsTests; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestHost.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/TestHost"; TVOS_DEPLOYMENT_TARGET = 14.0; WATCHOS_DEPLOYMENT_TARGET = 7.0; }; @@ -732,6 +997,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 70C422DB2C3B6C7600E6DC51 /* Build configuration list for PBXNativeTarget "TestHost" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 70C422DC2C3B6C7600E6DC51 /* Debug */, + 70C422DD2C3B6C7600E6DC51 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; OBJ_2 /* Build configuration list for PBXProject "CareKitEssentials" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/CareKitEssentials.xcodeproj/xcshareddata/xcschemes/TestHost.xcscheme b/CareKitEssentials.xcodeproj/xcshareddata/xcschemes/TestHost.xcscheme new file mode 100644 index 00000000..39cd52cd --- /dev/null +++ b/CareKitEssentials.xcodeproj/xcshareddata/xcschemes/TestHost.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/CareKitEssentials/Extensions/OCKAnyOutcome+Sequence.swift b/Sources/CareKitEssentials/Extensions/OCKAnyOutcome+Sequence.swift new file mode 100644 index 00000000..94d1971b --- /dev/null +++ b/Sources/CareKitEssentials/Extensions/OCKAnyOutcome+Sequence.swift @@ -0,0 +1,88 @@ +// +// OCKAnyOutcome+Sequence.swift +// CareKitEssentials +// +// Created by Corey Baker on 7/7/24. +// + +import CareKitStore +import Foundation + +public extension Sequence where Element: OCKAnyOutcome { + + /** + Returns the `OCKAnyOutcome` sorted in order from newest to oldest with respect to a given `KeyPath`. + When necessary, can specify a cutoff value for the respective `KeyPath` to be less than or equal to. + + - parameter keyPath: An optional `Comparable` `KeyPath` to sort the `OCKAnyOutcome`'s by. + - parameter lessThanEqualTo: The value that the `keyPath` of all `OCKAnyOutcome`'s should + be less than or equal to. If this value is `nil`, the + - returns: Returns an array of `OCKAnyOutcome` sorted from newest to oldest with respect to `keyPath`. + - throws: An error when the `keyPath` cannot be unwrapped for any of the `OCKAnyOutcome` values + in the array. + */ + func sortedNewestToOldest( + _ keyPath: KeyPath, + lessThanEqualTo value: V? = nil + ) throws -> [Element] where V: Comparable { + let outcomes = try compactMap { outcome -> Element? in + guard let outcomeKeyValue = outcome[keyPath: keyPath] else { + throw CareKitEssentialsError.couldntUnwrapRequiredField + } + // If there's a value to compare to, check that the element + // is less than or equal to this value. + if let value = value { + guard outcomeKeyValue <= value else { + return nil + } + return outcome + } + return outcome + } + let sortedOutcomes = try outcomes.sorted(by: { + guard let firstKeyValue = $0[keyPath: keyPath], + let secondKeyValue = $1[keyPath: keyPath] else { + // Should never occur due to compactMap above + throw CareKitEssentialsError.couldntUnwrapRequiredField + } + return firstKeyValue > secondKeyValue + }) + + return sortedOutcomes + } + + /** + Returns the `OCKAnyOutcome` sorted in order from newest to oldest with respect to a given `KeyPath`. + When necessary, can specify a cutoff value for the respective `KeyPath` to be less than or equal to. + + - parameter keyPath: A `Comparable` `KeyPath` to sort the `OCKAnyOutcome`'s by. + - parameter lessThanEqualTo: The value that the `keyPath` of all `OCKAnyOutcome`'s should + be less than or equal to. If this value is `nil`, the + - returns: Returns an array of `OCKAnyOutcome` sorted from newest to oldest with respect to `keyPath`. + */ + func sortedNewestToOldest( + _ keyPath: KeyPath, + lessThanEqualTo value: V? = nil + ) -> [Element] where V: Comparable { + let outcomes = compactMap { outcome -> Element? in + let outcomeKeyValue = outcome[keyPath: keyPath] + + // If there's a value to compare to, check that the element + // is less than or equal to this value. + if let value = value { + guard outcomeKeyValue <= value else { + return nil + } + return outcome + } + return outcome + } + let sortedOutcomes = outcomes.sorted(by: { + let firstKeyValue = $0[keyPath: keyPath] + let secondKeyValue = $1[keyPath: keyPath] + return firstKeyValue > secondKeyValue + }) + + return sortedOutcomes + } +} diff --git a/TestHost/Assets.xcassets/AccentColor.colorset/Contents.json b/TestHost/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/TestHost/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TestHost/Assets.xcassets/AppIcon.appiconset/Contents.json b/TestHost/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..532cd729 --- /dev/null +++ b/TestHost/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,63 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TestHost/Assets.xcassets/Contents.json b/TestHost/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/TestHost/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TestHost/ContentView.swift b/TestHost/ContentView.swift new file mode 100644 index 00000000..385d0e40 --- /dev/null +++ b/TestHost/ContentView.swift @@ -0,0 +1,24 @@ +// +// ContentView.swift +// TestHost +// +// Created by Corey Baker on 7/7/24. +// + +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + } + .padding() + } +} + +#Preview { + ContentView() +} diff --git a/TestHost/Preview Content/Preview Assets.xcassets/Contents.json b/TestHost/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/TestHost/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TestHost/TestHost.entitlements b/TestHost/TestHost.entitlements new file mode 100644 index 00000000..f2ef3ae0 --- /dev/null +++ b/TestHost/TestHost.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/TestHost/TestHostApp.swift b/TestHost/TestHostApp.swift new file mode 100644 index 00000000..0c14c51a --- /dev/null +++ b/TestHost/TestHostApp.swift @@ -0,0 +1,17 @@ +// +// TestHostApp.swift +// TestHost +// +// Created by Corey Baker on 7/7/24. +// + +import SwiftUI + +@main +struct TestHostApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/Tests/CareKitEssentialsTests/CareKitEssentialsTests.swift b/Tests/CareKitEssentialsTests/CareKitEssentialsTests.swift deleted file mode 100644 index 9f68520d..00000000 --- a/Tests/CareKitEssentialsTests/CareKitEssentialsTests.swift +++ /dev/null @@ -1,6 +0,0 @@ -import XCTest -@testable import CareKitEssentials - -final class CareKitEssentialsTests: XCTestCase { - -} diff --git a/Tests/CareKitEssentialsTests/OCKOutcomeExtensionsTests.swift b/Tests/CareKitEssentialsTests/OCKOutcomeExtensionsTests.swift new file mode 100644 index 00000000..81f48c51 --- /dev/null +++ b/Tests/CareKitEssentialsTests/OCKOutcomeExtensionsTests.swift @@ -0,0 +1,99 @@ +import XCTest +import CareKitStore +@testable import CareKitEssentials + +final class OCKOutcomeExtensionsTests: XCTestCase { + + func testSortedNewestToOldestOptionalKeyPath() async throws { + let firstDate = Date() + let secondDate = firstDate + 1 + let taskUUID = UUID() + var firstOutcome = OCKOutcome( + taskUUID: taskUUID, + taskOccurrenceIndex: 0, + values: [] + ) + firstOutcome.createdDate = firstDate + + var secondOutcome = OCKOutcome( + taskUUID: taskUUID, + taskOccurrenceIndex: 1, + values: [] + ) + secondOutcome.createdDate = secondDate + + let outcomes = [firstOutcome, secondOutcome] + + // Test sorted properly + XCTAssertNotEqual(firstOutcome, secondOutcome) + let sortedByCreatedDate = try outcomes.sortedNewestToOldest( + \.createdDate + ) + XCTAssertEqual(sortedByCreatedDate.count, 2) + XCTAssertEqual(sortedByCreatedDate.first, secondOutcome) + XCTAssertEqual(sortedByCreatedDate.last, firstOutcome) + + // Test lessThanKeyPath + let sortedByCreatedDate2 = try outcomes.sortedNewestToOldest( + \.createdDate, + lessThanEqualTo: firstDate + ) + XCTAssertEqual(sortedByCreatedDate2.count, 1) + XCTAssertEqual(sortedByCreatedDate2.first, firstOutcome) + let sortedByCreatedDate3 = try outcomes.sortedNewestToOldest( + \.createdDate, + lessThanEqualTo: secondDate + ) + XCTAssertEqual(sortedByCreatedDate3.count, 2) + XCTAssertEqual(sortedByCreatedDate3.first, secondOutcome) + XCTAssertEqual(sortedByCreatedDate3.last, firstOutcome) + + // Test KeyPath of nil should throw error + XCTAssertThrowsError(try outcomes.sortedNewestToOldest( + \.updatedDate + )) + } + + func testSortedNewestToOldestRequiredKeyPath() async throws { + let firstIndex = 0 + let secondIndex = firstIndex + 1 + let taskUUID = UUID() + let firstOutcome = OCKOutcome( + taskUUID: taskUUID, + taskOccurrenceIndex: firstIndex, + values: [] + ) + + let secondOutcome = OCKOutcome( + taskUUID: taskUUID, + taskOccurrenceIndex: secondIndex, + values: [] + ) + + let outcomes = [firstOutcome, secondOutcome] + + // Test sorted properly + XCTAssertNotEqual(firstOutcome, secondOutcome) + let sortedByOccurrenceIndex = outcomes.sortedNewestToOldest( + \.taskOccurrenceIndex + ) + XCTAssertEqual(sortedByOccurrenceIndex.count, 2) + XCTAssertEqual(sortedByOccurrenceIndex.first, secondOutcome) + XCTAssertEqual(sortedByOccurrenceIndex.last, firstOutcome) + + // Test lessThanKeyPath + let sortedByOccurrenceIndex2 = outcomes.sortedNewestToOldest( + \.taskOccurrenceIndex, + lessThanEqualTo: firstIndex + ) + XCTAssertEqual(sortedByOccurrenceIndex2.count, 1) + XCTAssertEqual(sortedByOccurrenceIndex2.first, firstOutcome) + let sortedByOccurrenceIndex3 = outcomes.sortedNewestToOldest( + \.taskOccurrenceIndex, + lessThanEqualTo: secondIndex + ) + XCTAssertEqual(sortedByOccurrenceIndex3.count, 2) + XCTAssertEqual(sortedByOccurrenceIndex3.first, secondOutcome) + XCTAssertEqual(sortedByOccurrenceIndex3.last, firstOutcome) + } +}