diff --git a/Package.swift b/Package.swift index fd18de8..fef817d 100644 --- a/Package.swift +++ b/Package.swift @@ -21,7 +21,7 @@ let package = Package( .library(name: "SpeziHealthKit", targets: ["SpeziHealthKit"]) ], dependencies: [ - .package(url: "https://github.com/StanfordSpezi/Spezi", .upToNextMinor(from: "0.5.0")) + .package(url: "https://github.com/StanfordSpezi/Spezi", .upToNextMinor(from: "0.7.0")) ], targets: [ .target( diff --git a/Sources/SpeziHealthKit/Adapter/HKSampleRemovalContext.swift b/Sources/SpeziHealthKit/Adapter/HKSampleRemovalContext.swift deleted file mode 100644 index 26c37e7..0000000 --- a/Sources/SpeziHealthKit/Adapter/HKSampleRemovalContext.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// This source file is part of the Stanford Spezi open-source project -// -// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md) -// -// SPDX-License-Identifier: MIT -// - -@preconcurrency import HealthKit -import Spezi - - -public struct HKSampleRemovalContext: Identifiable, Sendable { - public let id: HKSample.ID - public let sampleType: HKSampleType - - - public init(id: HKSample.ID, sampleType: HKSampleType) { - self.id = id - self.sampleType = sampleType - } -} diff --git a/Sources/SpeziHealthKit/CollectSample/CollectSample.swift b/Sources/SpeziHealthKit/CollectSample/CollectSample.swift index 8dccf96..5da8845 100644 --- a/Sources/SpeziHealthKit/CollectSample/CollectSample.swift +++ b/Sources/SpeziHealthKit/CollectSample/CollectSample.swift @@ -35,11 +35,10 @@ public struct CollectSample: HealthKitDataSourceDescription { } - public func dataSources( + public func dataSources( healthStore: HKHealthStore, - standard: S, - adapter: HealthKit.HKSampleAdapter + standard: any HealthKitConstraint ) -> [any HealthKitDataSource] { - collectSamples.dataSources(healthStore: healthStore, standard: standard, adapter: adapter) + collectSamples.dataSources(healthStore: healthStore, standard: standard) } } diff --git a/Sources/SpeziHealthKit/CollectSample/CollectSamples.swift b/Sources/SpeziHealthKit/CollectSample/CollectSamples.swift index 1f679b6..efc642a 100644 --- a/Sources/SpeziHealthKit/CollectSample/CollectSamples.swift +++ b/Sources/SpeziHealthKit/CollectSample/CollectSamples.swift @@ -34,19 +34,17 @@ public struct CollectSamples: HealthKitDataSourceDescription { } - public func dataSources( + public func dataSources( healthStore: HKHealthStore, - standard: S, - adapter: HealthKit.HKSampleAdapter + standard: any HealthKitConstraint ) -> [any HealthKitDataSource] { sampleTypes.map { sampleType in - HealthKitSampleDataSource( + HealthKitSampleDataSource( healthStore: healthStore, standard: standard, sampleType: sampleType, predicate: predicate, - deliverySetting: deliverySetting, - adapter: adapter + deliverySetting: deliverySetting ) } } diff --git a/Sources/SpeziHealthKit/CollectSample/HealthKitSampleDataSource.swift b/Sources/SpeziHealthKit/CollectSample/HealthKitSampleDataSource.swift index fbe7120..8bbe76f 100644 --- a/Sources/SpeziHealthKit/CollectSample/HealthKitSampleDataSource.swift +++ b/Sources/SpeziHealthKit/CollectSample/HealthKitSampleDataSource.swift @@ -7,19 +7,18 @@ // import HealthKit +import OSLog import Spezi import SwiftUI -final class HealthKitSampleDataSource: HealthKitDataSource { +final class HealthKitSampleDataSource: HealthKitDataSource { let healthStore: HKHealthStore - let standard: ComponentStandard + let standard: any HealthKitConstraint let sampleType: HKSampleType let predicate: NSPredicate? let deliverySetting: HealthKitDeliverySetting - let adapter: HealthKit.HKSampleAdapter - var active = false private lazy var anchorUserDefaultsKey = UserDefaults.Keys.healthKitAnchorPrefix.appending(sampleType.identifier) @@ -29,24 +28,24 @@ final class HealthKitSampleDataSource: HealthKitDat } } - - required init( // swiftlint:disable:this function_default_parameter_at_end + // We disable the SwiftLint as we order the parameters in a logical order and + // therefore don't put the predicate at the end here. + // swiftlint:disable function_default_parameter_at_end + required init( healthStore: HKHealthStore, - standard: ComponentStandard, + standard: any HealthKitConstraint, sampleType: HKSampleType, - predicate: NSPredicate? = nil, // We order the parameters in a logical order and therefore don't put the predicate at the end here. - deliverySetting: HealthKitDeliverySetting, - adapter: HealthKit.HKSampleAdapter + predicate: NSPredicate? = nil, + deliverySetting: HealthKitDeliverySetting ) { self.healthStore = healthStore self.standard = standard self.sampleType = sampleType self.deliverySetting = deliverySetting - self.adapter = adapter if predicate == nil { self.predicate = HKQuery.predicateForSamples( - withStart: HealthKitSampleDataSource.loadDefaultQueryDate(for: sampleType), + withStart: HealthKitSampleDataSource.loadDefaultQueryDate(for: sampleType), end: nil, options: .strictEndDate ) @@ -54,6 +53,7 @@ final class HealthKitSampleDataSource: HealthKitDat self.predicate = predicate } } + // swiftlint:enable function_default_parameter_at_end private static func loadDefaultQueryDate(for sampleType: HKSampleType) -> Date { @@ -105,69 +105,57 @@ final class HealthKitSampleDataSource: HealthKitDat return } - switch deliverySetting { - case .manual: - await standard.registerDataSource(adapter.transform(anchoredSingleObjectQuery())) - case .anchorQuery: - active = true - await standard.registerDataSource(adapter.transform(anchoredContinousObjectQuery())) - case .background: - active = true - let healthKitSamples = healthStore.startObservation(for: [sampleType], withPredicate: predicate) - .flatMap { _ in + do { + switch deliverySetting { + case .manual: + anchoredSingleObjectQuery() + case .anchorQuery: + active = true + try await anchoredContinousObjectQuery() + case .background: + active = true + for try await _ in healthStore.startObservation(for: [sampleType], withPredicate: predicate) { self.anchoredSingleObjectQuery() } - await standard.registerDataSource(adapter.transform(healthKitSamples)) + } + } catch { + Logger.healthKit.error("\(error.localizedDescription)") } } - private func anchoredSingleObjectQuery() -> AsyncThrowingStream, Error> { - AsyncThrowingStream { continuation in - Task { - let results = try await healthStore.anchoredSingleObjectQuery( - for: self.sampleType, - using: self.anchor, - withPredicate: predicate - ) - self.anchor = results.anchor - for result in results.elements { - continuation.yield(result) - } - continuation.finish() - } + private func anchoredSingleObjectQuery() { + Task { + let resultsAnchor = try await healthStore.anchoredSingleObjectQuery( + for: self.sampleType, + using: self.anchor, + withPredicate: predicate, + standard: self.standard + ) + self.anchor = resultsAnchor } } - private func anchoredContinousObjectQuery() async -> any TypedAsyncSequence> { - AsyncThrowingStream { continuation in - Task { - try await healthStore.requestAuthorization(toShare: [], read: [sampleType]) - - let anchorDescriptor = healthStore.anchorDescriptor(sampleType: sampleType, predicate: predicate, anchor: anchor) - - let updateQueue = anchorDescriptor.results(for: healthStore) - - do { - for try await results in updateQueue { - if Task.isCancelled { - continuation.finish() - return - } - - for deletedObject in results.deletedObjects { - continuation.yield(.removal(HKSampleRemovalContext(id: deletedObject.uuid, sampleType: sampleType))) - } - - for addedSample in results.addedSamples { - continuation.yield(.addition(addedSample)) - } - self.anchor = results.newAnchor - } - } catch { - continuation.finish(throwing: error) - } + private func anchoredContinousObjectQuery() async throws { + try await healthStore.requestAuthorization(toShare: [], read: [sampleType]) + + let anchorDescriptor = healthStore.anchorDescriptor(sampleType: sampleType, predicate: predicate, anchor: anchor) + + let updateQueue = anchorDescriptor.results(for: healthStore) + + for try await results in updateQueue { + if Task.isCancelled { + return + } + + for deletedObject in results.deletedObjects { + await standard.remove(sample: deletedObject) + } + + for addedSample in results.addedSamples { + await standard.add(sample: addedSample) } + self.anchor = results.newAnchor } } diff --git a/Sources/SpeziHealthKit/HealthKit Extensions/HKHealthStore+AnchoredObjectQuery.swift b/Sources/SpeziHealthKit/HealthKit Extensions/HKHealthStore+AnchoredObjectQuery.swift index 908ec55..172fdef 100644 --- a/Sources/SpeziHealthKit/HealthKit Extensions/HKHealthStore+AnchoredObjectQuery.swift +++ b/Sources/SpeziHealthKit/HealthKit Extensions/HKHealthStore+AnchoredObjectQuery.swift @@ -16,32 +16,32 @@ extension HKSample: Identifiable { } } - extension HKHealthStore { + // We disable the SwiftLint as we order the parameters in a logical order and + // therefore don't put the predicate at the end here. + // swiftlint:disable function_default_parameter_at_end func anchoredSingleObjectQuery( for sampleType: HKSampleType, using anchor: HKQueryAnchor? = nil, - withPredicate predicate: NSPredicate? = nil - ) async throws -> (elements: [DataChange], anchor: HKQueryAnchor) { + withPredicate predicate: NSPredicate? = nil, + standard: any HealthKitConstraint + ) async throws -> (HKQueryAnchor) { try await self.requestAuthorization(toShare: [], read: [sampleType]) - + let anchorDescriptor = anchorDescriptor(sampleType: sampleType, predicate: predicate, anchor: anchor) - let result = try await anchorDescriptor.result(for: self) - - var elements: [DataChange] = [] - elements.reserveCapacity(result.deletedObjects.count + result.addedSamples.count) - + for deletedObject in result.deletedObjects { - elements.append(.removal(HKSampleRemovalContext(id: deletedObject.uuid, sampleType: sampleType))) + await standard.remove(sample: deletedObject) } - + for addedSample in result.addedSamples { - elements.append(.addition(addedSample)) + await standard.add(sample: addedSample) } - - return (elements, result.newAnchor) + + return (result.newAnchor) } + // swiftlint:enable function_default_parameter_at_end func anchorDescriptor( diff --git a/Sources/SpeziHealthKit/HealthKit Extensions/HKHealthStore+SampleQuery.swift b/Sources/SpeziHealthKit/HealthKit Extensions/HKHealthStore+SampleQuery.swift index 1063d4b..6428c2d 100644 --- a/Sources/SpeziHealthKit/HealthKit Extensions/HKHealthStore+SampleQuery.swift +++ b/Sources/SpeziHealthKit/HealthKit Extensions/HKHealthStore+SampleQuery.swift @@ -30,18 +30,19 @@ extension HKHealthStore { return try await sampleQueryDescriptor.result(for: self) } - + // We disable the SwiftLint as we order the parameters in a logical order and + // therefore don't put the predicate at the end here. + // swiftlint:disable function_default_parameter_at_end func sampleQueryStream( for sampleType: HKSampleType, - withPredicate predicate: NSPredicate? = nil - ) -> AsyncThrowingStream, Error> { - AsyncThrowingStream { continuation in - Task { - for sample in try await sampleQuery(for: sampleType, withPredicate: predicate) { - continuation.yield(.addition(sample)) - } - continuation.finish() + withPredicate predicate: NSPredicate? = nil, + standard: any HealthKitConstraint + ) { + _Concurrency.Task { + for sample in try await sampleQuery(for: sampleType, withPredicate: predicate) { + await standard.add(sample: sample) } } } + // swiftlint:enable function_default_parameter_at_end } diff --git a/Sources/SpeziHealthKit/HealthKit.swift b/Sources/SpeziHealthKit/HealthKit.swift index b3720a2..5a78c97 100644 --- a/Sources/SpeziHealthKit/HealthKit.swift +++ b/Sources/SpeziHealthKit/HealthKit.swift @@ -11,9 +11,25 @@ import Spezi import SwiftUI -/// The ``HealthKit`` module enables the collection of HealthKit data and transforms it to the component's standard's base type using an `Adapter` (``HealthKit/HKSampleAdapter``) +/// The ``HealthKit`` module enables the collection of HealthKit data. /// -/// Use the ``HealthKit/init(_:adapter:)`` initializer to define different ``HealthKitDataSourceDescription``s to define the data collection. +/// Configuration for the ``SpeziHealthKit`` module. +/// +/// Make sure that your standard in your Spezi Application conforms to the ``HealthKitConstraint`` +/// protocol to receive HealthKit data. +/// ```swift +/// actor ExampleStandard: Standard, HealthKitConstraint { +/// func add(sample: HKSample) async { +/// ... +/// } +/// +/// func remove(sample: HKDeletedObject) { +/// ... +/// } +/// } +/// ``` +/// +/// Use the ``HealthKit/init(_:)`` initializer to define different ``HealthKitDataSourceDescription``s to define the data collection. /// You can, e.g., use ``CollectSample`` to collect a wide variaty of `HKSampleTypes`: /// ```swift /// class ExampleAppDelegate: SpeziAppDelegate { @@ -41,27 +57,19 @@ import SwiftUI /// HKQuantityType(.restingHeartRate), /// deliverySetting: .manual() /// ) -/// } adapter: { -/// TestAppHealthKitAdapter() /// } /// } /// } /// } /// } /// ``` -public final class HealthKit: Module { - /// The ``HealthKit/HKSampleAdapter`` type defines the mapping of `HKSample`s to the component's standard's base type. - public typealias HKSampleAdapter = any Adapter - - - @StandardActor var standard: ComponentStandard - +public final class HealthKit: Module { + @StandardActor var standard: any HealthKitConstraint let healthStore: HKHealthStore let healthKitDataSourceDescriptions: [HealthKitDataSourceDescription] - let adapter: HKSampleAdapter lazy var healthKitComponents: [any HealthKitDataSource] = { healthKitDataSourceDescriptions - .flatMap { $0.dataSources(healthStore: healthStore, standard: standard, adapter: adapter) } + .flatMap { $0.dataSources(healthStore: healthStore, standard: standard) } }() private var healthKitSampleTypes: Set { @@ -85,10 +93,8 @@ public final class HealthKit: Module { /// Creates a new instance of the ``HealthKit`` module. /// - Parameters: /// - healthKitDataSourceDescriptions: The ``HealthKitDataSourceDescription``s define what data is collected by the ``HealthKit`` module. You can, e.g., use ``CollectSample`` to collect a wide variaty of `HKSampleTypes`. - /// - adapter: The ``HealthKit/HKSampleAdapter`` type defines the mapping of `HKSample`s to the component's standard's base type. public init( - @HealthKitDataSourceDescriptionBuilder _ healthKitDataSourceDescriptions: () -> ([HealthKitDataSourceDescription]), - @AdapterBuilder adapter: () -> (HKSampleAdapter) + @HealthKitDataSourceDescriptionBuilder _ healthKitDataSourceDescriptions: () -> ([HealthKitDataSourceDescription]) ) { precondition( HKHealthStore.isHealthDataAvailable(), @@ -103,10 +109,8 @@ public final class HealthKit: Module { ) let healthStore = HKHealthStore() - let adapter = adapter() let healthKitDataSourceDescriptions = healthKitDataSourceDescriptions() - self.adapter = adapter self.healthKitDataSourceDescriptions = healthKitDataSourceDescriptions self.healthStore = healthStore } diff --git a/Sources/SpeziHealthKit/HealthKitConstraint.swift b/Sources/SpeziHealthKit/HealthKitConstraint.swift new file mode 100644 index 0000000..69c5276 --- /dev/null +++ b/Sources/SpeziHealthKit/HealthKitConstraint.swift @@ -0,0 +1,38 @@ +// +// This source file is part of the Stanford Spezi open-source project +// +// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md) +// +// SPDX-License-Identifier: MIT +// + +import HealthKit +import Spezi + + +/// A Constraint which all `Standard` instances must conform to when using the Spezi HealthKit module. +/// +/// +/// Make sure that your standard in your Spezi Application conforms to the ``HealthKitConstraint`` +/// protocol to receive HealthKit data. +/// ```swift +/// actor ExampleStandard: Standard, HealthKitConstraint { +/// func add(sample: HKSample) async { +/// ... +/// } +/// +/// func remove(sample: HKDeletedObject) { +/// ... +/// } +/// } +/// ``` +/// +public protocol HealthKitConstraint: Standard { + /// Notifies the ``Standard`` about the addition of a HealthKit ``HKSample`` sample instance. + /// - Parameter sample: The `HKSample` that should be added. + func add(sample: HKSample) async + + /// Notifies the ``Standard`` about the removal of a HealthKit sample as defined by the `HKDeletedObject`. + /// - Parameter sample: The `HKDeletedObject` is a sample that should be removed. + func remove(sample: HKDeletedObject) async +} diff --git a/Sources/SpeziHealthKit/HealthKitDataSource/HealthKitDataSourceDescription.swift b/Sources/SpeziHealthKit/HealthKitDataSource/HealthKitDataSourceDescription.swift index e6f0b4b..ae3cb57 100644 --- a/Sources/SpeziHealthKit/HealthKitDataSource/HealthKitDataSourceDescription.swift +++ b/Sources/SpeziHealthKit/HealthKitDataSource/HealthKitDataSourceDescription.swift @@ -16,10 +16,9 @@ public protocol HealthKitDataSourceDescription { var sampleTypes: Set { get } - /// The ``HealthKitDataSourceDescription/dataSources(healthStore:standard:adapter:)`` method creates ``HealthKitDataSource`` swhen the HealthKit component is instantiated. + /// The ``HealthKitDataSourceDescription/dataSources(healthStore:standard:)`` method creates ``HealthKitDataSource`` swhen the HealthKit component is instantiated. /// - Parameters: /// - healthStore: The `HKHealthStore` instance that the queries should be performed on. /// - standard: The `Standard` instance that is used in the software system. - /// - adapter: An adapter that can adapt HealthKit data to the corresponding data standard. - func dataSources(healthStore: HKHealthStore, standard: S, adapter: HealthKit.HKSampleAdapter) -> [HealthKitDataSource] + func dataSources(healthStore: HKHealthStore, standard: any HealthKitConstraint) -> [HealthKitDataSource] } diff --git a/Sources/SpeziHealthKit/Logging/Logger+HealthKit.swift b/Sources/SpeziHealthKit/Logging/Logger+HealthKit.swift new file mode 100644 index 0000000..468f613 --- /dev/null +++ b/Sources/SpeziHealthKit/Logging/Logger+HealthKit.swift @@ -0,0 +1,20 @@ +// +// This source file is part of the Stanford Spezi open-source project +// +// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md) +// +// SPDX-License-Identifier: MIT +// + + +import OSLog + +extension Logger { + // swiftlint:disable force_unwrapping + /// Using the bundle identifier to ensure a unique identifier. + private static var subsystem = Bundle.main.bundleIdentifier! + // swiftlint:enable force_unwrapping + + /// Logs the view cycles like a view that appeared. + static let healthKit = Logger(subsystem: subsystem, category: "healthkit") +} diff --git a/Tests/SpeziHealthKitTests/Shared/MockAdapterActor.swift b/Tests/SpeziHealthKitTests/Shared/MockAdapterActor.swift deleted file mode 100644 index 671e7bf..0000000 --- a/Tests/SpeziHealthKitTests/Shared/MockAdapterActor.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// This source file is part of the Stanford Spezi open-source project -// -// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md) -// -// SPDX-License-Identifier: MIT -// - -import HealthKit -import Spezi -import SpeziHealthKit -import XCTSpezi - -actor MockAdapterActor: Adapter { - typealias InputElement = HKSample - typealias InputRemovalContext = HKSampleRemovalContext - typealias OutputElement = TestAppStandard.BaseType - typealias OutputRemovalContext = TestAppStandard.RemovalContext - - - func transform( - _ asyncSequence: some TypedAsyncSequence> - ) async -> any TypedAsyncSequence> { - asyncSequence.map { element in - element.map( - element: { OutputElement(id: String(describing: $0.id)) }, - removalContext: { OutputRemovalContext(id: $0.id.uuidString) } - ) - } - } -} diff --git a/Tests/SpeziHealthKitTests/SpeziHealthKitTests.swift b/Tests/SpeziHealthKitTests/SpeziHealthKitTests.swift index e27c4a5..c0d047b 100644 --- a/Tests/SpeziHealthKitTests/SpeziHealthKitTests.swift +++ b/Tests/SpeziHealthKitTests/SpeziHealthKitTests.swift @@ -17,13 +17,11 @@ final class SpeziHealthKitTests: XCTestCase { HKQuantityType(.distanceWalkingRunning) ] - let healthKitComponent: HealthKit = HealthKit { + let healthKitComponent = HealthKit { CollectSamples( collectedSamples, deliverySetting: .anchorQuery(.afterAuthorizationAndApplicationWillLaunch) ) - } adapter: { - MockAdapterActor() } override func tearDown() { diff --git a/Tests/UITests/TestApp/ExampleStandard.swift b/Tests/UITests/TestApp/ExampleStandard.swift new file mode 100644 index 0000000..3f53aff --- /dev/null +++ b/Tests/UITests/TestApp/ExampleStandard.swift @@ -0,0 +1,33 @@ +// +// This source file is part of the Stanford Spezi open-source project +// +// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md) +// +// SPDX-License-Identifier: MIT +// + +@preconcurrency import HealthKit +import Spezi +import SpeziHealthKit + + +/// An example Standard used for the configuration. +actor ExampleStandard: Standard, ObservableObject, ObservableObjectProvider { + @Published @MainActor var addedResponses = [HKSample]() +} + +extension ExampleStandard: HealthKitConstraint { + func add(sample: HKSample) async { + _Concurrency.Task { @MainActor in + addedResponses.append(sample) + } + } + + func remove(sample: HKDeletedObject) async { + _Concurrency.Task { @MainActor in + if let index = addedResponses.firstIndex(where: { $0.uuid == sample.uuid }) { + addedResponses.remove(at: index) + } + } + } +} diff --git a/Tests/UITests/TestApp/HealthKitTestsView.swift b/Tests/UITests/TestApp/HealthKitTestsView.swift index c4e9c0b..e164dd6 100644 --- a/Tests/UITests/TestApp/HealthKitTestsView.swift +++ b/Tests/UITests/TestApp/HealthKitTestsView.swift @@ -7,16 +7,15 @@ // import Combine +import HealthKit import SpeziHealthKit import SwiftUI import XCTSpezi struct HealthKitTestsView: View { - @EnvironmentObject var healthKitComponent: HealthKit - @EnvironmentObject var standard: TestAppStandard - @State var dataChanges: [String] = [] - @State var cancellable: AnyCancellable? + @EnvironmentObject var healthKitComponent: HealthKit + @EnvironmentObject var standard: ExampleStandard var body: some View { @@ -29,21 +28,10 @@ struct HealthKitTestsView: View { triggerDataSourceCollection() } HStack { - List(dataChanges, id: \.self) { element in - Text(element) + List(standard.addedResponses, id: \.self) { element in + Text(element.sampleType.identifier) } } - .task { - self.dataChanges = await standard.dataChanges.map { $0.id } - cancellable = standard.objectWillChange.sink { - Task { @MainActor in - self.dataChanges = await standard.dataChanges.map { $0.id } - } - } - } - .onDisappear { - cancellable?.cancel() - } } diff --git a/Tests/UITests/TestApp/TestAppDelegate.swift b/Tests/UITests/TestApp/TestAppDelegate.swift index db7651c..8b92b6f 100644 --- a/Tests/UITests/TestApp/TestAppDelegate.swift +++ b/Tests/UITests/TestApp/TestAppDelegate.swift @@ -14,8 +14,8 @@ import XCTSpezi class TestAppDelegate: SpeziAppDelegate { override var configuration: Configuration { - Configuration(standard: TestAppStandard()) { - HealthKit { + Configuration(standard: ExampleStandard()) { + HealthKit { CollectSample( HKQuantityType.electrocardiogramType(), deliverySetting: .background(.manual) @@ -36,8 +36,6 @@ class TestAppDelegate: SpeziAppDelegate { HKQuantityType(.restingHeartRate), deliverySetting: .manual() ) - } adapter: { - TestAppHealthKitAdapter() } } } diff --git a/Tests/UITests/TestApp/TestAppHealthKitAdapter.swift b/Tests/UITests/TestApp/TestAppHealthKitAdapter.swift deleted file mode 100644 index 3dd0747..0000000 --- a/Tests/UITests/TestApp/TestAppHealthKitAdapter.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// This source file is part of the Stanford Spezi open-source project -// -// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md) -// -// SPDX-License-Identifier: MIT -// - -@preconcurrency import HealthKit -import Spezi -import SpeziHealthKit -import XCTSpezi - - -actor TestAppHealthKitAdapter: SingleValueAdapter { - typealias InputElement = HKSample - typealias InputRemovalContext = HKSampleRemovalContext - typealias OutputElement = TestAppStandard.BaseType - typealias OutputRemovalContext = TestAppStandard.RemovalContext - - - func transform(element: InputElement) throws -> OutputElement { - TestAppStandard.BaseType(id: element.sampleType.identifier) - } - - func transform(removalContext: InputRemovalContext) throws -> OutputRemovalContext { - OutputRemovalContext(id: removalContext.id.uuidString) - } -} diff --git a/Tests/UITests/UITests.xcodeproj/project.pbxproj b/Tests/UITests/UITests.xcodeproj/project.pbxproj index 1cd884c..a6c0bb5 100644 --- a/Tests/UITests/UITests.xcodeproj/project.pbxproj +++ b/Tests/UITests/UITests.xcodeproj/project.pbxproj @@ -14,9 +14,9 @@ 2F85827329E776AC0021D637 /* TestAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F85827229E776AC0021D637 /* TestAppDelegate.swift */; }; 2F85827629E776D10021D637 /* Spezi in Frameworks */ = {isa = PBXBuildFile; productRef = 2F85827529E776D10021D637 /* Spezi */; }; 2F85827829E776D10021D637 /* XCTSpezi in Frameworks */ = {isa = PBXBuildFile; productRef = 2F85827729E776D10021D637 /* XCTSpezi */; }; - 2F85827A29E777980021D637 /* TestAppHealthKitAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F85827929E777980021D637 /* TestAppHealthKitAdapter.swift */; }; 2F85827F29E7782C0021D637 /* XCTestExtensions in Frameworks */ = {isa = PBXBuildFile; productRef = 2F85827E29E7782C0021D637 /* XCTestExtensions */; }; 2FA7382C290ADFAA007ACEB9 /* TestApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA7382B290ADFAA007ACEB9 /* TestApp.swift */; }; + 390F29612A785A98000A236E /* ExampleStandard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390F29602A785A98000A236E /* ExampleStandard.swift */; }; 97B029102A5710C800946EF8 /* XCTHealthKit in Frameworks */ = {isa = PBXBuildFile; productRef = 97B0290F2A5710C800946EF8 /* XCTHealthKit */; }; /* End PBXBuildFile section */ @@ -38,10 +38,10 @@ 2F85826D29E776690021D637 /* SpeziHealthKitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpeziHealthKitTests.swift; sourceTree = ""; }; 2F85827029E776780021D637 /* HealthKitTestsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HealthKitTestsView.swift; sourceTree = ""; }; 2F85827229E776AC0021D637 /* TestAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAppDelegate.swift; sourceTree = ""; }; - 2F85827929E777980021D637 /* TestAppHealthKitAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestAppHealthKitAdapter.swift; sourceTree = ""; }; 2F85828329E77C4A0021D637 /* TestApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TestApp.entitlements; sourceTree = ""; }; 2FA7382B290ADFAA007ACEB9 /* TestApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestApp.swift; sourceTree = ""; }; 2FB0758A299DDB9000C0B37F /* TestApp.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TestApp.xctestplan; sourceTree = ""; }; + 390F29602A785A98000A236E /* ExampleStandard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleStandard.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -93,7 +93,7 @@ children = ( 2FA7382B290ADFAA007ACEB9 /* TestApp.swift */, 2F85827229E776AC0021D637 /* TestAppDelegate.swift */, - 2F85827929E777980021D637 /* TestAppHealthKitAdapter.swift */, + 390F29602A785A98000A236E /* ExampleStandard.swift */, 2F85827029E776780021D637 /* HealthKitTestsView.swift */, 2F85828329E77C4A0021D637 /* TestApp.entitlements */, 2F6D139928F5F386007C25D6 /* Assets.xcassets */, @@ -126,6 +126,7 @@ 2F6D138E28F5F384007C25D6 /* Sources */, 2F6D138F28F5F384007C25D6 /* Frameworks */, 2F6D139028F5F384007C25D6 /* Resources */, + 390F295F2A78596E000A236E /* ShellScript */, ); buildRules = ( ); @@ -224,13 +225,33 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 390F295F2A78596E000A236E /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\nif which swiftlint > /dev/null; then\n cd ../../ && swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 2F6D138E28F5F384007C25D6 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 2F85827A29E777980021D637 /* TestAppHealthKitAdapter.swift in Sources */, 2FA7382C290ADFAA007ACEB9 /* TestApp.swift in Sources */, + 390F29612A785A98000A236E /* ExampleStandard.swift in Sources */, 2F85827129E776780021D637 /* HealthKitTestsView.swift in Sources */, 2F85827329E776AC0021D637 /* TestAppDelegate.swift in Sources */, ); @@ -375,10 +396,11 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = TestApp/TestApp.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = 637867499T; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; ENABLE_TESTING_SEARCH_PATHS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -395,6 +417,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.spezi.healthkit.testapp; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; @@ -408,10 +431,11 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = TestApp/TestApp.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = 637867499T; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; ENABLE_TESTING_SEARCH_PATHS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -428,6 +452,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.spezi.healthkit.testapp; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; @@ -441,7 +466,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 637867499T; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.spezi.healthkit.testappuitests; @@ -460,7 +485,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 637867499T; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.spezi.healthkit.testappuitests; @@ -539,10 +564,11 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = TestApp/TestApp.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = 637867499T; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; ENABLE_TESTING_SEARCH_PATHS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -559,6 +585,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.spezi.healthkit.testapp; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; @@ -572,7 +599,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 637867499T; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.spezi.healthkit.testappuitests; @@ -626,7 +653,7 @@ repositoryURL = "https://github.com/StanfordSpezi/Spezi.git"; requirement = { kind = upToNextMinorVersion; - minimumVersion = 0.5.0; + minimumVersion = 0.7.0; }; }; 2F85827B29E778110021D637 /* XCRemoteSwiftPackageReference "XCTestExtensions" */ = { diff --git a/Tests/UITests/UITests.xcodeproj/xcshareddata/xcschemes/TestApp.xcscheme b/Tests/UITests/UITests.xcodeproj/xcshareddata/xcschemes/TestApp.xcscheme new file mode 100644 index 0000000..b8e2be0 --- /dev/null +++ b/Tests/UITests/UITests.xcodeproj/xcshareddata/xcschemes/TestApp.xcscheme @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +