PropertySpec.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. //
  2. // PropertySpec.swift
  3. // ReactiveCocoa
  4. //
  5. // Created by Justin Spahr-Summers on 2015-01-23.
  6. // Copyright (c) 2015 GitHub. All rights reserved.
  7. //
  8. import Result
  9. import Nimble
  10. import Quick
  11. import ReactiveCocoa
  12. private let initialPropertyValue = "InitialValue"
  13. private let subsequentPropertyValue = "SubsequentValue"
  14. private let finalPropertyValue = "FinalValue"
  15. class PropertySpec: QuickSpec {
  16. override func spec() {
  17. describe("ConstantProperty") {
  18. it("should have the value given at initialization") {
  19. let constantProperty = ConstantProperty(initialPropertyValue)
  20. expect(constantProperty.value) == initialPropertyValue
  21. }
  22. it("should yield a signal that interrupts observers without emitting any value.") {
  23. let constantProperty = ConstantProperty(initialPropertyValue)
  24. var signalInterrupted = false
  25. var hasUnexpectedEventsEmitted = false
  26. constantProperty.signal.observe { event in
  27. switch event {
  28. case .Interrupted:
  29. signalInterrupted = true
  30. case .Next, .Failed, .Completed:
  31. hasUnexpectedEventsEmitted = true
  32. }
  33. }
  34. expect(signalInterrupted) == true
  35. expect(hasUnexpectedEventsEmitted) == false
  36. }
  37. it("should yield a producer that sends the current value then completes") {
  38. let constantProperty = ConstantProperty(initialPropertyValue)
  39. var sentValue: String?
  40. var signalCompleted = false
  41. constantProperty.producer.start { event in
  42. switch event {
  43. case let .Next(value):
  44. sentValue = value
  45. case .Completed:
  46. signalCompleted = true
  47. case .Failed, .Interrupted:
  48. break
  49. }
  50. }
  51. expect(sentValue) == initialPropertyValue
  52. expect(signalCompleted) == true
  53. }
  54. }
  55. describe("MutableProperty") {
  56. it("should have the value given at initialization") {
  57. let mutableProperty = MutableProperty(initialPropertyValue)
  58. expect(mutableProperty.value) == initialPropertyValue
  59. }
  60. it("should yield a producer that sends the current value then all changes") {
  61. let mutableProperty = MutableProperty(initialPropertyValue)
  62. var sentValue: String?
  63. mutableProperty.producer.startWithNext { sentValue = $0 }
  64. expect(sentValue) == initialPropertyValue
  65. mutableProperty.value = subsequentPropertyValue
  66. expect(sentValue) == subsequentPropertyValue
  67. mutableProperty.value = finalPropertyValue
  68. expect(sentValue) == finalPropertyValue
  69. }
  70. it("should yield a producer that sends the current value then all changes, even if the value actually remains unchanged") {
  71. let mutableProperty = MutableProperty(initialPropertyValue)
  72. var count = 0
  73. mutableProperty.producer.startWithNext { _ in count = count + 1 }
  74. expect(count) == 1
  75. mutableProperty.value = initialPropertyValue
  76. expect(count) == 2
  77. mutableProperty.value = initialPropertyValue
  78. expect(count) == 3
  79. }
  80. it("should yield a signal that emits subsequent changes to the value") {
  81. let mutableProperty = MutableProperty(initialPropertyValue)
  82. var sentValue: String?
  83. mutableProperty.signal.observeNext { sentValue = $0 }
  84. expect(sentValue).to(beNil())
  85. mutableProperty.value = subsequentPropertyValue
  86. expect(sentValue) == subsequentPropertyValue
  87. mutableProperty.value = finalPropertyValue
  88. expect(sentValue) == finalPropertyValue
  89. }
  90. it("should yield a signal that emits subsequent changes to the value, even if the value actually remains unchanged") {
  91. let mutableProperty = MutableProperty(initialPropertyValue)
  92. var count = 0
  93. mutableProperty.signal.observeNext { _ in count = count + 1 }
  94. expect(count) == 0
  95. mutableProperty.value = initialPropertyValue
  96. expect(count) == 1
  97. mutableProperty.value = initialPropertyValue
  98. expect(count) == 2
  99. }
  100. it("should complete its producer when deallocated") {
  101. var mutableProperty: MutableProperty? = MutableProperty(initialPropertyValue)
  102. var producerCompleted = false
  103. mutableProperty!.producer.startWithCompleted { producerCompleted = true }
  104. mutableProperty = nil
  105. expect(producerCompleted) == true
  106. }
  107. it("should complete its signal when deallocated") {
  108. var mutableProperty: MutableProperty? = MutableProperty(initialPropertyValue)
  109. var signalCompleted = false
  110. mutableProperty!.signal.observeCompleted { signalCompleted = true }
  111. mutableProperty = nil
  112. expect(signalCompleted) == true
  113. }
  114. it("should yield a producer which emits the latest value and complete even if the property is deallocated") {
  115. var mutableProperty: MutableProperty? = MutableProperty(initialPropertyValue)
  116. let producer = mutableProperty!.producer
  117. var producerCompleted = false
  118. var hasUnanticipatedEvent = false
  119. var latestValue = mutableProperty?.value
  120. mutableProperty!.value = subsequentPropertyValue
  121. mutableProperty = nil
  122. producer.start { event in
  123. switch event {
  124. case let .Next(value):
  125. latestValue = value
  126. case .Completed:
  127. producerCompleted = true
  128. case .Interrupted, .Failed:
  129. hasUnanticipatedEvent = true
  130. }
  131. }
  132. expect(hasUnanticipatedEvent) == false
  133. expect(producerCompleted) == true
  134. expect(latestValue) == subsequentPropertyValue
  135. }
  136. it("should modify the value atomically") {
  137. let property = MutableProperty(initialPropertyValue)
  138. expect(property.modify({ _ in subsequentPropertyValue })) == initialPropertyValue
  139. expect(property.value) == subsequentPropertyValue
  140. }
  141. it("should modify the value atomically and subsquently send out a Next event with the new value") {
  142. let property = MutableProperty(initialPropertyValue)
  143. var value: String?
  144. property.producer.startWithNext {
  145. value = $0
  146. }
  147. expect(value) == initialPropertyValue
  148. expect(property.modify({ _ in subsequentPropertyValue })) == initialPropertyValue
  149. expect(property.value) == subsequentPropertyValue
  150. expect(value) == subsequentPropertyValue
  151. }
  152. it("should swap the value atomically") {
  153. let property = MutableProperty(initialPropertyValue)
  154. expect(property.swap(subsequentPropertyValue)) == initialPropertyValue
  155. expect(property.value) == subsequentPropertyValue
  156. }
  157. it("should swap the value atomically and subsquently send out a Next event with the new value") {
  158. let property = MutableProperty(initialPropertyValue)
  159. var value: String?
  160. property.producer.startWithNext {
  161. value = $0
  162. }
  163. expect(value) == initialPropertyValue
  164. expect(property.swap(subsequentPropertyValue)) == initialPropertyValue
  165. expect(property.value) == subsequentPropertyValue
  166. expect(value) == subsequentPropertyValue
  167. }
  168. it("should perform an action with the value") {
  169. let property = MutableProperty(initialPropertyValue)
  170. let result: Bool = property.withValue { $0.isEmpty }
  171. expect(result) == false
  172. expect(property.value) == initialPropertyValue
  173. }
  174. it("should not deadlock on recursive value access") {
  175. let (producer, observer) = SignalProducer<Int, NoError>.pipe()
  176. let property = MutableProperty(0)
  177. var value: Int?
  178. property <~ producer
  179. property.producer.startWithNext { _ in
  180. value = property.value
  181. }
  182. observer.sendNext(10)
  183. expect(value) == 10
  184. }
  185. it("should not deadlock on recursive value access with a closure") {
  186. let (producer, observer) = SignalProducer<Int, NoError>.pipe()
  187. let property = MutableProperty(0)
  188. var value: Int?
  189. property <~ producer
  190. property.producer.startWithNext { _ in
  191. value = property.withValue { $0 + 1 }
  192. }
  193. observer.sendNext(10)
  194. expect(value) == 11
  195. }
  196. it("should not deadlock on recursive observation") {
  197. let property = MutableProperty(0)
  198. var value: Int?
  199. property.producer.startWithNext { _ in
  200. property.producer.startWithNext { x in value = x }
  201. }
  202. expect(value) == 0
  203. property.value = 1
  204. expect(value) == 1
  205. }
  206. it("should not deadlock on recursive ABA observation") {
  207. let propertyA = MutableProperty(0)
  208. let propertyB = MutableProperty(0)
  209. var value: Int?
  210. propertyA.producer.startWithNext { _ in
  211. propertyB.producer.startWithNext { _ in
  212. propertyA.producer.startWithNext { x in value = x }
  213. }
  214. }
  215. expect(value) == 0
  216. propertyA.value = 1
  217. expect(value) == 1
  218. }
  219. }
  220. describe("AnyProperty") {
  221. describe("from a PropertyType") {
  222. it("should pass through behaviors of the input property") {
  223. let constantProperty = ConstantProperty(initialPropertyValue)
  224. let property = AnyProperty(constantProperty)
  225. var sentValue: String?
  226. var signalSentValue: String?
  227. var producerCompleted = false
  228. var signalInterrupted = false
  229. property.producer.start { event in
  230. switch event {
  231. case let .Next(value):
  232. sentValue = value
  233. case .Completed:
  234. producerCompleted = true
  235. case .Failed, .Interrupted:
  236. break
  237. }
  238. }
  239. property.signal.observe { event in
  240. switch event {
  241. case let .Next(value):
  242. signalSentValue = value
  243. case .Interrupted:
  244. signalInterrupted = true
  245. case .Failed, .Completed:
  246. break
  247. }
  248. }
  249. expect(sentValue) == initialPropertyValue
  250. expect(signalSentValue).to(beNil())
  251. expect(producerCompleted) == true
  252. expect(signalInterrupted) == true
  253. }
  254. }
  255. describe("from a value and SignalProducer") {
  256. it("should initially take on the supplied value") {
  257. let property = AnyProperty(
  258. initialValue: initialPropertyValue,
  259. producer: SignalProducer.never)
  260. expect(property.value) == initialPropertyValue
  261. }
  262. it("should take on each value sent on the producer") {
  263. let property = AnyProperty(
  264. initialValue: initialPropertyValue,
  265. producer: SignalProducer(value: subsequentPropertyValue))
  266. expect(property.value) == subsequentPropertyValue
  267. }
  268. }
  269. describe("from a value and Signal") {
  270. it("should initially take on the supplied value, then values sent on the signal") {
  271. let (signal, observer) = Signal<String, NoError>.pipe()
  272. let property = AnyProperty(
  273. initialValue: initialPropertyValue,
  274. signal: signal)
  275. expect(property.value) == initialPropertyValue
  276. observer.sendNext(subsequentPropertyValue)
  277. expect(property.value) == subsequentPropertyValue
  278. }
  279. }
  280. }
  281. describe("DynamicProperty") {
  282. var object: ObservableObject!
  283. var property: DynamicProperty!
  284. let propertyValue: () -> Int? = {
  285. if let value: AnyObject = property?.value {
  286. return value as? Int
  287. } else {
  288. return nil
  289. }
  290. }
  291. beforeEach {
  292. object = ObservableObject()
  293. expect(object.rac_value) == 0
  294. property = DynamicProperty(object: object, keyPath: "rac_value")
  295. }
  296. afterEach {
  297. object = nil
  298. }
  299. it("should read the underlying object") {
  300. expect(propertyValue()) == 0
  301. object.rac_value = 1
  302. expect(propertyValue()) == 1
  303. }
  304. it("should write the underlying object") {
  305. property.value = 1
  306. expect(object.rac_value) == 1
  307. expect(propertyValue()) == 1
  308. }
  309. it("should yield a producer that sends the current value and then the changes for the key path of the underlying object") {
  310. var values: [Int] = []
  311. property.producer.startWithNext { value in
  312. expect(value).notTo(beNil())
  313. values.append(value as! Int)
  314. }
  315. expect(values) == [ 0 ]
  316. property.value = 1
  317. expect(values) == [ 0, 1 ]
  318. object.rac_value = 2
  319. expect(values) == [ 0, 1, 2 ]
  320. }
  321. 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") {
  322. var values: [Int] = []
  323. property.producer.startWithNext { value in
  324. expect(value).notTo(beNil())
  325. values.append(value as! Int)
  326. }
  327. expect(values) == [ 0 ]
  328. property.value = 0
  329. expect(values) == [ 0, 0 ]
  330. object.rac_value = 0
  331. expect(values) == [ 0, 0, 0 ]
  332. }
  333. it("should yield a signal that emits subsequent values for the key path of the underlying object") {
  334. var values: [Int] = []
  335. property.signal.observeNext { value in
  336. expect(value).notTo(beNil())
  337. values.append(value as! Int)
  338. }
  339. expect(values) == []
  340. property.value = 1
  341. expect(values) == [ 1 ]
  342. object.rac_value = 2
  343. expect(values) == [ 1, 2 ]
  344. }
  345. it("should yield a signal that emits subsequent values for the key path of the underlying object, even if the value actually remains unchanged") {
  346. var values: [Int] = []
  347. property.signal.observeNext { value in
  348. expect(value).notTo(beNil())
  349. values.append(value as! Int)
  350. }
  351. expect(values) == []
  352. property.value = 0
  353. expect(values) == [ 0 ]
  354. object.rac_value = 0
  355. expect(values) == [ 0, 0 ]
  356. }
  357. it("should have a completed producer when the underlying object deallocates") {
  358. var completed = false
  359. property = {
  360. // Use a closure so this object has a shorter lifetime.
  361. let object = ObservableObject()
  362. let property = DynamicProperty(object: object, keyPath: "rac_value")
  363. property.producer.startWithCompleted {
  364. completed = true
  365. }
  366. expect(completed) == false
  367. expect(property.value).notTo(beNil())
  368. return property
  369. }()
  370. expect(completed).toEventually(beTruthy())
  371. expect(property.value).to(beNil())
  372. }
  373. it("should have a completed signal when the underlying object deallocates") {
  374. var completed = false
  375. property = {
  376. // Use a closure so this object has a shorter lifetime.
  377. let object = ObservableObject()
  378. let property = DynamicProperty(object: object, keyPath: "rac_value")
  379. property.signal.observeCompleted {
  380. completed = true
  381. }
  382. expect(completed) == false
  383. expect(property.value).notTo(beNil())
  384. return property
  385. }()
  386. expect(completed).toEventually(beTruthy())
  387. expect(property.value).to(beNil())
  388. }
  389. it("should retain property while DynamicProperty's underlying object is retained"){
  390. weak var dynamicProperty: DynamicProperty? = property
  391. property = nil
  392. expect(dynamicProperty).toNot(beNil())
  393. object = nil
  394. expect(dynamicProperty).to(beNil())
  395. }
  396. }
  397. describe("map") {
  398. it("should transform the current value and all subsequent values") {
  399. let property = MutableProperty(1)
  400. let mappedProperty = property
  401. .map { $0 + 1 }
  402. .map { $0 + 2 }
  403. expect(mappedProperty.value) == 4
  404. property.value = 2
  405. expect(mappedProperty.value) == 5
  406. }
  407. }
  408. describe("binding") {
  409. describe("from a Signal") {
  410. it("should update the property with values sent from the signal") {
  411. let (signal, observer) = Signal<String, NoError>.pipe()
  412. let mutableProperty = MutableProperty(initialPropertyValue)
  413. mutableProperty <~ signal
  414. // Verify that the binding hasn't changed the property value:
  415. expect(mutableProperty.value) == initialPropertyValue
  416. observer.sendNext(subsequentPropertyValue)
  417. expect(mutableProperty.value) == subsequentPropertyValue
  418. }
  419. it("should tear down the binding when disposed") {
  420. let (signal, observer) = Signal<String, NoError>.pipe()
  421. let mutableProperty = MutableProperty(initialPropertyValue)
  422. let bindingDisposable = mutableProperty <~ signal
  423. bindingDisposable.dispose()
  424. observer.sendNext(subsequentPropertyValue)
  425. expect(mutableProperty.value) == initialPropertyValue
  426. }
  427. it("should tear down the binding when bound signal is completed") {
  428. let (signal, observer) = Signal<String, NoError>.pipe()
  429. let mutableProperty = MutableProperty(initialPropertyValue)
  430. let bindingDisposable = mutableProperty <~ signal
  431. expect(bindingDisposable.disposed) == false
  432. observer.sendCompleted()
  433. expect(bindingDisposable.disposed) == true
  434. }
  435. it("should tear down the binding when the property deallocates") {
  436. let (signal, _) = Signal<String, NoError>.pipe()
  437. var mutableProperty: MutableProperty<String>? = MutableProperty(initialPropertyValue)
  438. let bindingDisposable = mutableProperty! <~ signal
  439. mutableProperty = nil
  440. expect(bindingDisposable.disposed) == true
  441. }
  442. }
  443. describe("from a SignalProducer") {
  444. it("should start a signal and update the property with its values") {
  445. let signalValues = [initialPropertyValue, subsequentPropertyValue]
  446. let signalProducer = SignalProducer<String, NoError>(values: signalValues)
  447. let mutableProperty = MutableProperty(initialPropertyValue)
  448. mutableProperty <~ signalProducer
  449. expect(mutableProperty.value) == signalValues.last!
  450. }
  451. it("should tear down the binding when disposed") {
  452. let (signalProducer, observer) = SignalProducer<String, NoError>.pipe()
  453. let mutableProperty = MutableProperty(initialPropertyValue)
  454. let disposable = mutableProperty <~ signalProducer
  455. disposable.dispose()
  456. observer.sendNext(subsequentPropertyValue)
  457. expect(mutableProperty.value) == initialPropertyValue
  458. }
  459. it("should tear down the binding when bound signal is completed") {
  460. let (signalProducer, observer) = SignalProducer<String, NoError>.pipe()
  461. let mutableProperty = MutableProperty(initialPropertyValue)
  462. let disposable = mutableProperty <~ signalProducer
  463. observer.sendCompleted()
  464. expect(disposable.disposed) == true
  465. }
  466. it("should tear down the binding when the property deallocates") {
  467. let signalValues = [initialPropertyValue, subsequentPropertyValue]
  468. let signalProducer = SignalProducer<String, NoError>(values: signalValues)
  469. var mutableProperty: MutableProperty<String>? = MutableProperty(initialPropertyValue)
  470. let disposable = mutableProperty! <~ signalProducer
  471. mutableProperty = nil
  472. expect(disposable.disposed) == true
  473. }
  474. }
  475. describe("from another property") {
  476. it("should take the source property's current value") {
  477. let sourceProperty = ConstantProperty(initialPropertyValue)
  478. let destinationProperty = MutableProperty("")
  479. destinationProperty <~ sourceProperty.producer
  480. expect(destinationProperty.value) == initialPropertyValue
  481. }
  482. it("should update with changes to the source property's value") {
  483. let sourceProperty = MutableProperty(initialPropertyValue)
  484. let destinationProperty = MutableProperty("")
  485. destinationProperty <~ sourceProperty.producer
  486. sourceProperty.value = subsequentPropertyValue
  487. expect(destinationProperty.value) == subsequentPropertyValue
  488. }
  489. it("should tear down the binding when disposed") {
  490. let sourceProperty = MutableProperty(initialPropertyValue)
  491. let destinationProperty = MutableProperty("")
  492. let bindingDisposable = destinationProperty <~ sourceProperty.producer
  493. bindingDisposable.dispose()
  494. sourceProperty.value = subsequentPropertyValue
  495. expect(destinationProperty.value) == initialPropertyValue
  496. }
  497. it("should tear down the binding when the source property deallocates") {
  498. var sourceProperty: MutableProperty<String>? = MutableProperty(initialPropertyValue)
  499. let destinationProperty = MutableProperty("")
  500. destinationProperty <~ sourceProperty!.producer
  501. sourceProperty = nil
  502. // TODO: Assert binding was torn down?
  503. }
  504. it("should tear down the binding when the destination property deallocates") {
  505. let sourceProperty = MutableProperty(initialPropertyValue)
  506. var destinationProperty: MutableProperty<String>? = MutableProperty("")
  507. let bindingDisposable = destinationProperty! <~ sourceProperty.producer
  508. destinationProperty = nil
  509. expect(bindingDisposable.disposed) == true
  510. }
  511. }
  512. describe("to a dynamic property") {
  513. var object: ObservableObject!
  514. var property: DynamicProperty!
  515. beforeEach {
  516. object = ObservableObject()
  517. expect(object.rac_value) == 0
  518. property = DynamicProperty(object: object, keyPath: "rac_value")
  519. }
  520. afterEach {
  521. object = nil
  522. }
  523. it("should bridge values sent on a signal to Objective-C") {
  524. let (signal, observer) = Signal<Int, NoError>.pipe()
  525. property <~ signal
  526. observer.sendNext(1)
  527. expect(object.rac_value) == 1
  528. }
  529. it("should bridge values sent on a signal producer to Objective-C") {
  530. let producer = SignalProducer<Int, NoError>(value: 1)
  531. property <~ producer
  532. expect(object.rac_value) == 1
  533. }
  534. it("should bridge values from a source property to Objective-C") {
  535. let source = MutableProperty(1)
  536. property <~ source
  537. expect(object.rac_value) == 1
  538. }
  539. }
  540. }
  541. }
  542. }
  543. private class ObservableObject: NSObject {
  544. dynamic var rac_value: Int = 0
  545. }