From 710811878d659ea32fda1c5c561bbb9775d2bca5 Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Tue, 18 Jun 2024 11:02:59 -0400 Subject: [PATCH] feat: working on tests for pressure estimation feature to resolve filter drop bugs --- .../EquipmentSettingsForm.swift | 1 + .../FlaggedMeasurementsList.swift | 67 +++++++- .../FlaggedMeasurementListTests.swift | 154 +++++++++++++++++- 3 files changed, 216 insertions(+), 6 deletions(-) diff --git a/Sources/PressureEstimationsFeature/EquipmentSettingsForm.swift b/Sources/PressureEstimationsFeature/EquipmentSettingsForm.swift index cc988d7..bda63ef 100644 --- a/Sources/PressureEstimationsFeature/EquipmentSettingsForm.swift +++ b/Sources/PressureEstimationsFeature/EquipmentSettingsForm.swift @@ -5,6 +5,7 @@ import SharedModels import Styleguide import SwiftUI +#warning("Revert rated static pressures section.") @Reducer public struct EquipmentSettingsForm { diff --git a/Sources/PressureEstimationsFeature/FlaggedMeasurementsList.swift b/Sources/PressureEstimationsFeature/FlaggedMeasurementsList.swift index 766c4eb..9f2504f 100644 --- a/Sources/PressureEstimationsFeature/FlaggedMeasurementsList.swift +++ b/Sources/PressureEstimationsFeature/FlaggedMeasurementsList.swift @@ -17,7 +17,7 @@ public struct FlaggedMeasurementsList: Sendable { @ObservableState @dynamicMemberLookup - public struct State: Equatable { + public struct State: Equatable, Sendable { @Presents public var destination: Destination.State? @Shared var sharedSettings: SharedPressureEstimationState @@ -130,16 +130,38 @@ public struct FlaggedMeasurementsList: Sendable { ) } state.destination = nil - return handleEstimationForm(form: form, state: state) + return .run { [state] send in + do { + guard let flaggedEstimation = try await self._handleEstimationForm(form: form, state: state) + else { return } + await send(.receive(.success(.estimatedFlaggedMeasurement(flaggedEstimation)))) + } catch { + await send(.receive(.failure(error))) + } + } + } case .editButtonTapped(id: _): return .none case .onAppear: - guard let equipmentMeasurement = state.sharedSettings.equipmentMeasurement else { - return .none + guard state.sharedSettings.flaggedEquipmentMeasurement == nil + else { return .none } + + guard let equipmentMeasurement = state.sharedSettings.equipmentMeasurement + else { + return .fail( + """ + Equipment measurement is not set, skipping generating flagged measurement for existing + equipment. + + This is considered an application logic error. + """, + logger: logger + ) } + if state.sharedSettings.budgets == nil { state.sharedSettings.budgets = .init( equipmentType: state.sharedSettings.equipmentMeasurement!.equipmentType, @@ -159,6 +181,42 @@ public struct FlaggedMeasurementsList: Sendable { .ifLet(\.$destination, action: \.destination) } + enum EstimationFormFailure: Error { + case invalidState + } + + func _handleEstimationForm(form: EstimationForm.State, state: State) async throws -> SharedPressureEstimationState.FlaggedEstimationContainer? { + guard let equipmentMeasurement = state.sharedSettings.equipmentMeasurement, + let budgets = state.sharedSettings.budgets + else { + throw EstimationFormFailure.invalidState + } + + guard let estimationState = ensureHasChanges( + formState: form, + flaggedEstimations: state.sharedSettings.flaggedEstimations + ) else { + logger.debug("No changes found, not generating a new flagged estimation measurement.") + return nil + } + + let flaggedMeasurement = try await flaggedMeasurement( + airflow: estimationState.cfm.airflow, + budgets: budgets, + equipmentMeasurement: equipmentMeasurement, + filterPressureDrop: parseFilterPressureDrop(formState: form, sharedSettings: state.sharedSettings), + ratedStaticPresures: state.sharedSettings.ratedStaticPressures, + tons: form.coolingCapacity + ) + + return SharedPressureEstimationState.FlaggedEstimationContainer( + id: form.id ?? uuid(), + estimationState: estimationState, + flaggedMeasurement: flaggedMeasurement + ) + } + + #warning("This is making it hard to test, perhaps don't return an effect here.") private func handleEstimationForm(form: EstimationForm.State, state: State) -> Effect { guard let equipmentMeasurement = state.sharedSettings.equipmentMeasurement, let budgets = state.sharedSettings.budgets @@ -179,7 +237,6 @@ public struct FlaggedMeasurementsList: Sendable { flaggedEstimations: state.sharedSettings.flaggedEstimations ) else { logger.debug("No changes found, not generating a new flagged estimation measurement.") - print("No changes found, not generating a new flagged estimation measurement.") return .none } diff --git a/Tests/PressureEstimationsFeatureTests/FlaggedMeasurementListTests.swift b/Tests/PressureEstimationsFeatureTests/FlaggedMeasurementListTests.swift index 0da2cf2..11ddf10 100644 --- a/Tests/PressureEstimationsFeatureTests/FlaggedMeasurementListTests.swift +++ b/Tests/PressureEstimationsFeatureTests/FlaggedMeasurementListTests.swift @@ -1,8 +1,10 @@ import ComposableArchitecture +import SharedModels import Testing +import XCTest @testable import PressureEstimationsFeature -struct FlaggedMeasurementListTests { +struct FlaggedMeasurementListStateTests { @Test( "Ensure Estimation Has Changes", @@ -20,9 +22,159 @@ struct FlaggedMeasurementListTests { ) #expect(result != nil) } + + @Test( + "Handle Estimation Form", + .tags(.flaggedMeasurementList) + ) + func handleEstimationForm() async throws { + try await withDependencies { + $0.estimatedPressuresClient = .liveValue + $0.uuid = .incrementing + } operation: { + @Dependency(\.estimatedPressuresClient) var client + + let reducer = FlaggedMeasurementsList() + let result = try await reducer._handleEstimationForm( + form: .init( + existingMeasurement: SharedReader(Shared(.mock(type: .airHandler))), + cfmTextField: 350, + name: "Test" + ), + state: .init(sharedSettings: Shared(.init( + budgets: .init(equipmentType: .airHandler, fanType: .constantSpeed), + equipmentMeasurement: .mock(type: .airHandler), + flaggedEquipmentMeasurement: .init( + budgets: .init(equipmentType: .airHandler, fanType: .constantSpeed), + measurement: .mock(type: .airHandler), + ratedPressures: .init(), + tons: .three + ) + ))) + ) + + let measurement = try await client.estimatedPressure( + equipmentMeasurement: .mock(type: .airHandler), + airflow: 1050, + filterPressureDrop: 0.1 + ) + + #expect(result != nil) + #expect(result?.estimationState == .init( + cfm: .cfmPerTon(350, .three), + filterPressureDrop: 0.1, + name: "Test" + )) + + #expect(result?.flaggedMeasurement == .init( + budgets: .init(equipmentType: .airHandler, fanType: .constantSpeed), + measurement: measurement, + ratedPressures: .init(), + tons: .three) + ) + } + } } +final class FlaggedMeasurementListReducerTests: XCTestCase { + + override func invokeTest() { + withDependencies { + $0.uuid = .incrementing + $0.estimatedPressuresClient = .liveValue + } operation: { + super.invokeTest() + } + } + + @MainActor + func testFlaggedMeasurementListReducer() async throws { + @Dependency(\.estimatedPressuresClient) var estimatedPressuresClient + + let store = TestStore( + initialState: FlaggedMeasurementsList.State( + sharedSettings: Shared(SharedPressureEstimationState( + budgets: nil, + equipmentMeasurement: .mock(type: .airHandler), + equipmentMetadata: .init( + coolingCapacity: .three, + fanType: .constantSpeed, + ratedStaticPressures: .init() + ) + )) + ) + ) { + FlaggedMeasurementsList() + } + + let budgets = BudgetedPercentEnvelope( + equipmentType: .airHandler, + fanType: .constantSpeed + ) + + let flaggedMeasurement = EquipmentMeasurement.FlaggedMeasurement.init( + budgets: budgets, + measurement: .mock(type: .airHandler), + ratedPressures: .init(), + tons: .three + ) + + await store.send(.view(.onAppear)) { + $0.sharedSettings.budgets = budgets + $0.sharedSettings.flaggedEquipmentMeasurement = flaggedMeasurement + } + + await store.send(.view(.addButtonTapped)) { + $0.destination = .estimationForm(.init( + existingMeasurement: $0.$sharedSettings.equipmentMeasurement, + coolingCapacity: $0.sharedSettings.equipmentMetadata.coolingCapacity + )) + } + + store.exhaustivity = .off + + await store.send(.destination(.presented(.estimationForm(.binding(.set(\.cfmTextField, 350)))))) + await store.send(.destination(.presented(.estimationForm(.binding(.set(\.name, "Test")))))) + await store.send(.destination(.presented(.estimationForm(.binding(.set(\.filterPressureDrop, 0.1)))))) + +// store.exhaustivity = .on + let pressureResult = try await estimatedPressuresClient.estimatedPressure( + equipmentMeasurement: .mock(type: .airHandler), + airflow: 1050, + filterPressureDrop: 0.1 + ) + let flaggedResult = EquipmentMeasurement.FlaggedMeasurement( + budgets: budgets, + measurement: pressureResult, + ratedPressures: .init(), + tons: .three + ) + + store.exhaustivity = .off(showSkippedAssertions: true) +// store.exhaustivity = .on + + await store.send(.view(.destination(.doneButtonTapped))) { + $0.destination = nil + } +// await store.receive(\.receive.failure, timeout: 10) +// await store.receive(\.receive.success.estimatedFlaggedMeasurement, timeout: 10) { +// $0.sharedSettings.flaggedEstimations[id: UUID(0)] = .init( +// id: UUID(0), +// estimationState: .init( +// cfm: .cfmPerTon(350, .three), +// filterPressureDrop: 0.1, +// name: "Test" +// ), +// flaggedMeasurement: flaggedResult +// ) +// } +// + await store.finish() + + } +} + extension Tag { @Tag static var flaggedMeasurementList: Self }