From 31ee433bbfbaa58a8dd0f55b60fe928cb7c18bf9 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 10 Aug 2016 14:57:20 +0200 Subject: [PATCH] Add react exercise --- config.json | 3 +- exercises/react/Example.fs | 56 +++++++++++++++++++++ exercises/react/ReactTest.fs | 96 ++++++++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 exercises/react/Example.fs create mode 100644 exercises/react/ReactTest.fs diff --git a/config.json b/config.json index aed19c303..2aba5a25d 100644 --- a/config.json +++ b/config.json @@ -101,7 +101,8 @@ "sgf-parsing", "transpose", "tree-building", - "grep" + "grep", + "react" ], "deprecated": [ "binary", diff --git a/exercises/react/Example.fs b/exercises/react/Example.fs new file mode 100644 index 000000000..17c1955e1 --- /dev/null +++ b/exercises/react/Example.fs @@ -0,0 +1,56 @@ +module React + +[] +type Cell() = + abstract Value : int with get, set + abstract Changed: IEvent + +type InputCell(initialValue: int) = + inherit Cell() + + let mutable value = initialValue + let changed = new Event() + + override this.Changed = changed.Publish + + override this.Value + with get() = value + and set(newValue : int) = + if value <> newValue then + value <- newValue + changed.Trigger(newValue) + +type ComputeCell(inputs: Cell list, compute: (int list -> int)) = + inherit Cell() + + let computeValue() = inputs |> List.map (fun x -> x.Value) |> compute + + let mutable value = computeValue() + let changed = new Event() + + let updateValue() = + let newValue = computeValue() + + if newValue <> value then + value <- newValue + changed.Trigger(newValue) + + let subscribeToInputChanges() = + [for input in inputs do + input.Changed.Add(fun _ -> updateValue())] + |> ignore + + do + subscribeToInputChanges() + + override this.Changed = changed.Publish + + override this.Value + with get() = value + and set(v : int) = failwith "Cannot directly set value of compute cell" + +let mkInputCell value = new InputCell(value) +let mkComputeCell (inputs: Cell list) (compute: (int list -> int)) = new ComputeCell(inputs, compute) + +let setValue value (cell: Cell) = cell.Value <- value +let value (cell: Cell) = cell.Value \ No newline at end of file diff --git a/exercises/react/ReactTest.fs b/exercises/react/ReactTest.fs new file mode 100644 index 000000000..285786ead --- /dev/null +++ b/exercises/react/ReactTest.fs @@ -0,0 +1,96 @@ +module ReactTest + +open NUnit.Framework + +open React + +[] +let ``Setting the value of an input cell changes the observable value`` () = + let i1 = mkInputCell 1 + + Assert.That(value i1 , Is.EqualTo(1)) + setValue 2 i1 |> ignore + Assert.That(value i1 , Is.EqualTo(2)) + +[] +[] +let ``The value of a compute is determined by the value of the dependencies`` () = + let i1 = mkInputCell 1 + let c1 = mkComputeCell [i1] (fun values -> values.[0] + 1) + + Assert.That(c1.Value, Is.EqualTo(2)) + setValue 2 i1 |> ignore + Assert.That(c1.Value, Is.EqualTo(3)) + +[] +[] +let ``Compute cells can depend on other compute cells`` () = + let i1 = mkInputCell 1 + let c1 = mkComputeCell [i1] (fun values -> values.[0] + 1) + let c2 = mkComputeCell [i1] (fun values -> values.[0] - 1) + let c3 = mkComputeCell [c1; c2] (fun values -> values.[0] * values.[1]) + + Assert.That(c3.Value, Is.EqualTo(0)) + setValue 3 i1 |> ignore + Assert.That(c3.Value, Is.EqualTo(8)) + +[] +[] +let ``Compute cells can have callbacks`` () = + let i1 = mkInputCell 1 + let c1 = mkComputeCell [i1] (fun values -> values.[0] + 1) + let mutable observed = [] + c1.Changed.Add(fun value -> observed <- observed @ [value]) |> ignore + + Assert.That(observed, Is.EqualTo([])) + setValue 2 i1 |> ignore + Assert.That(observed, Is.EqualTo([3])) + +[] +[] +let ``Callbacks only trigger on change`` () = + let i1 = mkInputCell 1 + let c1 = mkComputeCell [i1] (fun values -> if values.[0] > 2 then values.[0] + 1 else 2) + let mutable observerCalled = 0 + c1.Changed.Add(fun value -> observerCalled <- observerCalled + 1) |> ignore + + setValue 1 i1 |> ignore + Assert.That(observerCalled, Is.EqualTo(0)) + setValue 2 i1 |> ignore + Assert.That(observerCalled, Is.EqualTo(0)) + setValue 3 i1 |> ignore + Assert.That(observerCalled, Is.EqualTo(1)) + +[] +[] +let ``Callbacks can be removed`` () = + let i1 = mkInputCell 1 + let c1 = mkComputeCell [i1] (fun values -> values.[0] + 1) + let mutable observed1 = [] + let mutable observed2 = [] + + let changedHandler1 = Handler(fun _ value -> observed1 <- observed1 @ [value]) + c1.Changed.AddHandler changedHandler1 + c1.Changed.Add(fun value -> observed2 <- observed2 @ [value]) |> ignore + + setValue 2 i1 |> ignore + Assert.That(observed1, Is.EqualTo([3])) + Assert.That(observed2, Is.EqualTo([3])) + + c1.Changed.RemoveHandler changedHandler1 + setValue 3 i1 |> ignore + Assert.That(observed1, Is.EqualTo([3])) + Assert.That(observed2, Is.EqualTo([3; 4])) + +[] +[] +let ``Callbacks should only be called once even if multiple dependencies have changed`` () = + let i1 = mkInputCell 1 + let c1 = mkComputeCell [i1] (fun values -> values.[0] + 1) + let c2 = mkComputeCell [i1] (fun values -> values.[0] - 1) + let c3 = mkComputeCell [c2] (fun values -> values.[0] - 1) + let c4 = mkComputeCell [c1; c3] (fun values -> values.[0] * values.[1]) + let mutable changed4 = 0 + c1.Changed.Add(fun value -> changed4 <- changed4 + 1) |> ignore + setValue 3 i1 |> ignore + Assert.That(changed4, Is.EqualTo(1)) \ No newline at end of file