import AuthClient import DatabaseClient import Dependencies import Foundation import HTMLSnapshotTesting import Logging import ManualDClient import ManualDCore import ProjectClient import SnapshotTesting import Testing import ViewController @Suite(.snapshots(record: .failed)) struct ViewControllerTests { @Test func home() async throws { try await withDependencies { $0.viewController = .liveValue $0.auth = .failing } operation: { @Dependency(\.viewController) var viewController let home = try await viewController.view(.test(.home)) assertSnapshot(of: home, as: .html) } } @Test func ductulator() async throws { try await withDependencies { $0.viewController = .liveValue $0.auth = .failing } operation: { @Dependency(\.viewController) var viewController let view = try await viewController.view(.test(.ductulator(.index))) assertSnapshot(of: view, as: .html) } } @Test func login() async throws { try await withDependencies { $0.viewController = .liveValue $0.auth = .failing } operation: { @Dependency(\.viewController) var viewController let login = try await viewController.view(.test(.login(.index()))) assertSnapshot(of: login, as: .html) } } @Test func signup() async throws { try await withDependencies { $0.viewController = .liveValue $0.auth = .failing } operation: { @Dependency(\.viewController) var viewController let signup = try await viewController.view(.test(.login(.index()))) assertSnapshot(of: signup, as: .html) } } @Test func userProfile() async throws { try await withDefaultDependencies { @Dependency(\.viewController) var viewController let html = try await viewController.view(.test(.user(.profile(.index)))) assertSnapshot(of: html, as: .html) } } @Test func projectIndex() async throws { let project = withDependencies { $0.uuid = .incrementing $0.date = .constant(.mock) } operation: { Project.mock } try await withDefaultDependencies { $0.database.projects.fetch = { _, _ in .init(items: [project], metadata: .init(page: 1, per: 25, total: 1)) } } operation: { @Dependency(\.viewController) var viewController let html = try await viewController.view(.test(.project(.index))) assertSnapshot(of: html, as: .html) } } @Test func projectDetail() async throws { let ( project, rooms, equipment, tels, componentLosses, trunks ) = withDependencies { $0.uuid = .incrementing $0.date = .constant(.mock) } operation: { let project = Project.mock let rooms = Room.mock(projectID: project.id) let equipment = EquipmentInfo.mock(projectID: project.id) let tels = EquivalentLength.mock(projectID: project.id) let componentLosses = ComponentPressureLoss.mock(projectID: project.id) let trunks = TrunkSize.mock(projectID: project.id, rooms: rooms) return ( project, rooms, equipment, tels, componentLosses, trunks ) } let mockDuctSizes = DuctSizes.mock( equipmentInfo: equipment, rooms: rooms, trunks: trunks, shr: project.sensibleHeatRatio ?? 0.83 ) try await withDefaultDependencies { $0.database.projects.get = { _ in project } $0.database.projects.getCompletedSteps = { _ in .init(equipmentInfo: true, rooms: true, equivalentLength: true, frictionRate: true) } $0.database.projects.getSensibleHeatRatio = { _ in 0.83 } $0.database.rooms.fetch = { _ in rooms } $0.database.equipment.fetch = { _ in equipment } $0.database.equivalentLengths.fetch = { _ in tels } $0.database.equivalentLengths.fetchMax = { _ in .init(supply: tels.first, return: tels.last) } $0.database.componentLosses.fetch = { _ in componentLosses } $0.projectClient.calculateRoomDuctSizes = { _ in mockDuctSizes.rooms } $0.projectClient.calculateTrunkDuctSizes = { _ in mockDuctSizes.trunks } } operation: { @Dependency(\.viewController) var viewController var html = try await viewController.view(.test(.project(.detail(project.id, .index)))) assertSnapshot(of: html, as: .html) html = try await viewController.view(.test(.project(.detail(project.id, .rooms(.index))))) assertSnapshot(of: html, as: .html) html = try await viewController.view(.test(.project(.detail(project.id, .equipment(.index))))) assertSnapshot(of: html, as: .html) html = try await viewController.view( .test(.project(.detail(project.id, .equivalentLength(.index))))) assertSnapshot(of: html, as: .html) html = try await viewController.view( .test(.project(.detail(project.id, .frictionRate(.index))))) assertSnapshot(of: html, as: .html) html = try await viewController.view( .test(.project(.detail(project.id, .ductSizing(.index))))) assertSnapshot(of: html, as: .html) } } func createUserDependencies() -> (User, User.Profile) { withDependencies { $0.uuid = .incrementing $0.date = .constant(.mock) } operation: { let user = User.mock let profile = User.Profile.mock(userID: user.id) return (user, profile) } } @discardableResult func withDefaultDependencies( isolation: isolated (any Actor)? = #isolation, _ updateDependencies: (inout DependencyValues) async throws -> Void = { _ in }, operation: () async throws -> R ) async rethrows -> R { let (user, profile) = createUserDependencies() return try await withDependencies { $0.viewController = .liveValue $0.auth.currentUser = { user } $0.database.userProfiles.fetch = { _ in profile } $0.manualD = .liveValue try await updateDependencies(&$0) } operation: { try await operation() } } } extension Date { static let mock = Self(timeIntervalSince1970: 1_234_567_890) } extension ViewController.Request { static func test( _ route: SiteRoute.View, isHtmxRequest: Bool = false, logger: Logger = .init(label: "ViewControllerTests") ) -> Self { .init(route: route, isHtmxRequest: isHtmxRequest, logger: logger) } } extension AuthClient { static let failing = Self( createAndLogin: { _ in throw TestError() }, currentUser: { throw TestError() }, login: { _ in throw TestError() }, logout: { throw TestError() } ) } struct TestError: Error {}