Skip to content

Commit 7dc2744

Browse files
authored
Do not override custom reset & refresh behavior (#116)
1 parent bc2615c commit 7dc2744

File tree

4 files changed

+162
-126
lines changed

4 files changed

+162
-126
lines changed

Sources/Atoms/Core/StoreContext.swift

Lines changed: 44 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ internal struct StoreContext {
7272
return cache.value
7373
}
7474
else {
75-
let cache = makeNewCache(of: atom, for: key, override: override)
75+
let cache = makeCache(of: atom, for: key, override: override)
7676
notifyUpdateToObservers()
7777

7878
if checkAndRelease(for: key) {
@@ -116,13 +116,13 @@ internal struct StoreContext {
116116
let override = lookupOverride(of: atom)
117117
let scopeKey = lookupScopeKey(of: atom, isScopedOverriden: override?.isScoped ?? false)
118118
let key = AtomKey(atom, scopeKey: scopeKey)
119-
let newCache = lookupCache(of: atom, for: key) ?? makeNewCache(of: atom, for: key, override: override)
119+
let cache = getCache(of: atom, for: key, override: override)
120120

121121
// Add an `Edge` from the upstream to downstream.
122122
store.graph.dependencies[transaction.key, default: []].insert(key)
123123
store.graph.children[key, default: []].insert(transaction.key)
124124

125-
return newCache.value
125+
return cache.value
126126
}
127127

128128
@usableFromInline
@@ -135,7 +135,7 @@ internal struct StoreContext {
135135
let override = lookupOverride(of: atom)
136136
let scopeKey = lookupScopeKey(of: atom, isScopedOverriden: override?.isScoped ?? false)
137137
let key = AtomKey(atom, scopeKey: scopeKey)
138-
let newCache = lookupCache(of: atom, for: key) ?? makeNewCache(of: atom, for: key, override: override)
138+
let cache = getCache(of: atom, for: key, override: override)
139139
let subscription = Subscription(
140140
location: subscriber.location,
141141
requiresObjectUpdate: requiresObjectUpdate,
@@ -152,7 +152,7 @@ internal struct StoreContext {
152152
notifyUpdateToObservers()
153153
}
154154

155-
return newCache.value
155+
return cache.value
156156
}
157157

158158
@usableFromInline
@@ -161,7 +161,8 @@ internal struct StoreContext {
161161
let override = lookupOverride(of: atom)
162162
let scopeKey = lookupScopeKey(of: atom, isScopedOverriden: override?.isScoped ?? false)
163163
let key = AtomKey(atom, scopeKey: scopeKey)
164-
let context = prepareForTransaction(of: atom, for: key)
164+
let state = lookupState(of: atom, for: key) ?? makeEphemeralState(of: atom)
165+
let context = prepareForTransaction(of: atom, for: key, state: state)
165166
let value: Node.Loader.Value
166167

167168
if let override {
@@ -172,9 +173,6 @@ internal struct StoreContext {
172173
}
173174

174175
guard let cache = lookupCache(of: atom, for: key) else {
175-
// Release the temporarily created state.
176-
// Do not notify update to observers here because refresh doesn't create a new cache.
177-
release(for: key)
178176
return value
179177
}
180178

@@ -191,21 +189,11 @@ internal struct StoreContext {
191189
let override = lookupOverride(of: atom)
192190
let scopeKey = lookupScopeKey(of: atom, isScopedOverriden: override?.isScoped ?? false)
193191
let key = AtomKey(atom, scopeKey: scopeKey)
194-
let state = getState(of: atom, for: key)
195-
let value: Node.Loader.Value
196-
197-
if let override {
198-
value = override.value(atom)
199-
}
200-
else {
201-
let context = AtomCurrentContext(store: self, coordinator: state.coordinator)
202-
value = await atom.refresh(context: context)
203-
}
192+
let state = lookupState(of: atom, for: key) ?? makeEphemeralState(of: atom)
193+
let context = AtomCurrentContext(store: self, coordinator: state.coordinator)
194+
let value = await atom.refresh(context: context)
204195

205196
guard let transaction = state.transaction, let cache = lookupCache(of: atom, for: key) else {
206-
// Release the temporarily created state.
207-
// Do not notify update to observers here because refresh doesn't create a new cache.
208-
release(for: key)
209197
return value
210198
}
211199

@@ -225,7 +213,7 @@ internal struct StoreContext {
225213
let key = AtomKey(atom, scopeKey: scopeKey)
226214

227215
if let cache = lookupCache(of: atom, for: key) {
228-
let newCache = makeNewCache(of: atom, for: key, override: override)
216+
let newCache = makeCache(of: atom, for: key, override: override)
229217
update(atom: atom, for: key, value: newCache.value, cache: cache, order: .newValue)
230218
}
231219
}
@@ -235,17 +223,10 @@ internal struct StoreContext {
235223
let override = lookupOverride(of: atom)
236224
let scopeKey = lookupScopeKey(of: atom, isScopedOverriden: override?.isScoped ?? false)
237225
let key = AtomKey(atom, scopeKey: scopeKey)
226+
let state = lookupState(of: atom, for: key) ?? makeEphemeralState(of: atom)
227+
let context = AtomCurrentContext(store: self, coordinator: state.coordinator)
238228

239-
guard let override else {
240-
let state = getState(of: atom, for: key)
241-
let context = AtomCurrentContext(store: self, coordinator: state.coordinator)
242-
return atom.reset(context: context)
243-
}
244-
245-
if let cache = lookupCache(of: atom, for: key) {
246-
let newCache = makeNewCache(of: atom, for: key, override: override)
247-
update(atom: atom, for: key, value: newCache.value, cache: cache, order: .newValue)
248-
}
229+
atom.reset(context: context)
249230
}
250231

251232
@usableFromInline
@@ -318,9 +299,11 @@ internal struct StoreContext {
318299
}
319300

320301
private extension StoreContext {
321-
func prepareForTransaction<Node: Atom>(of atom: Node, for key: AtomKey) -> AtomLoaderContext<Node.Loader.Value, Node.Loader.Coordinator> {
322-
let state = getState(of: atom, for: key)
323-
302+
func prepareForTransaction<Node: Atom>(
303+
of atom: Node,
304+
for key: AtomKey,
305+
state: AtomState<Node.Coordinator>
306+
) -> AtomLoaderContext<Node.Loader.Value, Node.Loader.Coordinator> {
324307
// Terminate the ongoing transaction first.
325308
state.transaction?.terminate()
326309

@@ -479,15 +462,23 @@ private extension StoreContext {
479462
}
480463

481464
func getState<Node: Atom>(of atom: Node, for key: AtomKey) -> AtomState<Node.Coordinator> {
482-
func makeState() -> AtomState<Node.Coordinator> {
483-
let coordinator = atom.makeCoordinator()
484-
let state = AtomState(coordinator: coordinator)
485-
store.state.states[key] = state
465+
if let state = lookupState(of: atom, for: key) {
486466
return state
487467
}
488468

469+
let state = makeEphemeralState(of: atom)
470+
store.state.states[key] = state
471+
return state
472+
}
473+
474+
func makeEphemeralState<Node: Atom>(of atom: Node) -> AtomState<Node.Coordinator> {
475+
let coordinator = atom.makeCoordinator()
476+
return AtomState(coordinator: coordinator)
477+
}
478+
479+
func lookupState<Node: Atom>(of atom: Node, for key: AtomKey) -> AtomState<Node.Coordinator>? {
489480
guard let baseState = store.state.states[key] else {
490-
return makeState()
481+
return nil
491482
}
492483

493484
guard let state = baseState as? AtomState<Node.Coordinator> else {
@@ -506,19 +497,27 @@ private extension StoreContext {
506497

507498
// Release the invalid registration as a fallback.
508499
release(for: key)
509-
notifyUpdateToObservers()
510-
return makeState()
500+
return nil
511501
}
512502

513503
return state
514504
}
515505

516-
func makeNewCache<Node: Atom>(
506+
func getCache<Node: Atom>(
517507
of atom: Node,
518508
for key: AtomKey,
519509
override: AtomOverride<Node>?
520510
) -> AtomCache<Node> {
521-
let context = prepareForTransaction(of: atom, for: key)
511+
lookupCache(of: atom, for: key) ?? makeCache(of: atom, for: key, override: override)
512+
}
513+
514+
func makeCache<Node: Atom>(
515+
of atom: Node,
516+
for key: AtomKey,
517+
override: AtomOverride<Node>?
518+
) -> AtomCache<Node> {
519+
let state = getState(of: atom, for: key)
520+
let context = prepareForTransaction(of: atom, for: key, state: state)
522521
let value: Node.Loader.Value
523522

524523
if let override {
@@ -555,7 +554,6 @@ private extension StoreContext {
555554

556555
// Release the invalid registration as a fallback.
557556
release(for: key)
558-
notifyUpdateToObservers()
559557
return nil
560558
}
561559

Tests/AtomsTests/Attribute/RefreshableTests.swift

Lines changed: 63 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -19,51 +19,70 @@ final class RefreshableTests: XCTestCase {
1919
let observer = Observer { snapshots.append($0) }
2020
let context = StoreContext(store: store, observers: [observer])
2121

22-
let phase0 = await context.refresh(atom)
23-
XCTAssertEqual(phase0.value, 1)
24-
XCTAssertNil(store.state.caches[key])
25-
XCTAssertNil(store.state.states[key])
26-
XCTAssertTrue(snapshots.isEmpty)
27-
28-
var updateCount = 0
29-
let phase1 = context.watch(atom, subscriber: subscriber, requiresObjectUpdate: false) {
30-
updateCount += 1
22+
do {
23+
// Should call custom refresh behavior
24+
25+
let phase0 = await context.refresh(atom)
26+
XCTAssertEqual(phase0.value, 1)
27+
XCTAssertNil(store.state.caches[key])
28+
XCTAssertNil(store.state.states[key])
29+
XCTAssertTrue(snapshots.isEmpty)
30+
31+
var updateCount = 0
32+
let phase1 = context.watch(atom, subscriber: subscriber, requiresObjectUpdate: false) {
33+
updateCount += 1
34+
}
35+
36+
XCTAssertTrue(phase1.isSuspending)
37+
38+
snapshots.removeAll()
39+
40+
let phase2 = await context.refresh(atom)
41+
XCTAssertEqual(phase2.value, 1)
42+
XCTAssertNotNil(store.state.states[key])
43+
XCTAssertEqual((store.state.caches[key] as? AtomCache<TestCustomRefreshableAtom<Just<Int>>>)?.value, .success(1))
44+
XCTAssertEqual(updateCount, 1)
45+
XCTAssertEqual(
46+
snapshots.map { $0.caches.mapValues { $0.value as? AsyncPhase<Int, Never> } },
47+
[[key: .success(1)]]
48+
)
49+
50+
context.unwatch(atom, subscriber: subscriber)
51+
}
52+
53+
do {
54+
// Custom refresh behavior should not be overridden
55+
56+
let scopeKey = ScopeKey(token: ScopeKey.Token())
57+
let overrideAtomKey = AtomKey(atom, scopeKey: scopeKey)
58+
let scopedContext = context.scoped(
59+
scopeKey: scopeKey,
60+
scopeID: ScopeID(DefaultScopeID()),
61+
observers: [],
62+
overrides: [
63+
OverrideKey(atom): AtomOverride<TestCustomRefreshableAtom<Just<Int>>>(isScoped: true) { _ in .success(2) }
64+
]
65+
)
66+
67+
let phase0 = scopedContext.watch(atom, subscriber: subscriber, requiresObjectUpdate: false) {}
68+
XCTAssertEqual(phase0.value, 2)
69+
70+
let phase1 = await scopedContext.refresh(atom)
71+
XCTAssertEqual(phase1.value, 1)
72+
XCTAssertNotNil(store.state.states[overrideAtomKey])
73+
XCTAssertEqual(
74+
(store.state.caches[overrideAtomKey] as? AtomCache<TestCustomRefreshableAtom<Just<Int>>>)?.value,
75+
.success(1)
76+
)
3177
}
3278

33-
XCTAssertTrue(phase1.isSuspending)
34-
35-
snapshots.removeAll()
36-
37-
let phase2 = await context.refresh(atom)
38-
XCTAssertEqual(phase2.value, 1)
39-
XCTAssertNotNil(store.state.states[key])
40-
XCTAssertEqual((store.state.caches[key] as? AtomCache<TestCustomRefreshableAtom<Just<Int>>>)?.value, .success(1))
41-
XCTAssertEqual(updateCount, 1)
42-
XCTAssertEqual(
43-
snapshots.map { $0.caches.mapValues { $0.value as? AsyncPhase<Int, Never> } },
44-
[[key: .success(1)]]
45-
)
46-
47-
let scopeKey = ScopeKey(token: ScopeKey.Token())
48-
let overrideAtomKey = AtomKey(atom, scopeKey: scopeKey)
49-
let scopedContext = context.scoped(
50-
scopeKey: scopeKey,
51-
scopeID: ScopeID(DefaultScopeID()),
52-
observers: [],
53-
overrides: [
54-
OverrideKey(atom): AtomOverride<TestCustomRefreshableAtom<Just<Int>>>(isScoped: true) { _ in .success(2) }
55-
]
56-
)
57-
58-
let phase3 = scopedContext.watch(atom, subscriber: subscriber, requiresObjectUpdate: false) {}
59-
XCTAssertEqual(phase3.value, 2)
60-
61-
let phase4 = await scopedContext.refresh(atom)
62-
XCTAssertEqual(phase4.value, 2)
63-
XCTAssertNotNil(store.state.states[overrideAtomKey])
64-
XCTAssertEqual(
65-
(store.state.caches[overrideAtomKey] as? AtomCache<TestCustomRefreshableAtom<Just<Int>>>)?.value,
66-
.success(2)
67-
)
79+
do {
80+
// Should not make new state and cache
81+
82+
let value = await context.refresh(atom)
83+
84+
XCTAssertNil(store.state.states[key])
85+
XCTAssertNil(store.state.caches[key])
86+
}
6887
}
6988
}

0 commit comments

Comments
 (0)