-
-
Notifications
You must be signed in to change notification settings - Fork 18
Previous modifier #198
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
Merged
Merged
Previous modifier #198
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| public extension Atom { | ||
| /// Provides the previous value of the atom instead of the current value. | ||
| /// | ||
| /// ```swift | ||
| /// struct CounterAtom: StateAtom, Hashable { | ||
| /// func defaultValue(context: Context) -> Int { | ||
| /// 0 | ||
| /// } | ||
| /// } | ||
| /// | ||
| /// struct ExampleView: View { | ||
| /// @Watch(CounterAtom()) | ||
| /// var currentValue | ||
| /// | ||
| /// @Watch(CounterAtom().previous) | ||
| /// var previousValue | ||
| /// | ||
| /// var body: some View { | ||
| /// VStack { | ||
| /// Text("Current: \(currentValue)") | ||
| /// Text("Previous: \(previousValue ?? 0)") | ||
| /// } | ||
| /// } | ||
| /// } | ||
| /// ``` | ||
| /// | ||
| var previous: ModifiedAtom<Self, PreviousModifier<Produced>> { | ||
| modifier(PreviousModifier()) | ||
| } | ||
| } | ||
|
|
||
| /// A modifier that provides the previous value of the atom instead of the current value. | ||
| /// | ||
| /// Use ``Atom/previous`` instead of using this modifier directly. | ||
| public struct PreviousModifier<Base>: AtomModifier { | ||
| /// A type of base value to be modified. | ||
| public typealias Base = Base | ||
|
|
||
| /// A type of value the modified atom produces. | ||
| public typealias Produced = Base? | ||
|
|
||
| /// A type representing the stable identity of this atom associated with an instance. | ||
| public struct Key: Hashable, Sendable {} | ||
|
|
||
| /// A unique value used to identify the modifier internally. | ||
| public var key: Key { | ||
| Key() | ||
| } | ||
|
|
||
| /// A producer that produces the value of this atom. | ||
| public func producer(atom: some Atom<Base>) -> AtomProducer<Produced> { | ||
| AtomProducer { context in | ||
| context.transaction { context in | ||
| let value = context.watch(atom) | ||
| let storage = context.watch(StorageAtom()) | ||
| let previous = storage.previous | ||
| storage.previous = value | ||
| return previous | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private extension PreviousModifier { | ||
| @MainActor | ||
| final class Storage { | ||
| var previous: Base? | ||
| } | ||
|
|
||
| struct StorageAtom: ValueAtom, Hashable { | ||
| func value(context: Context) -> Storage { | ||
| Storage() | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| import XCTest | ||
|
|
||
| @testable import Atoms | ||
|
|
||
| final class PreviousModifierTests: XCTestCase { | ||
| @MainActor | ||
| func testPrevious() { | ||
| let atom = TestStateAtom(defaultValue: "initial") | ||
| let context = AtomTestContext() | ||
|
|
||
| XCTAssertNil(context.watch(atom.previous)) | ||
|
|
||
| // Update the atom value | ||
| context[atom] = "second" | ||
|
|
||
| // Now previous should return the initial value | ||
| XCTAssertEqual(context.watch(atom.previous), "initial") | ||
|
|
||
| // Update again | ||
| context[atom] = "third" | ||
|
|
||
| // Previous should now return "second" | ||
| XCTAssertEqual(context.watch(atom.previous), "second") | ||
|
|
||
| // Another update | ||
| context[atom] = "fourth" | ||
|
|
||
| // Previous should now return "third" | ||
| XCTAssertEqual(context.watch(atom.previous), "third") | ||
| } | ||
|
|
||
| @MainActor | ||
| func testPreviousWithMultipleWatchers() { | ||
| let atom = TestStateAtom(defaultValue: 100) | ||
| let context = AtomTestContext() | ||
|
|
||
| // Watch both current and previous | ||
| XCTAssertEqual(context.watch(atom), 100) | ||
| XCTAssertNil(context.watch(atom.previous)) | ||
|
|
||
| // Update the value | ||
| context[atom] = 200 | ||
|
|
||
| // Check both watchers | ||
| XCTAssertEqual(context.watch(atom), 200) | ||
| XCTAssertEqual(context.watch(atom.previous), 100) | ||
|
|
||
| // Update again | ||
| context[atom] = 300 | ||
|
|
||
| XCTAssertEqual(context.watch(atom), 300) | ||
| XCTAssertEqual(context.watch(atom.previous), 200) | ||
| } | ||
|
|
||
| @MainActor | ||
| func testPreviousUpdatesDownstream() { | ||
| let atom = TestStateAtom(defaultValue: 0) | ||
| let context = AtomTestContext() | ||
| var updatedCount = 0 | ||
|
|
||
| context.onUpdate = { | ||
| updatedCount += 1 | ||
| } | ||
|
|
||
| // Initial watch | ||
| XCTAssertEqual(updatedCount, 0) | ||
| XCTAssertNil(context.watch(atom.previous)) | ||
|
|
||
| // First update | ||
| context[atom] = 1 | ||
| XCTAssertEqual(updatedCount, 1) | ||
| XCTAssertEqual(context.watch(atom.previous), 0) | ||
|
|
||
| // Second update | ||
| context[atom] = 2 | ||
| XCTAssertEqual(updatedCount, 2) | ||
| XCTAssertEqual(context.watch(atom.previous), 1) | ||
| } | ||
|
|
||
| @MainActor | ||
| func testKey() { | ||
| let modifier = PreviousModifier<Int>() | ||
|
|
||
| XCTAssertEqual(modifier.key, modifier.key) | ||
| XCTAssertEqual(modifier.key.hashValue, modifier.key.hashValue) | ||
| } | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.