Skip to content

Commit 157b165

Browse files
optimistic updates
1 parent 125219d commit 157b165

File tree

2 files changed

+158
-3
lines changed

2 files changed

+158
-3
lines changed

src/Client/SAFE.fs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,4 +185,53 @@ module RemoteData =
185185
/// `Loaded x -> Loading x`;
186186
/// `NotStarted -> Loading None`;
187187
/// `Loading x -> Loading x`;
188-
let startLoading (remote: RemoteData<'T>) = remote.StartLoading
188+
let startLoading (remote: RemoteData<'T>) = remote.StartLoading
189+
190+
///A type which represents optimistic updates.
191+
type Optimistic<'T> =
192+
| NonExistant
193+
| Exists of value:'T * prev:'T option
194+
with
195+
/// Retrieves the current value
196+
member this.Value =
197+
match this with
198+
| NonExistant -> None
199+
| Exists (v, pv) -> Some v
200+
201+
/// Updates the current value, shifting the existing current value to previous.
202+
member this.Update (value: 'T) =
203+
match this with
204+
| NonExistant -> NonExistant
205+
| Exists (v, pv) -> Exists (value, Some v)
206+
207+
/// Rolls back to the previous value, discarding the current one.
208+
member this.Rollback () =
209+
match this with
210+
| NonExistant -> NonExistant
211+
| Exists (_, Some pv) -> Exists (pv , None)
212+
| Exists (_, None) -> NonExistant
213+
214+
/// Maps the underlying optimistic value, when it exists, into another shape.
215+
member this.Map (f: 'T -> 'U) =
216+
match this with
217+
| NonExistant -> NonExistant
218+
| Exists (v, pv) -> Exists (f v, pv |> Option.map f)
219+
220+
/// Module containing functions for working with Optimistic type
221+
module Optimistic =
222+
/// Creates a new Optimistic value with no history
223+
let create value =
224+
Exists (value, None)
225+
226+
/// Creates an empty Optimistic value
227+
let empty =
228+
NonExistant
229+
230+
/// Updates the current value, shifting existing value to previous
231+
let update value (optimistic: Optimistic<'T>) = optimistic.Update value
232+
233+
/// Rolls back to the previous value
234+
let rollback (optimistic: Optimistic<'T>) = optimistic.Rollback()
235+
236+
/// Maps both current and previous values
237+
let map f (optimistic: Optimistic<'T>) = optimistic.Map f

test/Client/Program.fs

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module Client.Tests
1+
module Client.Tests
22

33
open Fable.Mocha
44
open SAFE
@@ -124,6 +124,112 @@ let remoteData =
124124
| RemoteDataCase.LoadingPopulated -> Loading (Some true)
125125
| RemoteDataCase.Loaded -> Loading (Some true))
126126
]
127+
let optimistic =
128+
testList "Optimistic" [
129+
testList "create" [
130+
testCase "creates new value with no history" <| fun _ ->
131+
let opt = Optimistic.create 42
132+
match opt with
133+
| Exists (value, prev) ->
134+
Expect.equal value 42 "Current value should be set"
135+
Expect.equal prev None "Previous value should be None"
136+
| NonExistant ->
137+
failtest "Should not be NonExistant"
138+
]
139+
140+
testList "empty" [
141+
testCase "creates empty optimistic value" <| fun _ ->
142+
let opt = Optimistic.empty
143+
Expect.equal opt NonExistant "Should be NonExistant"
144+
]
145+
146+
testList "Value property" [
147+
testCase "returns Some for existing value" <| fun _ ->
148+
let opt = Optimistic.create 42
149+
Expect.equal opt.Value (Some 42) "Should return Some with current value"
150+
151+
testCase "returns None for NonExistant" <| fun _ ->
152+
let opt = Optimistic.empty
153+
Expect.equal opt.Value None "Should return None for NonExistant"
154+
]
155+
156+
testList "update" [
157+
testCase "updates value and shifts previous" <| fun _ ->
158+
let opt = Optimistic.create 42
159+
let updated = opt.Update 84
160+
match updated with
161+
| Exists (value, prev) ->
162+
Expect.equal value 84 "Current value should be updated"
163+
Expect.equal prev (Some 42) "Previous value should be old current"
164+
| NonExistant ->
165+
failtest "Should not be NonExistant"
166+
167+
testCase "update on NonExistant remains NonExistant" <| fun _ ->
168+
let opt = Optimistic.empty
169+
let updated = opt.Update 42
170+
Expect.equal updated NonExistant "Should remain NonExistant"
171+
]
172+
173+
testList "rollback" [
174+
testCase "rolls back to previous value" <| fun _ ->
175+
let opt = Optimistic.create 42 |> fun o -> o.Update 84
176+
let rolled = opt.Rollback()
177+
match rolled with
178+
| Exists (value, prev) ->
179+
Expect.equal value 42 "Current value should be previous"
180+
Expect.equal prev None "Previous value should be None"
181+
| NonExistant ->
182+
failtest "Should not be NonExistant"
183+
184+
testCase "rollback on NonExistant remains NonExistant" <| fun _ ->
185+
let opt = Optimistic.empty
186+
let rolled = opt.Rollback()
187+
Expect.equal rolled NonExistant "Should remain NonExistant"
188+
]
189+
190+
testList "map" [
191+
testCase "maps both current and previous values" <| fun _ ->
192+
let opt = Optimistic.create 42 |> fun o -> o.Update 84
193+
let mapped = opt.Map string
194+
match mapped with
195+
| Exists (value, prev) ->
196+
Expect.equal value "84" "Current value should be mapped"
197+
Expect.equal prev (Some "42") "Previous value should be mapped"
198+
| NonExistant ->
199+
failtest "Should not be NonExistant"
200+
201+
testCase "map on NonExistant remains NonExistant" <| fun _ ->
202+
let opt = Optimistic.empty
203+
let mapped = opt.Map string
204+
Expect.equal mapped NonExistant "Should remain NonExistant"
205+
]
206+
207+
testList "module functions" [
208+
testCase "update function matches member" <| fun _ ->
209+
let opt = Optimistic.create 42
210+
let memberUpdate = opt.Update 84
211+
let moduleUpdate = Optimistic.update 84 opt
212+
Expect.equal moduleUpdate memberUpdate "Module update should match member update"
213+
214+
testCase "rollback function matches member" <| fun _ ->
215+
let opt = Optimistic.create 42 |> fun o -> o.Update 84
216+
let memberRollback = opt.Rollback()
217+
let moduleRollback = Optimistic.rollback opt
218+
Expect.equal moduleRollback memberRollback "Module rollback should match member rollback"
219+
220+
testCase "map function matches member" <| fun _ ->
221+
let opt = Optimistic.create 42
222+
let memberMap = opt.Map string
223+
let moduleMap = Optimistic.map string opt
224+
Expect.equal moduleMap memberMap "Module map should match member map"
225+
]
226+
]
227+
228+
let allTests =
229+
testList "All Tests" [
230+
remoteData
231+
optimistic
232+
]
127233

128234
[<EntryPoint>]
129-
let main _ = Mocha.runTests remoteData
235+
let main _ = Mocha.runTests allTests

0 commit comments

Comments
 (0)