profile
viewpoint

lixar/CLTokenInputView 0

A replica of iOS's native contact bubbles UI

nmccann/Adafruit_SSD1306 0

Adafruit_SSD1306 library ported for Spark

nmccann/CGFloatType 0

Provides various methods and functions to deal with CGFloat on 64-bit systems (CGFLOAT_IS_DOUBLE).

nmccann/CLTokenInputView 0

A replica of iOS's native contact bubbles UI

nmccann/CocoaPods 0

The Objective-C library dependency manager.

nmccann/Core 0

The models used within the cocoapods gem

nmccann/ErrorKit 0

iOS library for making NSError handling easier

nmccann/fastlane 0

The easiest way to automate building and releasing your iOS and Android apps

nmccann/later 0

A javascript library for defining recurring schedules and calculating future (or past) occurrences for them. Includes support for using English phrases and Cron schedules. Works in Node and in the browser.

pull request commentpointfreeco/swift-composable-architecture

Better support enum state and struct actions with optional paths

Thank you for the feedback - I can confirm that changing the implementation to use run allowed me to implement it as an extension on Reducer, making it a purely additive change.

That said, perhaps it could be merged in as an experimental feature? It could be psuedo-namespaced under an experimental extension on Reducer.

To me being able to represent mutually exclusive state seems like a natural requirement for most projects. Admittedly the current syntax makes it a little clunky, but that could be refined with usage. But I understand that increasing the surface area of an open source project should not be taken lightly.

stephencelis

comment created time in a month

startedpointfreeco/swift-composable-architecture

started time in a month

pull request commentpointfreeco/swift-composable-architecture

Better support enum state and struct actions with optional paths

@stephencelis I've been experimenting with this in isolation here, and must say that I do like it. Some notes:

  • I might be missing something as I wasn't able to implement it purely additively - the changes to Reducer.swift couldn't be implemented outside of the library (even when providing different names for otherwise conflicting properties/functions) as they make use of reducer which is private. I opted to change my SPM dependency to target this branch instead, but that also again means that we'd potentially be stuck on an out of date branch (unless it's periodically rebased on the latest).
  • It's possible for parent reducers to get actions for states they are not currently in. This could be viewed as a feature or a bug I suppose. But if you're considering these to be truly mutually exclusive, it might be preferable to throw an error/warning if the reducer receives unexpected actions, particularly since the child reducer for the unexpected action doesn't receive it (from what I can tell) ** Can be worked around by having the parent reducer switch on a combination of the state and the action, and ignore actions that are not allowed for the current state.
  • Would a SwitchStore be possible/desirable? In my example I was able to get the desired behaviour with a bunch of IfLetStores, but that doesn't make it obvious to a reader that these are mutually exclusive conditions. I could also wrap the IfLetStores in a normal switch statement, but that results in some redundancy since I am then unwrapping the state even though I know it is non-nil. There might be some obvious workaround for this that I am missing.

If this is something I can help with, either through working on an implementation, or expanding my experiment to cover more use cases, I'd be happy to do so. I think this would be a great feature to have in the architecture for a few reasons - my particular use case could be handled by a series of nested optional stores, but the nesting becomes cumbersome, as does the optionality. Being able to use an enum allows for more modularization - in my example, the CountrySelector is self-contained and doesn't know that it is pre-requisite for Home. Similarly, if I had something like deep-linking that could provide a country without having to manually select it, the app's state could be changed to Home without having to involve a CountrySelector at all.

stephencelis

comment created time in a month

push eventnmccann/ComposableArchitectureExperiments

Noah McCann

commit sha abb6c3ba893dd493b873d805db8d971f18ab1032

Initial commit

view details

push time in a month

create barnchnmccann/ComposableArchitectureExperiments

branch : main

created branch time in a month

created repositorynmccann/ComposableArchitectureExperiments

created time in a month

Pull request review commentpointfreeco/swift-composable-architecture

Breakpoint instead of assert in optional/forEach

 public struct Reducer<State, Action, Environment> {   ///       }   ///     )   ///-  /// Take care when combining optional reducers into parent domains, as order matters. Always-  /// combine optional reducers _before_ parent reducers that can `nil` out the associated optional-  /// state.+  /// Take care when combining optional reducers into parent domains. An optional reducer cannot+  /// process actions in its domain when its state is `nil`. If a child action is sent to an+  /// optional reducer when child state is `nil`, it is generally considered a logic error. There+  /// are a few ways in which these errors can sneak into a code base:+  ///+  ///   * A parent reducer sets child state to `nil` when processing a child action and runs+  ///     _before_ the child reducer:+  ///+  ///         let parentReducer = Reducer<ParentState, ParentAction, ParentEnvironment>.combine(+  ///           // When combining reducers, the parent reducer runs first+  ///           Reducer { state, action, environment in+  ///             switch action {+  ///             case .child(.didDisappear):+  ///               // And `nil`s out child state when processing a child action+  ///               state.child = nil+  ///               return .none+  ///             ...+  ///             }+  ///           },+  ///           // Before the child reducer runs+  ///           childReducer.optional().pullback(...)+  ///         )+  ///+  ///         let childReducer = Reducer<+  ///           ChildState, ChildAction, ChildEnvironment+  ///         > { state, action environment in+  ///           case .didDisappear:+  ///             // This action is never received here because child state is `nil` in the parent+  ///           ...+  ///         }+  ///+  ///     To ensure that a child reducer can process any action that a parent may use to `nil` out+  ///     its state, combine it _before_ the parent:+  ///+  ///         let parentReducer = Reducer<ParentState, ParentAction, ParentEnvironment>.combine(+  ///           // The child runs first+  ///           childReducer.optional().pullback(...),+  ///           // The parent runs after+  ///           Reducer { state, action, environment in+  ///             ...+  ///           }+  ///         )+  ///+  ///   * A child effect feeds a child action back into the store when child state is `nil`:+  ///+  ///         let childReducer = Reducer<+  ///           ChildState, ChildAction, ChildEnvironment+  ///         > { state, action environment in+  ///           switch action {+  ///           case .onAppear:+  ///             // An effect may want to feed its result back to the child domain in an action+  ///             return environment.apiClient+  ///               .request()+  ///               .map(ChildAction.response)+  ///+  ///           case let .response(response):+  ///             // But the child cannot process this action if its state is `nil` in the parent+  ///           ...+  ///           }+  ///         }+  ///+  ///     It is perfectly reasonable to ignore the result of an effect when child state is `nil`,+  ///     for example one-off effects that you don't want to cancel. However, many long-living+  ///     effects _should_ be explicitly canceled when tearing down a child domain:+  ///+  ///         let childReducer = Reducer<+  ///           ChildState, ChildAction, ChildEnvironment+  ///         > { state, action environment in+  ///           struct MotionId: Hashable {}+  ///+  ///           switch action {+  ///           case .onAppear:+  ///             // Mark long-living effects that shouldn't outlive their domain cancellable+  ///             return environment.motionClient+  ///               .start()+  ///               .map(ChildAction.motion)+  ///               .cancellable(id: MotionId())+  ///+  ///           case .onDisappear:+  ///             // And explicitly cancel them when the domain is torn down+  ///             return .cancel(id: MotionId())+  ///           ...+  ///           }+  ///         }+  ///+  ///   * A view store sends a child action when child state is `nil`:+  ///+  ///         WithViewStore(self.parentStore) { parentViewStore in+  ///           // If child state is `nil`, it cannot process this action.+  ///           Button("Child Action") { parentViewStore.send(.child(.action)) }+  ///           ...+  ///         }+  ///+  ///     Use `Store.scope` with`IfLetStore` or `Store.ifLet` to ensure that views can only send+  ///     child actions when the child domain is non-`nil`.+  ///+  ///         IfLetStore(+  ///           self.parentStore.scope(state: { $0.child }, action: { .child($0) }+  ///         ) { childStore in+  ///           // This destination only appears when child state is non-`nil`+  ///           WithViewStore(childStore) { childViewStore in+  ///             // So this action can only be sent when child state is non-`nil`+  ///             Button("Child Action") { childViewStore.send(.action) }+  ///           }+  ///           ...+  ///         }   ///   /// - See also: `IfLetStore`, a SwiftUI helper for transforming a store on optional state into a   ///   store on non-optional state.   /// - See also: `Store.ifLet`, a UIKit helper for doing imperative work with a store on optional   ///   state.-  public func optional(_ file: StaticString = #file, _ line: UInt = #line) -> Reducer<+  ///+  /// - Parameter breakpointOnNil: Raises `SIGTRAP` signal when an action is sent to the reducer+  ///   but state is `nil`. This is generally considered a logic error, as a child reducer cannot+  ///   process a child action for unavailable child state.+  /// - Returns: A reducer that works on optional state.+  public func optional(+    breakpointOnNil: Bool = true,

Thank you for making this change - I definitely understand the reasons for the previous strict behaviour, but I think this is a nice balance. Default to strict, with the option to ignore if needed.

I've run into a particular case recently with a NavigationLink's binding that sends a "dismiss" action after the child state is nullified, and the only way I've found to work around it is to put in a delay to let the navigation complete before nulling out the child state. Ignoring the error would be preferable to the delay. Though I acknowledge that there may be something incorrect about how I'm using the navigation link that is causing the problem.

stephencelis

comment created time in a month

PullRequestReviewEvent

pull request commentpointfreeco/swift-composable-architecture

Better support enum state and struct actions with optional paths

This is definitely something I'd like to see in the architecture. I'm currently struggling with a scenario that would be best represented with an enum-based state, essentially:

- Logged In
--- Initial
--- Province Selected
--- Province And City Selected
--- Province And City And Street Selected
----- Something that requires Province/City/Street
- Logged Out

Without the ability to represent these as mutually exclusive states, I'm having to have a bunch of optionals, but then have to deal with situations that shouldn't be possible (ex. if you clear the city, it should also clear the street and anything that relied upon it). These can be worked around, but increase the surface area for tests - if they could be represented as mutually exclusive then those situations wouldn't be possible, and would thus not need to be tested.

I'll consider pasting the relevant code into our project to try it out in a real world scenario, but I'm a bit hesitant to do so as it might put us in a situation where we're no longer able to easily update to future versions of the architecture.

stephencelis

comment created time in a month

issue commentpointfreeco/swift-composable-architecture

TicTacToe Crash When Interrupting 2FA Login

Should this be closed given that the fix has been merged? Or left open until a more general solution is available?

nmccann

comment created time in a month

issue commentpointfreeco/swift-composable-architecture

TicTacToe Crash When Interrupting 2FA Login

Thank you for the quick response and solution!

nmccann

comment created time in a month

Pull request review commentpointfreeco/swift-composable-architecture

Fix Tic Tac Toe Optional Bug

 public let twoFactorReducer = Reducer<TwoFactorState, TwoFactorAction, TwoFactor       .receive(on: environment.mainQueue)       .catchToEffect()       .map(TwoFactorAction.twoFactorResponse)+      .cancellable(id: TwoFactorTearDownToken())

Is it possible to have multiple cancellation tokens? For instance, one unique token that could be used for each type of effect in a reducer, and another "general" token that could be used to cancel everything? So for each effect you'd have to add a cancellable for both tokens.

That may not be much nicer than the enum approach you mentioned in another comment, but I'm just wondering if it's feasible.

stephencelis

comment created time in a month

PullRequestReviewEvent

Pull request review commentpointfreeco/swift-composable-architecture

Fix Tic Tac Toe Optional Bug

 public let loginReducer =        case .twoFactorDismissed:         state.twoFactor = nil-        return .none+        return .cancel(id: TwoFactorTearDownToken())

This approach works well for this particular case (that of a single long lived effect in an immediate child), but what about an alternative where you send an action to the child before nilling it out? Pseudocode:

case childDismissed:
send teardown action to child
then nil out state.child

This seems like it may be easier to extract to a higher order reducer (like the one that handles lifecycles). That also serves to encapsulate the teardown logic in the child. Also, if you had a deeply nested hierarchy it would allow the parent to tell it's child to teardown, which would in turn tell it's own child to teardown etc.

stephencelis

comment created time in a month

PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commentpointfreeco/swift-composable-architecture

Fix Tic Tac Toe Optional Bug

 public enum TwoFactorAction: Equatable {   case twoFactorResponse(Result<AuthenticationResponse, AuthenticationError>) } +public struct TwoFactorTearDownToken: Hashable {

I like the approach of using an enum - even if you aren't using it for the purposes of tearing things down, that seems preferable to having a large number of empty structs to handle multiple cancellations.

stephencelis

comment created time in a month

issue openedpointfreeco/swift-composable-architecture

TicTacToe Crash When Interrupting 2FA Login

Describe the bug When running the TicTacToe application, it is possible to interrupt a long lived side effect on the Two Factor Authentication screen, which results in a crash due to an assertion: Fatal error: "LoginAction.twoFactorDismissed" was received by an optional reducer when its state was "nil".

To Reproduce

  • Run TicTacToe example project
  • Select SwiftUI (though the same bug affects the UIKit version)
  • Login with an email containing 2fa
  • Enter 1234 code
  • Tap Submit
  • Before request completes, press "< Login" in the top left corner
  • App crashes due to fatal error

Expected behavior App should return to login screen without crashing

Environment

  • Xcode 11.7

Additional context This sounds similar to #259 , but isn't quite the same error. This also sounds similar to the issue discussed in the forums here, and might be able to be fixed by the solution described there.

This issue covers one of the biggest unknowns I have with this architecture - you're incentivized to break up your code into reducers that focus on a particular task and child reducers don't need to know about their parents, however if the parent has an optional child, then that child needs to expose some way of cancelling all active effects so that the parent can clean up after them (which, as a parent, that is pretty realistic but not super convenient).

It'd be nice to see a canonical example of handling an optional child with long lived side effects that could get interrupted/cancelled. And if explicitly sending a "cancel" action to the child is the expected process, is there a better way to scale that? For example, if you have an optional child which itself has optional children with side effects that need to be canceled, it gets complicated.

created time in a month

Pull request review commentpointfreeco/swift-composable-architecture

Added some tests for Shared State case study.

+import Combine+import ComposableArchitecture+import XCTest++@testable import SwiftUICaseStudies++class SharedStateTests: XCTestCase {+  func testTabRestoredOnReset() {+    let store = TestStore(+      initialState: SharedState(),+      reducer: sharedStateReducer,+      environment: ()+    )+    +    store.assert(+      .send(.selectTab(.profile)) {+        $0.currentTab = .profile+        $0.profile = .init(currentTab: .profile, count: 0, maxCount: 0, minCount: 0, numberOfCounts: 0)+      },+      .send(.profile(.resetCounterButtonTapped)) {+        $0.currentTab = .counter+        $0.profile = .init(currentTab: .counter, count: 0, maxCount: 0, minCount: 0, numberOfCounts: 0)+      })+  }++  func testTabSelection() {+    let store = TestStore(+      initialState: SharedState(),+      reducer: sharedStateReducer,+      environment: ()+    )++    store.assert(+      .send(.selectTab(.profile)) {+        $0.currentTab = .profile+        $0.profile = .init(currentTab: .profile, count: 0, maxCount: 0, minCount: 0, numberOfCounts: 0)+      },+      .send(.selectTab(.counter)) {+        $0.currentTab = .counter+        $0.profile = .init(currentTab: .counter, count: 0, maxCount: 0, minCount: 0, numberOfCounts: 0)+      })+  }++  func testSharedCounts() {+    let store = TestStore(+      initialState: SharedState(),+      reducer: sharedStateReducer,+      environment: ()+    )++    store.assert(+      .send(.counter(.incrementButtonTapped)) {+        $0.counter = .init(alert: nil, count: 1, maxCount: 1, minCount: 0, numberOfCounts: 1)+        $0.profile = .init(currentTab: .counter, count: 1, maxCount: 1, minCount: 0, numberOfCounts: 1)+      },+      .send(.counter(.decrementButtonTapped)) {+        $0.counter = .init(alert: nil, count: 0, maxCount: 1, minCount: 0, numberOfCounts: 2)+        $0.profile = .init(currentTab: .counter, count: 0, maxCount: 1, minCount: 0, numberOfCounts: 2)+      },+      .send(.counter(.decrementButtonTapped)) {+        $0.counter = .init(alert: nil, count: -1, maxCount: 1, minCount: -1, numberOfCounts: 3)+        $0.profile = .init(currentTab: .counter, count: -1, maxCount: 1, minCount: -1, numberOfCounts: 3)+      })

That is more readable, though the downside is that it doesn't mention the profile at all, so it may not be clear to readers that the changes to counter are also expected to change the profile.

nmccann

comment created time in 2 months

PullRequestReviewEvent

push eventnmccann/swift-composable-architecture

Noah McCann

commit sha ad1342e0a486c5a751184bb2193f7c2818d415c2

Modified tests to focus more on what is changing between steps

view details

push time in 2 months

Pull request review commentpointfreeco/swift-composable-architecture

Added some tests for Shared State case study.

+import Combine+import ComposableArchitecture+import XCTest++@testable import SwiftUICaseStudies++class SharedStateTests: XCTestCase {+  func testTabRestoredOnReset() {+    let store = TestStore(+      initialState: SharedState(),+      reducer: sharedStateReducer,+      environment: ()+    )+    +    store.assert(+      .send(.selectTab(.profile)) {+        $0.currentTab = .profile+        $0.profile = .init(currentTab: .profile, count: 0, maxCount: 0, minCount: 0, numberOfCounts: 0)+      },+      .send(.profile(.resetCounterButtonTapped)) {+        $0.currentTab = .counter+        $0.profile = .init(currentTab: .counter, count: 0, maxCount: 0, minCount: 0, numberOfCounts: 0)+      })+  }++  func testTabSelection() {+    let store = TestStore(+      initialState: SharedState(),+      reducer: sharedStateReducer,+      environment: ()+    )++    store.assert(+      .send(.selectTab(.profile)) {+        $0.currentTab = .profile+        $0.profile = .init(currentTab: .profile, count: 0, maxCount: 0, minCount: 0, numberOfCounts: 0)+      },+      .send(.selectTab(.counter)) {+        $0.currentTab = .counter+        $0.profile = .init(currentTab: .counter, count: 0, maxCount: 0, minCount: 0, numberOfCounts: 0)+      })+  }++  func testSharedCounts() {+    let store = TestStore(+      initialState: SharedState(),+      reducer: sharedStateReducer,+      environment: ()+    )++    store.assert(+      .send(.counter(.incrementButtonTapped)) {+        $0.counter = .init(alert: nil, count: 1, maxCount: 1, minCount: 0, numberOfCounts: 1)

Thank you for that clarification. I think this shows a bit of a flaw with doing shared state - it introduces a bit of "magic" that is hard to reason about. It's good that the architecture is able to support this, but I think I'd try hard to avoid using it in my own applications (or perhaps limit it to read only shared state, as opposed to bidirectional sharing, like the "reset profile" action does).

In regards to the assertions, I acknowledge that it is good to guide users towards more exhaustive tests, but that could also have the downside of users skipping some tests due to the hoops they would have to jump through to handle them.

Perhaps a future improvement could be an alternative less strict assert (maybe softAssert? fuzzyAssert? unsafeAssert?). That said, the testability of this architecture is a big reason why I became interested in it.

nmccann

comment created time in 2 months

PullRequestReviewEvent

Pull request review commentpointfreeco/swift-composable-architecture

Added some tests for Shared State case study.

+import Combine+import ComposableArchitecture+import XCTest++@testable import SwiftUICaseStudies++class SharedStateTests: XCTestCase {+  func testTabRestoredOnReset() {+    let store = TestStore(+      initialState: SharedState(),+      reducer: sharedStateReducer,+      environment: ()+    )+    +    store.assert(+      .send(.selectTab(.profile)) {+        $0.currentTab = .profile+        $0.profile = .init(currentTab: .profile, count: 0, maxCount: 0, minCount: 0, numberOfCounts: 0)+      },+      .send(.profile(.resetCounterButtonTapped)) {+        $0.currentTab = .counter+        $0.profile = .init(currentTab: .counter, count: 0, maxCount: 0, minCount: 0, numberOfCounts: 0)+      })+  }++  func testTabSelection() {+    let store = TestStore(+      initialState: SharedState(),+      reducer: sharedStateReducer,+      environment: ()+    )++    store.assert(+      .send(.selectTab(.profile)) {+        $0.currentTab = .profile+        $0.profile = .init(currentTab: .profile, count: 0, maxCount: 0, minCount: 0, numberOfCounts: 0)+      },+      .send(.selectTab(.counter)) {+        $0.currentTab = .counter+        $0.profile = .init(currentTab: .counter, count: 0, maxCount: 0, minCount: 0, numberOfCounts: 0)+      })+  }++  func testSharedCounts() {+    let store = TestStore(+      initialState: SharedState(),+      reducer: sharedStateReducer,+      environment: ()+    )++    store.assert(+      .send(.counter(.incrementButtonTapped)) {+        $0.counter = .init(alert: nil, count: 1, maxCount: 1, minCount: 0, numberOfCounts: 1)

Is there a way to send an action to the store without having to assert the changes it makes? I was originally sending a few actions, and only checking the state on the last action, but found that the tests failed because each send step said the state had unexpectedly changed. As a result I opted to check the state for each action, which is arguably more correct, but it would still be nice to do such things.

Oddly I was able to bypass the test failure by only checking the state of the counter or the profile, I didn't have to do both - I'm not quite sure why that is (though I ended up checking both anyways since that seems more correct, and makes it more obvious that the two are mirroring one another).

By this I mean:

.send(.counter(.incrementButtonTapped)) //This by itself caused a test failure due to unexpected state change

.send(.counter(.incrementButtonTapped)) {
$0.counter = .init(alert: nil, count: 1, maxCount: 1, minCount: 0, numberOfCounts: 1)
} //This didn't have a test failure, even though the profile also changed but wasn't explicitly checked

.send(.counter(.incrementButtonTapped)) {
$0.profile = .init(currentTab: .counter, count: 1, maxCount: 1, minCount: 0, numberOfCounts: 1)
} //This also didn't have a test failure, even though I didn't explicitly check the counter state, which also changed.

I'm confused why I can't just omit the closure altogether, yet I can just omit one of the state checks.

nmccann

comment created time in 2 months

PullRequestReviewEvent

push eventnmccann/swift-composable-architecture

Noah McCann

commit sha 4a91ef88a4d18d5d4376dc565d39919b5f43b8ab

Expanded upon tests by also validating that state is mirrored State is expected to be mirrored (or shared) between the Counter and Profile - the tests have been updated to verify this.

view details

push time in 2 months

pull request commentpointfreeco/swift-composable-architecture

Added some tests for Shared State case study.

You're absolutely right - I'll push a commit with additional tests using that method.

nmccann

comment created time in 2 months

Pull request review commentpointfreeco/swift-composable-architecture

Added some tests for Shared State case study.

+import Combine+import ComposableArchitecture+import XCTest++@testable import SwiftUICaseStudies++class SharedStateTests: XCTestCase {+  func testTabRestoredOnReset() {+    let store = TestStore(+      initialState: SharedState(),+      reducer: sharedStateReducer,+      environment: ()+    )+    +    store.assert(+      .send(.selectTab(.profile)) {+        $0.currentTab = .profile+      },+      .send(.profile(.resetCounterButtonTapped)) {+        $0.currentTab = .counter+      })+  }++  func testTabSelection() {+    let store = TestStore(+      initialState: SharedState(),+      reducer: sharedStateReducer,+      environment: ()+    )++    store.assert(+      .send(.selectTab(.profile)) {+        $0.currentTab = .profile+      },+      .send(.selectTab(.counter)) {+        $0.currentTab = .counter+      })+  }++  func testIsPrimeWhenPrime() {+    let store = TestStore(+      initialState: SharedState.CounterState(alert: nil, count: 3, maxCount: 0, minCount: 0, numberOfCounts: 0),+      reducer: sharedStateCounterReducer,+      environment: ()+    )++    store.assert(+      .send(.isPrimeButtonTapped) {+        $0.alert = .init(+          title: "👍 The number \($0.count) is prime!"+        )+      },+      .send(.alertDismissed) {+        $0.alert = nil+      }+    )+  }++  func testIsPrimeWhenNotPrime() {+    let store = TestStore(+      initialState: SharedState.CounterState(alert: nil, count: 6, maxCount: 0, minCount: 0, numberOfCounts: 0),+      reducer: sharedStateCounterReducer,+      environment: ()+    )++    store.assert(+      .send(.isPrimeButtonTapped) {+        $0.alert = .init(

Another case where it might be nice to have a test Step that allows delegation to XCTAssert with access to the state (doesn't need to be XCTAssert, could just return true if step successful/false if not), is to allow fuzzy matching - ex. if you want to check that alert has some value, but you don't care exactly what value. This could allow for tests to be less brittle (exact wording of alert doesn't matter), or to delegate that precision to other tests (ex. one test already checks the exact wording, another test is checking something that requires the alert to be presented but doesn't care about the wording).

nmccann

comment created time in 2 months

PullRequestReviewEvent

PR opened pointfreeco/swift-composable-architecture

Added some tests for Shared State case study.

This is a follow up to #260 - rather than removing something that I (erroneously) thought was unused, I've added tests to verify it's behaviour.

I attempted to write some other tests for the shared behaviour, for example by incrementing the counter, then asserting that the profile's count has the correct value, but was unable to test this because the setters for ProfileState are private. I had two ideas to address this:

  1. Remove the private access modifier (but they may be there for some reason - ex. to further indicate that these should just "mirror" state from elsewhere rather than being changed directly)
  2. Add a new type of Step that allows you to delegate to XCTAssert, similar to do but it would pass in the current state, allowing you to assert it's current values without having to construct a new instance of the state. Not sure how feasible/desirable this is however.
+84 -3

0 comment

3 changed files

pr created time in 2 months

create barnchnmccann/swift-composable-architecture

branch : add-tests

created branch time in 2 months

PR closed pointfreeco/swift-composable-architecture

Removed unused `currentTab` property

While reviewing how to handle shared state, I was confused as to why a nested state object was being passed the currentTab, and found that it was being unused. I could see some valid use cases in which you'd need to do this, but in all such cases I'd expect the nested state (or one of it's children) to use the property - so I'm guessing this was unintentional.

+0 -4

2 comments

1 changed file

nmccann

pr closed time in 2 months

pull request commentpointfreeco/swift-composable-architecture

Removed unused `currentTab` property

Ah, you are totally right. That isn’t particularly obvious, but I should have realized it was intentional. I’ll close this PR and I’ll open another one that adds a test case at least for this particular situation (to avoid anyone else thinking something similar in the future), and I’ll try to add test cases for the other functionality if time allows.

Thank you again for your work on this architecture!

nmccann

comment created time in 2 months

PR opened pointfreeco/swift-composable-architecture

Removed unused `currentTab` property

While reviewing how to handle shared state, I was confused as to why a nested state object was being passed the currentTab, and found that it was being unused. I could see some valid use cases in which you'd need to do this, but in all such cases I'd expect the nested state (or one of it's children) to use the property - so I'm guessing this was unintentional.

+0 -4

0 comment

1 changed file

pr created time in 2 months

create barnchnmccann/swift-composable-architecture

branch : remove-unused

created branch time in 2 months

fork nmccann/swift-composable-architecture

A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.

https://www.pointfree.co/

fork in 2 months

more