* Deprecate `Shared`'s optional dynamic member lookup overload
Currently, `Shared`'s dynamic member lookup is overloaded for
convenience:
```swift
$shared // Shared<Root>
$shared.value // Shared<Value>
$shared.optionalValue // Shared<Value>?
```
Unfortunately, this is also perhaps surprising, and goes against the
grain when folks might expect to be handed a `Shared<Value?>`.
Also, there are times when you have a `Shared<Value?>` already, and you
want to unwrap it. Dynamic member lookup doesn't help there (unless you
know you can call `$shared[dynamicMember: \.self]`), and so you need a
totally different operation to handle it:
```swift
if let unwrappedShared = Shared($optional) {
// Do something with 'Shared<Value>'
}
```
So let's avoid this confusion in the future and focus on a single API
for unwrapping shared values, which is the failable initializer that
mirrors an API on `Binding`.
* wip
* Update SharedReader.swift
* Update Shared.swift
* Update SharedReader.swift
* Add `ifLet` SwiftUI integration warning
We recently added a warning when we detect that `forEach` wasn't
integrated with a reducer when path elements are pushed or popped from
the stack, and I realized that we could do similarly for `ifLet` during
dismissal.
* docc
* Add `ViewStore.binding` migration examples to guide
The 1.7 guide doesn't have examples of more complex bindings, so let's
add one.
* fix
* wip
* fix
* Added autoclosure around default value for `PersistenceKeyDefault`
* Added autoclosure around default value for `PersistenceKeyDefault`
---------
Co-authored-by: Sean <sean@snowfort.software>
* Precondition: persistence keys must match value
We currently soft-fail if a persistence key is reused across different
types. At runtime a persistence key will write over the other's value,
and vice versa, and data is lost.
Reusing the same file storage URL or app storage key with conflicting
types is a programmer error and ideally should crash over data
corruption.
* wip
* wip
* Have `store.finish()` assert no received actions
`store.finish()` should do much of the same work as `store.deinit`, but
asynchronously with a timeout. This PR updates things so that folks with
long-living test stores (_e.g._ held onto by the test case) have the
ability to assert that there are no unreceived actions.
* test
* Add docs about long-living stores
* Runtime warn when reducer is missing stack integration
If SwiftUI writes to a binding and the state is invalid (it is not
inserting or removing elements from the stack), then the most likely
cause is a missing `forEach` in the reducer (or a missing integration in
some parent reducer).
This commit adds a runtime warning to detect this issue.
* wip
* Cancel `TestStore` effects when root feature is dismissed
Right now, if a leaf feature is tested and it dismisses itself while
effects are in-flight, the only way to get tests passing is to
explicitly tell the store or any tasks returned by `store.send` to
cancel. Further, you must use an expectation in such tests to assert
that `dismiss` was called in the first place.
This PR changes this behavior to automatically cancel a test store's
effects if it is dismissed, and it prohibits sending more actions to it
after dismiss has been called.
* wip
* wip
* Add `@CasePathable @dynamicMemberLookup` to 1.4 migration guide
This came up recently and I was hoping to point to a part of the
migration guide that explained it, but it didn't exist.
* wip
* wip
Currently, popping two layers in a nav tree will cause intermediate,
invalid stores to send `dismiss` actions when they shouldn't. This
branch avoids this by checking the store's validity before sending the
action along.
Fixes#3031.