| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733 |
- //
- // PropertySpec.swift
- // ReactiveCocoa
- //
- // Created by Justin Spahr-Summers on 2015-01-23.
- // Copyright (c) 2015 GitHub. All rights reserved.
- //
- import Result
- import Nimble
- import Quick
- import ReactiveCocoa
- private let initialPropertyValue = "InitialValue"
- private let subsequentPropertyValue = "SubsequentValue"
- private let finalPropertyValue = "FinalValue"
- class PropertySpec: QuickSpec {
- override func spec() {
- describe("ConstantProperty") {
- it("should have the value given at initialization") {
- let constantProperty = ConstantProperty(initialPropertyValue)
- expect(constantProperty.value) == initialPropertyValue
- }
- it("should yield a signal that interrupts observers without emitting any value.") {
- let constantProperty = ConstantProperty(initialPropertyValue)
- var signalInterrupted = false
- var hasUnexpectedEventsEmitted = false
- constantProperty.signal.observe { event in
- switch event {
- case .Interrupted:
- signalInterrupted = true
- case .Next, .Failed, .Completed:
- hasUnexpectedEventsEmitted = true
- }
- }
- expect(signalInterrupted) == true
- expect(hasUnexpectedEventsEmitted) == false
- }
- it("should yield a producer that sends the current value then completes") {
- let constantProperty = ConstantProperty(initialPropertyValue)
- var sentValue: String?
- var signalCompleted = false
- constantProperty.producer.start { event in
- switch event {
- case let .Next(value):
- sentValue = value
- case .Completed:
- signalCompleted = true
- case .Failed, .Interrupted:
- break
- }
- }
- expect(sentValue) == initialPropertyValue
- expect(signalCompleted) == true
- }
- }
- describe("MutableProperty") {
- it("should have the value given at initialization") {
- let mutableProperty = MutableProperty(initialPropertyValue)
- expect(mutableProperty.value) == initialPropertyValue
- }
- it("should yield a producer that sends the current value then all changes") {
- let mutableProperty = MutableProperty(initialPropertyValue)
- var sentValue: String?
- mutableProperty.producer.startWithNext { sentValue = $0 }
- expect(sentValue) == initialPropertyValue
- mutableProperty.value = subsequentPropertyValue
- expect(sentValue) == subsequentPropertyValue
- mutableProperty.value = finalPropertyValue
- expect(sentValue) == finalPropertyValue
- }
- it("should yield a producer that sends the current value then all changes, even if the value actually remains unchanged") {
- let mutableProperty = MutableProperty(initialPropertyValue)
- var count = 0
- mutableProperty.producer.startWithNext { _ in count = count + 1 }
- expect(count) == 1
- mutableProperty.value = initialPropertyValue
- expect(count) == 2
- mutableProperty.value = initialPropertyValue
- expect(count) == 3
- }
- it("should yield a signal that emits subsequent changes to the value") {
- let mutableProperty = MutableProperty(initialPropertyValue)
- var sentValue: String?
- mutableProperty.signal.observeNext { sentValue = $0 }
- expect(sentValue).to(beNil())
- mutableProperty.value = subsequentPropertyValue
- expect(sentValue) == subsequentPropertyValue
- mutableProperty.value = finalPropertyValue
- expect(sentValue) == finalPropertyValue
- }
- it("should yield a signal that emits subsequent changes to the value, even if the value actually remains unchanged") {
- let mutableProperty = MutableProperty(initialPropertyValue)
- var count = 0
- mutableProperty.signal.observeNext { _ in count = count + 1 }
- expect(count) == 0
- mutableProperty.value = initialPropertyValue
- expect(count) == 1
- mutableProperty.value = initialPropertyValue
- expect(count) == 2
- }
- it("should complete its producer when deallocated") {
- var mutableProperty: MutableProperty? = MutableProperty(initialPropertyValue)
- var producerCompleted = false
- mutableProperty!.producer.startWithCompleted { producerCompleted = true }
- mutableProperty = nil
- expect(producerCompleted) == true
- }
- it("should complete its signal when deallocated") {
- var mutableProperty: MutableProperty? = MutableProperty(initialPropertyValue)
- var signalCompleted = false
- mutableProperty!.signal.observeCompleted { signalCompleted = true }
- mutableProperty = nil
- expect(signalCompleted) == true
- }
- it("should yield a producer which emits the latest value and complete even if the property is deallocated") {
- var mutableProperty: MutableProperty? = MutableProperty(initialPropertyValue)
- let producer = mutableProperty!.producer
- var producerCompleted = false
- var hasUnanticipatedEvent = false
- var latestValue = mutableProperty?.value
- mutableProperty!.value = subsequentPropertyValue
- mutableProperty = nil
- producer.start { event in
- switch event {
- case let .Next(value):
- latestValue = value
- case .Completed:
- producerCompleted = true
- case .Interrupted, .Failed:
- hasUnanticipatedEvent = true
- }
- }
- expect(hasUnanticipatedEvent) == false
- expect(producerCompleted) == true
- expect(latestValue) == subsequentPropertyValue
- }
- it("should modify the value atomically") {
- let property = MutableProperty(initialPropertyValue)
- expect(property.modify({ _ in subsequentPropertyValue })) == initialPropertyValue
- expect(property.value) == subsequentPropertyValue
- }
- it("should modify the value atomically and subsquently send out a Next event with the new value") {
- let property = MutableProperty(initialPropertyValue)
- var value: String?
- property.producer.startWithNext {
- value = $0
- }
- expect(value) == initialPropertyValue
- expect(property.modify({ _ in subsequentPropertyValue })) == initialPropertyValue
- expect(property.value) == subsequentPropertyValue
- expect(value) == subsequentPropertyValue
- }
- it("should swap the value atomically") {
- let property = MutableProperty(initialPropertyValue)
- expect(property.swap(subsequentPropertyValue)) == initialPropertyValue
- expect(property.value) == subsequentPropertyValue
- }
- it("should swap the value atomically and subsquently send out a Next event with the new value") {
- let property = MutableProperty(initialPropertyValue)
- var value: String?
- property.producer.startWithNext {
- value = $0
- }
- expect(value) == initialPropertyValue
- expect(property.swap(subsequentPropertyValue)) == initialPropertyValue
- expect(property.value) == subsequentPropertyValue
- expect(value) == subsequentPropertyValue
- }
- it("should perform an action with the value") {
- let property = MutableProperty(initialPropertyValue)
- let result: Bool = property.withValue { $0.isEmpty }
- expect(result) == false
- expect(property.value) == initialPropertyValue
- }
- it("should not deadlock on recursive value access") {
- let (producer, observer) = SignalProducer<Int, NoError>.pipe()
- let property = MutableProperty(0)
- var value: Int?
- property <~ producer
- property.producer.startWithNext { _ in
- value = property.value
- }
- observer.sendNext(10)
- expect(value) == 10
- }
- it("should not deadlock on recursive value access with a closure") {
- let (producer, observer) = SignalProducer<Int, NoError>.pipe()
- let property = MutableProperty(0)
- var value: Int?
- property <~ producer
- property.producer.startWithNext { _ in
- value = property.withValue { $0 + 1 }
- }
- observer.sendNext(10)
- expect(value) == 11
- }
- it("should not deadlock on recursive observation") {
- let property = MutableProperty(0)
- var value: Int?
- property.producer.startWithNext { _ in
- property.producer.startWithNext { x in value = x }
- }
- expect(value) == 0
- property.value = 1
- expect(value) == 1
- }
- it("should not deadlock on recursive ABA observation") {
- let propertyA = MutableProperty(0)
- let propertyB = MutableProperty(0)
- var value: Int?
- propertyA.producer.startWithNext { _ in
- propertyB.producer.startWithNext { _ in
- propertyA.producer.startWithNext { x in value = x }
- }
- }
- expect(value) == 0
- propertyA.value = 1
- expect(value) == 1
- }
- }
- describe("AnyProperty") {
- describe("from a PropertyType") {
- it("should pass through behaviors of the input property") {
- let constantProperty = ConstantProperty(initialPropertyValue)
- let property = AnyProperty(constantProperty)
- var sentValue: String?
- var signalSentValue: String?
- var producerCompleted = false
- var signalInterrupted = false
- property.producer.start { event in
- switch event {
- case let .Next(value):
- sentValue = value
- case .Completed:
- producerCompleted = true
- case .Failed, .Interrupted:
- break
- }
- }
- property.signal.observe { event in
- switch event {
- case let .Next(value):
- signalSentValue = value
- case .Interrupted:
- signalInterrupted = true
- case .Failed, .Completed:
- break
- }
- }
- expect(sentValue) == initialPropertyValue
- expect(signalSentValue).to(beNil())
- expect(producerCompleted) == true
- expect(signalInterrupted) == true
- }
- }
-
- describe("from a value and SignalProducer") {
- it("should initially take on the supplied value") {
- let property = AnyProperty(
- initialValue: initialPropertyValue,
- producer: SignalProducer.never)
-
- expect(property.value) == initialPropertyValue
- }
-
- it("should take on each value sent on the producer") {
- let property = AnyProperty(
- initialValue: initialPropertyValue,
- producer: SignalProducer(value: subsequentPropertyValue))
-
- expect(property.value) == subsequentPropertyValue
- }
- }
-
- describe("from a value and Signal") {
- it("should initially take on the supplied value, then values sent on the signal") {
- let (signal, observer) = Signal<String, NoError>.pipe()
- let property = AnyProperty(
- initialValue: initialPropertyValue,
- signal: signal)
-
- expect(property.value) == initialPropertyValue
-
- observer.sendNext(subsequentPropertyValue)
-
- expect(property.value) == subsequentPropertyValue
- }
- }
- }
- describe("DynamicProperty") {
- var object: ObservableObject!
- var property: DynamicProperty!
- let propertyValue: () -> Int? = {
- if let value: AnyObject = property?.value {
- return value as? Int
- } else {
- return nil
- }
- }
- beforeEach {
- object = ObservableObject()
- expect(object.rac_value) == 0
- property = DynamicProperty(object: object, keyPath: "rac_value")
- }
- afterEach {
- object = nil
- }
- it("should read the underlying object") {
- expect(propertyValue()) == 0
- object.rac_value = 1
- expect(propertyValue()) == 1
- }
- it("should write the underlying object") {
- property.value = 1
- expect(object.rac_value) == 1
- expect(propertyValue()) == 1
- }
- it("should yield a producer that sends the current value and then the changes for the key path of the underlying object") {
- var values: [Int] = []
- property.producer.startWithNext { value in
- expect(value).notTo(beNil())
- values.append(value as! Int)
- }
- expect(values) == [ 0 ]
- property.value = 1
- expect(values) == [ 0, 1 ]
- object.rac_value = 2
- expect(values) == [ 0, 1, 2 ]
- }
- it("should yield a producer that sends the current value and then the changes for the key path of the underlying object, even if the value actually remains unchanged") {
- var values: [Int] = []
- property.producer.startWithNext { value in
- expect(value).notTo(beNil())
- values.append(value as! Int)
- }
- expect(values) == [ 0 ]
- property.value = 0
- expect(values) == [ 0, 0 ]
- object.rac_value = 0
- expect(values) == [ 0, 0, 0 ]
- }
- it("should yield a signal that emits subsequent values for the key path of the underlying object") {
- var values: [Int] = []
- property.signal.observeNext { value in
- expect(value).notTo(beNil())
- values.append(value as! Int)
- }
- expect(values) == []
- property.value = 1
- expect(values) == [ 1 ]
- object.rac_value = 2
- expect(values) == [ 1, 2 ]
- }
- it("should yield a signal that emits subsequent values for the key path of the underlying object, even if the value actually remains unchanged") {
- var values: [Int] = []
- property.signal.observeNext { value in
- expect(value).notTo(beNil())
- values.append(value as! Int)
- }
- expect(values) == []
- property.value = 0
- expect(values) == [ 0 ]
- object.rac_value = 0
- expect(values) == [ 0, 0 ]
- }
- it("should have a completed producer when the underlying object deallocates") {
- var completed = false
- property = {
- // Use a closure so this object has a shorter lifetime.
- let object = ObservableObject()
- let property = DynamicProperty(object: object, keyPath: "rac_value")
- property.producer.startWithCompleted {
- completed = true
- }
- expect(completed) == false
- expect(property.value).notTo(beNil())
- return property
- }()
- expect(completed).toEventually(beTruthy())
- expect(property.value).to(beNil())
- }
- it("should have a completed signal when the underlying object deallocates") {
- var completed = false
- property = {
- // Use a closure so this object has a shorter lifetime.
- let object = ObservableObject()
- let property = DynamicProperty(object: object, keyPath: "rac_value")
- property.signal.observeCompleted {
- completed = true
- }
- expect(completed) == false
- expect(property.value).notTo(beNil())
- return property
- }()
- expect(completed).toEventually(beTruthy())
- expect(property.value).to(beNil())
- }
- it("should retain property while DynamicProperty's underlying object is retained"){
- weak var dynamicProperty: DynamicProperty? = property
-
- property = nil
- expect(dynamicProperty).toNot(beNil())
-
- object = nil
- expect(dynamicProperty).to(beNil())
- }
- }
- describe("map") {
- it("should transform the current value and all subsequent values") {
- let property = MutableProperty(1)
- let mappedProperty = property
- .map { $0 + 1 }
- .map { $0 + 2 }
- expect(mappedProperty.value) == 4
- property.value = 2
- expect(mappedProperty.value) == 5
- }
- }
- describe("binding") {
- describe("from a Signal") {
- it("should update the property with values sent from the signal") {
- let (signal, observer) = Signal<String, NoError>.pipe()
- let mutableProperty = MutableProperty(initialPropertyValue)
- mutableProperty <~ signal
- // Verify that the binding hasn't changed the property value:
- expect(mutableProperty.value) == initialPropertyValue
- observer.sendNext(subsequentPropertyValue)
- expect(mutableProperty.value) == subsequentPropertyValue
- }
- it("should tear down the binding when disposed") {
- let (signal, observer) = Signal<String, NoError>.pipe()
- let mutableProperty = MutableProperty(initialPropertyValue)
- let bindingDisposable = mutableProperty <~ signal
- bindingDisposable.dispose()
- observer.sendNext(subsequentPropertyValue)
- expect(mutableProperty.value) == initialPropertyValue
- }
-
- it("should tear down the binding when bound signal is completed") {
- let (signal, observer) = Signal<String, NoError>.pipe()
-
- let mutableProperty = MutableProperty(initialPropertyValue)
-
- let bindingDisposable = mutableProperty <~ signal
-
- expect(bindingDisposable.disposed) == false
- observer.sendCompleted()
- expect(bindingDisposable.disposed) == true
- }
-
- it("should tear down the binding when the property deallocates") {
- let (signal, _) = Signal<String, NoError>.pipe()
- var mutableProperty: MutableProperty<String>? = MutableProperty(initialPropertyValue)
- let bindingDisposable = mutableProperty! <~ signal
- mutableProperty = nil
- expect(bindingDisposable.disposed) == true
- }
- }
- describe("from a SignalProducer") {
- it("should start a signal and update the property with its values") {
- let signalValues = [initialPropertyValue, subsequentPropertyValue]
- let signalProducer = SignalProducer<String, NoError>(values: signalValues)
- let mutableProperty = MutableProperty(initialPropertyValue)
- mutableProperty <~ signalProducer
- expect(mutableProperty.value) == signalValues.last!
- }
- it("should tear down the binding when disposed") {
- let (signalProducer, observer) = SignalProducer<String, NoError>.pipe()
- let mutableProperty = MutableProperty(initialPropertyValue)
- let disposable = mutableProperty <~ signalProducer
- disposable.dispose()
- observer.sendNext(subsequentPropertyValue)
- expect(mutableProperty.value) == initialPropertyValue
- }
- it("should tear down the binding when bound signal is completed") {
- let (signalProducer, observer) = SignalProducer<String, NoError>.pipe()
- let mutableProperty = MutableProperty(initialPropertyValue)
- let disposable = mutableProperty <~ signalProducer
- observer.sendCompleted()
- expect(disposable.disposed) == true
- }
- it("should tear down the binding when the property deallocates") {
- let signalValues = [initialPropertyValue, subsequentPropertyValue]
- let signalProducer = SignalProducer<String, NoError>(values: signalValues)
- var mutableProperty: MutableProperty<String>? = MutableProperty(initialPropertyValue)
- let disposable = mutableProperty! <~ signalProducer
- mutableProperty = nil
- expect(disposable.disposed) == true
- }
- }
- describe("from another property") {
- it("should take the source property's current value") {
- let sourceProperty = ConstantProperty(initialPropertyValue)
- let destinationProperty = MutableProperty("")
- destinationProperty <~ sourceProperty.producer
- expect(destinationProperty.value) == initialPropertyValue
- }
- it("should update with changes to the source property's value") {
- let sourceProperty = MutableProperty(initialPropertyValue)
- let destinationProperty = MutableProperty("")
- destinationProperty <~ sourceProperty.producer
- sourceProperty.value = subsequentPropertyValue
- expect(destinationProperty.value) == subsequentPropertyValue
- }
- it("should tear down the binding when disposed") {
- let sourceProperty = MutableProperty(initialPropertyValue)
- let destinationProperty = MutableProperty("")
- let bindingDisposable = destinationProperty <~ sourceProperty.producer
- bindingDisposable.dispose()
- sourceProperty.value = subsequentPropertyValue
- expect(destinationProperty.value) == initialPropertyValue
- }
- it("should tear down the binding when the source property deallocates") {
- var sourceProperty: MutableProperty<String>? = MutableProperty(initialPropertyValue)
- let destinationProperty = MutableProperty("")
- destinationProperty <~ sourceProperty!.producer
- sourceProperty = nil
- // TODO: Assert binding was torn down?
- }
- it("should tear down the binding when the destination property deallocates") {
- let sourceProperty = MutableProperty(initialPropertyValue)
- var destinationProperty: MutableProperty<String>? = MutableProperty("")
- let bindingDisposable = destinationProperty! <~ sourceProperty.producer
- destinationProperty = nil
- expect(bindingDisposable.disposed) == true
- }
- }
- describe("to a dynamic property") {
- var object: ObservableObject!
- var property: DynamicProperty!
- beforeEach {
- object = ObservableObject()
- expect(object.rac_value) == 0
- property = DynamicProperty(object: object, keyPath: "rac_value")
- }
- afterEach {
- object = nil
- }
- it("should bridge values sent on a signal to Objective-C") {
- let (signal, observer) = Signal<Int, NoError>.pipe()
- property <~ signal
- observer.sendNext(1)
- expect(object.rac_value) == 1
- }
- it("should bridge values sent on a signal producer to Objective-C") {
- let producer = SignalProducer<Int, NoError>(value: 1)
- property <~ producer
- expect(object.rac_value) == 1
- }
- it("should bridge values from a source property to Objective-C") {
- let source = MutableProperty(1)
- property <~ source
- expect(object.rac_value) == 1
- }
- }
- }
- }
- }
- private class ObservableObject: NSObject {
- dynamic var rac_value: Int = 0
- }
|