Adding tests for search screen
This commit is contained in:
parent
071a6f800d
commit
0c4b14debf
@ -26,7 +26,8 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
|
||||
@ -20,7 +20,6 @@ final class SearchCoordinator {
|
||||
let resolver = ServiceContainer.shared
|
||||
let viewModel = SearchViewModel(
|
||||
apiService: resolver.resolve(ApiServiceProtocol.self),
|
||||
storageService: resolver.resolve(StorageServiceProtocol.self),
|
||||
vehicleService: resolver.resolve(VehicleServiceProtocol.self)
|
||||
)
|
||||
viewModel.coordinator = self
|
||||
|
||||
@ -15,7 +15,6 @@ import Combine
|
||||
final class SearchViewModel: ACHudContainer {
|
||||
|
||||
let apiService: ApiServiceProtocol
|
||||
let storageService: StorageServiceProtocol
|
||||
let vehicleService: VehicleServiceProtocol
|
||||
var coordinator: SearchCoordinator?
|
||||
|
||||
@ -35,8 +34,10 @@ final class SearchViewModel: ACHudContainer {
|
||||
if searchText != oldValue {
|
||||
filter.searchString = searchText
|
||||
searchTask = Task { [filter] in
|
||||
try? await Task.sleep(for: .milliseconds(500))
|
||||
do {
|
||||
try await Task.sleep(for: .milliseconds(500))
|
||||
await reloadData(with: filter)
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -51,11 +52,9 @@ final class SearchViewModel: ACHudContainer {
|
||||
}
|
||||
|
||||
init(apiService: ApiServiceProtocol,
|
||||
storageService: StorageServiceProtocol,
|
||||
vehicleService: VehicleServiceProtocol) {
|
||||
|
||||
self.apiService = apiService
|
||||
self.storageService = storageService
|
||||
self.vehicleService = vehicleService
|
||||
}
|
||||
|
||||
@ -80,7 +79,6 @@ final class SearchViewModel: ACHudContainer {
|
||||
|
||||
func loadSearchResults(filter: Filter) async throws {
|
||||
|
||||
let query = filter.searchString
|
||||
let response = try await apiService.getVehicles(with: filter, pageToken: pageToken, pageSize: 20)
|
||||
|
||||
if response.items.isEmpty {
|
||||
@ -172,18 +170,22 @@ final class SearchViewModel: ACHudContainer {
|
||||
}
|
||||
|
||||
func updateVehicle(_ vehicle: VehicleDto) async {
|
||||
await wrapWithToast { [weak self] in
|
||||
guard let self else {
|
||||
do {
|
||||
hud = .progress
|
||||
let (updatedVehicle, errors) = try await vehicleService.updateSearch(number: vehicle.number)
|
||||
|
||||
if !errors.isEmpty {
|
||||
showErrors(errors)
|
||||
return
|
||||
}
|
||||
|
||||
let updatedVehicle = try await apiService.checkVehicle(by: vehicle.getNumber(), notes: [], events: [], force: true)
|
||||
try await storageService.updateVehicle(dto: updatedVehicle, policy: .ifExists)
|
||||
|
||||
if let index = vehicles.firstIndex(where: { $0.number == updatedVehicle.number }) {
|
||||
vehicles[index] = updatedVehicle
|
||||
vehicleSections = vehicles.groupedByDate(type: .updatedDate)
|
||||
}
|
||||
hud = nil
|
||||
} catch {
|
||||
hud = .error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import Foundation
|
||||
|
||||
public final class PagedResponse<T>: Decodable, Sendable where T: Decodable & Sendable {
|
||||
public let count: Int?
|
||||
public let pageToken: String?
|
||||
public let items: [T]
|
||||
public struct PagedResponse<T>: Decodable, Sendable where T: Decodable & Sendable {
|
||||
public var count: Int?
|
||||
public var pageToken: String?
|
||||
public var items: [T]
|
||||
|
||||
public init() {
|
||||
self.items = []
|
||||
self.count = nil
|
||||
public init(items: [T]) {
|
||||
self.count = items.count
|
||||
self.pageToken = nil
|
||||
self.items = items
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ struct HistoryTests {
|
||||
|
||||
var viewModel: HistoryViewModel
|
||||
|
||||
init() async {
|
||||
init() {
|
||||
|
||||
viewModel = HistoryViewModel(
|
||||
apiService: apiServiceMock,
|
||||
|
||||
241
AutoCatTests/SearchTests.swift
Normal file
241
AutoCatTests/SearchTests.swift
Normal file
@ -0,0 +1,241 @@
|
||||
//
|
||||
// SearchTests.swift
|
||||
// AutoCatTests
|
||||
//
|
||||
// Created by Selim Mustafaev on 22.02.2025.
|
||||
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import Testing
|
||||
import Mockable
|
||||
import AutoCatCore
|
||||
@testable import AutoCat
|
||||
|
||||
@MainActor
|
||||
struct SearchTests {
|
||||
|
||||
let apiServiceMock = MockApiServiceProtocol()
|
||||
let vehicleServiceMock = MockVehicleServiceProtocol()
|
||||
|
||||
var viewModel: SearchViewModel
|
||||
|
||||
init() {
|
||||
|
||||
viewModel = SearchViewModel(
|
||||
apiService: apiServiceMock,
|
||||
vehicleService: vehicleServiceMock
|
||||
)
|
||||
}
|
||||
|
||||
@Test("Initial load")
|
||||
func initialLoad() async {
|
||||
|
||||
given(apiServiceMock)
|
||||
.getVehicles(with: .any, pageToken: .value(nil), pageSize: .any)
|
||||
.willReturn(PagedResponse(items: [VehicleDto.normal]))
|
||||
|
||||
await viewModel.onAppear()
|
||||
|
||||
verify(apiServiceMock)
|
||||
.getVehicles(with: .any, pageToken: .value(nil), pageSize: .any)
|
||||
.called(.once)
|
||||
|
||||
#expect(viewModel.vehicles == [.normal])
|
||||
#expect(viewModel.vehicleSections.count == 1)
|
||||
#expect(viewModel.searchText == "")
|
||||
#expect(viewModel.hud == nil)
|
||||
}
|
||||
|
||||
@Test("Initial load error")
|
||||
func initialLoadError() async {
|
||||
|
||||
given(apiServiceMock)
|
||||
.getVehicles(with: .any, pageToken: .value(nil), pageSize: .any)
|
||||
.willThrow(TestError.generic)
|
||||
|
||||
await viewModel.onAppear()
|
||||
|
||||
#expect(viewModel.vehicles.isEmpty)
|
||||
#expect(viewModel.hud == .error(TestError.generic))
|
||||
}
|
||||
|
||||
@Test("Search")
|
||||
func search() async throws {
|
||||
|
||||
given(apiServiceMock)
|
||||
.getVehicles(with: .any, pageToken: .any, pageSize: .any)
|
||||
.willReturn(PagedResponse(items: [VehicleDto.normal]))
|
||||
|
||||
await viewModel.onAppear()
|
||||
|
||||
viewModel.searchText = "test1"
|
||||
viewModel.searchText = "test2"
|
||||
try await Task.sleep(for: .milliseconds(600))
|
||||
viewModel.searchText = "test3"
|
||||
try await Task.sleep(for: .milliseconds(600))
|
||||
|
||||
verify(apiServiceMock)
|
||||
.getVehicles(with: .any, pageToken: .any, pageSize: .any)
|
||||
.called(.exactly(3))
|
||||
|
||||
#expect(viewModel.vehicles == [.normal])
|
||||
#expect(viewModel.vehicleSections.count == 1)
|
||||
#expect(viewModel.filter.searchString == "test3")
|
||||
#expect(viewModel.hud == nil)
|
||||
}
|
||||
|
||||
@Test("Search error")
|
||||
func searchError() async throws {
|
||||
|
||||
var filterWithString = Filter()
|
||||
filterWithString.searchString = "test"
|
||||
|
||||
given(apiServiceMock)
|
||||
.getVehicles(with: .any, pageToken: .any, pageSize: .any)
|
||||
.willReturn(PagedResponse(items: [VehicleDto.normal]))
|
||||
.getVehicles(with: .any, pageToken: .any, pageSize: .any)
|
||||
.willThrow(TestError.generic)
|
||||
|
||||
await viewModel.onAppear()
|
||||
viewModel.searchText = "test"
|
||||
try await Task.sleep(for: .milliseconds(600))
|
||||
|
||||
#expect(viewModel.vehicles.isEmpty)
|
||||
#expect(viewModel.hud == .error(TestError.generic))
|
||||
}
|
||||
|
||||
@Test("Pagination")
|
||||
func pagination() async {
|
||||
|
||||
given(apiServiceMock)
|
||||
.getVehicles(with: .any, pageToken: .any, pageSize: .any)
|
||||
.willReturn(PagedResponse(items: [VehicleDto.normal]))
|
||||
.getVehicles(with: .any, pageToken: .any, pageSize: .any)
|
||||
.willReturn(PagedResponse(items: [VehicleDto.normal]))
|
||||
.getVehicles(with: .any, pageToken: .any, pageSize: .any)
|
||||
.willReturn(PagedResponse(items: []))
|
||||
|
||||
await viewModel.onAppear()
|
||||
await viewModel.loadMoreData()
|
||||
await viewModel.loadMoreData()
|
||||
|
||||
verify(apiServiceMock)
|
||||
.getVehicles(with: .any, pageToken: .any, pageSize: .any)
|
||||
.called(.exactly(3))
|
||||
|
||||
#expect(viewModel.vehicles.count == 2)
|
||||
#expect(viewModel.hasMoreData == false)
|
||||
#expect(viewModel.hud == nil)
|
||||
}
|
||||
|
||||
@Test("Pagination error")
|
||||
func paginationError() async {
|
||||
|
||||
given(apiServiceMock)
|
||||
.getVehicles(with: .any, pageToken: .any, pageSize: .any)
|
||||
.willReturn(PagedResponse(items: [VehicleDto.normal]))
|
||||
.getVehicles(with: .any, pageToken: .any, pageSize: .any)
|
||||
.willReturn(PagedResponse(items: [VehicleDto.normal]))
|
||||
.getVehicles(with: .any, pageToken: .any, pageSize: .any)
|
||||
.willThrow(TestError.generic)
|
||||
|
||||
await viewModel.onAppear()
|
||||
await viewModel.loadMoreData()
|
||||
await viewModel.loadMoreData()
|
||||
|
||||
verify(apiServiceMock)
|
||||
.getVehicles(with: .any, pageToken: .any, pageSize: .any)
|
||||
.called(.exactly(3))
|
||||
|
||||
#expect(viewModel.vehicles.count == 2)
|
||||
#expect(viewModel.hasMoreData == false)
|
||||
#expect(viewModel.hud == .error(TestError.generic))
|
||||
}
|
||||
|
||||
@Test("Update vehicle")
|
||||
func updateVehicle() async {
|
||||
|
||||
let updatedVehicle: VehicleDto = .normal
|
||||
.addEvent(.valid)
|
||||
|
||||
given(apiServiceMock)
|
||||
.getVehicles(with: .any, pageToken: .any, pageSize: .any)
|
||||
.willReturn(PagedResponse(items: [VehicleDto.normal]))
|
||||
|
||||
given(vehicleServiceMock)
|
||||
.updateSearch(number: .value(VehicleDto.normal.number))
|
||||
.willReturn((updatedVehicle, []))
|
||||
|
||||
await viewModel.onAppear()
|
||||
await viewModel.updateVehicle(.normal)
|
||||
|
||||
verify(apiServiceMock)
|
||||
.getVehicles(with: .any, pageToken: .any, pageSize: .any)
|
||||
.called(.once)
|
||||
|
||||
verify(vehicleServiceMock)
|
||||
.updateSearch(number: .any)
|
||||
.called(.once)
|
||||
|
||||
#expect(viewModel.vehicles.first?.events.isEmpty == false)
|
||||
#expect(viewModel.hud == nil)
|
||||
}
|
||||
|
||||
@Test("Update vehicle error")
|
||||
func updateVehicleError() async {
|
||||
|
||||
let updatedVehicle: VehicleDto = .normal
|
||||
.addEvent(.valid)
|
||||
|
||||
given(apiServiceMock)
|
||||
.getVehicles(with: .any, pageToken: .any, pageSize: .any)
|
||||
.willReturn(PagedResponse(items: [VehicleDto.normal]))
|
||||
|
||||
given(vehicleServiceMock)
|
||||
.updateSearch(number: .value(VehicleDto.normal.number))
|
||||
.willReturn((updatedVehicle, [TestError.generic]))
|
||||
|
||||
await viewModel.onAppear()
|
||||
await viewModel.updateVehicle(.normal)
|
||||
|
||||
verify(apiServiceMock)
|
||||
.getVehicles(with: .any, pageToken: .any, pageSize: .any)
|
||||
.called(.once)
|
||||
|
||||
verify(vehicleServiceMock)
|
||||
.updateSearch(number: .any)
|
||||
.called(.once)
|
||||
|
||||
#expect(viewModel.vehicles.first?.events.isEmpty == true)
|
||||
|
||||
if case .message(_, _) = viewModel.hud {} else {
|
||||
Issue.record("hud must be in .message state")
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Update vehicle error 2")
|
||||
func updateVehicleError2() async {
|
||||
|
||||
given(apiServiceMock)
|
||||
.getVehicles(with: .any, pageToken: .any, pageSize: .any)
|
||||
.willReturn(PagedResponse(items: [VehicleDto.normal]))
|
||||
|
||||
given(vehicleServiceMock)
|
||||
.updateSearch(number: .value(VehicleDto.normal.number))
|
||||
.willThrow(TestError.generic)
|
||||
|
||||
await viewModel.onAppear()
|
||||
await viewModel.updateVehicle(.normal)
|
||||
|
||||
verify(apiServiceMock)
|
||||
.getVehicles(with: .any, pageToken: .any, pageSize: .any)
|
||||
.called(.once)
|
||||
|
||||
verify(vehicleServiceMock)
|
||||
.updateSearch(number: .any)
|
||||
.called(.once)
|
||||
|
||||
#expect(viewModel.vehicles.first?.events.isEmpty == true)
|
||||
#expect(viewModel.hud == .error(TestError.generic))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user