Skip to content

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

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 5 commits into
base: main
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/SAFE.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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like the case names could perhaps be clearer.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be represented as an option instead? like

type Optimistic<'T> = (current: 'T * previous: 'T option) option

type Optimistic<'T> =
| NonExistent
| 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
member this.Rollback () =
member this.RollBack () =

"Rollback" is a noun, "roll back" is the verb

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I honestly prefer Rollback

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
108 changes: 107 additions & 1 deletion test/SAFE.Client.Tests/Program.fs
Original file line number Diff line number Diff line change
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
Loading