Skip to content

Commit

Permalink
Fix camera example test (#2137)
Browse files Browse the repository at this point in the history
  • Loading branch information
aleksproger authored May 7, 2024
1 parent b1eee26 commit 95d0c80
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 103 deletions.
17 changes: 9 additions & 8 deletions Apps/Examples/Examples/All Examples/CameraForExample.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import UIKit
@_spi(Experimental) import MapboxMaps
import SwiftUI

@available(iOS 13.0, *)
final class CameraForExample: UIViewController, ExampleProtocol {
Expand All @@ -19,20 +18,20 @@ final class CameraForExample: UIViewController, ExampleProtocol {
addPolygons()

setCamera(immediately: true, onMapLoaded: true)
finish()
}

private func setCamera(immediately: Bool, onMapLoaded: Bool) {
if immediately {
setCamera(coordinates: selectedPlace, mapPadding: .zero, coordinatesPadding: .zero)
}

if onMapLoaded {
mapView.mapboxMap.onMapLoaded.observeNext { [weak self] _ in
guard let self else { return }
mapView.mapboxMap.onMapLoaded.observeNext { [weak self] _ in
guard let self else { return }
if onMapLoaded {
setCamera(coordinates: selectedPlace, mapPadding: .zero, coordinatesPadding: .zero)
}.store(in: &cancelables)
}
}
finish() // for testing purposes
}.store(in: &cancelables)
}

private func setCamera(
Expand Down Expand Up @@ -95,7 +94,9 @@ final class CameraForExample: UIViewController, ExampleProtocol {
bottomSheet.layer.opacity = 0.8
bottomSheet.backgroundColor = .white
bottomSheet.selectedPlace = selectedPlace
bottomSheet.onSelectionChanged = setCamera(coordinates:mapPadding:coordinatesPadding:)
bottomSheet.onSelectionChanged = { [weak self] cordinates, mapPadding, coordinatesPadding in
self?.setCamera(coordinates: cordinates, mapPadding: mapPadding, coordinatesPadding: coordinatesPadding)
}

view.addSubview(bottomSheet)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ final class ExampleTableViewController: UITableViewController {
if let exampleTitleToStart = UserDefaults.standard.value(forKey: startingExampleTitleKey) as? String, shouldReopenLastExample {

let initialExample = allExamples
.compactMap({ $0["examples"] as? [Example] })
.flatMap({ $0 })
.flatMap(\.examples)
.first(where: { $0.title == exampleTitleToStart })
if let initialExample = initialExample {
open(example: initialExample, animated: false)
Expand Down Expand Up @@ -87,15 +86,15 @@ extension ExampleTableViewController {
return nil
}

return allExamples[section]["title"] as? String
return allExamples[section].title
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering {
return filteredExamples.count
}

let examples = allExamples[section]["examples"] as! [Example]
let examples = allExamples[section].examples
return examples.count
}

Expand All @@ -106,7 +105,7 @@ extension ExampleTableViewController {
if isFiltering {
example = filteredExamples[indexPath.row]
} else {
let examples = allExamples[indexPath.section]["examples"] as! [Example]
let examples = allExamples[indexPath.section].examples
example = examples[indexPath.row]
}

Expand All @@ -130,15 +129,15 @@ extension ExampleTableViewController {
if isFiltering {
example = filteredExamples[indexPath.row]
} else {
let examples = allExamples[indexPath.section]["examples"] as! [Example]
let examples = allExamples[indexPath.section].examples
example = examples[indexPath.row]
}

open(example: example)
}

func filterContentForSearchText(_ searchText: String) {
let flatExamples = allExamples.flatMap { $0["examples"] as! [Example] }
let flatExamples = allExamples.flatMap(\.examples)
if searchText.isEmpty {
filteredExamples = flatExamples
} else {
Expand Down
68 changes: 19 additions & 49 deletions Apps/Examples/Examples/Models/Examples.swift
Original file line number Diff line number Diff line change
Expand Up @@ -308,55 +308,25 @@ struct Examples {
}

extension Examples {
static let all: [[String : Any]] = [
[
"title": "Getting started",
"examples": gettingStartedExamples
],
[
"title": "3D and Fill Extrusions",
"examples": threeDExamples
],
[
"title": "Annotations",
"examples": annotationExamples
],
[
"title": "Camera",
"examples": cameraExamples
],
[
"title": "Lab",
"examples": labExamples
],
[
"title": "Location",
"examples": locationExamples
],
[
"title": "Offline",
"examples": offlineExamples
],
[
"title": "Snapshot",
"examples": snapshotExamples
],
[
"title": "Style",
"examples": styleExamples
],
[
"title": "User Interaction",
"examples": userInteractionExamples
],
[
"title": "Accessibility",
"examples": accessibilityExamples
],
[ "title": "Globe and Atmosphere",
"examples": globeAndAtmosphere
]
]
struct Category {
let title: String
let examples: [Example]
}

static let all: [Category] = .init {
Category(title: "Getting started", examples: gettingStartedExamples)
Category(title: "3D and Fill Extrusions", examples: threeDExamples)
Category(title: "Annotations", examples: annotationExamples)
Category(title: "Camera", examples: cameraExamples)
Category(title: "Lab", examples: labExamples)
Category(title: "Location", examples: locationExamples)
Category(title: "Offline", examples: offlineExamples)
Category(title: "Snapshot", examples: snapshotExamples)
Category(title: "Style", examples: styleExamples)
Category(title: "User Interaction", examples: userInteractionExamples)
Category(title: "Accessibility", examples: accessibilityExamples)
Category(title: "Globe and Atmosphere", examples: globeAndAtmosphere)
}
}

extension Array {
Expand Down
42 changes: 20 additions & 22 deletions Tests/ExamplesTests/ExamplesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,26 @@ class ExamplesTests: XCTestCase {

func testExampleClassExists() throws {

for category in Examples.all {
// swiftlint:disable:next force_cast
for example in category["examples"] as! [Example] {
// Check view controller can be extrapolated from the example file name.
XCTAssert(example.type is UIViewController.Type)

// Check if the example file name has the word "Example" appended to the end.
let string = "\(example.type)"
let indexEnd = string.index(string.endIndex, offsetBy: -("Example".count))
XCTAssertTrue(string[indexEnd...] == "Example")

// Check that examples have descriptions.
XCTAssertFalse(example.description.isEmpty, "Example '\(example.type)' should have a description.")

// Check that examples have titles.
XCTAssertFalse(example.title.isEmpty, "Example '\(example.type)' should have a title.")

// Check that example titles do not end in punctuation
if let last = example.title.last {
XCTAssertTrue(CharacterSet(charactersIn: String(last)).isDisjoint(with: .punctuationCharacters),
"Title for example '\(example.type)' should not end with punctuation.")
}
for example in Examples.all.flatMap(\.examples) {
print("Validating \(example)")
// Check view controller can be extrapolated from the example file name.
XCTAssert(example.type is UIViewController.Type)

// Check if the example file name has the word "Example" appended to the end.
let string = "\(example.type)"
let indexEnd = string.index(string.endIndex, offsetBy: -("Example".count))
XCTAssertTrue(string[indexEnd...] == "Example")

// Check that examples have descriptions.
XCTAssertFalse(example.description.isEmpty, "Example '\(example.type)' should have a description.")

// Check that examples have titles.
XCTAssertFalse(example.title.isEmpty, "Example '\(example.type)' should have a title.")

// Check that example titles do not end in punctuation
if let last = example.title.last {
XCTAssertTrue(CharacterSet(charactersIn: String(last)).isDisjoint(with: .punctuationCharacters),
"Title for example '\(example.type)' should not end with punctuation.")
}
}
}
Expand Down
40 changes: 23 additions & 17 deletions Tests/ExamplesTests/TestableExampleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import ObjectiveC.runtime
import MapboxMaps

class TestableExampleTests: XCTestCase {
private var example: Example!
private var example: Example?
private weak var weakExampleViewController: UIViewController?
private weak var weakMapView: MapView?
private var exampleControllerRemovedExpectation: XCTestExpectation?
Expand All @@ -13,40 +13,46 @@ class TestableExampleTests: XCTestCase {
let newTestSuite = XCTestSuite(forTestCaseClass: TestableExampleTests.self)

guard let method = class_getInstanceMethod(Self.self, #selector(runExample)) else {
fatalError()
fatalError("Cannot find method 'runExample'")
}

let existingImpl = method_getImplementation(method)

for category in Examples.all {
// swiftlint:disable:next force_cast
for example in category["examples"] as! [Example] {
// Add a method for this test, but using the same implementation
let selectorName = "test\(example.type)"
let testSelector = Selector((selectorName))
class_addMethod(Self.self, testSelector, existingImpl, "v@:f")

let test = TestableExampleTests(selector: testSelector)
test.example = example
newTestSuite.addTest(test)
}
for example in Examples.all.flatMap(\.examples) {
// Add a method for this test, but using the same implementation
let selectorName = "test\(example.type)"
let testSelector = Selector((selectorName))
class_addMethod(Self.self, testSelector, existingImpl, "v@:f")

let test = TestableExampleTests(selector: testSelector)
test.example = example
print("Adding test for \(example)")
newTestSuite.addTest(test)
}

return newTestSuite
}

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

// check for the example view controller and its mapview leaking
XCTAssertNil(weakExampleViewController)
XCTAssertNil(weakMapView)
// this check may also fail when finish is called earlier than all stored async operation that cpature self were completed and it will lead to delayed deinitialization
XCTAssertNil(weakExampleViewController, "Example viewController is part of a memory leak")
XCTAssertNil(weakMapView, "Example mapView is part of a memory leak")
}

@objc private func runExample() {
guard let navigationController = UIApplication.shared.windows.first?.rootViewController as? UINavigationController else {
XCTFail("Root controller is not a UINavigationController")
return
}

// example can be nil, when the test is repeated, because test runner uses default `init(selector:)` and cannot set example property to the test
guard let example else {
return XCTFail("Currently selected example is nil")
}

navigationController.delegate = self

let exampleViewController = example.makeViewController()
Expand All @@ -60,7 +66,7 @@ class TestableExampleTests: XCTestCase {
let result = XCTWaiter().wait(for: [expectation], timeout: example.testTimeout)
switch result {
case .completed:
break
print("Example: \(example.title) completed.")
case .timedOut:
XCTFail("Example: \(example.title) timed out. Don't forget to call finish().")
default:
Expand Down

0 comments on commit 95d0c80

Please sign in to comment.