feat: Begin using Tagged types
All checks were successful
CI / Linux Tests (push) Successful in 5m23s

This commit is contained in:
2026-01-29 17:10:35 -05:00
parent 18a5ef06d3
commit 9379774fae
22 changed files with 167 additions and 150 deletions

View File

@@ -1,5 +1,5 @@
{ {
"originHash" : "c3efcfd33bc1490f59ae406e4e5292027b2d01cafee9fc625652213505df50fb", "originHash" : "300df15f5af26da69cfcf959d16ee1b9eada6101dc105a17fc01ddd244d476b4",
"pins" : [ "pins" : [
{ {
"identity" : "async-http-client", "identity" : "async-http-client",
@@ -397,6 +397,15 @@
"version" : "1.6.3" "version" : "1.6.3"
} }
}, },
{
"identity" : "swift-tagged",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-tagged",
"state" : {
"revision" : "3907a9438f5b57d317001dc99f3f11b46882272b",
"version" : "0.10.0"
}
},
{ {
"identity" : "swift-url-routing", "identity" : "swift-url-routing",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",

View File

@@ -26,6 +26,7 @@ let package = Package(
.package(url: "https://github.com/apple/swift-nio.git", from: "2.65.0"), .package(url: "https://github.com/apple/swift-nio.git", from: "2.65.0"),
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.0.0"), .package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.0.0"),
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.12.0"), .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.12.0"),
.package(url: "https://github.com/pointfreeco/swift-tagged", from: "0.6.0"),
.package(url: "https://github.com/pointfreeco/swift-url-routing.git", from: "0.6.2"), .package(url: "https://github.com/pointfreeco/swift-url-routing.git", from: "0.6.2"),
.package(url: "https://github.com/pointfreeco/vapor-routing.git", from: "0.1.3"), .package(url: "https://github.com/pointfreeco/vapor-routing.git", from: "0.1.3"),
.package(url: "https://github.com/pointfreeco/swift-case-paths.git", from: "1.6.0"), .package(url: "https://github.com/pointfreeco/swift-case-paths.git", from: "1.6.0"),
@@ -61,6 +62,12 @@ let package = Package(
.product(name: "Vapor", package: "vapor"), .product(name: "Vapor", package: "vapor"),
] ]
), ),
.testTarget(
name: "ApiRouteTests",
dependencies: [
.target(name: "ManualDCore")
]
),
.target( .target(
name: "AuthClient", name: "AuthClient",
dependencies: [ dependencies: [
@@ -137,22 +144,17 @@ let package = Package(
.target( .target(
name: "ManualDCore", name: "ManualDCore",
dependencies: [ dependencies: [
.product(name: "CasePaths", package: "swift-case-paths"),
.product(name: "Dependencies", package: "swift-dependencies"), .product(name: "Dependencies", package: "swift-dependencies"),
.product(name: "Fluent", package: "fluent"), .product(name: "Fluent", package: "fluent"),
.product(name: "Tagged", package: "swift-tagged"),
.product(name: "URLRouting", package: "swift-url-routing"), .product(name: "URLRouting", package: "swift-url-routing"),
.product(name: "CasePaths", package: "swift-case-paths"),
]
),
.testTarget(
name: "ApiRouteTests",
dependencies: [
.target(name: "ManualDCore")
] ]
), ),
.target( .target(
name: "ManualDClient", name: "ManualDClient",
dependencies: [ dependencies: [
"ManualDCore", .target(name: "ManualDCore"),
.product(name: "Dependencies", package: "swift-dependencies"), .product(name: "Dependencies", package: "swift-dependencies"),
.product(name: "DependenciesMacros", package: "swift-dependencies"), .product(name: "DependenciesMacros", package: "swift-dependencies"),
] ]

View File

@@ -31,7 +31,7 @@ struct DependenciesMiddleware: AsyncMiddleware {
try await values.yield { try await values.yield {
try await withDependencies { try await withDependencies {
$0.apiController = apiController $0.apiController = apiController
$0.authClient = .live(on: request) $0.auth = .live(on: request)
$0.database = database $0.database = database
// $0.dateFormatter = .liveValue // $0.dateFormatter = .liveValue
$0.viewController = viewController $0.viewController = viewController

View File

@@ -5,17 +5,23 @@ import ManualDCore
import Vapor import Vapor
extension DependencyValues { extension DependencyValues {
public var authClient: AuthClient { /// Authentication dependency, for handling authentication tasks.
public var auth: AuthClient {
get { self[AuthClient.self] } get { self[AuthClient.self] }
set { self[AuthClient.self] = newValue } set { self[AuthClient.self] = newValue }
} }
} }
/// Represents authentication tasks that are used in the application.
@DependencyClient @DependencyClient
public struct AuthClient: Sendable { public struct AuthClient: Sendable {
/// Create a new user and log them in.
public var createAndLogin: @Sendable (User.Create) async throws -> User public var createAndLogin: @Sendable (User.Create) async throws -> User
/// Get the current user.
public var currentUser: @Sendable () throws -> User public var currentUser: @Sendable () throws -> User
/// Login a user.
public var login: @Sendable (User.Login) async throws -> User public var login: @Sendable (User.Login) async throws -> User
/// Logout a user.
public var logout: @Sendable () throws -> Void public var logout: @Sendable () throws -> Void
} }

View File

@@ -4,22 +4,33 @@ import FluentKit
import ManualDCore import ManualDCore
extension DependencyValues { extension DependencyValues {
/// The database dependency.
public var database: DatabaseClient { public var database: DatabaseClient {
get { self[DatabaseClient.self] } get { self[DatabaseClient.self] }
set { self[DatabaseClient.self] = newValue } set { self[DatabaseClient.self] = newValue }
} }
} }
/// Represents the database interactions used by the application.
@DependencyClient @DependencyClient
public struct DatabaseClient: Sendable { public struct DatabaseClient: Sendable {
/// Database migrations.
public var migrations: Migrations public var migrations: Migrations
/// Interactions with the projects table.
public var projects: Projects public var projects: Projects
/// Interactions with the rooms table.
public var rooms: Rooms public var rooms: Rooms
/// Interactions with the equipment table.
public var equipment: Equipment public var equipment: Equipment
/// Interactions with the component losses table.
public var componentLosses: ComponentLosses public var componentLosses: ComponentLosses
/// Interactions with the equivalent lengths table.
public var equivalentLengths: EquivalentLengths public var equivalentLengths: EquivalentLengths
/// Interactions with the users table.
public var users: Users public var users: Users
/// Interactions with the user profiles table.
public var userProfiles: UserProfiles public var userProfiles: UserProfiles
/// Interactions with the trunk sizes table.
public var trunkSizes: TrunkSizes public var trunkSizes: TrunkSizes
@DependencyClient @DependencyClient
@@ -147,23 +158,3 @@ extension DatabaseClient: TestDependencyKey {
) )
} }
} }
extension DatabaseClient.Migrations: DependencyKey {
public static let testValue = Self()
public static let liveValue = Self(
all: {
[
Project.Migrate(),
User.Migrate(),
User.Token.Migrate(),
User.Profile.Migrate(),
ComponentPressureLoss.Migrate(),
EquipmentInfo.Migrate(),
Room.Migrate(),
EquivalentLength.Migrate(),
TrunkSize.Migrate(),
]
}
)
}

View File

@@ -0,0 +1,22 @@
import Dependencies
import ManualDCore
extension DatabaseClient.Migrations: DependencyKey {
public static let testValue = Self()
public static let liveValue = Self(
all: {
[
Project.Migrate(),
User.Migrate(),
User.Token.Migrate(),
User.Profile.Migrate(),
ComponentPressureLoss.Migrate(),
EquipmentInfo.Migrate(),
Room.Migrate(),
EquivalentLength.Migrate(),
TrunkSize.Migrate(),
]
}
)
}

View File

@@ -4,6 +4,7 @@ import Foundation
import Vapor import Vapor
extension DependencyValues { extension DependencyValues {
/// Dependency used for file operations.
public var fileClient: FileClient { public var fileClient: FileClient {
get { self[FileClient.self] } get { self[FileClient.self] }
set { self[FileClient.self] = newValue } set { self[FileClient.self] = newValue }
@@ -14,9 +15,27 @@ extension DependencyValues {
public struct FileClient: Sendable { public struct FileClient: Sendable {
public typealias OnCompleteHandler = @Sendable () async throws -> Void public typealias OnCompleteHandler = @Sendable () async throws -> Void
public var writeFile: @Sendable (String, String) async throws -> Void /// Write contents to a file.
public var removeFile: @Sendable (String) async throws -> Void ///
public var streamFile: @Sendable (String, @escaping OnCompleteHandler) async throws -> Response /// > Warning: This will overwrite a file if it exists.
public var writeFile: @Sendable (_ contents: String, _ path: String) async throws -> Void
/// Remove a file.
public var removeFile: @Sendable (_ path: String) async throws -> Void
/// Stream a file.
public var streamFile:
@Sendable (_ path: String, @escaping OnCompleteHandler) async throws -> Response
/// Stream a file at the given path.
///
/// - Paramters:
/// - path: The path to the file to stream.
/// - onComplete: Completion handler to run when done streaming the file.
public func streamFile(
at path: String,
onComplete: @escaping OnCompleteHandler = {}
) async throws -> Response {
try await streamFile(path, onComplete)
}
} }
extension FileClient: TestDependencyKey { extension FileClient: TestDependencyKey {

View File

@@ -97,16 +97,16 @@ func roundSize(_ size: Double) throws -> Int {
} }
} }
func velocity(cfm: Int, roundSize: Int) -> Int { func velocity(cfm: CFM, roundSize: Int) -> Int {
let cfm = Double(cfm) let cfm = Double(cfm.rawValue)
let roundSize = Double(roundSize) let roundSize = Double(roundSize)
let velocity = cfm / (pow(roundSize / 24, 2) * 3.14) let velocity = cfm / (pow(roundSize / 24, 2) * 3.14)
return Int(round(velocity)) return Int(round(velocity))
} }
func flexSize(_ request: ManualDClient.DuctSizeRequest) throws -> Int { func flexSize(_ cfm: CFM, _ frictionRate: DesignFrictionRate) throws -> Int {
let cfm = pow(Double(request.designCFM), 0.4) let cfm = pow(Double(cfm.rawValue), 0.4)
let fr = pow(request.frictionRate / 1.76, 0.2) let fr = pow(frictionRate.rawValue / 1.76, 0.2)
let size = 0.55 * (cfm / fr) let size = 0.55 * (cfm / fr)
return try roundSize(size) return try roundSize(size)
} }

View File

@@ -4,6 +4,7 @@ import Logging
import ManualDCore import ManualDCore
extension DependencyValues { extension DependencyValues {
/// Dependency that performs manual-d duct sizing calculations.
public var manualD: ManualDClient { public var manualD: ManualDClient {
get { self[ManualDClient.self] } get { self[ManualDClient.self] }
set { self[ManualDClient.self] = newValue } set { self[ManualDClient.self] = newValue }
@@ -15,12 +16,24 @@ extension DependencyValues {
/// ///
@DependencyClient @DependencyClient
public struct ManualDClient: Sendable { public struct ManualDClient: Sendable {
public var ductSize: @Sendable (DuctSizeRequest) async throws -> DuctSizeResponse public var ductSize: @Sendable (CFM, DesignFrictionRate) async throws -> DuctSizeResponse
public var frictionRate: @Sendable (FrictionRateRequest) async throws -> FrictionRate public var frictionRate: @Sendable (FrictionRateRequest) async throws -> FrictionRate
public var totalEquivalentLength: @Sendable (TotalEquivalentLengthRequest) async throws -> Int
public var rectangularSize: public var rectangularSize:
@Sendable (RectangularSizeRequest) async throws -> RectangularSizeResponse @Sendable (RectangularSizeRequest) async throws -> RectangularSizeResponse
public func ductSize(
cfm designCFM: Int,
frictionRate designFrictionRate: Double
) async throws -> DuctSizeResponse {
try await ductSize(.init(rawValue: designCFM), .init(rawValue: designFrictionRate))
}
public func ductSize(
cfm designCFM: Double,
frictionRate designFrictionRate: Double
) async throws -> DuctSizeResponse {
try await ductSize(.init(rawValue: Int(designCFM)), .init(rawValue: designFrictionRate))
}
} }
extension ManualDClient: TestDependencyKey { extension ManualDClient: TestDependencyKey {
@@ -29,19 +42,6 @@ extension ManualDClient: TestDependencyKey {
extension ManualDClient { extension ManualDClient {
public struct DuctSizeRequest: Codable, Equatable, Sendable {
public let designCFM: Int
public let frictionRate: Double
public init(
designCFM: Int,
frictionRate: Double
) {
self.designCFM = designCFM
self.frictionRate = frictionRate
}
}
public struct DuctSizeResponse: Codable, Equatable, Sendable { public struct DuctSizeResponse: Codable, Equatable, Sendable {
public let calculatedSize: Double public let calculatedSize: Double
@@ -82,31 +82,14 @@ extension ManualDClient {
public struct FrictionRateResponse: Codable, Equatable, Sendable { public struct FrictionRateResponse: Codable, Equatable, Sendable {
public let availableStaticPressure: Double public let availableStaticPressure: Double
public let frictionRate: Double public let frictionRate: DesignFrictionRate
public init(availableStaticPressure: Double, frictionRate: Double) { public init(availableStaticPressure: Double, frictionRate: DesignFrictionRate) {
self.availableStaticPressure = availableStaticPressure self.availableStaticPressure = availableStaticPressure
self.frictionRate = frictionRate self.frictionRate = frictionRate
} }
} }
public struct TotalEquivalentLengthRequest: Codable, Equatable, Sendable {
public let trunkLengths: [Int]
public let runoutLengths: [Int]
public let effectiveLengthGroups: [EffectiveLengthGroup]
public init(
trunkLengths: [Int],
runoutLengths: [Int],
effectiveLengthGroups: [EffectiveLengthGroup]
) {
self.trunkLengths = trunkLengths
self.runoutLengths = runoutLengths
self.effectiveLengthGroups = effectiveLengthGroups
}
}
public struct RectangularSizeRequest: Codable, Equatable, Sendable { public struct RectangularSizeRequest: Codable, Equatable, Sendable {
public let roundSize: Int public let roundSize: Int
public let height: Int public let height: Int

View File

@@ -4,19 +4,19 @@ import ManualDCore
extension ManualDClient: DependencyKey { extension ManualDClient: DependencyKey {
public static let liveValue: Self = .init( public static let liveValue: Self = .init(
ductSize: { request in ductSize: { cfm, frictionRate in
guard request.designCFM > 0 else { guard cfm > 0 else {
throw ManualDError(message: "Design CFM should be greater than 0.") throw ManualDError(message: "Design CFM should be greater than 0.")
} }
let fr = pow(request.frictionRate, 0.5) let fr = pow(frictionRate.rawValue, 0.5)
let ductulatorSize = pow(Double(request.designCFM) / (3.12 * fr), 0.38) let ductulatorSize = pow(Double(cfm.rawValue) / (3.12 * fr), 0.38)
let finalSize = try roundSize(ductulatorSize) let finalSize = try roundSize(ductulatorSize)
let flexSize = try flexSize(request) let flexSize = try flexSize(cfm, frictionRate)
return .init( return .init(
calculatedSize: ductulatorSize, calculatedSize: ductulatorSize,
finalSize: finalSize, finalSize: finalSize,
flexSize: flexSize, flexSize: flexSize,
velocity: velocity(cfm: request.designCFM, roundSize: finalSize) velocity: velocity(cfm: cfm, roundSize: finalSize)
) )
}, },
frictionRate: { request in frictionRate: { request in
@@ -28,14 +28,17 @@ extension ManualDClient: DependencyKey {
let totalComponentLosses = request.componentPressureLosses.total let totalComponentLosses = request.componentPressureLosses.total
let availableStaticPressure = request.externalStaticPressure - totalComponentLosses let availableStaticPressure = request.externalStaticPressure - totalComponentLosses
let frictionRate = availableStaticPressure * 100.0 / Double(request.totalEffectiveLength) let frictionRate = availableStaticPressure * 100.0 / Double(request.totalEffectiveLength)
return .init(availableStaticPressure: availableStaticPressure, value: frictionRate) return .init(
}, availableStaticPressure: availableStaticPressure,
totalEquivalentLength: { request in value: .init(rawValue: frictionRate)
let trunkLengths = request.trunkLengths.reduce(0) { $0 + $1 } )
let runoutLengths = request.runoutLengths.reduce(0) { $0 + $1 }
let groupLengths = request.effectiveLengthGroups.totalEffectiveLength
return trunkLengths + runoutLengths + groupLengths
}, },
// totalEquivalentLength: { request in
// let trunkLengths = request.trunkLengths.reduce(0) { $0 + $1 }
// let runoutLengths = request.runoutLengths.reduce(0) { $0 + $1 }
// let groupLengths = request.effectiveLengthGroups.totalEffectiveLength
// return trunkLengths + runoutLengths + groupLengths
// },
rectangularSize: { request in rectangularSize: { request in
let width = (Double.pi * (pow(Double(request.roundSize) / 2.0, 2.0))) / Double(request.height) let width = (Double.pi * (pow(Double(request.roundSize) / 2.0, 2.0))) / Double(request.height)
return .init(height: request.height, width: Int(width.rounded(.toNearestOrEven))) return .init(height: request.height, width: Int(width.rounded(.toNearestOrEven)))

View File

@@ -1,4 +1,17 @@
import Foundation import Foundation
import Tagged
extension Tagged where RawValue == Double {
public func string(digits: Int = 2) -> String {
rawValue.string(digits: digits)
}
}
extension Tagged where RawValue == Int {
public func string() -> String {
rawValue.string()
}
}
extension Double { extension Double {

View File

@@ -7,13 +7,13 @@ public struct FrictionRate: Codable, Equatable, Sendable {
/// minus the ``ComponentPressureLoss``es for the project. /// minus the ``ComponentPressureLoss``es for the project.
public let availableStaticPressure: Double public let availableStaticPressure: Double
/// The calculated design friction rate value. /// The calculated design friction rate value.
public let value: Double public let value: DesignFrictionRate
/// Whether the design friction rate is within a valid range. /// Whether the design friction rate is within a valid range.
public var hasErrors: Bool { error != nil } public var hasErrors: Bool { error != nil }
public init( public init(
availableStaticPressure: Double, availableStaticPressure: Double,
value: Double value: DesignFrictionRate
) { ) {
self.availableStaticPressure = availableStaticPressure self.availableStaticPressure = availableStaticPressure
self.value = value self.value = value

View File

@@ -0,0 +1,7 @@
import Tagged
public enum CFMTag {}
public typealias CFM = Tagged<CFMTag, Int>
public enum DesignFrictionRateTag {}
public typealias DesignFrictionRate = Tagged<DesignFrictionRateTag, Double>

View File

@@ -52,7 +52,8 @@ extension ManualDClient {
let coolingCFM = coolingPercent * Double(sharedRequest.equipmentInfo.coolingCFM) let coolingCFM = coolingPercent * Double(sharedRequest.equipmentInfo.coolingCFM)
let designCFM = DuctSizes.DesignCFM(heating: heatingCFM, cooling: coolingCFM) let designCFM = DuctSizes.DesignCFM(heating: heatingCFM, cooling: coolingCFM)
let sizes = try await self.ductSize( let sizes = try await self.ductSize(
.init(designCFM: Int(designCFM.value), frictionRate: sharedRequest.designFrictionRate) cfm: designCFM.value,
frictionRate: sharedRequest.designFrictionRate
) )
for n in 1...room.registerCount { for n in 1...room.registerCount {
@@ -111,7 +112,8 @@ extension ManualDClient {
let coolingCFM = coolingPercent * Double(sharedRequest.equipmentInfo.coolingCFM) let coolingCFM = coolingPercent * Double(sharedRequest.equipmentInfo.coolingCFM)
let designCFM = DuctSizes.DesignCFM(heating: heatingCFM, cooling: coolingCFM) let designCFM = DuctSizes.DesignCFM(heating: heatingCFM, cooling: coolingCFM)
let sizes = try await self.ductSize( let sizes = try await self.ductSize(
.init(designCFM: Int(designCFM.value), frictionRate: sharedRequest.designFrictionRate) cfm: designCFM.value,
frictionRate: sharedRequest.designFrictionRate
) )
var width: Int? = nil var width: Int? = nil
if let height = trunk.height { if let height = trunk.height {

View File

@@ -42,13 +42,10 @@ extension ProjectClient: DependencyKey {
request: database.makePdfRequest(projectID) request: database.makePdfRequest(projectID)
) )
let response = try await fileClient.streamFile( let response = try await fileClient.streamFile(at: pdfResponse.pdfPath) {
pdfResponse.pdfPath, try await fileClient.removeFile(pdfResponse.htmlPath)
{ try await fileClient.removeFile(pdfResponse.pdfPath)
try await fileClient.removeFile(pdfResponse.htmlPath) }
try await fileClient.removeFile(pdfResponse.pdfPath)
}
)
response.headers.replaceOrAdd(name: .contentType, value: "application/octet-stream") response.headers.replaceOrAdd(name: .contentType, value: "application/octet-stream")
response.headers.replaceOrAdd( response.headers.replaceOrAdd(

View File

@@ -1,4 +1,5 @@
import Elementary import Elementary
import Tagged
public struct Badge<Inner: HTML>: HTML, Sendable where Inner: Sendable { public struct Badge<Inner: HTML>: HTML, Sendable where Inner: Sendable {
@@ -25,4 +26,12 @@ extension Badge where Inner == Number {
public init(number: Double, digits: Int = 2) { public init(number: Double, digits: Int = 2) {
self.inner = Number(number, digits: digits) self.inner = Number(number, digits: digits)
} }
public init<T>(number: Tagged<T, Int>) {
self.inner = Number(number.rawValue)
}
public init<T>(number: Tagged<T, Double>, digits: Int = 2) {
self.inner = Number(number.rawValue, digits: digits)
}
} }

View File

@@ -54,14 +54,14 @@ extension ViewController: DependencyKey {
extension ViewController.Request { extension ViewController.Request {
func currentUser() throws -> User { func currentUser() throws -> User {
@Dependency(\.authClient.currentUser) var currentUser @Dependency(\.auth.currentUser) var currentUser
return try currentUser() return try currentUser()
} }
func authenticate( func authenticate(
_ login: User.Login _ login: User.Login
) async throws -> User { ) async throws -> User {
@Dependency(\.authClient) var auth @Dependency(\.auth) var auth
return try await auth.login(login) return try await auth.login(login)
} }
@@ -69,7 +69,7 @@ extension ViewController.Request {
func createAndAuthenticate( func createAndAuthenticate(
_ signup: User.Create _ signup: User.Create
) async throws -> User { ) async throws -> User {
@Dependency(\.authClient) var auth @Dependency(\.auth) var auth
return try await auth.createAndLogin(signup) return try await auth.createAndLogin(signup)
} }
} }

View File

@@ -24,59 +24,13 @@ struct ManualDClientTests {
@Test @Test
func ductSize() async throws { func ductSize() async throws {
let response = try await manualD.ductSize( let response = try await manualD.ductSize(88, 0.06)
.init(designCFM: 88, frictionRate: 0.06)
)
#expect(numberFormatter.string(for: response.calculatedSize) == "6.07") #expect(numberFormatter.string(for: response.calculatedSize) == "6.07")
#expect(response.finalSize == 7) #expect(response.finalSize == 7)
#expect(response.flexSize == 7) #expect(response.flexSize == 7)
#expect(response.velocity == 329) #expect(response.velocity == 329)
} }
// @Test
// func frictionRate() async throws {
// let response = try await manualD.frictionRate(
// .init(
// externalStaticPressure: 0.5,
// componentPressureLosses: .mock,
// totalEffectiveLength: 185
// )
// )
// #expect(numberFormatter.string(for: response.availableStaticPressure) == "0.11")
// #expect(numberFormatter.string(for: response.frictionRate) == "0.06")
// }
// @Test
// func frictionRateFails() async throws {
// await #expect(throws: ManualDError.self) {
// _ = try await manualD.frictionRate(
// .init(
// externalStaticPressure: 0.5,
// componentPressureLosses: .mock,
// totalEffectiveLength: 0
// )
// )
// }
// }
@Test
func totalEffectiveLength() async throws {
let response = try await manualD.totalEquivalentLength(
.init(
trunkLengths: [25],
runoutLengths: [10],
effectiveLengthGroups: [
// NOTE: These are made up and may not correspond to actual manual-d group tel's.
EffectiveLengthGroup(group: 1, letter: "a", effectiveLength: 20, category: .supply),
EffectiveLengthGroup(group: 2, letter: "a", effectiveLength: 30, category: .supply),
EffectiveLengthGroup(group: 3, letter: "a", effectiveLength: 10, category: .supply),
EffectiveLengthGroup(group: 12, letter: "a", effectiveLength: 10, category: .supply),
]
)
)
#expect(response == 105)
}
@Test @Test
func equivalentRectangularDuct() async throws { func equivalentRectangularDuct() async throws {
let response = try await manualD.rectangularSize(.init(round: 7, height: 8)) let response = try await manualD.rectangularSize(.init(round: 7, height: 8))

View File

@@ -18,7 +18,7 @@ struct ViewControllerTests {
func login() async throws { func login() async throws {
try await withDependencies { try await withDependencies {
$0.viewController = .liveValue $0.viewController = .liveValue
$0.authClient = .failing $0.auth = .failing
} operation: { } operation: {
@Dependency(\.viewController) var viewController @Dependency(\.viewController) var viewController
@@ -31,7 +31,7 @@ struct ViewControllerTests {
func signup() async throws { func signup() async throws {
try await withDependencies { try await withDependencies {
$0.viewController = .liveValue $0.viewController = .liveValue
$0.authClient = .failing $0.auth = .failing
} operation: { } operation: {
@Dependency(\.viewController) var viewController @Dependency(\.viewController) var viewController
@@ -163,7 +163,7 @@ struct ViewControllerTests {
return try await withDependencies { return try await withDependencies {
$0.viewController = .liveValue $0.viewController = .liveValue
$0.authClient.currentUser = { user } $0.auth.currentUser = { user }
$0.database.userProfiles.fetch = { _ in profile } $0.database.userProfiles.fetch = { _ in profile }
$0.manualD = .liveValue $0.manualD = .liveValue
try await updateDependencies(&$0) try await updateDependencies(&$0)