Skip to content

Add a new helper type for SAFE.Client: Optimistic #4

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion src/Client/SAFE.fs
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,53 @@ module RemoteData =
/// `Loaded x -> Loading x`;
/// `NotStarted -> Loading None`;
/// `Loading x -> Loading x`;
let startLoading (remote: RemoteData<'T>) = remote.StartLoading
let startLoading (remote: RemoteData<'T>) = remote.StartLoading

///A type which represents optimistic updates.
type Optimistic<'T> =
| NonExistant
| Exists of value:'T * prev:'T option
with
/// Retrieves the current value
member this.Value =
match this with
| NonExistant -> None
| Exists (v, pv) -> Some v

/// Updates the current value, shifting the existing current value to previous.
member this.Update (value: 'T) =
match this with
| NonExistant -> NonExistant
| Exists (v, pv) -> Exists (value, Some v)

/// Rolls back to the previous value, discarding the current one.
member this.Rollback () =
match this with
| NonExistant -> NonExistant
| Exists (_, Some pv) -> Exists (pv , None)
| Exists (_, None) -> NonExistant

/// Maps the underlying optimistic value, when it exists, into another shape.
member this.Map (f: 'T -> 'U) =
match this with
| NonExistant -> NonExistant
| Exists (v, pv) -> Exists (f v, pv |> Option.map f)

/// Module containing functions for working with Optimistic type
module Optimistic =
/// Creates a new Optimistic value with no history
let create value =
Exists (value, None)

/// Creates an empty Optimistic value
let empty =
NonExistant

/// Updates the current value, shifting existing value to previous
let update value (optimistic: Optimistic<'T>) = optimistic.Update value

/// Rolls back to the previous value
let rollback (optimistic: Optimistic<'T>) = optimistic.Rollback()

/// Maps both current and previous values
let map f (optimistic: Optimistic<'T>) = optimistic.Map f
110 changes: 108 additions & 2 deletions test/Client/Program.fs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module Client.Tests
module Client.Tests

open Fable.Mocha
open SAFE
Expand Down Expand Up @@ -124,6 +124,112 @@ let remoteData =
| RemoteDataCase.LoadingPopulated -> Loading (Some true)
| RemoteDataCase.Loaded -> Loading (Some true))
]
let optimistic =
testList "Optimistic" [
testList "create" [
testCase "creates new value with no history" <| fun _ ->
let opt = Optimistic.create 42
match opt with
| Exists (value, prev) ->
Expect.equal value 42 "Current value should be set"
Expect.equal prev None "Previous value should be None"
| NonExistant ->
failtest "Should not be NonExistant"
]

testList "empty" [
testCase "creates empty optimistic value" <| fun _ ->
let opt = Optimistic.empty
Expect.equal opt NonExistant "Should be NonExistant"
]

testList "Value property" [
testCase "returns Some for existing value" <| fun _ ->
let opt = Optimistic.create 42
Expect.equal opt.Value (Some 42) "Should return Some with current value"

testCase "returns None for NonExistant" <| fun _ ->
let opt = Optimistic.empty
Expect.equal opt.Value None "Should return None for NonExistant"
]

testList "update" [
testCase "updates value and shifts previous" <| fun _ ->
let opt = Optimistic.create 42
let updated = opt.Update 84
match updated with
| Exists (value, prev) ->
Expect.equal value 84 "Current value should be updated"
Expect.equal prev (Some 42) "Previous value should be old current"
| NonExistant ->
failtest "Should not be NonExistant"

testCase "update on NonExistant remains NonExistant" <| fun _ ->
let opt = Optimistic.empty
let updated = opt.Update 42
Expect.equal updated NonExistant "Should remain NonExistant"
]

testList "rollback" [
testCase "rolls back to previous value" <| fun _ ->
let opt = Optimistic.create 42 |> fun o -> o.Update 84
let rolled = opt.Rollback()
match rolled with
| Exists (value, prev) ->
Expect.equal value 42 "Current value should be previous"
Expect.equal prev None "Previous value should be None"
| NonExistant ->
failtest "Should not be NonExistant"

testCase "rollback on NonExistant remains NonExistant" <| fun _ ->
let opt = Optimistic.empty
let rolled = opt.Rollback()
Expect.equal rolled NonExistant "Should remain NonExistant"
]

testList "map" [
testCase "maps both current and previous values" <| fun _ ->
let opt = Optimistic.create 42 |> fun o -> o.Update 84
let mapped = opt.Map string
match mapped with
| Exists (value, prev) ->
Expect.equal value "84" "Current value should be mapped"
Expect.equal prev (Some "42") "Previous value should be mapped"
| NonExistant ->
failtest "Should not be NonExistant"

testCase "map on NonExistant remains NonExistant" <| fun _ ->
let opt = Optimistic.empty
let mapped = opt.Map string
Expect.equal mapped NonExistant "Should remain NonExistant"
]

testList "module functions" [
testCase "update function matches member" <| fun _ ->
let opt = Optimistic.create 42
let memberUpdate = opt.Update 84
let moduleUpdate = Optimistic.update 84 opt
Expect.equal moduleUpdate memberUpdate "Module update should match member update"

testCase "rollback function matches member" <| fun _ ->
let opt = Optimistic.create 42 |> fun o -> o.Update 84
let memberRollback = opt.Rollback()
let moduleRollback = Optimistic.rollback opt
Expect.equal moduleRollback memberRollback "Module rollback should match member rollback"

testCase "map function matches member" <| fun _ ->
let opt = Optimistic.create 42
let memberMap = opt.Map string
let moduleMap = Optimistic.map string opt
Expect.equal moduleMap memberMap "Module map should match member map"
]
]

let allTests =
testList "All Tests" [
remoteData
optimistic
]

[<EntryPoint>]
let main _ = Mocha.runTests remoteData
let main _ = Mocha.runTests allTests