Skip to content

Commit

Permalink
Adding Testing (#75)
Browse files Browse the repository at this point in the history
# *Initial Tests*

## ♻️ Current situation & Problem
*Link any open issues or pull requests (PRs) related to this PR. Please
ensure that all non-trivial PRs are first tracked and discussed in an
existing GitHub issue or discussion.*


## ⚙️ Release Notes 
*Add a bullet point list summary of the feature and possible migration
guides if this is a breaking change so this section can be added to the
release notes.*
*Include code snippets that provide examples of the feature implemented
or links to the documentation if it appends or changes the public
interface.*


## 📚 Documentation
*Please ensure that you properly document any additions in conformance
to [Spezi Documentation
Guide](https://github.com/StanfordSpezi/.github/blob/main/DOCUMENTATIONGUIDE.md).*
*You can use this section to describe your solution, but we encourage
contributors to document your reasoning and changes using in-line
documentation.*


## ✅ Testing
*Please ensure that the PR meets the testing requirements set by CodeCov
and that new functionality is appropriately tested.*
*This section describes important information about the tests and why
some elements might not be testable.*


## 📝 Code of Conduct & Contributing Guidelines 

By submitting creating this pull request, you agree to follow our [Code
of
Conduct](https://github.com/CS342/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/CS342/.github/blob/main/CONTRIBUTING.md):
- [ ] I agree to follow the [Code of
Conduct](https://github.com/CS342/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/CS342/.github/blob/main/CONTRIBUTING.md).
  • Loading branch information
kcallon committed Mar 14, 2024
1 parent 14e0ee7 commit 5659109
Show file tree
Hide file tree
Showing 13 changed files with 130 additions and 20 deletions.
4 changes: 4 additions & 0 deletions Intake.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
2FF53D8B2A8725DE00042B76 /* SpeziMockWebService in Frameworks */ = {isa = PBXBuildFile; productRef = 2FF53D8A2A8725DE00042B76 /* SpeziMockWebService */; };
2FF53D8D2A8729D600042B76 /* IntakeStandard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FF53D8C2A8729D600042B76 /* IntakeStandard.swift */; };
3C89F66D2B9D948B00A4F52D /* PatientInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C89F66C2B9D948B00A4F52D /* PatientInfo.swift */; };
510CAAF12BA0DFFB00872B1A /* MedicationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510CAAF02BA0DFFB00872B1A /* MedicationTests.swift */; };
511827962B740192002033A0 /* SurgeryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511827952B740191002033A0 /* SurgeryView.swift */; };
51805C122B81853800D17109 /* IntakeMedication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51805C112B81853700D17109 /* IntakeMedication.swift */; };
51805C152B81857100D17109 /* IntakeMedicationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51805C142B81857100D17109 /* IntakeMedicationViewModel.swift */; };
Expand Down Expand Up @@ -180,6 +181,7 @@
2FE5DCAC29EE6107004B9AB4 /* AccountOnboarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountOnboarding.swift; sourceTree = "<group>"; };
2FF53D8C2A8729D600042B76 /* IntakeStandard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntakeStandard.swift; sourceTree = "<group>"; };
3C89F66C2B9D948B00A4F52D /* PatientInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatientInfo.swift; sourceTree = "<group>"; };
510CAAF02BA0DFFB00872B1A /* MedicationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MedicationTests.swift; sourceTree = "<group>"; };
511827952B740191002033A0 /* SurgeryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SurgeryView.swift; sourceTree = "<group>"; };
51805C112B81853700D17109 /* IntakeMedication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntakeMedication.swift; sourceTree = "<group>"; };
51805C142B81857100D17109 /* IntakeMedicationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntakeMedicationViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -560,6 +562,7 @@
isa = PBXGroup;
children = (
2F4E237D2989A2FE0013F3D9 /* LaunchTests.swift */,
510CAAF02BA0DFFB00872B1A /* MedicationTests.swift */,
);
path = IntakeUITests;
sourceTree = "<group>";
Expand Down Expand Up @@ -928,6 +931,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
510CAAF12BA0DFFB00872B1A /* MedicationTests.swift in Sources */,
2F4E237E2989A2FE0013F3D9 /* LaunchTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
12 changes: 12 additions & 0 deletions Intake.xcodeproj/xcshareddata/xcschemes/Intake.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@
argument = "--disableFirebase"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "--skipToScrollable"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--testPatient"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--showOnboarding"
isEnabled = "NO">
Expand All @@ -93,6 +101,10 @@
argument = "--testSchedule"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--testMedication"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--useFirebaseEmulator"
isEnabled = "NO">
Expand Down
1 change: 1 addition & 0 deletions Intake/Allergy Records/AllergyLLMAssistant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ struct UpdateAllergyFunction: LLMFunction {
}
}

// The AllergyLLMAssistant allows the user to ask the chat questions about their current allergies and add new allergies to their data.
struct AllergyLLMAssistant: View {
@Environment(DataStore.self) private var data
@Environment(NavigationPathWrapper.self) private var navigationPath
Expand Down
6 changes: 5 additions & 1 deletion Intake/Home.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ struct StartButton: View {

var body: some View {
Button(action: {
navigationPath.append(NavigationViews.general)
if FeatureFlags.testMedication {
navigationPath.append(NavigationViews.medication)
} else {
navigationPath.append(NavigationViews.general)
}
}) {
Text("Create New Form")
.font(.headline)
Expand Down
10 changes: 9 additions & 1 deletion Intake/IntakeTestingSetup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
//
// SPDX-License-Identifier: MIT
//

import ModelsR4
import SpeziFHIR
import SpeziFHIRMockPatients
import SwiftUI

private struct IntakeAppTestingSetup: ViewModifier {
@AppStorage(StorageKeys.onboardingFlowComplete) var completedOnboardingFlow = false
@Environment(FHIRStore.self) private var store

func body(content: Content) -> some View {
content
Expand All @@ -20,6 +23,11 @@ private struct IntakeAppTestingSetup: ViewModifier {
if FeatureFlags.showOnboarding {
completedOnboardingFlow = false
}
if FeatureFlags.testPatient {
let bundle = await ModelsR4.Bundle.gonzalo160Duenas839
store.removeAllResources()
store.load(bundle: bundle)
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions Intake/Medication View/IntakeDosage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import Foundation
import SpeziMedication

// The IntakeDosage struct has a localizedDescription that describes the dosage information.
struct IntakeDosage: Dosage, Codable {
var localizedDescription: String
}
1 change: 1 addition & 0 deletions Intake/Medication View/IntakeMedication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import Foundation
import SpeziMedication

// This describes the IntakeMedication struct which contains a localizedDescription (medication name) and a list of dosages.
struct IntakeMedication: Medication, Comparable, Codable {
var localizedDescription: String
var dosages: [IntakeDosage]
Expand Down
1 change: 1 addition & 0 deletions Intake/Medication View/IntakeMedicationInstance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import Foundation
import SpeziMedication

// This defines an IntakeMedicationInstance which is composed of an id, an IntakeMedication type, a dosage, and a schedule.
struct IntakeMedicationInstance: MedicationInstance, MedicationInstanceInitializable, Codable {
let id: UUID
let type: IntakeMedication
Expand Down
12 changes: 10 additions & 2 deletions Intake/Medication View/IntakeMedicationViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import SpeziFHIR
import SpeziMedication
import SwiftUI

// The IntakeMedicationSettingsViewModel takes the patient's FHIRStore medications and adds any that match to the medicationOptions to the medicationInstances list which is then used for the MedicationContentView.
@Observable
class IntakeMedicationSettingsViewModel: Module, MedicationSettingsViewModel, CustomStringConvertible {
var medicationInstances: Set<IntakeMedicationInstance> = []
Expand Down Expand Up @@ -46,7 +47,9 @@ class IntakeMedicationSettingsViewModel: Module, MedicationSettingsViewModel, Cu
.joined(separator: ", ")
}

// The init is modified from the SpeziMedication examples to load in the existing patient medications from their FHIRStore data.
init(existingMedications: [FHIRResource]) { // swiftlint:disable:this function_body_length
// medicationOptions provides the list of medications options chosen as the most common medications from the sample patients
self.medicationOptions = [
IntakeMedication(
localizedDescription: "Hydrochlorothiazide 25 MG Oral Tablet",
Expand Down Expand Up @@ -99,17 +102,20 @@ class IntakeMedicationSettingsViewModel: Module, MedicationSettingsViewModel, Cu
]

var foundMedications: [IntakeMedicationInstance] = []
// This function matches any patient medication from FHIRStore to a medication in medicationOptions.
if !existingMedications.isEmpty {
for medication in existingMedications {
for option in medicationOptions where option.localizedDescription == medication.displayName {
var medSchedule: SpeziMedication.Schedule
let medRequest = medicationRequest(resource: medication)
if case .boolean(let asNeeded) = medRequest?.dosageInstruction?.first?.asNeeded {
// Checks if medication is asNeeded, otherwise finds the frequency in days.
if let asNeededbool = asNeeded.value?.bool {
if asNeededbool {
medSchedule = SpeziMedication.Schedule(frequency: .asNeeded)
} else {
let intValue: Int
// Need to convert from FHIRDecimal to int.
let interval = medRequest?.dosageInstruction?.first?.timing?.repeat?.period?.value?.decimal
if let interval = interval {
intValue = interval.int
Expand All @@ -122,7 +128,7 @@ class IntakeMedicationSettingsViewModel: Module, MedicationSettingsViewModel, Cu
guard let firstDosage = option.dosages.first else {
continue
}

// Create an IntakeMedicationInstance to the data.
let intakeMedicationInstance = IntakeMedicationInstance(
type: option,
dosage: firstDosage,
Expand All @@ -139,7 +145,8 @@ class IntakeMedicationSettingsViewModel: Module, MedicationSettingsViewModel, Cu
func persist(medicationInstances: Set<IntakeMedicationInstance>) async throws {
self.medicationInstances = medicationInstances
}


// Converts a FHIRResource into a MedicationRequest.
func medicationRequest(resource: FHIRResource) -> MedicationRequest? {
guard case let .r4(resource) = resource.versionedResource,
let medicationRequest = resource as? ModelsR4.MedicationRequest else {
Expand All @@ -149,6 +156,7 @@ class IntakeMedicationSettingsViewModel: Module, MedicationSettingsViewModel, Cu
}
}

// Needed to convert the FHIRDecimal into an Int.
extension Decimal {
var int: Int {
let intVal = NSDecimalNumber(decimal: self).intValue // swiftlint:disable:this legacy_objc_type
Expand Down
43 changes: 27 additions & 16 deletions Intake/Medication View/MedicationContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,49 @@ import SpeziFHIR
import SpeziMedication
import SwiftUI

// This view displays the medications in the patient's FHIR data, and allows them to add, update and delete their medications.
struct MedicationContentView: View {
@Environment(FHIRStore.self) private var fhirStore
@Environment(NavigationPathWrapper.self) private var navigationPath
@Environment(DataStore.self) private var data
@State private var presentSettings = false

@State private var medicationSettingsViewModel: IntakeMedicationSettingsViewModel?

var body: some View {
VStack {
if let medicationSettingsViewModel {
MedicationSettings(allowEmptySave: true, medicationSettingsViewModel: medicationSettingsViewModel) {
data.medicationData = medicationSettingsViewModel.medicationInstances
navigationPath.path.append(NavigationViews.allergies)
if FeatureFlags.skipToScrollable {
data.medicationData = medicationSettingsViewModel.medicationInstances
navigationPath.path.append(NavigationViews.pdfs)
} else {
data.medicationData = medicationSettingsViewModel.medicationInstances
navigationPath.path.append(NavigationViews.allergies)
}
}
.navigationTitle("Medications")
.navigationBarItems(trailing: NavigationLink(destination: MedicationLLMAssistant(presentingAccount: .constant(false))) {
Text("Chat")
})
.navigationTitle("Medications")
.navigationBarItems(trailing: NavigationLink(destination: MedicationLLMAssistant(presentingAccount: .constant(false))) {
Text("Chat")
})
} else {
ProgressView()
}
}
.task {
let patientMedications = fhirStore.llmMedications
self.medicationSettingsViewModel = IntakeMedicationSettingsViewModel(existingMedications: patientMedications)
var initialData: Set<IntakeMedicationInstance> = []
if let newMed = self.medicationSettingsViewModel?.medicationInstances {
initialData = newMed
}
data.medicationData = initialData
// Updates the medicationSettingsViewModel init if there's a change to the patient's fhirStore medications.
.onChange(of: fhirStore.llmMedications) {
medicationSettingsViewModel = .init(existingMedications: fhirStore.llmMedications)
}
// Task to initialize the MedicationSettingsViewModel with the patient's existing fhirStore medications.
.task {
let patientMedications = fhirStore.llmMedications
self.medicationSettingsViewModel = IntakeMedicationSettingsViewModel(existingMedications: patientMedications)
var initialData: Set<IntakeMedicationInstance> = []
if let newMed = self.medicationSettingsViewModel?.medicationInstances {
initialData = newMed
}
data.medicationData = initialData
}
}

init() {}
Expand Down
2 changes: 2 additions & 0 deletions Intake/Medication View/MedicationLLMAssistant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import SpeziLLMLocal
import SpeziLLMOpenAI
import SwiftUI

// Adds the current patient medications to the system prompt.
func getCurrentPatientMedications(medicationList: Set<IntakeMedicationInstance>) -> String? {
var medicationDetails = "The patient is currently taking several medications:"
print(medicationList)
Expand All @@ -31,6 +32,7 @@ func getCurrentPatientMedications(medicationList: Set<IntakeMedicationInstance>)
return medicationDetails.isEmpty ? nil : medicationDetails
}

// Provides medication LLM assistant functionality to allow the patient to ask about their current medications.
struct MedicationLLMAssistant: View {
@Environment(DataStore.self) private var data
@Environment(NavigationPathWrapper.self) private var navigationPath
Expand Down
3 changes: 3 additions & 0 deletions Intake/SharedContext/FeatureFlags.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ enum FeatureFlags {
#endif
/// Adds a test task to the schedule at the current time
static let testSchedule = CommandLine.arguments.contains("--testSchedule")
static let testPatient = CommandLine.arguments.contains("--testPatient")
static let testMedication = CommandLine.arguments.contains("--testMedication")
static let skipToScrollable = CommandLine.arguments.contains("--skipToScrollable")
}
54 changes: 54 additions & 0 deletions IntakeUITests/MedicationTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// MedicationTests.swift
// IntakeUITests
//
// Created by Kate Callon on 3/12/24.
//
//
// This source file is part of the Intake based on the Stanford Spezi Template Application project
//
// SPDX-FileCopyrightText: 2023 Stanford University
//
// SPDX-License-Identifier: MIT
//

import Foundation
import XCTest

class MedicationTests: XCTestCase {
override func setUpWithError() throws {
try super.setUpWithError()

continueAfterFailure = false

let app = XCUIApplication()
app.launchArguments = ["--skipOnboarding", "--disableFirebase", "--testPatient", "--testMedication", "--skipToScrollable"]
app.launch()
}

func testMedications() throws {
let app = XCUIApplication()

// Small workaround to wait until the madications loaded into main memory
sleep(8)

XCTAssertEqual(app.state, .runningForeground)
app.buttons["Create New Form"].tap()

XCTAssertTrue(app.staticTexts["Hydrochlorothiazide 25 MG Oral Tablet"].waitForExistence(timeout: 10))
XCTAssertTrue(app.staticTexts["amLODIPine 2.5 MG Oral Tablet"].waitForExistence(timeout: 10))
XCTAssertTrue(app.navigationBars["Medication Settings"].buttons["Add New Medication"].waitForExistence(timeout: 2))
XCTAssertTrue(app.navigationBars["Medication Settings"].buttons["Chat"].waitForExistence(timeout: 2))
app.navigationBars["Medication Settings"].buttons["Add New Medication"].tap()
app.buttons["Verapamil Hydrochloride 40 MG"].tap()
app.buttons["Save Dosage"].tap()
app.buttons["Add Medication"].tap()
XCTAssertTrue(app.staticTexts["Verapamil Hydrochloride 40 MG"].waitForExistence(timeout: 5))
app.buttons["Save Medications"].tap()
XCTAssertTrue(app.navigationBars["Patient Form"].waitForExistence(timeout: 2))
// XCTAssertTrue(app.staticTexts["Hydrochlorothiazide 25 MG Oral Tablet"].waitForExistence(timeout: 5))
// XCTAssertTrue(app.staticTexts["amLODIPine 2.5 MG Oral Tablet"].waitForExistence(timeout: 5))
// XCTAssertTrue(app.staticTexts["Verapamil Hydrochloride 40 MG"].waitForExistence(timeout: 5))
// XCTAssertTrue(app.staticTexts["2.5 MG - Every Day"].waitForExistence(timeout: 5))
}
}

0 comments on commit 5659109

Please sign in to comment.