Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/test-spm-codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ jobs:
run: xcrun llvm-cov export -format="lcov" .build/debug/${{ inputs.test_bundle }}.xctest/Contents/MacOS/${{ inputs.test_bundle }} -instr-profile .build/debug/codecov/default.profdata > coverage_report.lcov

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage_report.lcov
slug: NSStudent/EasyLinkSwiftSDK
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
[![Swift Package Manager](https://img.shields.io/badge/SwiftPM-compatible-brightgreen.svg)](https://www.swift.org/package-manager/)
[![Platforms](https://img.shields.io/badge/platforms-iOS%2016%20%7C%20macOS%2013-lightgrey.svg)](Package.swift)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Swift Package Index Swift Versions](https://img.shields.io/endpoint?url=https://swiftpackageindex.com/api/packages/NSStudent/EasyLinkSwiftSDK/badge?type=swift-versions)](https://swiftpackageindex.com/NSStudent/EasyLinkSwiftSDK)
[![Swift Package Index Platforms](https://img.shields.io/endpoint?url=https://swiftpackageindex.com/api/packages/NSStudent/EasyLinkSwiftSDK/badge?type=platforms)](https://swiftpackageindex.com/NSStudent/EasyLinkSwiftSDK)
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FNSStudent%2FEasyLinkSwiftSDK%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/NSStudent/EasyLinkSwiftSDK)
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FNSStudent%2FEasyLinkSwiftSDK%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/NSStudent/EasyLinkSwiftSDK)

`EasyLinkSwiftSDK` is a native Swift package for discovering and communicating with [Chessnut](https://www.chessnutech.com) electronic chessboards over Bluetooth Low Energy.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import EasyLinkSwiftSDK
import XCTest

final class CoreBluetoothEasyLinkTransportTests: XCTestCase {
func testWriteThrowsDisconnectedWhenNotConnected() async throws {
let transport = CoreBluetoothEasyLinkTransport(profile: .classic)

do {
try await transport.write([0x01, 0x02])
XCTFail("Expected disconnected error")
} catch EasyLinkError.disconnected {
} catch {
XCTFail("Unexpected error: \(error)")
}
}

func testDisconnectYieldsDisconnectedNotification() async {
let transport = CoreBluetoothEasyLinkTransport(profile: .move)
var iterator = transport.notifications.makeAsyncIterator()

await transport.disconnect()

let notification = await iterator.next()
XCTAssertEqual(notification, .disconnected)
}

func testDisconnectWhenNeverConnectedCompletes() async {
let transport = CoreBluetoothEasyLinkTransport(profile: .classic)
await transport.disconnect()
}

func testWriteAfterDisconnectThrowsDisconnected() async throws {
let transport = CoreBluetoothEasyLinkTransport(profile: .move)
await transport.disconnect()

do {
try await transport.write([0x21, 0x01, 0x00])
XCTFail("Expected disconnected error")
} catch EasyLinkError.disconnected {
} catch {
XCTFail("Unexpected error: \(error)")
}
}

func testInitWithDeviceID() {
let id = UUID()
let transport = CoreBluetoothEasyLinkTransport(profile: .move, deviceID: id)
XCTAssertNotNil(transport)
}

func testInitWithoutDeviceID() {
let transport = CoreBluetoothEasyLinkTransport(profile: .classic)
XCTAssertNotNil(transport)
}

func testConnectCancellationDoesNotHang() async {
let transport = CoreBluetoothEasyLinkTransport(profile: .classic)

let task = Task<Void, Error> {
try await transport.connect()
}

try? await Task.sleep(nanoseconds: 50_000_000)
task.cancel()

do {
try await task.value
} catch is CancellationError {
} catch is EasyLinkError {
} catch {
XCTFail("Unexpected error type: \(error)")
}
}

func testMultipleDisconnectsDoNotCrash() async {
let transport = CoreBluetoothEasyLinkTransport(profile: .classic)
await transport.disconnect()
await transport.disconnect()
}
}
20 changes: 20 additions & 0 deletions Tests/EasyLinkSwiftSDKTests/EasyLinkScannerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import EasyLinkSwiftSDK
import XCTest

final class EasyLinkScannerTests: XCTestCase {
func testScanImmediateCancellationDoesNotCrash() async {
let task = Task {
for await _ in EasyLinkScanner.scan(profile: .classic) {}
}
task.cancel()
await task.value
}

func testScanCancellationForMoveProfileDoesNotCrash() async {
let task = Task {
for await _ in EasyLinkScanner.scan(profile: .move) {}
}
task.cancel()
await task.value
}
}
43 changes: 43 additions & 0 deletions Tests/EasyLinkSwiftSDKTests/ProtocolConstantsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@testable import EasyLinkSwiftSDK
import XCTest

final class ProtocolConstantsTests: XCTestCase {
func testClassicBatteryCommand() {
XCTAssertEqual(BoardProfile.classic.batteryCommand, [0x29, 0x01, 0x00])
}

func testMoveBatteryCommand() {
XCTAssertEqual(BoardProfile.move.batteryCommand, [0x41, 0x01, 0x0C])
}

func testEnableRealtimeModeCommand() {
XCTAssertEqual(ProtocolConstants.enableRealtimeMode, [0x21, 0x01, 0x00])
}

func testClassicMatchesChessnutBoardNames() {
XCTAssertTrue(BoardProfile.classic.matchesPeripheralName("Chessnut Air"))
XCTAssertTrue(BoardProfile.classic.matchesPeripheralName("Chessnut Air+"))
XCTAssertTrue(BoardProfile.classic.matchesPeripheralName("Chessnut Pro"))
XCTAssertTrue(BoardProfile.classic.matchesPeripheralName("Chessnut Go"))
}

func testClassicDoesNotMatchChessnutMove() {
XCTAssertFalse(BoardProfile.classic.matchesPeripheralName("Chessnut Move"))
}

func testClassicDoesNotMatchUnrelatedNames() {
XCTAssertFalse(BoardProfile.classic.matchesPeripheralName("Unknown Device"))
XCTAssertFalse(BoardProfile.classic.matchesPeripheralName(""))
}

func testMoveMatchesExactName() {
XCTAssertTrue(BoardProfile.move.matchesPeripheralName("Chessnut Move"))
}

func testMoveDoesNotMatchOtherBoardNames() {
XCTAssertFalse(BoardProfile.move.matchesPeripheralName("Chessnut Air"))
XCTAssertFalse(BoardProfile.move.matchesPeripheralName("Chessnut Pro"))
XCTAssertFalse(BoardProfile.move.matchesPeripheralName("Chessnut"))
XCTAssertFalse(BoardProfile.move.matchesPeripheralName(""))
}
}
Loading