diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 11eda46..8ab81f0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,27 +14,12 @@ jobs:
strategy:
matrix:
xcode:
- - 11.3
- 11.4
- 11.5
+ - 11.6
steps:
- uses: actions/checkout@v2
- name: Select Xcode ${{ matrix.xcode }}
run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app
- name: Run tests
- run: make test-swift
-
- examples:
- runs-on: macOS-latest
- strategy:
- matrix:
- xcode:
- - 11.3
- - 11.4
- - 11.5
- steps:
- - uses: actions/checkout@v2
- - name: Select Xcode ${{ matrix.xcode }}
- run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app
- - name: Run tests
- run: make test-workspace
+ run: make test
diff --git a/ComposableArchitecture.xcworkspace/contents.xcworkspacedata b/ComposableArchitecture.xcworkspace/contents.xcworkspacedata
index ca3329e..8602eff 100644
--- a/ComposableArchitecture.xcworkspace/contents.xcworkspacedata
+++ b/ComposableArchitecture.xcworkspace/contents.xcworkspacedata
@@ -4,4 +4,7 @@
+
+
diff --git a/Examples/CaseStudies/CaseStudies.xcodeproj/project.pbxproj b/Examples/CaseStudies/CaseStudies.xcodeproj/project.pbxproj
index a46146f..d48f609 100644
--- a/Examples/CaseStudies/CaseStudies.xcodeproj/project.pbxproj
+++ b/Examples/CaseStudies/CaseStudies.xcodeproj/project.pbxproj
@@ -9,7 +9,7 @@
/* Begin PBXBuildFile section */
7854FF92249318910094D8A8 /* UIKitCaseStudiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7854FF91249318910094D8A8 /* UIKitCaseStudiesTests.swift */; };
78634FE924930375006E231F /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 78634FE824930375006E231F /* RxCocoa */; };
- 78B64BD42492F5EC00A47CE0 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = 78B64BD32492F5EC00A47CE0 /* ComposableArchitecture */; };
+ 78BC6CAC24D9BD200061AB2D /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = 78BC6CAB24D9BD200061AB2D /* ComposableArchitecture */; };
DC25DC5F2450F13200082E81 /* IfLetStoreController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC25DC5E2450F13200082E81 /* IfLetStoreController.swift */; };
DC25DC612450F2B000082E81 /* LoadThenNavigate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC25DC602450F2B000082E81 /* LoadThenNavigate.swift */; };
DC25DC642450F2DF00082E81 /* ActivityIndicatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC25DC632450F2DF00082E81 /* ActivityIndicatorViewController.swift */; };
@@ -82,8 +82,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 78BC6CAC24D9BD200061AB2D /* ComposableArchitecture in Frameworks */,
78634FE924930375006E231F /* RxCocoa in Frameworks */,
- 78B64BD42492F5EC00A47CE0 /* ComposableArchitecture in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -187,8 +187,8 @@
);
name = UIKitCaseStudies;
packageProductDependencies = (
- 78B64BD32492F5EC00A47CE0 /* ComposableArchitecture */,
78634FE824930375006E231F /* RxCocoa */,
+ 78BC6CAB24D9BD200061AB2D /* ComposableArchitecture */,
);
productName = UIKitCaseStudies;
productReference = DC4C6EA72450DD380066A05D /* UIKitCaseStudies.app */;
@@ -245,8 +245,8 @@
);
mainGroup = DC89C40A24460F95006900B9;
packageReferences = (
- 78B64BD22492F5EC00A47CE0 /* XCRemoteSwiftPackageReference "rx-swift-composable-architecture" */,
78634FE724930375006E231F /* XCRemoteSwiftPackageReference "RxSwift" */,
+ 78BC6CAA24D9BD200061AB2D /* XCRemoteSwiftPackageReference "rxswift-composable-architecture" */,
);
productRefGroup = DC89C41424460F95006900B9 /* Products */;
projectDirPath = "";
@@ -560,12 +560,12 @@
minimumVersion = 5.1.1;
};
};
- 78B64BD22492F5EC00A47CE0 /* XCRemoteSwiftPackageReference "rx-swift-composable-architecture" */ = {
+ 78BC6CAA24D9BD200061AB2D /* XCRemoteSwiftPackageReference "rxswift-composable-architecture" */ = {
isa = XCRemoteSwiftPackageReference;
- repositoryURL = "git@github.com:dannyhertz/rx-swift-composable-architecture.git";
+ repositoryURL = "https://github.com/dannyhertz/rxswift-composable-architecture.git";
requirement = {
kind = upToNextMajorVersion;
- minimumVersion = 0.3.0;
+ minimumVersion = 0.6.0;
};
};
/* End XCRemoteSwiftPackageReference section */
@@ -576,9 +576,9 @@
package = 78634FE724930375006E231F /* XCRemoteSwiftPackageReference "RxSwift" */;
productName = RxCocoa;
};
- 78B64BD32492F5EC00A47CE0 /* ComposableArchitecture */ = {
+ 78BC6CAB24D9BD200061AB2D /* ComposableArchitecture */ = {
isa = XCSwiftPackageProductDependency;
- package = 78B64BD22492F5EC00A47CE0 /* XCRemoteSwiftPackageReference "rx-swift-composable-architecture" */;
+ package = 78BC6CAA24D9BD200061AB2D /* XCRemoteSwiftPackageReference "rxswift-composable-architecture" */;
productName = ComposableArchitecture;
};
/* End XCSwiftPackageProductDependency section */
diff --git a/Makefile b/Makefile
index b9e564c..b23928e 100644
--- a/Makefile
+++ b/Makefile
@@ -2,17 +2,10 @@ PLATFORM_IOS = iOS Simulator,name=iPhone 11 Pro Max
PLATFORM_MACOS = macOS
PLATFORM_TVOS = tvOS Simulator,name=Apple TV 4K (at 1080p)
-default: test-all
+default: test
-test-all: test-swift test-workspace
-
-test-swift:
- swift test \
- --enable-pubgrub-resolver \
- --enable-test-discovery \
- --parallel
-
-test-workspace:
+test:
+ instruments -s devices
xcodebuild test \
-scheme ComposableArchitecture \
-destination platform="$(PLATFORM_IOS)"
@@ -22,8 +15,11 @@ test-workspace:
xcodebuild test \
-scheme ComposableArchitecture \
-destination platform="$(PLATFORM_TVOS)"
+ xcodebuild test \
+ -scheme "CaseStudies (UIKit)" \
+ -destination platform="$(PLATFORM_IOS)"
format:
swift format --in-place --recursive ./Package.swift ./Sources ./Tests
-.PHONY: format test-all test-swift test-workspace
+.PHONY: format test
\ No newline at end of file
diff --git a/Sources/ComposableArchitecture/UIKit/IfLetUIKit.swift b/Sources/ComposableArchitecture/UIKit/IfLetUIKit.swift
index 74e1dd8..7ba05ba 100644
--- a/Sources/ComposableArchitecture/UIKit/IfLetUIKit.swift
+++ b/Sources/ComposableArchitecture/UIKit/IfLetUIKit.swift
@@ -43,17 +43,27 @@ extension Store {
then unwrap: @escaping (Store) -> Void,
else: @escaping () -> Void
) -> Disposable where State == Wrapped? {
- self.scope(
- state: { state in
- return
- state
- .distinctUntilChanged({ ($0 != nil) == ($1 != nil) })
- .do(onNext: { if $0 == nil { `else`() } })
- .compactMap { $0 }
- },
- action: { $0 }
- )
- .subscribe(onNext: unwrap)
+
+ let elseDisposable = self
+ .scope(
+ state: { state in
+ state.distinctUntilChanged({ ($0 != nil) == ($1 != nil) })
+ }
+ )
+ .subscribe(onNext: { store in
+ if store.state == nil { `else`() }
+ })
+
+ let unwrapDisposable = self
+ .scope(
+ state: { state in
+ state.distinctUntilChanged({ ($0 != nil) == ($1 != nil) })
+ .compactMap { $0 }
+ }
+ )
+ .subscribe(onNext: unwrap)
+
+ return CompositeDisposable(elseDisposable, unwrapDisposable)
}
/// An overload of `ifLet(then:else:)` for the times that you do not want to handle the `else`
diff --git a/Tests/ComposableArchitectureTests/StoreTests.swift b/Tests/ComposableArchitectureTests/StoreTests.swift
index 0be18c0..aeeecda 100644
--- a/Tests/ComposableArchitectureTests/StoreTests.swift
+++ b/Tests/ComposableArchitectureTests/StoreTests.swift
@@ -234,4 +234,120 @@ final class StoreTests: XCTestCase {
store.send(.incr)
XCTAssertEqual(ViewStore(store).state, 100_000)
}
+
+ func testPublisherScope() {
+ let appReducer = Reducer { state, action, _ in
+ state += action ? 1 : 0
+ return .none
+ }
+
+ let parentStore = Store(initialState: 0, reducer: appReducer, environment: ())
+
+ var outputs: [Int] = []
+
+ parentStore
+ .scope { $0.distinctUntilChanged() }
+ .subscribe(onNext: {
+ outputs.append($0.state)
+ })
+ .disposed(by: disposeBag)
+
+ XCTAssertEqual(outputs, [0])
+
+ parentStore.send(true)
+ XCTAssertEqual(outputs, [0, 1])
+
+ parentStore.send(false)
+ XCTAssertEqual(outputs, [0, 1])
+ parentStore.send(false)
+ XCTAssertEqual(outputs, [0, 1])
+ parentStore.send(false)
+ XCTAssertEqual(outputs, [0, 1])
+ parentStore.send(false)
+ XCTAssertEqual(outputs, [0, 1])
+ }
+
+ func testIfLetAfterScope() {
+ struct AppState {
+ var count: Int?
+ }
+
+ let appReducer = Reducer { state, action, _ in
+ state.count = action
+ return .none
+ }
+
+ let parentStore = Store(initialState: AppState(), reducer: appReducer, environment: ())
+
+ // NB: This test needs to hold a strong reference to the emitted stores
+ var outputs: [Int?] = []
+ var stores: [Any] = []
+
+ parentStore
+ .scope(state: \.count)
+ .ifLet(
+ then: { store in
+ stores.append(store)
+ outputs.append(store.state)
+ },
+ else: {
+ outputs.append(nil)
+ })
+ .disposed(by: disposeBag)
+
+ XCTAssertEqual(outputs, [nil])
+
+ parentStore.send(1)
+ XCTAssertEqual(outputs, [nil, 1])
+
+ parentStore.send(nil)
+ XCTAssertEqual(outputs, [nil, 1, nil])
+
+ parentStore.send(1)
+ XCTAssertEqual(outputs, [nil, 1, nil, 1])
+
+ parentStore.send(nil)
+ XCTAssertEqual(outputs, [nil, 1, nil, 1, nil])
+
+ parentStore.send(1)
+ XCTAssertEqual(outputs, [nil, 1, nil, 1, nil, 1])
+
+ parentStore.send(nil)
+ XCTAssertEqual(outputs, [nil, 1, nil, 1, nil, 1, nil])
+ }
+
+ func testIfLetTwo() {
+ let parentStore = Store(
+ initialState: 0,
+ reducer: Reducer { state, action, _ in
+ if action {
+ state? += 1
+ return .none
+ } else {
+ return Effect(value: true)
+ .observeOn(MainScheduler.instance)
+ .eraseToEffect()
+ }
+ },
+ environment: ()
+ )
+
+ parentStore.ifLet { childStore in
+ let vs = ViewStore(childStore)
+
+ vs
+ .publisher
+ .subscribe(onNext: { _ in })
+ .disposed(by: self.disposeBag)
+
+ vs.send(false)
+ _ = XCTWaiter.wait(for: [.init()], timeout: 0.1)
+ vs.send(false)
+ _ = XCTWaiter.wait(for: [.init()], timeout: 0.1)
+ vs.send(false)
+ _ = XCTWaiter.wait(for: [.init()], timeout: 0.1)
+ XCTAssertEqual(vs.state, 3)
+ }
+ .disposed(by: disposeBag)
+ }
}