diff --git a/src/Fabulous.Tests/CmdTests.fs b/src/Fabulous.Tests/CmdTests.fs new file mode 100644 index 000000000..073b6d8fc --- /dev/null +++ b/src/Fabulous.Tests/CmdTests.fs @@ -0,0 +1,43 @@ +namespace Fabulous.Tests + +open Fabulous +open NUnit.Framework + +type CmdTestsMsg = NewValue of int + +module CmdTestsHelper = + let execute dispatch (cmd: Cmd<'msg>) = + for sub in cmd do + sub dispatch + +[] +type ``Cmd tests``() = + [] + member _.``Cmd.debounce only dispatch the last message``() = + async { + let mutable actualValue = None + + let dispatch msg = + if actualValue.IsNone then + actualValue <- Some msg + + let triggerCmd = Cmd.debounce 100 NewValue + + triggerCmd 1 |> CmdTestsHelper.execute dispatch + do! Async.Sleep 50 + triggerCmd 2 |> CmdTestsHelper.execute dispatch + do! Async.Sleep 75 + triggerCmd 3 |> CmdTestsHelper.execute dispatch + do! Async.Sleep 125 + + Assert.AreEqual(Some(NewValue 3), actualValue) + + actualValue <- None + + triggerCmd 4 |> CmdTestsHelper.execute dispatch + do! Async.Sleep 75 + triggerCmd 5 |> CmdTestsHelper.execute dispatch + do! Async.Sleep 125 + + Assert.AreEqual(Some(NewValue 5), actualValue) + } diff --git a/src/Fabulous.Tests/Fabulous.Tests.fsproj b/src/Fabulous.Tests/Fabulous.Tests.fsproj index 9205b80d0..d60baac45 100644 --- a/src/Fabulous.Tests/Fabulous.Tests.fsproj +++ b/src/Fabulous.Tests/Fabulous.Tests.fsproj @@ -16,6 +16,7 @@ + diff --git a/src/Fabulous/Cmd.fs b/src/Fabulous/Cmd.fs index 61366023e..d1a8b19e7 100644 --- a/src/Fabulous/Cmd.fs +++ b/src/Fabulous/Cmd.fs @@ -1,5 +1,6 @@ namespace Fabulous +open System.Threading open System.Threading.Tasks /// Dispatch - feed new message into the processing loop @@ -104,3 +105,26 @@ module Cmd = dispatch(failure ex) } |> ignore ] + + /// Command to issue a message if no other message has been issued within the specified timeout + let debounce (timeout: int) (fn: 'value -> 'msg) : 'value -> Cmd<'msg> = + let mutable cts: CancellationTokenSource = null + + fun (value: 'value) -> + [ fun dispatch -> + if cts <> null then + cts.Cancel() + cts.Dispose() + + cts <- new CancellationTokenSource() + + Async.Start( + async { + do! Async.Sleep(timeout) + dispatch(fn value) + + cts.Dispose() + cts <- null + }, + cts.Token + ) ]