Skip to content

Commit 1562143

Browse files
authored
Merge pull request #24 from NordicSemiconductor/develop
Version 0.11.1
2 parents b9baa3a + 05ee6a7 commit 1562143

File tree

16 files changed

+267
-44
lines changed

16 files changed

+267
-44
lines changed

CoreBluetoothMock.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'CoreBluetoothMock'
3-
s.version = '0.11.0'
3+
s.version = '0.11.1'
44
s.summary = 'Mocking library for CoreBluetooth.'
55

66
s.description = <<-DESC

CoreBluetoothMock/Classes/CBMCentralManagerMock.swift

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ public class CBMCentralManagerMock: NSObject, CBMCentralManager {
236236
if proximity == .outOfRange {
237237
self.peripheral(peripheral,
238238
didDisconnectWithError: CBMError(.connectionTimeout))
239+
} else {
240+
self.peripheralBecameAvailable(peripheral)
239241
}
240242
}
241243

@@ -320,6 +322,32 @@ public class CBMCentralManagerMock: NSObject, CBMCentralManager {
320322
// TODO: notify a user registered for connection events
321323
}
322324

325+
/// Method called when a peripheral becomes available (in range).
326+
/// If there is a pending connection request, it will connect.
327+
/// - Parameter peripheral: The peripheral that came in range.
328+
internal static func peripheralBecameAvailable(_ peripheral: CBMPeripheralSpec) {
329+
// Is the peripheral simulated?
330+
guard peripherals.contains(peripheral) else {
331+
return
332+
}
333+
managers
334+
.compactMap { $0.ref }
335+
.forEach { manager in
336+
if let target = manager.peripherals[peripheral.identifier],
337+
target.state == .connecting {
338+
target.connect() { result in
339+
switch result {
340+
case .success:
341+
manager.delegate?.centralManager(manager, didConnect: target)
342+
case .failure(let error):
343+
manager.delegate?.centralManager(manager, didFailToConnect: target,
344+
error: error)
345+
}
346+
}
347+
}
348+
}
349+
}
350+
323351
/// Simulates the peripheral to disconnect from the device.
324352
/// All connected mock central managers will receive
325353
/// `peripheral(:didDisconnected:error)` callback.
@@ -477,7 +505,7 @@ public class CBMCentralManagerMock: NSObject, CBMCentralManager {
477505
guard peripherals.values.contains(mock) else {
478506
return
479507
}
480-
mock.connect { result in
508+
mock.connect() { result in
481509
switch result {
482510
case .success:
483511
self.delegate?.centralManager(self, didConnect: mock)
@@ -667,30 +695,47 @@ public class CBMPeripheralMock: CBMPeer, CBMPeripheral {
667695
// MARK: Connection
668696

669697
fileprivate func connect(completion: @escaping (Result<Void, Error>) -> ()) {
670-
// Ensure the device is connectable and disconnected.
698+
// Ensure the device is disconnected.
699+
guard state == .disconnected || state == .connecting else {
700+
return
701+
}
702+
// Connection is pending.
703+
state = .connecting
704+
// Ensure the device is connectable and in range.
671705
guard let delegate = mock.connectionDelegate,
672706
let interval = mock.connectionInterval,
673-
state == .disconnected else {
707+
mock.proximity != .outOfRange else {
708+
// There's no timeout on iOS. The device will connect when brought back
709+
// into range. To cancel pending connection, call disconnect().
674710
return
675711
}
676-
state = .connecting
677712
let result = delegate.peripheralDidReceiveConnectionRequest(mock)
678713
queue.asyncAfter(deadline: .now() + interval) { [weak self] in
679714
if let self = self, self.state == .connecting {
680715
if case .success = result {
681716
self.state = .connected
682717
self._canSendWriteWithoutResponse = true
683718
self.mock.virtualConnections += 1
719+
} else {
720+
self.state = .disconnected
684721
}
685722
completion(result)
686723
}
687724
}
688725
}
689726

690727
fileprivate func disconnect(completion: @escaping () -> ()) {
691-
// Ensure the device is connected.
728+
// Cancel pending connection.
729+
guard state != .connecting else {
730+
state = .disconnected
731+
queue.async {
732+
completion()
733+
}
734+
return
735+
}
736+
// Ensure the device is connectable and connected.
692737
guard let interval = mock.connectionInterval,
693-
state == .connected || state == .connecting else {
738+
state == .connected else {
694739
return
695740
}
696741
if #available(iOS 9.0, *), case .connected = state {

CoreBluetoothMock/Classes/CBMPeripheralSpec.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ public class CBMPeripheralSpec {
149149
///
150150
/// All connected mock central managers will receive
151151
/// `peripheral(:didDisconnected:error)` callback.
152-
/// - Parameter error: The disconnection reason. Use `CBError` or
153-
/// `CBATTError` errors.
152+
/// - Parameter error: The disconnection reason. Use `CBMError` or
153+
/// `CBMATTError` errors.
154154
public func simulateDisconnection(withError error: Error = CBMError(.peripheralDisconnected)) {
155155
CBMCentralManagerMock.peripheral(self, didDisconnectWithError: error)
156156
}

Example/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
PODS:
2-
- CoreBluetoothMock (0.11.0)
2+
- CoreBluetoothMock (0.11.1)
33

44
DEPENDENCIES:
55
- CoreBluetoothMock (from `../`)
@@ -9,7 +9,7 @@ EXTERNAL SOURCES:
99
:path: "../"
1010

1111
SPEC CHECKSUMS:
12-
CoreBluetoothMock: 158792f5f41671d5c61b882c38e804f35e0b9339
12+
CoreBluetoothMock: f201b575c3eb3f0753124d088595c6fe2d6dceec
1313

1414
PODFILE CHECKSUM: bfd9fc7193b211c18bbe632884c30bc5d6a4807c
1515

Example/Pods/Local Podspecs/CoreBluetoothMock.podspec.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Example/Pods/Manifest.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Example/Pods/Target Support Files/CoreBluetoothMock/CoreBluetoothMock-Info.plist

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright (c) 2020, Nordic Semiconductor
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without modification,
6+
* are permitted provided that the following conditions are met:
7+
*
8+
* 1. Redistributions of source code must retain the above copyright notice, this
9+
* list of conditions and the following disclaimer.
10+
*
11+
* 2. Redistributions in binary form must reproduce the above copyright notice, this
12+
* list of conditions and the following disclaimer in the documentation and/or
13+
* other materials provided with the distribution.
14+
*
15+
* 3. Neither the name of the copyright holder nor the names of its contributors may
16+
* be used to endorse or promote products derived from this software without
17+
* specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22+
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23+
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24+
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25+
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26+
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
* POSSIBILITY OF SUCH DAMAGE.
29+
*/
30+
31+
32+
import XCTest
33+
@testable import nRF_Blinky
34+
@testable import CoreBluetoothMock
35+
36+
/// This test simulates a scenario when a device failed to connect for some reason.
37+
///
38+
/// It is using the app and testing it by sending notifications that trigger different
39+
/// actions.
40+
class FailedConnectionTest: XCTestCase {
41+
42+
override func setUp() {
43+
// This method is called AFTER ScannerTableViewController.viewDidLoad()
44+
// where the BlinkyManager is instantiated. A separate mock manager
45+
// is not created in this test.
46+
// Initially mock Bluetooth adapter is powered Off.
47+
CBMCentralManagerMock.simulatePeripherals([blinky, hrm, thingy])
48+
CBMCentralManagerMock.simulateInitialState(.poweredOn)
49+
}
50+
51+
override func tearDown() {
52+
// We can't call CBMCentralManagerMock.tearDownSimulation() here.
53+
// That would invalidate the BlinkyManager in ScannerTableViewController.
54+
// The central manager must be reused, so let's just power mock off,
55+
// which will allow us to set different set of peripherals in another test.
56+
CBMCentralManagerMock.simulatePowerOff()
57+
}
58+
59+
func testScanningBlinky() {
60+
// Set up the devices in range.
61+
blinky.simulateProximityChange(.immediate)
62+
hrm.simulateProximityChange(.near)
63+
thingy.simulateProximityChange(.far)
64+
// Reset the blinky.
65+
blinky.simulateReset()
66+
67+
// Wait until the blinky is found.
68+
var target: BlinkyPeripheral?
69+
let found = XCTestExpectation(description: "Device found")
70+
Sim.onBlinkyDiscovery { blinky in
71+
XCTAssertEqual(blinky.advertisedName, "nRF Blinky")
72+
XCTAssert(blinky.isConnectable == true)
73+
XCTAssert(blinky.isConnected == false)
74+
XCTAssertGreaterThanOrEqual(blinky.RSSI.intValue, -70 - 15)
75+
XCTAssertLessThanOrEqual(blinky.RSSI.intValue, -70 + 15)
76+
target = blinky
77+
found.fulfill()
78+
}
79+
wait(for: [found], timeout: 3)
80+
XCTAssertNotNil(target, "nRF Blinky not found. Make sure you run the test on a simulator.")
81+
if target == nil {
82+
// Going further would cause a crash.
83+
return
84+
}
85+
86+
// Let's move Blinky out of range.
87+
blinky.simulateProximityChange(.outOfRange)
88+
89+
// Select found device.
90+
Sim.post(.selectPeripheral(at: 0))
91+
92+
// Wait until blinky is connected and ready.
93+
let connected = XCTestExpectation(description: "Connected")
94+
connected.isInverted = true
95+
target!.onConnected {
96+
connected.fulfill()
97+
}
98+
wait(for: [connected], timeout: 3)
99+
100+
let appDelegate = UIApplication.shared.delegate as! AppDelegate
101+
let navigationController = appDelegate.window!.rootViewController as! UINavigationController
102+
navigationController.popViewController(animated: true)
103+
}
104+
105+
}

Example/Tests/NormalBehaviorTest.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ class NormalBehaviorTest: XCTestCase {
138138

139139
// Simulate graceful disconnect.
140140
let disconnection = XCTestExpectation(description: "Disconnection")
141-
target!.onDisconnected {
141+
target!.onDisconnected { error in
142+
XCTAssertEqual((error as? CBMError)?.code, CBError.peripheralDisconnected)
142143
disconnection.fulfill()
143144
}
144145
blinky.simulateDisconnection()

Example/Tests/ResetTest.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ class ResetTest: XCTestCase {
123123

124124
// Simulate reset and button press afterwards.
125125
let reset = XCTestExpectation(description: "Reset")
126-
target!.onDisconnected {
126+
target!.onDisconnected { error in
127+
XCTAssertEqual((error as? CBMError)?.code, CBError.connectionTimeout)
127128
reset.fulfill()
128129
}
129130
blinky.simulateReset()

Example/nRFBlinky.xcodeproj/project.pbxproj

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
/* Begin PBXBuildFile section */
1010
2DBB24A841D22CDDB14787B6 /* BlinkyPeripheralEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DBB2D0A6C475A3BA45D6FAA /* BlinkyPeripheralEvents.swift */; };
11-
2DBB258568F7E07E0DFA3B4E /* EventInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DBB2F5ACA6DCE530B0DDE90 /* EventInterceptor.swift */; };
11+
2DBB258568F7E07E0DFA3B4E /* EventHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DBB2F5ACA6DCE530B0DDE90 /* EventHelper.swift */; };
1212
2DBB2A694C3D00999D8B3E29 /* BlinkyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DBB27B357E95965525A70A5 /* BlinkyManager.swift */; };
1313
2DBB2D9D87AA009D95C10DEE /* ResetTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DBB22CFA82E35CBA3BB326F /* ResetTest.swift */; };
1414
2DBB2F44478EB7F4C0DF3AA4 /* BlinkyManagerEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DBB27ABD18F3DAECC075CF2 /* BlinkyManagerEvents.swift */; };
@@ -28,6 +28,7 @@
2828
52B537AF23FBE84900372948 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 52B5379223FBE84900372948 /* LaunchScreen.storyboard */; };
2929
52B537B023FBE84900372948 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 52B5379423FBE84900372948 /* Main.storyboard */; };
3030
52B537B223FBE85100372948 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B537B123FBE85100372948 /* AppDelegate.swift */; };
31+
52E905CE24DD35A100BB5740 /* FailedConnectionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52E905CD24DD35A100BB5740 /* FailedConnectionTest.swift */; };
3132
607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; };
3233
7B2BC15014F59F63362D632E /* Pods_nRFBlinky.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7D451FCAEA31E307D873AF9E /* Pods_nRFBlinky.framework */; };
3334
9C3DAF3BDCE839F729AB18D4 /* Pods_nRFBlinky_nRFBlinky_UITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FAE5CCCE10D0C6B6720ACAB6 /* Pods_nRFBlinky_nRFBlinky_UITests.framework */; };
@@ -56,7 +57,7 @@
5657
2DBB27ABD18F3DAECC075CF2 /* BlinkyManagerEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlinkyManagerEvents.swift; sourceTree = "<group>"; };
5758
2DBB27B357E95965525A70A5 /* BlinkyManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlinkyManager.swift; sourceTree = "<group>"; };
5859
2DBB2D0A6C475A3BA45D6FAA /* BlinkyPeripheralEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlinkyPeripheralEvents.swift; sourceTree = "<group>"; };
59-
2DBB2F5ACA6DCE530B0DDE90 /* EventInterceptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventInterceptor.swift; sourceTree = "<group>"; };
60+
2DBB2F5ACA6DCE530B0DDE90 /* EventHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventHelper.swift; sourceTree = "<group>"; };
6061
453BB604F04AB8D6B8030CE1 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
6162
526EA53A240D2F8100BF70B2 /* nRFBlinky_UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = nRFBlinky_UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
6263
526EA53C240D2F8100BF70B2 /* UITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITests.swift; sourceTree = "<group>"; };
@@ -109,6 +110,7 @@
109110
52B537A323FBE84900372948 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = "<group>"; };
110111
52B537B123FBE85100372948 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
111112
52B537D723FC129400372948 /* MockPeripherals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPeripherals.swift; sourceTree = "<group>"; };
113+
52E905CD24DD35A100BB5740 /* FailedConnectionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedConnectionTest.swift; sourceTree = "<group>"; };
112114
593E9FFC1917DE0521FBC97F /* Pods-nRFBlinky_UITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-nRFBlinky_UITests.debug.xcconfig"; path = "Target Support Files/Pods-nRFBlinky_UITests/Pods-nRFBlinky_UITests.debug.xcconfig"; sourceTree = "<group>"; };
113115
607FACD01AFB9204008FA782 /* nRF Blinky.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "nRF Blinky.app"; sourceTree = BUILT_PRODUCTS_DIR; };
114116
607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -176,9 +178,10 @@
176178
isa = PBXGroup;
177179
children = (
178180
52A648F0240E83F000817F2F /* NormalBehaviorTest.swift */,
179-
52A648F8240E844700817F2F /* Supporting Files */,
180-
2DBB2F5ACA6DCE530B0DDE90 /* EventInterceptor.swift */,
181181
2DBB22CFA82E35CBA3BB326F /* ResetTest.swift */,
182+
52E905CD24DD35A100BB5740 /* FailedConnectionTest.swift */,
183+
2DBB2F5ACA6DCE530B0DDE90 /* EventHelper.swift */,
184+
52A648F8240E844700817F2F /* Supporting Files */,
182185
);
183186
path = Tests;
184187
sourceTree = "<group>";
@@ -606,7 +609,8 @@
606609
buildActionMask = 2147483647;
607610
files = (
608611
52A648F1240E83F000817F2F /* NormalBehaviorTest.swift in Sources */,
609-
2DBB258568F7E07E0DFA3B4E /* EventInterceptor.swift in Sources */,
612+
2DBB258568F7E07E0DFA3B4E /* EventHelper.swift in Sources */,
613+
52E905CE24DD35A100BB5740 /* FailedConnectionTest.swift in Sources */,
610614
2DBB2D9D87AA009D95C10DEE /* ResetTest.swift in Sources */,
611615
);
612616
runOnlyForDeploymentPostprocessing = 0;
@@ -939,13 +943,13 @@
939943
baseConfigurationReference = FDD05DB6B9B51CA381BE6886 /* Pods-nRFBlinky.debug.xcconfig */;
940944
buildSettings = {
941945
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
942-
CURRENT_PROJECT_VERSION = 2;
946+
CURRENT_PROJECT_VERSION = 1;
943947
DEVELOPMENT_TEAM = P3R8YQEV4L;
944948
GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
945949
INFOPLIST_FILE = nRFBlinky/Info.plist;
946950
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
947951
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
948-
MARKETING_VERSION = 1.3.0;
952+
MARKETING_VERSION = 1.3.1;
949953
MODULE_NAME = ExampleApp;
950954
PRODUCT_BUNDLE_IDENTIFIER = "com.nordicsemi.nrf-blinky";
951955
PRODUCT_NAME = "nRF Blinky";
@@ -960,12 +964,12 @@
960964
baseConfigurationReference = E20CFB7929AACA783363B9F4 /* Pods-nRFBlinky.release.xcconfig */;
961965
buildSettings = {
962966
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
963-
CURRENT_PROJECT_VERSION = 2;
967+
CURRENT_PROJECT_VERSION = 1;
964968
DEVELOPMENT_TEAM = P3R8YQEV4L;
965969
INFOPLIST_FILE = nRFBlinky/Info.plist;
966970
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
967971
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
968-
MARKETING_VERSION = 1.3.0;
972+
MARKETING_VERSION = 1.3.1;
969973
MODULE_NAME = ExampleApp;
970974
PRODUCT_BUNDLE_IDENTIFIER = "com.nordicsemi.nrf-blinky";
971975
PRODUCT_NAME = "nRF Blinky";

0 commit comments

Comments
 (0)