mirror of
https://github.com/samsonjs/NotificationSmuggler.git
synced 2026-04-27 14:57:38 +00:00
Add tests
This commit is contained in:
parent
ba2c16d298
commit
59effef18f
4 changed files with 169 additions and 9 deletions
|
|
@ -14,8 +14,11 @@ tktk
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
Honestly you should probably just copy [BetterNotification.swift]() into your project.
|
||||||
|
|
||||||
The only way to install this package is with Swift Package Manager (SPM). Please [file a new issue][] or submit a pull-request if you want to use something else.
|
The only way to install this package is with Swift Package Manager (SPM). Please [file a new issue][] or submit a pull-request if you want to use something else.
|
||||||
|
|
||||||
|
[BetterNotification.swift]: https://github.com/samsonjs/BetterNotification/blob/main/Sources/BetterNotification/BetterNotification.swift
|
||||||
[file a new issue]: https://github.com/samsonjs/BetterNotification/issues/new
|
[file a new issue]: https://github.com/samsonjs/BetterNotification/issues/new
|
||||||
|
|
||||||
### Supported Platforms
|
### Supported Platforms
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ public extension BetterNotification {
|
||||||
static var userInfoKey: String { notificationName.rawValue }
|
static var userInfoKey: String { notificationName.rawValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -
|
||||||
|
|
||||||
public extension Notification {
|
public extension Notification {
|
||||||
/// Creates a `Notification` instance for the given `BetterNotification` value.
|
/// Creates a `Notification` instance for the given `BetterNotification` value.
|
||||||
///
|
///
|
||||||
|
|
@ -41,8 +43,26 @@ public extension Notification {
|
||||||
userInfo: [BN.userInfoKey: betterNotification]
|
userInfo: [BN.userInfoKey: betterNotification]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extracts a `BetterNotification` value of the specified type from this `Notification`'s `userInfo`.
|
||||||
|
///
|
||||||
|
/// - Returns: The extracted `BetterNotification` value when found, otherwise `nil`.
|
||||||
|
func better<BN: BetterNotification>() -> BN? {
|
||||||
|
guard let object = userInfo?[BN.userInfoKey] else {
|
||||||
|
NSLog("[\(BN.self)] Object missing from userInfo[\"\(BN.userInfoKey)\"]")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let betterNotification = object as? BN else {
|
||||||
|
NSLog("[\(BN.self)] Failed to cast \(object) to \(BN.self)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return betterNotification
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -
|
||||||
|
|
||||||
public extension NotificationCenter {
|
public extension NotificationCenter {
|
||||||
/// Returns an `AsyncSequence` of notifications of a specific `BetterNotification` type.
|
/// Returns an `AsyncSequence` of notifications of a specific `BetterNotification` type.
|
||||||
///
|
///
|
||||||
|
|
@ -54,18 +74,18 @@ public extension NotificationCenter {
|
||||||
for betterType: BN.Type
|
for betterType: BN.Type
|
||||||
) -> any AsyncSequence<BN, Never> {
|
) -> any AsyncSequence<BN, Never> {
|
||||||
notifications(named: BN.notificationName)
|
notifications(named: BN.notificationName)
|
||||||
.compactMap { $0.userInfo?[BN.userInfoKey] as? BN }
|
.compactMap { $0.better() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a Combine publisher that emits `BetterNotification` values of a specific type.
|
/// Returns a Combine publisher that emits `BetterNotification` values of the given type.
|
||||||
///
|
///
|
||||||
/// - Parameter betterType: The `BetterNotification` type to observe.
|
/// - Parameter betterType: The `BetterNotification` type to observe.
|
||||||
/// - Returns: A publisher emitting `BetterNotification` values.
|
/// - Returns: A publisher emitting `BetterNotification` values.
|
||||||
func publisher<T: BetterNotification>(
|
func publisher<BN: BetterNotification>(
|
||||||
for betterType: T.Type
|
for betterType: BN.Type
|
||||||
) -> AnyPublisher<T, Never> {
|
) -> AnyPublisher<BN, Never> {
|
||||||
publisher(for: betterType.notificationName)
|
publisher(for: betterType.notificationName)
|
||||||
.compactMap { $0.userInfo?[T.userInfoKey] as? T }
|
.compactMap { $0.better() }
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,138 @@
|
||||||
import Testing
|
|
||||||
@testable import BetterNotification
|
@testable import BetterNotification
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
import Testing
|
||||||
|
|
||||||
@Test func example() async throws {
|
struct BetterNotificationTests {
|
||||||
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
|
@Test func notificationName() {
|
||||||
|
#expect(HitchhikersNotification.notificationName.rawValue == "BetterNotification:HitchhikersNotification")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func userInfoKey() {
|
||||||
|
#expect(HitchhikersNotification.userInfoKey == "BetterNotification:HitchhikersNotification")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Notification extensions
|
||||||
|
|
||||||
|
@Test func buildNotification() {
|
||||||
|
let hitchhikers = HitchhikersNotification(answer: 42)
|
||||||
|
let notification = Notification.better(hitchhikers)
|
||||||
|
#expect(notification.name == HitchhikersNotification.notificationName)
|
||||||
|
#expect(notification.userInfo?.count == 1)
|
||||||
|
let key = HitchhikersNotification.userInfoKey
|
||||||
|
let userInfoValue = notification.userInfo?[key] as? HitchhikersNotification
|
||||||
|
#expect(userInfoValue == hitchhikers)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func extractBetterPayload() {
|
||||||
|
let hitchhikers = HitchhikersNotification(answer: 42)
|
||||||
|
let notification = Notification.better(hitchhikers)
|
||||||
|
let extracted: HitchhikersNotification? = notification.better()
|
||||||
|
#expect(extracted == hitchhikers)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func extractBetterPayloadFailsOnWrongType() {
|
||||||
|
let imposter = Notification(
|
||||||
|
name: HitchhikersNotification.notificationName,
|
||||||
|
object: nil,
|
||||||
|
userInfo: [HitchhikersNotification.userInfoKey: "imposter"]
|
||||||
|
)
|
||||||
|
let extracted: HitchhikersNotification? = imposter.better()
|
||||||
|
#expect(extracted == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func extractBetterPayloadFailsOnMissingPayload() {
|
||||||
|
let wrongNotification = Notification(name: HitchhikersNotification.notificationName)
|
||||||
|
let extracted: HitchhikersNotification? = wrongNotification.better()
|
||||||
|
#expect(extracted == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: NotificationCenter extensions
|
||||||
|
|
||||||
|
// It's important that the tests and the notification-observing tasks are not on the same actor,
|
||||||
|
// so we make the tests @MainActor and observe notifications on another actor. Otherwise it's
|
||||||
|
// a deadlock.
|
||||||
|
|
||||||
|
@Test(.timeLimit(.minutes(1)))
|
||||||
|
@MainActor func notificationCenterAsyncSequence() async throws {
|
||||||
|
let center = NotificationCenter()
|
||||||
|
nonisolated(unsafe) var received = false
|
||||||
|
Task {
|
||||||
|
for await hitchhikers in center.notifications(for: HitchhikersNotification.self) {
|
||||||
|
#expect(hitchhikers.answer == 42)
|
||||||
|
received = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Task.yield()
|
||||||
|
|
||||||
|
let hitchhikers = HitchhikersNotification(answer: 42)
|
||||||
|
center.post(.better(hitchhikers))
|
||||||
|
while !received { await Task.yield() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(.timeLimit(.minutes(1)))
|
||||||
|
@MainActor func notificationCenterAsyncSequenceIgnoresInvalidPayloads() async throws {
|
||||||
|
let center = NotificationCenter()
|
||||||
|
nonisolated(unsafe) var received = false
|
||||||
|
let task = Task {
|
||||||
|
for await _ in center.notifications(for: HitchhikersNotification.self) {
|
||||||
|
received = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer { task.cancel() }
|
||||||
|
await Task.yield()
|
||||||
|
|
||||||
|
// Post an invalid one that should be ignored.
|
||||||
|
let imposter = Notification(
|
||||||
|
name: HitchhikersNotification.notificationName,
|
||||||
|
object: nil,
|
||||||
|
userInfo: [HitchhikersNotification.userInfoKey: "imposter"]
|
||||||
|
)
|
||||||
|
center.post(imposter)
|
||||||
|
|
||||||
|
try await Task.sleep(for: .milliseconds(10))
|
||||||
|
#expect(!received)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(.timeLimit(.minutes(1)))
|
||||||
|
@MainActor func notificationCenterPublisher() async {
|
||||||
|
var cancellables = Set<AnyCancellable>()
|
||||||
|
let center = NotificationCenter()
|
||||||
|
nonisolated(unsafe) var received = false
|
||||||
|
center.publisher(for: HitchhikersNotification.self)
|
||||||
|
.sink { hitchhikers in
|
||||||
|
#expect(hitchhikers.answer == 42)
|
||||||
|
received = true
|
||||||
|
}.store(in: &cancellables)
|
||||||
|
await Task.yield()
|
||||||
|
|
||||||
|
let hitchhikers = HitchhikersNotification(answer: 42)
|
||||||
|
center.post(.better(hitchhikers))
|
||||||
|
while !received { await Task.yield() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(.timeLimit(.minutes(1)))
|
||||||
|
@MainActor func notificationCenterPublisherIgnoresInvalidPayloads() async throws {
|
||||||
|
var cancellables = Set<AnyCancellable>()
|
||||||
|
let center = NotificationCenter()
|
||||||
|
nonisolated(unsafe) var received = false
|
||||||
|
center.publisher(for: HitchhikersNotification.self)
|
||||||
|
.sink { hitchhikers in
|
||||||
|
#expect(hitchhikers.answer == 42)
|
||||||
|
received = true
|
||||||
|
}.store(in: &cancellables)
|
||||||
|
await Task.yield()
|
||||||
|
|
||||||
|
// Post an invalid one that should be ignored.
|
||||||
|
let imposter = Notification(
|
||||||
|
name: HitchhikersNotification.notificationName,
|
||||||
|
object: nil,
|
||||||
|
userInfo: [HitchhikersNotification.userInfoKey: "imposter"]
|
||||||
|
)
|
||||||
|
center.post(imposter)
|
||||||
|
|
||||||
|
try await Task.sleep(for: .milliseconds(10))
|
||||||
|
#expect(!received)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import BetterNotification
|
||||||
|
|
||||||
|
struct HitchhikersNotification: BetterNotification, Equatable {
|
||||||
|
let answer: Int
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue