Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix regression: remove BindableAction dynamic case lookup #2886

Merged
merged 1 commit into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ final class BindingFormTests: XCTestCase {
BindingForm()
}

await store.send(\.sliderValue, 2) {
await store.send(\.binding.sliderValue, 2) {
$0.sliderValue = 2
}
await store.send(\.stepCount, 1) {
await store.send(\.binding.stepCount, 1) {
$0.sliderValue = 1
$0.stepCount = 1
}
await store.send(\.text, "Blob") {
await store.send(\.binding.text, "Blob") {
$0.text = "Blob"
}
await store.send(\.toggleIsOn, true) {
await store.send(\.binding.toggleIsOn, true) {
$0.toggleIsOn = true
}
await store.send(.resetButtonTapped) {
Expand Down
2 changes: 1 addition & 1 deletion Examples/SyncUps/SyncUpsTests/AppFeatureTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ final class AppFeatureTests: XCTestCase {
}

syncUp.title = "Blob"
await store.send(\.path[id:0].detail.destination.edit.syncUp, syncUp) {
await store.send(\.path[id:0].detail.destination.edit.binding.syncUp, syncUp) {
$0.path[id: 0]?.detail?.destination?.edit?.syncUp.title = "Blob"
}

Expand Down
2 changes: 1 addition & 1 deletion Examples/SyncUps/SyncUpsTests/SyncUpDetailTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ final class SyncUpDetailTests: XCTestCase {
}

syncUp.title = "Blob's Meeting"
await store.send(\.destination.edit.syncUp, syncUp) {
await store.send(\.destination.edit.binding.syncUp, syncUp) {
$0.destination?.edit?.syncUp.title = "Blob's Meeting"
}

Expand Down
2 changes: 1 addition & 1 deletion Examples/SyncUps/SyncUpsTests/SyncUpsListTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ final class SyncUpsListTests: XCTestCase {
}

syncUp.title = "Engineering"
await store.send(\.destination.add.syncUp, syncUp) {
await store.send(\.destination.add.binding.syncUp, syncUp) {
$0.destination?.add?.syncUp.title = "Engineering"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ final class AppCoreTests: XCTestCase {
}
}

await store.send(\.login.view.email, "blob@pointfree.co") {
await store.send(\.login.view.binding.email, "blob@pointfree.co") {
$0.login?.email = "blob@pointfree.co"
}
await store.send(\.login.view.password, "bl0bbl0b") {
await store.send(\.login.view.binding.password, "bl0bbl0b") {
$0.login?.password = "bl0bbl0b"
$0.login?.isFormValid = true
}
Expand All @@ -30,7 +30,7 @@ final class AppCoreTests: XCTestCase {
await store.receive(\.login.loginResponse.success) {
$0 = .newGame(NewGame.State())
}
await store.send(\.newGame.oPlayerName, "Blob Sr.") {
await store.send(\.newGame.binding.oPlayerName, "Blob Sr.") {
$0.newGame?.oPlayerName = "Blob Sr."
}
await store.send(\.newGame.logoutButtonTapped) {
Expand All @@ -50,11 +50,11 @@ final class AppCoreTests: XCTestCase {
}
}

await store.send(\.login.view.email, "blob@pointfree.co") {
await store.send(\.login.view.binding.email, "blob@pointfree.co") {
$0.login?.email = "blob@pointfree.co"
}

await store.send(\.login.view.password, "bl0bbl0b") {
await store.send(\.login.view.binding.password, "bl0bbl0b") {
$0.login?.password = "bl0bbl0b"
$0.login?.isFormValid = true
}
Expand All @@ -67,7 +67,7 @@ final class AppCoreTests: XCTestCase {
$0.login?.twoFactor = TwoFactor.State(token: "deadbeef")
}

await store.send(\.login.twoFactor.view.code, "1234") {
await store.send(\.login.twoFactor.view.binding.code, "1234") {
$0.login?.twoFactor?.code = "1234"
$0.login?.twoFactor?.isFormValid = true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ final class LoginCoreTests: XCTestCase {
}
}

await store.send(\.view.email, "2fa@pointfree.co") {
await store.send(\.view.binding.email, "2fa@pointfree.co") {
$0.email = "2fa@pointfree.co"
}
await store.send(\.view.password, "password") {
await store.send(\.view.binding.password, "password") {
$0.password = "password"
$0.isFormValid = true
}
Expand All @@ -32,7 +32,7 @@ final class LoginCoreTests: XCTestCase {
$0.isLoginRequestInFlight = false
$0.twoFactor = TwoFactor.State(token: "deadbeefdeadbeef")
}
await store.send(\.twoFactor.view.code, "1234") {
await store.send(\.twoFactor.view.binding.code, "1234") {
$0.twoFactor?.code = "1234"
$0.twoFactor?.isFormValid = true
}
Expand All @@ -58,10 +58,10 @@ final class LoginCoreTests: XCTestCase {
}
}

await store.send(\.view.email, "2fa@pointfree.co") {
await store.send(\.view.binding.email, "2fa@pointfree.co") {
$0.email = "2fa@pointfree.co"
}
await store.send(\.view.password, "password") {
await store.send(\.view.binding.password, "password") {
$0.password = "password"
$0.isFormValid = true
}
Expand All @@ -72,7 +72,7 @@ final class LoginCoreTests: XCTestCase {
$0.isLoginRequestInFlight = false
$0.twoFactor = TwoFactor.State(token: "deadbeefdeadbeef")
}
await store.send(\.twoFactor.view.code, "1234") {
await store.send(\.twoFactor.view.binding.code, "1234") {
$0.twoFactor?.code = "1234"
$0.twoFactor?.isFormValid = true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ final class NewGameCoreTests: XCTestCase {
}

func testFlow_NewGame_Integration() async {
await self.store.send(\.oPlayerName, "Blob Sr.") {
await self.store.send(\.binding.oPlayerName, "Blob Sr.") {
$0.oPlayerName = "Blob Sr."
}
await self.store.send(\.xPlayerName, "Blob Jr.") {
await self.store.send(\.binding.xPlayerName, "Blob Jr.") {
$0.xPlayerName = "Blob Jr."
}
await self.store.send(.letsPlayButtonTapped) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ final class TwoFactorCoreTests: XCTestCase {
}
}

await store.send(\.view.code, "1") {
await store.send(\.view.binding.code, "1") {
$0.code = "1"
}
await store.send(\.view.code, "12") {
await store.send(\.view.binding.code, "12") {
$0.code = "12"
}
await store.send(\.view.code, "123") {
await store.send(\.view.binding.code, "123") {
$0.code = "123"
}
await store.send(\.view.code, "1234") {
await store.send(\.view.binding.code, "1234") {
$0.code = "1234"
$0.isFormValid = true
}
Expand All @@ -44,7 +44,7 @@ final class TwoFactorCoreTests: XCTestCase {
}
}

await store.send(\.view.code, "1234") {
await store.send(\.view.binding.code, "1234") {
$0.code = "1234"
$0.isFormValid = true
}
Expand Down
18 changes: 9 additions & 9 deletions Examples/Todos/TodosTests/TodosTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ final class TodosTests: XCTestCase {
Todos()
}

await store.send(\.todos[id:UUID(0)].description, "Learn Composable Architecture") {
await store.send(\.todos[id:UUID(0)].binding.description, "Learn Composable Architecture") {
$0.todos[id: UUID(0)]?.description = "Learn Composable Architecture"
}
}
Expand All @@ -83,7 +83,7 @@ final class TodosTests: XCTestCase {
$0.continuousClock = self.clock
}

await store.send(\.todos[id:UUID(0)].isComplete, true) {
await store.send(\.todos[id:UUID(0)].binding.isComplete, true) {
$0.todos[id: UUID(0)]?.isComplete = true
}
await self.clock.advance(by: .seconds(1))
Expand Down Expand Up @@ -117,11 +117,11 @@ final class TodosTests: XCTestCase {
$0.continuousClock = self.clock
}

await store.send(\.todos[id:UUID(0)].isComplete, true) {
await store.send(\.todos[id:UUID(0)].binding.isComplete, true) {
$0.todos[id: UUID(0)]?.isComplete = true
}
await self.clock.advance(by: .milliseconds(500))
await store.send(\.todos[id:UUID(0)].isComplete, false) {
await store.send(\.todos[id:UUID(0)].binding.isComplete, false) {
$0.todos[id: UUID(0)]?.isComplete = false
}
await self.clock.advance(by: .seconds(1))
Expand Down Expand Up @@ -249,7 +249,7 @@ final class TodosTests: XCTestCase {
$0.continuousClock = self.clock
}

await store.send(\.editMode, .active) {
await store.send(\.binding.editMode, .active) {
$0.editMode = .active
}
await store.send(.move([0], 2)) {
Expand Down Expand Up @@ -296,10 +296,10 @@ final class TodosTests: XCTestCase {
$0.uuid = .incrementing
}

await store.send(\.editMode, .active) {
await store.send(\.binding.editMode, .active) {
$0.editMode = .active
}
await store.send(\.filter, .completed) {
await store.send(\.binding.filter, .completed) {
$0.filter = .completed
}
await store.send(.move([0], 2)) {
Expand Down Expand Up @@ -334,10 +334,10 @@ final class TodosTests: XCTestCase {
Todos()
}

await store.send(\.filter, .completed) {
await store.send(\.binding.filter, .completed) {
$0.filter = .completed
}
await store.send(\.todos[id:UUID(1)].description, "Did this already") {
await store.send(\.todos[id:UUID(1)].binding.description, "Did this already") {
$0.todos[id: UUID(1)]?.description = "Did this already"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,10 +295,10 @@ let store = TestStore(initialState: Settings.State()) {
Settings()
}

store.send(\.displayName, "Blob") {
store.send(\.binding.displayName, "Blob") {
$0.displayName = "Blob"
}
store.send(\.protectMyPosts, true) {
store.send(\.binding.protectMyPosts, true) {
$0.protectMyPosts = true
)
```
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ features, and provide symmetry to how actions are received:
> +store.send(\.path[id: 0].tap)
> ```
>
> * And ``BindableAction``s can dynamically chain into a key path of state:
> * And ``BindingAction``s can dynamically chain into a key path of state:
>
> ```diff
> -store.send(.binding(.set(\.firstName, "Blob")))
> +store.send(\.firstName, "Blob")
> +store.send(\.binding.firstName, "Blob")
> ```
>
> Together, these helpers can massively simplify asserting against nested actions:
Expand All @@ -81,7 +81,7 @@ features, and provide symmetry to how actions are received:
> - )
> - )
> -)
> +store.send(\.path[id: 0].destination.sheet.password, "blobisawesome")
> +store.send(\.path[id: 0].destination.sheet.binding.password, "blobisawesome")
> ```

### Overriding dependencies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -684,18 +684,10 @@ lines and is more resilient to future changes in the features that we don't nece
That is the basics of testing, but things get a little more complicated when you leverage the
concepts outlined in <doc:TreeBasedNavigation#Enum-state> in which you model multiple destinations
as an enum instead of multiple optionals. In order to assert on state changes when using enum
state you must be able to extract the associated state from the enum, make a mutation, and then
embed the new state back into the enum.

The library provides a tool to perform these steps in a single step. It's the
``PresentationState/subscript(case:)-7uqte`` defined on ``PresentationState`` which allows you to
modify the data inside a case of the destination enum:
state you must chain into the particular case to make a mutation:

```swift
await store.send(\.destination.counter.incrementButtonTapped) {
$0.$destination[case: \.counter]?.count = 4
$0.destination?.counter?.count = 4
}
```

Further, if `destination` is not of the `.counter` case when this test runs, then it will trigger
a test failure letting you know that you cannot modify an unrelated case.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class ContactsFeatureTests: XCTestCase {
)
}
await store.send(\.destination.addContact.setName, "Blob Jr.") {
$0.$destination[case: \.addContact]?.contact.name = "Blob Jr."
$0.destination?.addContact?.contact.name = "Blob Jr."
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class ContactsFeatureTests: XCTestCase {
)
}
await store.send(\.destination.addContact.setName, "Blob Jr.") {
$0.$destination[case: \.addContact]?.contact.name = "Blob Jr."
$0.destination?.addContact?.contact.name = "Blob Jr."
}
await store.send(\.destination.addContact.saveButtonTapped)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class ContactsFeatureTests: XCTestCase {
)
}
await store.send(\.destination.addContact.setName, "Blob Jr.") {
$0.$destination[case: \.addContact]?.contact.name = "Blob Jr."
$0.destination?.addContact?.contact.name = "Blob Jr."
}
await store.send(\.destination.addContact.saveButtonTapped)
await store.receive(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class ContactsFeatureTests: XCTestCase {
)
}
await store.send(\.destination.addContact.setName, "Blob Jr.") {
$0.$destination[case: \.addContact]?.contact.name = "Blob Jr."
$0.destination?.addContact?.contact.name = "Blob Jr."
}
await store.send(\.destination.addContact.saveButtonTapped)
await store.receive(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class ContactsFeatureTests: XCTestCase {
)
}
await store.send(\.destination.addContact.setName, "Blob Jr.") {
$0.$destination[case: \.addContact]?.contact.name = "Blob Jr."
$0.destination?.addContact?.contact.name = "Blob Jr."
}
await store.send(\.destination.addContact.saveButtonTapped)
await store.receive(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class ContactsFeatureTests: XCTestCase {
)
}
await store.send(\.destination.addContact.setName, "Blob Jr.") {
$0.$destination[case: \.addContact]?.contact.name = "Blob Jr."
$0.destination?.addContact?.contact.name = "Blob Jr."
}
await store.send(\.destination.addContact.saveButtonTapped)
await store.receive(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,12 @@
Assert on how state changed by mutating the `destination` through the `addContact` case of
the destination enum.

To do this we use a special subscript defined on
``ComposableArchitecture/PresentationState`` that allows you to modify the data in the case
of any enum.
To do this we can chain into the `addContact` case name directly and mutate a part of its
associated value.

> Tip: See ``ComposableArchitecture/PresentationState/subscript(case:)-7uqte`` for
> documentation on this subscript.
> Tip: To chain into an enum and mutate an associated value, the enum must be annotated with
> `@CasePathable` _and_ `@dynamicMemberLookup`. The `@Reducer` macro automatically applies
> these annotations to enum-based `State`, but you must manually apply it to other enums.

@Code(name: "ContactsFeatureTests.swift", file: 02-03-01-code-0011.swift)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,16 +222,4 @@
}
}
}

extension Case where Value: BindableAction, Value.State: ObservableState {
@_disfavoredOverload
public subscript<Member: Equatable & Sendable>(
dynamicMember keyPath: WritableKeyPath<Value.State, Member>
) -> Case<Member> {
Case<Member>(
embed: { self.embed(.binding(.set(keyPath, $0))) },
extract: { self.extract(from: $0)?.binding?.value.base as? Member }
)
}
}
#endif