Skip to content

Commit

Permalink
[SSDK-541] Add searchbox validation, change Demo to searchbox (#164)
Browse files Browse the repository at this point in the history
### Description
- Fixes [SSDK-541](https://mapbox.atlassian.net/browse/SSDK-541)
- Start adding search-box API validation
- Increase test coverage for search-box API usage

### Checklist
- [x] Update `CHANGELOG`

[SSDK-541]: https://mapbox.atlassian.net/browse/SSDK-541
  • Loading branch information
aokj4ck authored Feb 28, 2024
1 parent ababb00 commit f72f8f8
Show file tree
Hide file tree
Showing 35 changed files with 2,572 additions and 35 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Guide: https://keepachangelog.com/en/1.0.0/
## 2.0.0-rc.1

- [Discover] Fix charging station category canonical ID
- [Core] Change AbstractSearchEngine.init `supportSBS: Bool = false` parameter to `apiType: ApiType = .SBS`. This changes the default API engine for discover/category and other API requests to SBS. Add ApiType enum to represent non-Autofill and non-PlaceAutocomplete SearchEngine API types.
- [SearchUI] Rename MapboxPanelController.Configuration to .PanelConfiguration. This disambiguates PanelConfiguration from the broader Configuration struct.
- [Core] Update SwiftLint to 0.54.0 and SwiftFormat to 0.52.11
- [Core] Fix project compliance with linter, reformat Swift files
Expand Down
120 changes: 117 additions & 3 deletions MapboxSearch.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

16 changes: 10 additions & 6 deletions Sources/Demo/DiscoverViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ final class DiscoverViewController: UIViewController {
@IBOutlet private var mapView: MKMapView!
@IBOutlet private var segmentedControl: UISegmentedControl!

private let discover = Discover()
private let discover = Discover(apiType: .searchBox)

override func viewDidLoad() {
super.viewDidLoad()
Expand All @@ -17,16 +17,20 @@ final class DiscoverViewController: UIViewController {
// MARK: - Actions

extension DiscoverViewController {
fileprivate enum Constants {
static let regionResultsLimit = 50
}

@IBAction
private func handleSearchInRegionAction() {
let regionResultsLimit: Int
switch discover.apiType {
case .geocoding:
regionResultsLimit = 10
default:
regionResultsLimit = 100
}

discover.search(
for: currentSelectedCategory,
in: currentBoundingBox,
options: .init(limit: Constants.regionResultsLimit)
options: .init(limit: regionResultsLimit)
) { result in
switch result {
case .success(let results):
Expand Down
2 changes: 2 additions & 0 deletions Sources/MapboxSearch/InternalAPI/Engine/ApiType+Core.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ extension ApiType {
return .geocoding
case .SBS:
return .SBS
case .searchBox:
return .searchBox
}
}
}
2 changes: 2 additions & 0 deletions Sources/MapboxSearch/PublicAPI/Engine/ApiType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ public enum ApiType {

/// The Mapbox Single Box Search (a.k.a Federation API) - https://docs.mapbox.com/api/search/search/
case SBS

case searchBox
}
2 changes: 1 addition & 1 deletion Sources/MapboxSearch/PublicAPI/RouteOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public struct RouteOptions: Hashable {
/// Construct Route Options with Time route deviation
/// - Parameters:
/// - route: Route with coordinates
/// - time: Time route deviation
/// - time: Time route deviation in seconds. Must be within 60 seconds (1 minute) and 1\_800 seconds (30 minutes)
/// - sarType: Quality of deviation calculation (better cost more). Defaults to `.isochrone`
public init(route: Route, time: TimeInterval, sarType: Deviation.SARType = .isochrone) {
self.init(route: route, deviation: .time(Measurement(value: time, unit: .seconds), sarType))
Expand Down
54 changes: 53 additions & 1 deletion Sources/MapboxSearch/PublicAPI/SearchOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,26 @@ public struct SearchOptions {
info("SBS API doesn't support multiple languages at once. Search SDK will use the first")
}

if case .time(let value, _) = validSearchOptions.routeOptions?.deviation {
let minimumTime = Measurement(
value: 1,
unit: UnitDuration.minutes
)
.converted(to: .seconds)
let maximumTime = Measurement(
value: 30,
unit: UnitDuration.minutes
)
.converted(to: .seconds)
let timeRange = (minimumTime...maximumTime)

if !timeRange.contains(value) {
info(
"SBS API time_deviation must be within 1 minute and 30 minutes (found \(value.value) seconds)"
)
}
}

case .autofill:
let unsupportedFilterTypes: [SearchQueryType] = [.category]

Expand All @@ -318,7 +338,39 @@ public struct SearchOptions {
}

case .searchBox:
_Logger.searchSDK.warning("SearchBox API is not supported yet.")
let topLimit = 10

validSearchOptions.limit = limit.map { min($0, topLimit) }
if validSearchOptions.limit != limit {
info("search-box API supports as maximum as \(topLimit) limit.")
}

if languages.count > 1, let first = languages.first {
validSearchOptions.languages = [first]
info(
"search-box API doesn't support multiple languages at once. Search SDK will use the first ('\(first)')"
)
}

if case .time(let value, _) = validSearchOptions.routeOptions?.deviation {
let minimumTime = Measurement(
value: 1,
unit: UnitDuration.minutes
)
.converted(to: .seconds)
let maximumTime = Measurement(
value: 30,
unit: UnitDuration.minutes
)
.converted(to: .seconds)
let timeRange = (minimumTime...maximumTime)

if !timeRange.contains(value) {
info(
"search-box API time_deviation must be within 1 minute and 30 minutes (found \(value.value) seconds)"
)
}
}

@unknown default:
_Logger.searchSDK.warning("Unexpected engine API Type: \(apiType)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ public final class Discover {
private let searchEngine: CategorySearchEngine
private let userActivityReporter: CoreUserActivityReporter

public let apiType: ApiType

/// Basic internal initializer
/// - Parameters:
/// - accessToken: Mapbox Access Token to be used. Info.plist value for key `MGLMapboxAccessToken` will be used
/// for `nil` argument
/// - locationProvider: Provider configuration of LocationProvider that would grant location data by default
public convenience init(
accessToken: String? = nil,
locationProvider: LocationProvider? = DefaultLocationProvider()
locationProvider: LocationProvider? = DefaultLocationProvider(),
apiType: ApiType = .SBS
) {
guard let accessToken = accessToken ?? ServiceProvider.shared.getStoredAccessToken() else {
fatalError(
Expand All @@ -23,7 +26,7 @@ public final class Discover {
let searchEngine = CategorySearchEngine(
accessToken: accessToken,
locationProvider: locationProvider,
apiType: .SBS
apiType: apiType
)

let userActivityReporter = CoreUserActivityReporter.getOrCreate(
Expand All @@ -38,6 +41,7 @@ public final class Discover {

init(searchEngine: CategorySearchEngine, userActivityReporter: CoreUserActivityReporter) {
self.searchEngine = searchEngine
self.apiType = searchEngine.apiType
self.userActivityReporter = userActivityReporter
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import Foundation
extension Discover {
public struct Options {
/// Maximum number of results to return.
/// The maximum allowed value for SBS APIs is 100 results.
/// The maximum allowed value for geocoding APIs is 10 results.
public let limit: Int

/// List of language codes which used to provide localized results, order matters.
Expand Down
19 changes: 15 additions & 4 deletions Sources/MapboxSearchUI/MapboxSearchController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,14 @@ public class MapboxSearchController: UIViewController {
public required init(accessToken: String, configuration: Configuration = Configuration()) {
self.categorySearchEngine = CategorySearchEngine(
accessToken: accessToken,
locationProvider: configuration.locationProvider
locationProvider: configuration.locationProvider,
apiType: .searchBox
)
self.searchEngine = SearchEngine(
accessToken: accessToken,
locationProvider: configuration.locationProvider,
apiType: .searchBox
)
self.searchEngine = SearchEngine(accessToken: accessToken, locationProvider: configuration.locationProvider)
self.configuration = configuration

super.init(nibName: nil, bundle: .mapboxSearchUI)
Expand All @@ -171,8 +176,14 @@ public class MapboxSearchController: UIViewController {
/// - Parameters:
/// - configuration: configuration for search and categorySearch engines.
public required init(configuration: Configuration = Configuration()) {
self.categorySearchEngine = CategorySearchEngine(locationProvider: configuration.locationProvider)
self.searchEngine = SearchEngine(locationProvider: configuration.locationProvider)
self.categorySearchEngine = CategorySearchEngine(
locationProvider: configuration.locationProvider,
apiType: .searchBox
)
self.searchEngine = SearchEngine(
locationProvider: configuration.locationProvider,
apiType: .searchBox
)
self.configuration = configuration

super.init(nibName: nil, bundle: .mapboxSearchUI)
Expand Down
8 changes: 7 additions & 1 deletion Tests/Demo.xctestplan
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
{
"skippedTests" : [
"MockServerIntegrationTestCase",
"OfflineIntegrationTests"
"OfflineIntegrationTests",
"SearchBox_SearchEngineIntegrationTests\/testResolvedSearchResult()",
"SearchBox_SearchEngineIntegrationTests\/testResolvedSearchResultWhenQueryChanged()"
],
"target" : {
"containerPath" : "container:MapboxSearch.xcodeproj",
Expand All @@ -54,6 +56,10 @@
{
"skippedTests" : [
"BaseTestCase",
"FavoritesIntegrationTestCase\/testAddEditLocationRemoveFavorite()",
"FavoritesIntegrationTestCase\/testAddRemoveFavorite()",
"FavoritesIntegrationTestCase\/testAddRenameCancelRemoveFavorite()",
"FavoritesIntegrationTestCase\/testAddRenameRemoveFavorite()",
"MockServerUITestCase"
],
"target" : {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import CoreLocation
@testable import MapboxSearch
import XCTest

final class CategorySearchEngineIntegrationTests: MockServerIntegrationTestCase<SBSMockResponse> {
final class SBS_CategorySearchEngineIntegrationTests: MockServerIntegrationTestCase<SBSMockResponse> {
private var searchEngine: CategorySearchEngine!

override func setUpWithError() throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import CoreLocation
@testable import MapboxSearch
import XCTest

class SearchEngineIntegrationTests: MockServerIntegrationTestCase<SBSMockResponse> {
class SBS_SearchEngineIntegrationTests: MockServerIntegrationTestCase<SBSMockResponse> {
let delegate = SearchEngineDelegateStub()
var searchEngine: SearchEngine!

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import CoreLocation
@testable import MapboxSearch
import XCTest

final class SearchBox_CategorySearchEngineIntegrationTests: MockServerIntegrationTestCase<SearchBoxMockResponse> {
private var searchEngine: CategorySearchEngine!

override func setUpWithError() throws {
try super.setUpWithError()

let apiType = try XCTUnwrap(Mock.coreApiType.toSDKType())
searchEngine = CategorySearchEngine(
accessToken: "access-token",
serviceProvider: LocalhostMockServiceProvider.shared,
apiType: apiType
)
}

func testCategorySearch() throws {
try server.setResponse(.categoryCafe)

let expectation = XCTestExpectation(description: "Expecting results")
searchEngine.search(categoryName: "cafe") { result in
switch result {
case .success(let searchResults):
XCTAssertFalse(searchResults.isEmpty)
expectation.fulfill()
case .failure:
XCTFail("Error not expected")
}
expectation.fulfill()
}
wait(for: [expectation], timeout: 10)
}

func testCategorySearchFailed() throws {
try server.setResponse(.categoryCafe, statusCode: 500)

let expectation = XCTestExpectation(description: "Expecting failure")
searchEngine.search(categoryName: "cafe") { result in
switch result {
case .success:
XCTFail("Not expected")
case .failure(let searchError):
if case .generic(let code, _, _) = searchError {
XCTAssert(code == 500)
} else {
XCTFail("Not expected")
}
}
expectation.fulfill()
}
wait(for: [expectation], timeout: 10)
}
}
Loading

0 comments on commit f72f8f8

Please sign in to comment.