|
| 1 | +import Testing |
| 2 | +import MapKit |
| 3 | +import Contacts |
| 4 | +import Yosemite |
| 5 | +@testable import WooCommerce |
| 6 | + |
| 7 | +struct AddressMapPickerViewModelTests { |
| 8 | + private let mockCountryByCode: (String) -> Country? |
| 9 | + |
| 10 | + init() { |
| 11 | + mockCountryByCode = { countryCode in |
| 12 | + switch countryCode { |
| 13 | + case "US": |
| 14 | + return Country(code: "US", name: "USA", states: [ |
| 15 | + StateOfACountry(code: "CA", name: "Cali") |
| 16 | + ]) |
| 17 | + default: |
| 18 | + return nil |
| 19 | + } |
| 20 | + } |
| 21 | + } |
| 22 | + |
| 23 | + // MARK: - Initialization Tests |
| 24 | + |
| 25 | + @available(iOS 17, *) |
| 26 | + @Test func initialization_with_empty_fields_sets_properties_with_default_values() { |
| 27 | + // Given |
| 28 | + let emptyFields = AddressFormFields() |
| 29 | + |
| 30 | + // When |
| 31 | + let sut = AddressMapPickerViewModel(fields: emptyFields, countryByCode: mockCountryByCode) |
| 32 | + |
| 33 | + // Then |
| 34 | + #expect(sut.searchResults.isEmpty) |
| 35 | + #expect(sut.annotations.isEmpty) |
| 36 | + #expect(!sut.hasValidSelection) |
| 37 | + } |
| 38 | + |
| 39 | + // MARK: - Selection Tests |
| 40 | + |
| 41 | + @available(iOS 17, *) |
| 42 | + @Test func selectLocation_updates_annotations_and_hasValidSelection() async { |
| 43 | + // Given |
| 44 | + let fields = AddressFormFields() |
| 45 | + let mockSearchProvider = MockAddressMapLocalSearchProvider.withBasicCoordinates() |
| 46 | + let sut = AddressMapPickerViewModel(fields: fields, countryByCode: mockCountryByCode, searchProvider: mockSearchProvider) |
| 47 | + let searchCompletion = MockMKLocalSearchCompletion() |
| 48 | + |
| 49 | + // When |
| 50 | + await sut.selectLocation(searchCompletion) |
| 51 | + |
| 52 | + // Then |
| 53 | + #expect(sut.annotations.count == 1) |
| 54 | + #expect(sut.hasValidSelection == true) |
| 55 | + } |
| 56 | + |
| 57 | + // MARK: - Address Field Updates Tests |
| 58 | + |
| 59 | + @available(iOS 17, *) |
| 60 | + @Test func updateFields_with_no_selected_place_does_not_modify_fields() { |
| 61 | + // Given |
| 62 | + let sut = AddressMapPickerViewModel(fields: .init(), countryByCode: mockCountryByCode) |
| 63 | + var updatedFields = AddressFormFields() |
| 64 | + updatedFields.address1 = "Original Address" |
| 65 | + updatedFields.city = "Original City" |
| 66 | + |
| 67 | + // When |
| 68 | + sut.updateFields(&updatedFields) |
| 69 | + |
| 70 | + // Then |
| 71 | + #expect(updatedFields.address1 == "Original Address") |
| 72 | + #expect(updatedFields.city == "Original City") |
| 73 | + } |
| 74 | + |
| 75 | + @available(iOS 17, *) |
| 76 | + @Test func updateFields_when_country_not_found_in_countryByCode_sets_country_and_state_as_strings() async { |
| 77 | + // Given |
| 78 | + let mockSearchProvider = MockAddressMapLocalSearchProvider.withFrenchAddress() |
| 79 | + let sut = AddressMapPickerViewModel(fields: .init(), countryByCode: mockCountryByCode, searchProvider: mockSearchProvider) |
| 80 | + let searchCompletion = MockMKLocalSearchCompletion() |
| 81 | + |
| 82 | + await sut.selectLocation(searchCompletion) |
| 83 | + |
| 84 | + // When |
| 85 | + var updatedFields = AddressFormFields() |
| 86 | + sut.updateFields(&updatedFields) |
| 87 | + |
| 88 | + // Then |
| 89 | + #expect(updatedFields.address1 == "Tour Eiffel") |
| 90 | + #expect(updatedFields.city == "Paris") |
| 91 | + #expect(updatedFields.postcode == "75007") |
| 92 | + #expect(updatedFields.country == "FR") |
| 93 | + #expect(updatedFields.state == "Île-de-France") |
| 94 | + #expect(updatedFields.selectedCountry == nil) // Country is not found in countryByCode dictionary |
| 95 | + #expect(updatedFields.selectedState == nil) |
| 96 | + } |
| 97 | + |
| 98 | + @available(iOS 17, *) |
| 99 | + @Test func updateFields_when_country_is_found_in_countryByCode_sets_selected_country_and_state() async { |
| 100 | + // Given |
| 101 | + let mockSearchProvider = MockAddressMapLocalSearchProvider.withUSAddress() |
| 102 | + let sut = AddressMapPickerViewModel(fields: .init(), countryByCode: mockCountryByCode, searchProvider: mockSearchProvider) |
| 103 | + let searchCompletion = MockMKLocalSearchCompletion() |
| 104 | + |
| 105 | + await sut.selectLocation(searchCompletion) |
| 106 | + |
| 107 | + // When |
| 108 | + var updatedFields = AddressFormFields() |
| 109 | + sut.updateFields(&updatedFields) |
| 110 | + |
| 111 | + // Then |
| 112 | + #expect(updatedFields.address1 == "1 Apple Park Way") |
| 113 | + #expect(updatedFields.city == "Cupertino") |
| 114 | + #expect(updatedFields.postcode == "95014") |
| 115 | + #expect(updatedFields.country == "USA") |
| 116 | + #expect(updatedFields.state == "Cali") |
| 117 | + #expect(updatedFields.selectedCountry?.code == "US") |
| 118 | + #expect(updatedFields.selectedState?.code == "CA") |
| 119 | + } |
| 120 | +} |
| 121 | + |
| 122 | +// MARK: - Mock Classes |
| 123 | + |
| 124 | +final private class MockMKLocalSearchCompletion: MKLocalSearchCompletion {} |
| 125 | + |
| 126 | +final private class MockAddressMapLocalSearchProvider: AddressMapLocalSearchProviding { |
| 127 | + private let mockPlacemark: MKPlacemark |
| 128 | + |
| 129 | + init(mockPlacemark: MKPlacemark) { |
| 130 | + self.mockPlacemark = mockPlacemark |
| 131 | + } |
| 132 | + |
| 133 | + func search(for completion: MKLocalSearchCompletion) async throws -> MKLocalSearch.Response { |
| 134 | + let mockMapItem = MKMapItem(placemark: mockPlacemark) |
| 135 | + let mockResponse = MockMKLocalSearchResponse(mapItems: [mockMapItem]) |
| 136 | + return mockResponse |
| 137 | + } |
| 138 | +} |
| 139 | + |
| 140 | +final private class MockMKLocalSearchResponse: MKLocalSearch.Response { |
| 141 | + private let _mapItems: [MKMapItem] |
| 142 | + |
| 143 | + init(mapItems: [MKMapItem]) { |
| 144 | + self._mapItems = mapItems |
| 145 | + super.init() |
| 146 | + } |
| 147 | + |
| 148 | + override var mapItems: [MKMapItem] { _mapItems } |
| 149 | +} |
| 150 | + |
| 151 | +private extension MockAddressMapLocalSearchProvider { |
| 152 | + static func withFrenchAddress() -> MockAddressMapLocalSearchProvider { |
| 153 | + let postalAddress = CNMutablePostalAddress() |
| 154 | + postalAddress.street = "Tour Eiffel" |
| 155 | + postalAddress.city = "Paris" |
| 156 | + postalAddress.postalCode = "75007" |
| 157 | + postalAddress.country = "France" |
| 158 | + postalAddress.isoCountryCode = "FR" |
| 159 | + postalAddress.state = "Île-de-France" |
| 160 | + |
| 161 | + let placemark = MKPlacemark( |
| 162 | + coordinate: CLLocationCoordinate2D(latitude: 48.8584, longitude: 2.2945), |
| 163 | + postalAddress: postalAddress |
| 164 | + ) |
| 165 | + |
| 166 | + return MockAddressMapLocalSearchProvider(mockPlacemark: placemark) |
| 167 | + } |
| 168 | + |
| 169 | + static func withUSAddress() -> MockAddressMapLocalSearchProvider { |
| 170 | + let postalAddress = CNMutablePostalAddress() |
| 171 | + postalAddress.street = "1 Apple Park Way" |
| 172 | + postalAddress.city = "Cupertino" |
| 173 | + postalAddress.postalCode = "95014" |
| 174 | + postalAddress.country = "United States" |
| 175 | + postalAddress.isoCountryCode = "US" |
| 176 | + postalAddress.state = "CA" |
| 177 | + |
| 178 | + let placemark = MKPlacemark( |
| 179 | + coordinate: CLLocationCoordinate2D(latitude: 37.3349, longitude: -122.0090), |
| 180 | + postalAddress: postalAddress |
| 181 | + ) |
| 182 | + |
| 183 | + return MockAddressMapLocalSearchProvider(mockPlacemark: placemark) |
| 184 | + } |
| 185 | + |
| 186 | + static func withBasicCoordinates() -> MockAddressMapLocalSearchProvider { |
| 187 | + let placemark = MKPlacemark(coordinate: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194)) |
| 188 | + return MockAddressMapLocalSearchProvider(mockPlacemark: placemark) |
| 189 | + } |
| 190 | +} |
0 commit comments