diff --git a/Rex.xcodeproj/project.pbxproj b/Rex.xcodeproj/project.pbxproj index 4ce2402..1120059 100644 --- a/Rex.xcodeproj/project.pbxproj +++ b/Rex.xcodeproj/project.pbxproj @@ -43,6 +43,10 @@ D86FFBDA1B34B3F0001A89B3 /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = D86FFBD91B34B3F0001A89B3 /* Action.swift */; }; D86FFBDB1B34B3F0001A89B3 /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = D86FFBD91B34B3F0001A89B3 /* Action.swift */; }; D86FFBDD1B34B691001A89B3 /* UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D86FFBDC1B34B691001A89B3 /* UIButton.swift */; }; + D8A454061BD26A1A00C9E790 /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A454051BD26A1A00C9E790 /* Property.swift */; }; + D8A454071BD26A1A00C9E790 /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A454051BD26A1A00C9E790 /* Property.swift */; }; + D8A454091BD2772700C9E790 /* PropertyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A454081BD2772700C9E790 /* PropertyTests.swift */; }; + D8A4540A1BD2772700C9E790 /* PropertyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A454081BD2772700C9E790 /* PropertyTests.swift */; }; D8E4A6201B7BBB1600EAD8A8 /* UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5173EBC71B625A6800C9B48E /* UIBarButtonItem.swift */; }; D8E4A6211B7BBB2100EAD8A8 /* UIBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5173EBC51B625A2600C9B48E /* UIBarItem.swift */; }; D8F073161B863CE70047D546 /* UILabelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F073141B861B3A0047D546 /* UILabelTests.swift */; }; @@ -129,6 +133,8 @@ D86FFBD71B34B242001A89B3 /* UILabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UILabel.swift; sourceTree = ""; }; D86FFBD91B34B3F0001A89B3 /* Action.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = ""; }; D86FFBDC1B34B691001A89B3 /* UIButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIButton.swift; sourceTree = ""; }; + D8A454051BD26A1A00C9E790 /* Property.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Property.swift; sourceTree = ""; }; + D8A454081BD2772700C9E790 /* PropertyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PropertyTests.swift; sourceTree = ""; }; D8F073141B861B3A0047D546 /* UILabelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UILabelTests.swift; sourceTree = ""; }; D8F0973A1B17F2F7002E15BA /* NSData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSData.swift; sourceTree = ""; }; D8F0973C1B17F30D002E15BA /* NSUserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSUserDefaults.swift; sourceTree = ""; }; @@ -197,6 +203,7 @@ isa = PBXGroup; children = ( D86FFBD91B34B3F0001A89B3 /* Action.swift */, + D8A454051BD26A1A00C9E790 /* Property.swift */, D8003EBC1AFED01000D7D3C5 /* Signal.swift */, D8003EB81AFEC7A900D7D3C5 /* SignalProducer.swift */, 4238D5941B4D593E008534C0 /* AppKit */, @@ -219,6 +226,7 @@ D8003E9D1AFEC3D400D7D3C5 /* Tests */ = { isa = PBXGroup; children = ( + D8A454081BD2772700C9E790 /* PropertyTests.swift */, D8003EBE1AFED2F800D7D3C5 /* SignalTests.swift */, D8003EC01AFED30100D7D3C5 /* SignalProducerTests.swift */, D8F097461B17F5BF002E15BA /* Foundation */, @@ -489,6 +497,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D8A454061BD26A1A00C9E790 /* Property.swift in Sources */, D86FFBDA1B34B3F0001A89B3 /* Action.swift in Sources */, D86FFBD11B34AD6F001A89B3 /* Association.swift in Sources */, D8003EB91AFEC7A900D7D3C5 /* SignalProducer.swift in Sources */, @@ -507,6 +516,7 @@ D8F0974A1B17F5E1002E15BA /* NSObjectTests.swift in Sources */, D8003EC21AFED30F00D7D3C5 /* SignalTests.swift in Sources */, D8003EC31AFED30F00D7D3C5 /* SignalProducerTests.swift in Sources */, + D8A454091BD2772700C9E790 /* PropertyTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -521,6 +531,7 @@ D8F0973E1B17F30D002E15BA /* NSUserDefaults.swift in Sources */, D834572D1AFEE45B0070616A /* Signal.swift in Sources */, D8E4A6211B7BBB2100EAD8A8 /* UIBarItem.swift in Sources */, + D8A454071BD26A1A00C9E790 /* Property.swift in Sources */, D8E4A6201B7BBB1600EAD8A8 /* UIBarButtonItem.swift in Sources */, D8F097451B17F3C8002E15BA /* NSObject.swift in Sources */, D834572E1AFEE45B0070616A /* SignalProducer.swift in Sources */, @@ -540,6 +551,7 @@ 8289A2E81BD7F7900097FB60 /* UIViewTests.swift in Sources */, D8F073161B863CE70047D546 /* UILabelTests.swift in Sources */, 8295FD8A1B87352D007C9000 /* UIButtonTests.swift in Sources */, + D8A4540A1BD2772700C9E790 /* PropertyTests.swift in Sources */, D83457301AFEE45E0070616A /* SignalProducerTests.swift in Sources */, D83457411AFEE6050070616A /* SignalTests.swift in Sources */, 8295FD8D1B87374A007C9000 /* UIBarButtonItemTests.swift in Sources */, diff --git a/Source/Property.swift b/Source/Property.swift new file mode 100644 index 0000000..1ff3b2e --- /dev/null +++ b/Source/Property.swift @@ -0,0 +1,107 @@ +// +// Property.swift +// Rex +// +// Created by Neil Pankey on 10/17/15. +// Copyright (c) 2015 Neil Pankey. All rights reserved. +// + +import ReactiveCocoa + +extension PropertyType where Value == Bool { + public func and(other: P) -> AndProperty { + return AndProperty(terms: [AnyProperty(self), AnyProperty(other)]) + } + + public func and(other: AnyProperty) -> AndProperty { + return AndProperty(terms: [AnyProperty(self), other]) + } + + public func or(other: P) -> OrProperty { + return OrProperty(terms: [AnyProperty(self), AnyProperty(other)]) + } + + public func or(other: AnyProperty) -> OrProperty { + return OrProperty(terms: [AnyProperty(self), other]) + } + + public func not() -> NotProperty { + return NotProperty(source: AnyProperty(self), invert: true) + } +} + +public struct AndProperty: PropertyType { + public let terms: [AnyProperty] + + public var value: Bool { + return terms.reduce(true) { $0 && $1.value } + } + + public var producer: SignalProducer { + let producers = terms.map { $0.producer } + return combineLatest(producers).map { values in + return values.reduce(true) { $0 && $1 } + } + } + + public func and

(other: P) -> AndProperty { + return AndProperty(terms: terms + [AnyProperty(other)]) + } + + public func and(other: AnyProperty) -> AndProperty { + return AndProperty(terms: terms + [other]) + } + + private init(terms: [AnyProperty]) { + self.terms = terms + } +} + +public struct OrProperty: PropertyType { + public let terms: [AnyProperty] + + public var value: Bool { + return terms.reduce(false) { $0 || $1.value } + } + + public var producer: SignalProducer { + let producers = terms.map { $0.producer } + return combineLatest(producers).map { values in + return values.reduce(false) { $0 || $1 } + } + } + + public func or

(other: P) -> OrProperty { + return OrProperty(terms: terms + [AnyProperty(other)]) + } + + public func or(other: AnyProperty) -> OrProperty { + return OrProperty(terms: terms + [other]) + } + + private init(terms: [AnyProperty]) { + self.terms = terms + } +} + +public struct NotProperty: PropertyType { + private let source: AnyProperty + private let invert: Bool + + public var value: Bool { + return source.value != invert + } + + public var producer: SignalProducer { + return source.producer.map { $0 != self.invert } + } + + public func not() -> NotProperty { + return NotProperty(source: source, invert: !invert) + } + + private init(source: AnyProperty, invert: Bool) { + self.source = source + self.invert = invert + } +} diff --git a/Tests/PropertyTests.swift b/Tests/PropertyTests.swift new file mode 100644 index 0000000..a1fa20d --- /dev/null +++ b/Tests/PropertyTests.swift @@ -0,0 +1,89 @@ +// +// PropertyTests.swift +// Rex +// +// Created by Neil Pankey on 10/17/15. +// Copyright (c) 2015 Neil Pankey. All rights reserved. +// + +@testable import Rex +import ReactiveCocoa +import XCTest + +final class PropertyTests: XCTestCase { + + func testAndProperty() { + let lhs = MutableProperty(false), rhs = MutableProperty(false) + let and = lhs.and(rhs) + + var current: Bool! + and.producer.startWithNext { current = $0 } + + XCTAssertFalse(and.value) + XCTAssertFalse(current!) + + lhs.value = true + XCTAssertFalse(and.value) + XCTAssertFalse(current!) + + rhs.value = true + XCTAssertTrue(and.value) + XCTAssertTrue(current!) + + let (signal, pipe) = Signal.pipe() + let and2 = and.and(AnyProperty(initialValue: false, signal: signal)) + and2.producer.startWithNext { current = $0 } + + XCTAssertFalse(and2.value) + XCTAssertFalse(current!) + + pipe.sendNext(true) + XCTAssertTrue(and2.value) + XCTAssertTrue(current!) + } + + func testOrProperty() { + let lhs = MutableProperty(true), rhs = MutableProperty(true) + let or = lhs.or(rhs) + + var current: Bool! + or.producer.startWithNext { current = $0 } + + XCTAssertTrue(or.value) + XCTAssertTrue(current!) + + lhs.value = false + XCTAssertTrue(or.value) + XCTAssertTrue(current!) + + rhs.value = false + XCTAssertFalse(or.value) + XCTAssertFalse(current!) + + let (signal, pipe) = Signal.pipe() + let or2 = or.or(AnyProperty(initialValue: true, signal: signal)) + or2.producer.startWithNext { current = $0 } + + XCTAssertTrue(or2.value) + XCTAssertTrue(current!) + + pipe.sendNext(false) + XCTAssertFalse(or2.value) + XCTAssertFalse(current!) + } + + func testNotProperty() { + let source = MutableProperty(false) + let not = source.not() + + var current: Bool! + not.producer.startWithNext { current = $0 } + + XCTAssertTrue(not.value) + XCTAssertTrue(current!) + + source.value = true + XCTAssertFalse(not.value) + XCTAssertFalse(current!) + } +}