import XCTest import Foundation #if SSK_LEGACY @testable import SwiftSignalKitLegacy #else @testable import SwiftSignalKit2 #endif final class CoreSignalTests: XCTestCase { private func record(_ signal: Signal, into log: CoreEventLog) -> Disposable { return signal.start(next: { value in log.append("next \(value)") }, error: { error in log.append("error \(error)") }, completed: { log.append("completed") }) } func testSingleDeliversValueThenCompletion() { let log = CoreEventLog() let handle = self.record(Signal.single(7), into: log) XCTAssertEqual(log.events, ["next 7", "completed"]) handle.dispose() XCTAssertEqual(log.events, ["next 7", "completed"]) } func testCompleteDeliversOnlyCompletion() { let log = CoreEventLog() let handle = self.record(Signal.complete(), into: log) XCTAssertEqual(log.events, ["completed"]) handle.dispose() } func testFailDeliversOnlyError() { let log = CoreEventLog() let handle = self.record(Signal.fail("boom"), into: log) XCTAssertEqual(log.events, ["error boom"]) handle.dispose() XCTAssertEqual(log.events, ["error boom"]) } func testNeverDeliversNothing() { let log = CoreEventLog() let handle = self.record(Signal.never(), into: log) XCTAssertEqual(log.events, []) handle.dispose() XCTAssertEqual(log.events, []) } func testFreeSingleFunction() { let log = CoreEventLog() let signal: Signal = single("a", NoError.self) let handle = self.record(signal, into: log) XCTAssertEqual(log.events, ["next a", "completed"]) handle.dispose() } func testFreeFailFunction() { let log = CoreEventLog() let signal: Signal = fail(Int.self, "bad") let handle = self.record(signal, into: log) XCTAssertEqual(log.events, ["error bad"]) handle.dispose() } func testFreeCompleteFunction() { let log = CoreEventLog() let signal: Signal = complete(Int.self, String.self) let handle = self.record(signal, into: log) XCTAssertEqual(log.events, ["completed"]) handle.dispose() } func testFreeNeverFunction() { let log = CoreEventLog() let signal: Signal = never(Int.self, String.self) let handle = self.record(signal, into: log) XCTAssertEqual(log.events, []) handle.dispose() XCTAssertEqual(log.events, []) } func testStartWithoutCallbacksDoesNotCrash() { Signal.single(1).start().dispose() Signal.fail("e").start().dispose() Signal.complete().start().dispose() Signal.never().start().dispose() Signal.single(1).startStandalone().dispose() Signal.fail("e").startStrict().dispose() } func testGeneratorRunsSynchronouslyInsideStartOnCallerThread() { let log = CoreEventLog() let callerThread = Thread.current let signal = Signal { subscriber in log.append("generator sameThread=\(Thread.current == callerThread)") subscriber.putNext(1) log.append("generator end") return EmptyDisposable } log.append("before start") let handle = signal.start(next: { value in log.append("next \(value)") }) log.append("after start") XCTAssertEqual(log.events, ["before start", "generator sameThread=true", "next 1", "generator end", "after start"]) handle.dispose() } func testSignalIsColdAndRunsGeneratorOncePerStart() { let starts = CoreCounter() let signal = Signal { subscriber in let count = starts.increment() subscriber.putNext(count) return EmptyDisposable } let log = CoreEventLog() XCTAssertEqual(starts.value, 0) let first = self.record(signal, into: log) let second = self.record(signal, into: log) let third = signal.startStandalone(next: { value in log.append("standalone \(value)") }) let fourth = signal.startStrict(next: { value in log.append("strict \(value)") }) XCTAssertEqual(starts.value, 4) XCTAssertEqual(log.events, ["next 1", "next 2", "standalone 3", "strict 4"]) first.dispose() second.dispose() third.dispose() fourth.dispose() } func testStartStandaloneHasSameSemanticsAsStart() { let log = CoreEventLog() let holder = CoreSubscriberHolder() let inner = CoreCountingDisposable(log: log) let handle = Signal { subscriber in holder.subscriber = subscriber return inner }.startStandalone(next: { value in log.append("next \(value)") }, error: { error in log.append("error \(error)") }, completed: { log.append("completed") }) holder.subscriber?.putNext(1) holder.subscriber?.putCompletion() holder.subscriber?.putNext(2) handle.dispose() XCTAssertEqual(log.events, ["next 1", "completed", "dispose"]) XCTAssertEqual(inner.disposeCount, 1) } func testStartStrictReturnsStrictDisposableThatForwardsDispose() { let log = CoreEventLog() let inner = CoreCountingDisposable(log: log) let handle = Signal { subscriber in subscriber.putNext(3) return inner }.startStrict(next: { value in log.append("next \(value)") }) XCTAssertTrue(handle is StrictDisposable) XCTAssertEqual(log.events, ["next 3"]) handle.dispose() XCTAssertEqual(log.events, ["next 3", "dispose"]) XCTAssertEqual(inner.disposeCount, 1) } func testStartStrictOnSynchronouslyCompletedSignal() { let log = CoreEventLog() let handle = self.recordStrict(Signal.single(1), into: log) XCTAssertEqual(log.events, ["next 1", "completed"]) handle.dispose() XCTAssertEqual(log.events, ["next 1", "completed"]) } private func recordStrict(_ signal: Signal, into log: CoreEventLog) -> Disposable { return signal.startStrict(next: { value in log.append("next \(value)") }, error: { error in log.append("error \(error)") }, completed: { log.append("completed") }) } func testPipeOperatorAppliesFunction() { let result = 3 |> { $0 * 2 } XCTAssertEqual(result, 6) let chained = 1 |> { $0 + 1 } |> { $0 * 10 } XCTAssertEqual(chained, 20) } func testIdentityReturnsArgument() { XCTAssertEqual(identity(a: 5), 5) let object = CoreObject() XCTAssertTrue(identity(a: object) === object) } @available(macOS 10.15, iOS 13.0, *) func testGetAsyncReturnsSynchronousValue() async { let value = await Signal.single(42).get() XCTAssertEqual(value, 42) } @available(macOS 10.15, iOS 13.0, *) func testGetAsyncReturnsFirstOfSeveralValues() async { let signal = Signal { subscriber in subscriber.putNext(1) subscriber.putNext(2) subscriber.putNext(3) subscriber.putCompletion() return EmptyDisposable } let value = await signal.get() XCTAssertEqual(value, 1) } @available(macOS 10.15, iOS 13.0, *) func testGetAsyncReturnsValueEmittedLaterOnAnotherQueue() async { let queue = Queue(name: "CoreSignalTests.get") let signal = Signal { subscriber in queue.after(0.02) { subscriber.putNext("late") subscriber.putNext("ignored") } return EmptyDisposable } let value = await signal.get() XCTAssertEqual(value, "late") } }