From 3afc978cbc298130c2b010cbacb554e39489e281 Mon Sep 17 00:00:00 2001 From: Maciej Bielecki Date: Fri, 26 Apr 2024 18:29:02 +0200 Subject: [PATCH] Remove old DOM API, WeakDynamic and Widgets.{Button, Input, RadioGroup, Select) (#97) * Remove old DOM API and Widgets.{Button, Input, RadioGroup, Select) * Fix benchmark * Remove WeakDynamic * Format code * Revert package.json changes * Fix warnings * Fix formatting --- bench/Bench/Builder.js | 142 +++++++++------ bench/Bench/Builder.purs | 80 ++------- bench/Bench/Primitives.purs | 63 ++----- bench/BenchMain.js | 25 ++- bench/BenchMain.purs | 3 +- package.json | 2 +- spago.dhall | 6 +- src/Specular/Dom/Browser.js | 32 ++++ src/Specular/Dom/Browser.purs | 5 + src/Specular/Dom/Builder.purs | 61 +------ src/Specular/Dom/Builder/Class.purs | 62 +------ src/Specular/Dom/Element.purs | 27 ++- src/Specular/Dom/Element/Class.purs | 3 +- src/Specular/Dom/Node/Class.purs | 4 - src/Specular/Dom/Widgets/Button.purs | 20 --- src/Specular/Dom/Widgets/Input.js | 31 ---- src/Specular/Dom/Widgets/Input.purs | 151 ---------------- src/Specular/Dom/Widgets/RadioGroup.purs | 70 -------- src/Specular/Dom/Widgets/Select.purs | 76 -------- src/Specular/FRP.purs | 6 +- src/Specular/FRP/Base.purs | 2 +- src/Specular/FRP/Fix.purs | 90 ---------- src/Specular/FRP/List.purs | 51 ------ src/Specular/FRP/Replaceable.purs | 11 -- src/Specular/FRP/WeakDynamic.purs | 109 ------------ src/Specular/Ref.purs | 14 +- test/Test/Utils/Dom.purs | 3 +- test/browser/BrowserMain.purs | 8 - test/browser/BuilderSpec.purs | 187 ++++++-------------- test/browser/DemoMain.purs | 41 ++--- test/browser/InputWidgetsSpec.purs | 120 ------------- test/browser/ListSpec.purs | 74 +------- test/browser/NewBuilderSpec.purs | 38 +--- test/browser/RadioGroupSpec.purs | 34 ---- test/browser/examples/AsyncRequest.purs | 94 ++-------- test/browser/examples/Counter.purs | 92 ---------- test/browser/examples/CounterRef.purs | 8 +- test/browser/examples/Radio.purs | 24 --- test/browser/examples/RegistrationForm.purs | 137 ++++++-------- test/node/FixSpec.purs | 136 -------------- test/node/Main.purs | 4 - test/node/WeakDynamicSpec.purs | 68 ------- 42 files changed, 356 insertions(+), 1858 deletions(-) delete mode 100644 src/Specular/Dom/Node/Class.purs delete mode 100644 src/Specular/Dom/Widgets/Button.purs delete mode 100644 src/Specular/Dom/Widgets/Input.js delete mode 100644 src/Specular/Dom/Widgets/Input.purs delete mode 100644 src/Specular/Dom/Widgets/RadioGroup.purs delete mode 100644 src/Specular/Dom/Widgets/Select.purs delete mode 100644 src/Specular/FRP/Fix.purs delete mode 100644 src/Specular/FRP/WeakDynamic.purs delete mode 100644 test/browser/InputWidgetsSpec.purs delete mode 100644 test/browser/RadioGroupSpec.purs delete mode 100644 test/browser/examples/Counter.purs delete mode 100644 test/browser/examples/Radio.purs delete mode 100644 test/node/FixSpec.purs delete mode 100644 test/node/WeakDynamicSpec.purs diff --git a/bench/Bench/Builder.js b/bench/Bench/Builder.js index ce39603..e6aa736 100644 --- a/bench/Bench/Builder.js +++ b/bench/Bench/Builder.js @@ -11,43 +11,63 @@ // replicateM_Widget_ :: Int -> Widget Unit -> Widget Unit // replicateM_, optimized for Widget. -exports.replicateM_Widget_ = function(n) { - return function(widget) { - return function(env) { - for(var i = 0; i < n; i++) { +export function replicateM_Widget_(n) { + return function (widget) { + return function (env) { + for (var i = 0; i < n; i++) { widget(env); } }; }; -}; +} //////////////////////////////////////////////////////////////////////////////// // staticJS :: forall e. Int -> Eff e Unit // // This is what we're aiming for in terms of performance: imperative JS code // that just creates and appends the nodes. -exports.staticJS = function(n) { - return function() { - var parent = document.createElement('div'); - for(var i = 0; i < n; i++) { - elAttr('div', {'class':'foo'}, function(parent) { - elAttr('div', {'class':'bar'}, function(parent) { - text('foo', parent); - }, parent); - elAttr('div', {'class':'baz'}, function(parent) { - text('foo', parent); - }, parent); - elAttr('div', {'class':'thud'}, function(parent) { - text('foo', parent); - }, parent); - }, parent); +export function staticJS(n) { + return function () { + var parent = document.createElement("div"); + for (var i = 0; i < n; i++) { + elAttr( + "div", + { class: "foo" }, + function (parent) { + elAttr( + "div", + { class: "bar" }, + function (parent) { + text("foo", parent); + }, + parent + ); + elAttr( + "div", + { class: "baz" }, + function (parent) { + text("foo", parent); + }, + parent + ); + elAttr( + "div", + { class: "thud" }, + function (parent) { + text("foo", parent); + }, + parent + ); + }, + parent + ); } }; -}; +} function elAttr(tag, attrs, content, parent) { var el = document.createElement(tag); - for(var k in attrs) { + for (var k in attrs) { el.setAttribute(k, attrs[k]); } content(el); @@ -65,31 +85,31 @@ function text(t, parent) { // Like staticJS, but all functions are curried. This is more similar to what // PureScript emits, but still uses imperative sequencing instead of `bindE`. -exports.staticJS_c = function(n) { - return function() { - var parent = document.createElement('div'); - for(var i = 0; i < n; i++) { - elAttr_c('div')({'class':'foo'})(function(parent) { - elAttr_c('div')({'class':'bar'})(function(parent) { - text_c('foo')(parent); +export function staticJS_c(n) { + return function () { + var parent = document.createElement("div"); + for (var i = 0; i < n; i++) { + elAttr_c("div")({ class: "foo" })(function (parent) { + elAttr_c("div")({ class: "bar" })(function (parent) { + text_c("foo")(parent); })(parent); - elAttr_c('div')({'class':'baz'})(function(parent) { - text_c('foo')(parent); + elAttr_c("div")({ class: "baz" })(function (parent) { + text_c("foo")(parent); })(parent); - elAttr_c('div')({'class':'thud'})(function(parent) { - text_c('foo')(parent); + elAttr_c("div")({ class: "thud" })(function (parent) { + text_c("foo")(parent); })(parent); })(parent); } }; -}; +} function elAttr_c(tag) { - return function(attrs) { - return function(content) { - return function(parent) { + return function (attrs) { + return function (content) { + return function (parent) { var el = document.createElement(tag); - for(var k in attrs) { + for (var k in attrs) { el.setAttribute(k, attrs[k]); } content(el); @@ -100,7 +120,7 @@ function elAttr_c(tag) { } function text_c(t) { - return function(parent) { + return function (parent) { var node = document.createTextNode(t); parent.appendChild(node); }; @@ -112,33 +132,41 @@ function text_c(t) { // Functions are curried, and sequencing is done using monadic `bind`, though // specialized to `RIO` monad - "Reader + IO". -exports.staticJS_m = function(n) { - return function() { - var parent = document.createElement('div'); - replicateM_RIO(n, - elAttr_c('div')({'class':'foo'})( - bind_RIO( elAttr_c('div')({'class':'bar'})(text_c('foo')), function(_) { - return bind_RIO( elAttr_c('div')({'class':'baz'})(text_c('foo')), function(_) { - return elAttr_c('div')({'class':'thud'})(text_c('foo')); - }); - }) - ))(parent); +export function staticJS_m(n) { + return function () { + var parent = document.createElement("div"); + replicateM_RIO( + n, + elAttr_c("div")({ class: "foo" })( + bind_RIO( + elAttr_c("div")({ class: "bar" })(text_c("foo")), + function (_) { + return bind_RIO( + elAttr_c("div")({ class: "baz" })(text_c("foo")), + function (_) { + return elAttr_c("div")({ class: "thud" })(text_c("foo")); + } + ); + } + ) + ) + )(parent); }; -}; +} function bind_RIO(m, k) { - return function(env) { + return function (env) { return k(m(env))(env); }; -}; +} // Generic implementation of `replicateM` in terms of `bind_RIO`. function replicateM_RIO(n, x) { - if(n == 0) { - return function() {}; + if (n == 0) { + return function () {}; } else { - return bind_RIO(x, function(_) { - return replicateM_RIO(n-1, x); + return bind_RIO(x, function (_) { + return replicateM_RIO(n - 1, x); }); } } diff --git a/bench/Bench/Builder.purs b/bench/Bench/Builder.purs index f30d621..2f94d42 100644 --- a/bench/Bench/Builder.purs +++ b/bench/Bench/Builder.purs @@ -5,88 +5,35 @@ module Bench.Builder import Prelude import Bench.Types (Tests) -import Control.Monad.Reader (runReaderT) -import Data.List.Lazy (replicateM) import Data.Tuple (Tuple(Tuple)) import Effect (Effect) -import Specular.Dom.Browser (Node) -import Specular.Dom.Builder.Class (elAttr, elDynAttr, text) +import Specular.Dom.Browser (Node, createElement, (:=)) import Specular.Dom.Element as E -import Specular.Dom.Node.Class (createElement, (:=)) -import Specular.Dom.Widget (class MonadWidget, Widget, runWidgetInNode) +import Specular.Dom.Widget (Widget, runWidgetInNode) import Test.Utils.Dom (T3(T3)) --- | The widget we're rendering. -staticWidget :: forall m. MonadWidget m => Int -> m Unit -staticWidget n = - void $ replicateM n $ - elAttr "div" ("class" := "foo") $ do - elAttr "div" ("class" := "bar") $ do - text "foo" - elAttr "div" ("class" := "baz") $ do - text "foo" - elAttr "div" ("class" := "thud") $ do - text "foo" - --- | The same widget, but monomorphic. Can be used to test the effect of --- | polymorphism to some degree. -staticWidgetMono :: Int -> Widget Unit -staticWidgetMono n = - void $ replicateM n $ - elAttr "div" ("class" := "foo") $ do - elAttr "div" ("class" := "bar") $ do - text "foo" - elAttr "div" ("class" := "baz") $ do - text "foo" - elAttr "div" ("class" := "thud") $ do - text "foo" - --- | The same widget, but monomorphic. Can be used to test the effect of --- | polymorphism to some degree. -staticWidgetMonoOptReplicate :: Int -> Widget Unit -staticWidgetMonoOptReplicate n = - replicateM_Widget_ n $ - elAttr "div" ("class" := "foo") $ do - elAttr "div" ("class" := "bar") $ do - text "foo" - elAttr "div" ("class" := "baz") $ do - text "foo" - elAttr "div" ("class" := "thud") $ do - text "foo" - -staticWidgetMonoOptReplicateD :: Int -> Widget Unit -staticWidgetMonoOptReplicateD n = - replicateM_Widget_ n $ - elDynAttr "div" (pure $ "class" := "foo") do - elDynAttr "div" (pure $ "class" := "bar") do - text "foo" - elDynAttr "div" (pure $ "class" := "baz") do - text "foo" - elDynAttr "div" (pure $ "class" := "thud") do - text "foo" - foreign import replicateM_Widget_ :: Int -> Widget Unit -> Widget Unit staticWidgetNewApi :: Int -> Widget Unit staticWidgetNewApi n = replicateM_Widget_ n $ - E.el "div" [E.attrs ("class" := "foo")] do - E.el "div" [E.attrs ("class" := "bar")] do + E.el "div" [ E.attrs ("class" := "foo") ] do + E.el "div" [ E.attrs ("class" := "bar") ] do E.text "foo" - E.el "div" [E.attrs ("class" := "baz")] do + E.el "div" [ E.attrs ("class" := "baz") ] do E.text "foo" - E.el "div" [E.attrs ("class" := "thud")] do + E.el "div" [ E.attrs ("class" := "thud") ] do E.text "foo" staticWidgetNewApiD :: Int -> Widget Unit staticWidgetNewApiD n = replicateM_Widget_ n $ - E.el "div" [E.attrsD (pure ("class" := "foo"))] do - E.el "div" [E.attrsD (pure ("class" := "bar"))] do + E.el "div" [ E.attrsD (pure ("class" := "foo")) ] do + E.el "div" [ E.attrsD (pure ("class" := "bar")) ] do E.text "foo" - E.el "div" [E.attrsD (pure ("class" := "baz"))] do + E.el "div" [ E.attrsD (pure ("class" := "baz")) ] do E.text "foo" - E.el "div" [E.attrsD (pure ("class" := "thud"))] do + E.el "div" [ E.attrsD (pure ("class" := "thud")) ] do E.text "foo" -- See comments in the FFI module. @@ -99,15 +46,12 @@ builderTests = [ Tuple "js " (pure $ delay \_ -> staticJS 10) , Tuple "js curried " (pure $ delay \_ -> staticJS_c 10) , Tuple "js monad " (pure $ delay \_ -> staticJS_m 10) - , Tuple "Widget + elAttr " (pure $ delay \_ -> runWidget $ staticWidgetMonoOptReplicate 10) - , Tuple "Widget + elDynAttr " (pure $ delay \_ -> runWidget $ staticWidgetMonoOptReplicateD 10) , Tuple "Widget + attr " (pure $ delay \_ -> runWidget $ staticWidgetNewApi 10) , Tuple "Widget + attrD " (pure $ delay \_ -> runWidget $ staticWidgetNewApiD 10) - , Tuple "MonadWidget + ReaderT " (pure $ delay \_ -> runWidget $ runReaderT (staticWidget 10) unit) - , Tuple "MonadWidget + 2x ReaderT " (pure $ delay \_ -> runWidget $ flip runReaderT unit $ flip runReaderT unit $ staticWidget 10) ] - where delay x = pure unit >>= x + where + delay x = pure unit >>= x -- mechanics diff --git a/bench/Bench/Primitives.purs b/bench/Bench/Primitives.purs index a03c233..1396248 100644 --- a/bench/Bench/Primitives.purs +++ b/bench/Bench/Primitives.purs @@ -1,7 +1,4 @@ -module Bench.Primitives - ( dynamicTests - , weakDynamicTests - ) where +module Bench.Primitives (dynamicTests) where import Prelude @@ -12,7 +9,7 @@ import Data.Foldable (for_, sum) import Data.Traversable (sequence) import Data.Tuple (Tuple(..), fst) import Effect (Effect) -import Specular.FRP (Dynamic, WeakDynamic, holdDyn, holdWeakDyn, never, newDynamic, newEvent, subscribeWeakDyn_) +import Specular.FRP (Dynamic, holdDyn, newDynamic, newEvent) import Specular.FRP.Base (subscribeDyn_) dynamicTests :: Tests @@ -21,16 +18,18 @@ dynamicTests = , Tuple "20 subscribers" $ nsubscribers 20 , Tuple "30 subscribers" $ nsubscribers 30 , Tuple "40 subscribers" $ nsubscribers 40 - ] <> - [ Tuple "dyn" $ testDynFn1 pure - , Tuple "dyn fmap" $ testDynFn1 \d -> pure (add 1 <$> d) - , Tuple "dyn ap pure" $ testDynFn1 \d -> pure (pure (const 1) <*> d) - , Tuple "dyn ap self" $ testDynFn1 \d -> pure (add <$> d <*> d) - , Tuple "dyn bind self" $ testDynFn1 \d -> pure (d >>= \_ -> d) - , Tuple "dyn bind inner" $ testDynFn1 \d -> pure (pure 10 >>= \_ -> d) - , Tuple "dyn bind outer" $ testDynFn1 \d -> pure (d >>= \_ -> pure 10) - ] <> - nestedApplyTests + ] + <> + [ Tuple "dyn" $ testDynFn1 pure + , Tuple "dyn fmap" $ testDynFn1 \d -> pure (add 1 <$> d) + , Tuple "dyn ap pure" $ testDynFn1 \d -> pure (pure (const 1) <*> d) + , Tuple "dyn ap self" $ testDynFn1 \d -> pure (add <$> d <*> d) + , Tuple "dyn bind self" $ testDynFn1 \d -> pure (d >>= \_ -> d) + , Tuple "dyn bind inner" $ testDynFn1 \d -> pure (pure 10 >>= \_ -> d) + , Tuple "dyn bind outer" $ testDynFn1 \d -> pure (d >>= \_ -> pure 10) + ] + <> + nestedApplyTests nestedApplyTests :: Tests nestedApplyTests = @@ -51,14 +50,14 @@ nestedApplyTests = dynamics <- sequence $ replicate n do event <- newEvent holdDyn 0 event.event - pure $ map sum $ sequence ([d] <> dynamics) + pure $ map sum $ sequence ([ d ] <> dynamics) test_n_ap_last n = testDynFn1 \d -> do dynamics <- sequence $ replicate n do event <- newEvent holdDyn 0 event.event - pure $ map sum $ sequence (dynamics <> [d]) + pure $ map sum $ sequence (dynamics <> [ d ]) nsubscribers :: Int -> Effect (Effect Unit) nsubscribers n = @@ -77,37 +76,7 @@ testDynFn1 fn = subscribeDyn_ (\_ -> pure unit) dyn' pure (event.fire 1) -testDynFn2 :: (Dynamic Int -> Dynamic Int -> Host (Dynamic Int)) -> Effect (Effect Unit) -testDynFn2 fn = - runHost do - event <- newEvent - dyn <- holdDyn 0 event.event - dyn2 <- holdDyn 0 never - dyn' <- fn dyn dyn2 - subscribeDyn_ (\_ -> pure unit) dyn' - pure (event.fire 1) - type Host = CleanupT Effect runHost :: forall a. Host a -> Effect a runHost = map fst <<< runCleanupT - -weakDynamicTests :: Tests -weakDynamicTests = - [ Tuple "weak dyn" $ testWeakDynFn1 pure - , Tuple "weak dyn fmap" $ testWeakDynFn1 \d -> pure (add 1 <$> d) - , Tuple "weak dyn ap pure" $ testWeakDynFn1 \d -> pure (pure (const 1) <*> d) - , Tuple "weak dyn ap self" $ testWeakDynFn1 \d -> pure (add <$> d <*> d) - , Tuple "weak dyn bind self" $ testWeakDynFn1 \d -> pure (d >>= \_ -> d) - , Tuple "weak dyn bind inner" $ testWeakDynFn1 \d -> pure (pure 10 >>= \_ -> d) - , Tuple "weak dyn bind outer" $ testWeakDynFn1 \d -> pure (d >>= \_ -> pure 10) - ] - -testWeakDynFn1 :: (WeakDynamic Int -> Host (WeakDynamic Int)) -> Effect (Effect Unit) -testWeakDynFn1 fn = - runHost do - event <- newEvent - dyn <- holdWeakDyn event.event - dyn' <- fn dyn - subscribeWeakDyn_ (\_ -> pure unit) dyn' - pure ( event.fire 1) diff --git a/bench/BenchMain.js b/bench/BenchMain.js index 9c130ee..d80cd29 100644 --- a/bench/BenchMain.js +++ b/bench/BenchMain.js @@ -1,24 +1,31 @@ -exports.runBenchmark = function(tests) { - return function() { - tests.forEach(function(t) { - for(var k = 0; k < 1; k++) { +export function runBenchmark(tests) { + return function () { + tests.forEach(function (t) { + for (var k = 0; k < 1; k++) { var minTime = 99999; var M = 1000; var allStart = performance.now(); var n = 0; - while(performance.now() - allStart < 2000) { + while (performance.now() - allStart < 2000) { var start = performance.now(); - for(var j = 0; j < M; j++) { + for (var j = 0; j < M; j++) { t.fn(); } var elapsed = performance.now() - start; - if(elapsed < minTime) { + if (elapsed < minTime) { minTime = elapsed; } n += M; } - console.log(t.name + ': ' + (Math.round(1000000 * minTime / M) / 1000) + ' us (' + n + ' runs)'); + console.log( + t.name + + ": " + + Math.round((1000000 * minTime) / M) / 1000 + + " us (" + + n + + " runs)" + ); } }); }; -}; +} diff --git a/bench/BenchMain.purs b/bench/BenchMain.purs index 08a9966..b091cb8 100644 --- a/bench/BenchMain.purs +++ b/bench/BenchMain.purs @@ -3,7 +3,7 @@ module BenchMain where import Prelude import Bench.Builder (builderTests) -import Bench.Primitives (dynamicTests, weakDynamicTests) +import Bench.Primitives (dynamicTests) import Bench.Types (Tests) import Data.Traversable (for) import Data.Tuple (Tuple(Tuple)) @@ -16,7 +16,6 @@ main :: Effect Unit main = launchAff_ do bench "Builder" builderTests bench "Dynamic" dynamicTests - bench "WeakDynamic" weakDynamicTests bench :: String -> Tests -> Aff Unit bench suiteName tests = do diff --git a/package.json b/package.json index b8773a4..920ab27 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "build-bench": "spago bundle-app --main BenchMain --to output/BenchMain.js", "open-bench": "xdg-open ./bench/benchmark.html", "run-bench": "node bench/run.js", - "format": "purs-tidy format-in-place 'src/**/*.purs' 'test/**/*.purs' && prettier -w src test" + "format": "purs-tidy format-in-place 'src/**/*.purs' 'test/**/*.purs' 'bench/**/*.purs' && prettier -w src test" }, "author": "Maciej Bielecki ", "license": "MIT", diff --git a/spago.dhall b/spago.dhall index bdca796..221f791 100644 --- a/spago.dhall +++ b/spago.dhall @@ -12,14 +12,11 @@ , "foreign" , "foreign-object" , "functions" - , "integers" , "invariant" , "maybe" , "newtype" , "partial" , "prelude" - , "random" - , "record" , "refs" , "safe-coerce" , "spec" @@ -27,13 +24,12 @@ , "strings" , "transformers" , "tuples" - , "type-equality" , "typelevel-prelude" , "unsafe-coerce" , "unsafe-reference" ] , packages = ./packages.dhall -, sources = [ "src/**/*.purs", "test/**/*.purs" ] +, sources = [ "src/**/*.purs", "test/**/*.purs", "bench/**/*.purs" ] , license = "MIT" , repository = "https://github.com/restaumatic/purescript-specular" } diff --git a/src/Specular/Dom/Browser.js b/src/Specular/Dom/Browser.js index bf79350..c59d846 100644 --- a/src/Specular/Dom/Browser.js +++ b/src/Specular/Dom/Browser.js @@ -195,3 +195,35 @@ export function removeNode(node) { } }; } + +// getTextInputValue :: Node -> IOSync String +export function getTextInputValue(node) { + return function () { + return node.value; + }; +} + +// setTextInputValue :: Node -> String -> IOSync String +export function setTextInputValue(node) { + return function (value) { + return function () { + node.value = value; + }; + }; +} + +// getCheckboxChecked :: Node -> IOSync Boolean +export function getCheckboxChecked(node) { + return function () { + return node.checked; + }; +} + +// setCheckboxChecked :: Node -> Boolean -> IOSync Unit +export function setCheckboxChecked(node) { + return function (value) { + return function () { + return (node.checked = value); + }; + }; +} diff --git a/src/Specular/Dom/Browser.purs b/src/Specular/Dom/Browser.purs index 607b51e..8cc0c94 100644 --- a/src/Specular/Dom/Browser.purs +++ b/src/Specular/Dom/Browser.purs @@ -116,3 +116,8 @@ foreign import preventDefault :: Event -> Effect Unit -- | Get `innerHTML` of a node. foreign import innerHTML :: Node -> Effect String + +foreign import getCheckboxChecked :: Node -> Effect Boolean +foreign import setCheckboxChecked :: Node -> Boolean -> Effect Unit +foreign import getTextInputValue :: Node -> Effect String +foreign import setTextInputValue :: Node -> String -> Effect Unit diff --git a/src/Specular/Dom/Builder.purs b/src/Specular/Dom/Builder.purs index 773bea2..1669761 100644 --- a/src/Specular/Dom/Builder.purs +++ b/src/Specular/Dom/Builder.purs @@ -4,6 +4,7 @@ module Specular.Dom.Builder , local , unBuilder , mkBuilder' + , mkBuilder , runBuilder' , getParentNode ) where @@ -15,16 +16,13 @@ import Control.Monad.Cleanup (class MonadCleanup, onCleanup) import Control.Monad.Reader (ask, asks) import Control.Monad.Reader.Class (class MonadAsk, class MonadReader) import Control.Monad.Replace (class MonadReplace, Slot(Slot), newSlot) -import Data.Array as A import Data.Maybe (Maybe(..)) import Data.Tuple (Tuple(Tuple)) import Effect (Effect) import Effect.Class (class MonadEffect, liftEffect) import Effect.Uncurried (EffectFn1, EffectFn2, mkEffectFn2, runEffectFn1, runEffectFn2) -import Foreign.Object as SM import Specular.Dom.Builder.Class (class MonadDomBuilder) -import Specular.Dom.Browser (Node, appendChild, appendRawHtml, createDocumentFragment, createElementNS, createTextNode, insertBefore, parentNode, removeAllBetween, removeAttributes, setAttributes, setText, removeNode) -import Specular.FRP.WeakDynamic (subscribeWeakDyn_) +import Specular.Dom.Browser (Node, appendChild, createDocumentFragment, createTextNode, insertBefore, parentNode, removeAllBetween, removeNode) import Effect.Ref (modify_, new, read, write) import Specular.Internal.Effect (DelayedEffects, emptyDelayed, pushDelayed, sequenceEffects, unsafeFreezeDelayed) import Specular.Internal.RIO (RIO(..), rio, runRIO) @@ -84,13 +82,10 @@ runBuilderWithUserEnv userEnv parent (Builder f) = do getEnv :: forall env. Builder env (BuilderEnv env) getEnv = Builder ask -setParent :: forall env. Node -> BuilderEnv env -> BuilderEnv env -setParent parent env = env { parent = parent } - getParentNode :: forall env. Builder env Node getParentNode = Builder (asks _.parent) -instance monadReplaceBuilder :: MonadReplace (Builder env) where +instance MonadReplace (Builder env) where newSlot = do env <- getEnv @@ -159,58 +154,14 @@ instance monadReplaceBuilder :: MonadReplace (Builder env) where pure $ Slot replace destroy append -instance monadDomBuilderBuilder :: MonadDomBuilder (Builder env) where - - text str = mkBuilder \env -> do - node <- createTextNode str - appendChild node env.parent - - dynText dstr = do - node <- mkBuilder \env -> do - node <- createTextNode "" - appendChild node env.parent - pure node - subscribeWeakDyn_ (setText node) dstr - - rawHtml html = mkBuilder \env -> - appendRawHtml html env.parent - - elDynAttrNS' namespace tagName dynAttrs inner = do - env <- getEnv - node <- liftEffect $ createElementNS namespace tagName - - attrsRef <- liftEffect $ new mempty - let - resetAttributes newAttrs = do - oldAttrs <- read attrsRef - write newAttrs attrsRef - let - changed = SM.filterWithKey (\k v -> SM.lookup k oldAttrs /= Just v) newAttrs - removed = A.filter (\k -> not (k `SM.member` newAttrs)) $ SM.keys oldAttrs - - removeAttributes node removed - setAttributes node changed - - subscribeWeakDyn_ resetAttributes dynAttrs - result <- Builder $ RIO.local (setParent node) $ unBuilder inner - liftEffect $ appendChild node env.parent - pure (Tuple node result) - - elAttr tagName attrs inner = do - env <- getEnv - node <- liftEffect $ createElementNS Nothing tagName - liftEffect $ setAttributes node attrs - result <- Builder $ RIO.local (setParent node) $ unBuilder inner - liftEffect $ appendChild node env.parent - pure result - +instance MonadDomBuilder (Builder env) where liftBuilder fn = Builder (RIO fn) liftBuilderWithRun fn = Builder $ rio \env -> runEffectFn2 fn env (mkEffectFn2 \env' (Builder (RIO m)) -> runEffectFn1 m env') -instance semigroupBuilder :: Semigroup a => Semigroup (Builder node a) where +instance Semigroup a => Semigroup (Builder node a) where append = lift2 append -instance monoidBuilder :: Monoid a => Monoid (Builder node a) where +instance Monoid a => Monoid (Builder node a) where mempty = pure mempty diff --git a/src/Specular/Dom/Builder/Class.purs b/src/Specular/Dom/Builder/Class.purs index f67d2f2..e078389 100644 --- a/src/Specular/Dom/Builder/Class.purs +++ b/src/Specular/Dom/Builder/Class.purs @@ -4,17 +4,13 @@ import Prelude import Control.Monad.Cleanup (onCleanup) import Control.Monad.Reader (ReaderT(..), runReaderT) -import Control.Monad.Replace (class MonadReplace) import Control.Monad.Trans.Class (lift) -import Data.Maybe (Maybe(..)) -import Data.Tuple (Tuple, snd) import Effect (Effect) import Effect.Class (liftEffect) import Effect.Uncurried (EffectFn1, EffectFn2, mkEffectFn2, runEffectFn2) -import Specular.Dom.Browser (Attrs, EventType, Namespace, Node, TagName, addEventListener) +import Specular.Dom.Browser (EventType, Node, addEventListener) import Specular.Dom.Browser as DOM -import Specular.FRP (class MonadFRP, WeakDynamic, newEvent, weakDynamic_) -import Specular.FRP as FRP +import Specular.FRP (class MonadFRP) import Specular.Internal.Effect (DelayedEffects) type BuilderEnv env = @@ -24,68 +20,16 @@ type BuilderEnv env = } class Monad m <= MonadDomBuilder m where - text :: String -> m Unit - dynText :: WeakDynamic String -> m Unit - elDynAttrNS' :: forall a. Maybe Namespace -> TagName -> WeakDynamic Attrs -> m a -> m (Tuple Node a) - rawHtml :: String -> m Unit - - elAttr :: forall a. TagName -> Attrs -> m a -> m a - liftBuilder :: forall a. (forall env. EffectFn1 (BuilderEnv env) a) -> m a liftBuilderWithRun :: forall a b. (forall env. EffectFn2 (BuilderEnv env) (EffectFn2 (BuilderEnv env) (m b) b) a) -> m a -elDynAttr' :: forall m a. MonadDomBuilder m => String -> WeakDynamic Attrs -> m a -> m (Tuple Node a) -elDynAttr' = elDynAttrNS' Nothing - -elDynAttr - :: forall m a - . MonadDomBuilder m - => String - -> WeakDynamic Attrs - -> m a - -> m a -elDynAttr tagName dynAttrs inner = snd <$> elDynAttr' tagName dynAttrs inner - -elAttr' :: forall m a. MonadDomBuilder m => String -> Attrs -> m a -> m (Tuple Node a) -elAttr' tagName attrs inner = elDynAttr' tagName (pure attrs) inner - -elAttr_ :: forall m. MonadDomBuilder m => String -> Attrs -> m Unit -elAttr_ tagName attrs = elAttr tagName attrs (pure unit) - -el' :: forall m a. MonadDomBuilder m => String -> m a -> m (Tuple Node a) -el' tagName inner = elAttr' tagName mempty inner - -el :: forall m a. MonadDomBuilder m => String -> m a -> m a -el tagName inner = elAttr tagName mempty inner - -el_ :: forall m. MonadDomBuilder m => String -> m Unit -el_ tagName = el tagName (pure unit) - -dynRawHtml :: forall m. MonadDomBuilder m => MonadReplace m => MonadFRP m => WeakDynamic String -> m Unit -dynRawHtml dynHtml = weakDynamic_ (rawHtml <$> dynHtml) - -domEventWithSample :: forall m a. MonadFRP m => (DOM.Event -> Effect a) -> EventType -> Node -> m (FRP.Event a) -domEventWithSample sample eventType node = do - { event, fire } <- newEvent - onDomEvent eventType node (sample >=> fire) - pure event - -domEvent :: forall m. MonadFRP m => EventType -> Node -> m (FRP.Event Unit) -domEvent = domEventWithSample (\_ -> pure unit) - -- | Register a DOM event listener. onDomEvent :: forall m. MonadFRP m => EventType -> Node -> (DOM.Event -> Effect Unit) -> m Unit onDomEvent eventType node handler = do unsub <- liftEffect $ addEventListener eventType handler node onCleanup unsub -instance monadDomBuilderReaderT :: MonadDomBuilder m => MonadDomBuilder (ReaderT r m) where - text = lift <<< text - dynText = lift <<< dynText - elDynAttrNS' ns tag attrs body = ReaderT $ \env -> elDynAttrNS' ns tag attrs $ runReaderT body env - rawHtml = lift <<< rawHtml - elAttr tag attrs body = - ReaderT $ \env -> elAttr tag attrs $ runReaderT body env +instance MonadDomBuilder m => MonadDomBuilder (ReaderT r m) where liftBuilder b = lift (liftBuilder b) liftBuilderWithRun fn = ReaderT \e -> liftBuilderWithRun diff --git a/src/Specular/Dom/Element.purs b/src/Specular/Dom/Element.purs index e8e35b4..b0ef8da 100644 --- a/src/Specular/Dom/Element.purs +++ b/src/Specular/Dom/Element.purs @@ -1,8 +1,9 @@ module Specular.Dom.Element - ( module X + ( rawHtml , el' , el , el_ + , elNS' , Prop(..) , text @@ -53,23 +54,20 @@ import Data.Array as Array import Data.Maybe (Maybe(..)) import Data.Tuple (Tuple(..)) import Effect (foreachE, Effect) +import Effect.Ref (new, read, write) import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn4, mkEffectFn1, mkEffectFn2, mkEffectFn3, mkEffectFn4, runEffectFn1, runEffectFn2, runEffectFn3, runEffectFn4) import Foreign.Object as Object -import Specular.Dom.Browser (EventType, Node, appendChild, createTextNode, setText, (:=)) +import Specular.Dom.Browser (Attrs, EventType, Namespace, Node, TagName, appendChild, appendRawHtml, createElement, createElementNS, createTextNode, getCheckboxChecked, getTextInputValue, removeAttributes, setAttributes, setText, (:=)) import Specular.Dom.Browser as DOM -import Specular.Dom.Builder (mkBuilder', runBuilder') +import Specular.Dom.Builder (Builder, mkBuilder, mkBuilder', runBuilder') import Specular.Dom.Builder.Class (BuilderEnv) -import Specular.Dom.Builder.Class (rawHtml) as X -import Specular.Dom.Node.Class (Attrs, TagName, createElement, removeAttributes, setAttributes) import Specular.Dom.Widget (RWidget) -import Specular.Dom.Widgets.Input (getCheckboxChecked, getTextInputValue) import Specular.FRP (Dynamic, _subscribeEvent, changed, readDynamic, subscribeDyn_) -import Effect.Ref (new, read, write) import Specular.Internal.Effect (DelayedEffects, pushDelayed) -import Specular.Ref (Ref(..)) -import Unsafe.Coerce (unsafeCoerce) import Specular.Internal.Profiling as ProfilingInternal import Specular.Profiling as Profiling +import Specular.Ref (Ref(..)) +import Unsafe.Coerce (unsafeCoerce) newtype Prop = Prop (EffectFn2 Node DelayedEffects Unit) @@ -81,6 +79,12 @@ instance semigroupProp :: Semigroup Prop where instance monoidProp :: Monoid Prop where mempty = Prop $ mkEffectFn2 \_ _ -> pure unit +elNS' :: forall r a. Maybe Namespace -> TagName -> Array Prop -> RWidget r a -> RWidget r (Tuple Node a) +elNS' ns tagName props body = mkBuilder' $ mkEffectFn1 \env -> do + node <- createElementNS ns tagName + result <- runEffectFn4 initElement env node props body + pure (Tuple node result) + el' :: forall r a. TagName -> Array Prop -> RWidget r a -> RWidget r (Tuple Node a) el' tagName props body = mkBuilder' $ mkEffectFn1 \env -> do node <- createElement tagName @@ -341,3 +345,8 @@ classWhen enabled cls = if enabled then class_ cls else mempty classUnless :: Boolean -> ClassName -> Prop classUnless enabled cls = if enabled then mempty else class_ cls + +-- | Insert a chunk of HTML. +rawHtml :: forall env. String -> Builder env Unit +rawHtml html = mkBuilder \env -> + appendRawHtml html env.parent diff --git a/src/Specular/Dom/Element/Class.purs b/src/Specular/Dom/Element/Class.purs index fe74652..74027c1 100644 --- a/src/Specular/Dom/Element/Class.purs +++ b/src/Specular/Dom/Element/Class.purs @@ -5,10 +5,9 @@ import Prelude import Data.Tuple (Tuple(..), snd) import Effect (foreachE) import Effect.Uncurried (mkEffectFn2, runEffectFn2) -import Specular.Dom.Browser (Node, appendChild) +import Specular.Dom.Browser (Node, TagName, appendChild, createElement) import Specular.Dom.Builder.Class (liftBuilderWithRun) import Specular.Dom.Element (Prop(..)) -import Specular.Dom.Node.Class (TagName, createElement) import Specular.Dom.Widget (class MonadWidget) el' :: forall m a. MonadWidget m => TagName -> Array Prop -> m a -> m (Tuple Node a) diff --git a/src/Specular/Dom/Node/Class.purs b/src/Specular/Dom/Node/Class.purs deleted file mode 100644 index f4b222a..0000000 --- a/src/Specular/Dom/Node/Class.purs +++ /dev/null @@ -1,4 +0,0 @@ --- | Module preserved only for backwards compatibility. Use Specular.Dom.Browser instead. -module Specular.Dom.Node.Class (module X) where - -import Specular.Dom.Browser (Attrs, Event, EventType, Namespace, Node, TagName, addEventListener, addEventListenerImpl, appendChild, appendChildImpl, appendRawHtml, appendRawHtmlImpl, createDocumentFragment, createDocumentFragmentImpl, createElement, createElementImpl, createElementNS, createElementNSImpl, createTextNode, createTextNodeImpl, innerHTML, insertBefore, insertBeforeImpl, moveAllBetweenInclusive, moveAllBetweenInclusiveImpl, parentNode, parentNodeImpl, preventDefault, removeAllBetween, removeAllBetweenImpl, removeAttributes, removeAttributesImpl, setAttributes, setText, setTextImpl, (:=)) as X diff --git a/src/Specular/Dom/Widgets/Button.purs b/src/Specular/Dom/Widgets/Button.purs deleted file mode 100644 index cf6d305..0000000 --- a/src/Specular/Dom/Widgets/Button.purs +++ /dev/null @@ -1,20 +0,0 @@ -module Specular.Dom.Widgets.Button - ( buttonOnClick - ) where - -import Prelude - -import Data.Tuple (Tuple(..)) -import Specular.Dom.Builder.Class (domEventWithSample, elDynAttr') -import Specular.Dom.Node.Class (Attrs) -import Specular.Dom.Widget (class MonadWidget) -import Specular.FRP (Event, WeakDynamic) - --- | `buttonOnClick attrs body` - Creates a HTML `""" ) - it "reacts to password change" $ do + it "reacts to password change" do Tuple node _ <- runBuilderInDiv mainWidget passwordInput <- liftEffect $ querySelector ".password" node @@ -42,14 +37,14 @@ spec = describe "RegistrationForm" $ do -- NB: Input values are not present in innerHTML liftEffect (innerHTML node) `shouldReturn` ( """
""" - <> """
""" - <> """
""" + <> """
""" + <> """
""" <> """
Passwords do not match
""" <> """""" ) - it "reacts to submit button" $ do + it "reacts to submit button" do let showFormResult { login, password } = "login: " <> login <> ", password: " <> password Tuple node event <- runBuilderInDiv mainWidget @@ -78,77 +73,47 @@ type FormResult = -- | Renders a registration form. -- | Returns an Event that fires on "Register" button, -- | with the form data. -mainWidget :: forall m. MonadWidget m => m (Event FormResult) -mainWidget = fixFRP $ view >=> control - -view - :: forall m - . MonadWidget m - => { loginIsTaken :: WeakDynamic Boolean - , passwordsMatch :: WeakDynamic Boolean - } - -> m - { login :: Dynamic String - , password :: Dynamic String - , repeatPassword :: Dynamic String - , register :: Event Unit - } -view { loginIsTaken, passwordsMatch } = do - login <- el "div" $ do - el "label" $ text "Login: " - value <- textInputOnChange "" ("class" := "login") - weakDynamic_ $ flip map loginIsTaken $ \loginIsTakenValue -> - when loginIsTakenValue $ - text "Login already taken" - pure value - - password <- el "div" $ do - el "label" $ text "Password: " - textInputOnChange "" ("type" := "password" <> "class" := "password") - - repeatPassword <- el "div" $ do - el "label" $ text "Repeat password: " - textInputOnChange "" ("type" := "password" <> "class" := "repeat-password") - - weakDynamic_ $ flip map passwordsMatch $ \passwordsMatchValue -> - unless passwordsMatchValue - $ el "div" - $ text "Passwords do not match" - - register <- buttonOnClick (pure mempty) $ text "Register" - - pure { login, password, repeatPassword, register } - -control - :: forall m - . MonadEffect m - => MonadCleanup m - => { login :: Dynamic String - , password :: Dynamic String - , repeatPassword :: Dynamic String - , register :: Event Unit - } - -> m - ( Tuple - { loginIsTaken :: Dynamic Boolean - , passwordsMatch :: Dynamic Boolean - } - (Event FormResult) - ) -control { login, password, repeatPassword, register: registerButtonClicked } = do - let - loginIsTaken = map (_ == "admin") login - passwordsMatch = (==) <$> password <*> repeatPassword +mainWidget :: Widget (Event FormResult) +mainWidget = do + login <- Ref.new "" + password <- Ref.new "" + repeatPassword <- Ref.new "" - register = tagDyn formResult registerButtonClicked + result <- newEvent + + let + loginIsTaken = map (_ == "admin") (Ref.value login) + passwordsMatch = (==) <$> Ref.value password <*> Ref.value repeatPassword -- FIXME: This should be replaced by `rsequence` formResult :: Dynamic FormResult formResult = do - loginValue <- login - passwordValue <- password + loginValue <- Ref.value login + passwordValue <- Ref.value password pure { login: loginValue, password: passwordValue } - pure $ Tuple - { loginIsTaken, passwordsMatch } - register + register = do + value <- readDynamic formResult + result.fire value + + el_ "div" do + el_ "label" $ text "Login: " + el "input" [ class_ "login", bindValueOnChange login ] (pure unit) + whenD loginIsTaken do + text "Login already taken" + + el_ "div" do + el_ "label" $ text "Password: " + el "input" [ attr "type" "password", class_ "password", bindValueOnChange password ] (pure unit) + + el_ "div" do + el_ "label" $ text "Repeat password: " + el "input" [ attr "type" "password", class_ "repeat-password", bindValueOnChange repeatPassword ] (pure unit) + + unlessD passwordsMatch do + el_ "div" $ text "Passwords do not match" + + el "button" [ onClick_ register ] do + text "Register" + + pure result.event diff --git a/test/node/FixSpec.purs b/test/node/FixSpec.purs deleted file mode 100644 index 7d54cde..0000000 --- a/test/node/FixSpec.purs +++ /dev/null @@ -1,136 +0,0 @@ -module FixSpec where - -import Prelude hiding (append) - -import Control.Monad.Cleanup (runCleanupT) -import Effect.Ref (new) -import Data.Tuple (Tuple(..)) -import Specular.FRP (Dynamic, Event, WeakDynamic, holdDyn, never, newEvent, subscribeEvent_, weaken) -import Specular.FRP.Fix (fixDyn, fixEvent, fixFRP) -import Specular.FRP.WeakDynamic (subscribeWeakDyn_) -import Test.Spec (Spec, describe, it, pending') -import Test.Utils (append, liftEffect, shouldHaveInferredType, shouldHaveValue) -import Type.Prelude (Proxy(..)) - -spec :: Spec Unit -spec = do - describe "fixEvent" $ do - it "connects result Event to input Event" $ do - log <- liftEffect $ new [] - Tuple fire _ <- liftEffect $ runCleanupT - $ fixEvent - $ \input -> do - _ <- subscribeEvent_ (append log) input - - { event: output, fire } <- liftEffect newEvent - pure (Tuple output fire) - - liftEffect $ fire 1 - - log `shouldHaveValue` [ 1 ] - - {- - pending' "input and output occur simultaneously" $ do - log <- liftEffect $ new [] - Tuple fire _ <- liftEffect $ runCleanupT $ - fixEvent $ \input -> do - {event: output, fire} <- liftEffect newEvent - _ <- subscribeEvent_ (append log) $ - mergeEvents - (\_ -> pure "input") (\_ -> pure "output") (\_ _ -> pure "both") - input output - pure (Tuple output fire) - - liftEffect $ fire unit - - -- TODO: the result of this test is doubly wrong: - -- The "wrong" result is ["output", "input"], - -- but the implementation currently gives ["output", "both"] - log `shouldHaveValue` ["both"] --} - - describe "fixDyn" $ do - it "gives input WeakDynamic initial value" $ do - log <- liftEffect $ new [] - Tuple _ _ <- liftEffect $ runCleanupT - $ fixDyn - $ \input -> do - _ <- subscribeWeakDyn_ (append log) input - - output <- holdDyn 1 never - pure (Tuple output unit) - - log `shouldHaveValue` [ 1 ] - - it "propagates output changes to input" $ do - { event, fire } <- liftEffect newEvent - log <- liftEffect $ new [] - Tuple _ _ <- liftEffect $ runCleanupT - $ fixDyn - $ \input -> do - _ <- subscribeWeakDyn_ (append log) input - - output <- holdDyn 1 event - pure (Tuple output unit) - - liftEffect $ fire 2 - - log `shouldHaveValue` [ 1, 2 ] - - pending' "input and output change simultaneously" $ do - { event, fire } <- liftEffect newEvent - log <- liftEffect $ new [] - Tuple _ _ <- liftEffect $ runCleanupT - $ fixDyn - $ \input -> do - output <- holdDyn 1 event - _ <- subscribeWeakDyn_ (append log) $ Tuple <$> input <*> weaken output - pure (Tuple output unit) - - liftEffect $ fire 2 - - log `shouldHaveValue` [ Tuple 1 1, Tuple 2 2 ] - - describe "fixRecord" $ do - describe "type inference" $ do - it "empty record" $ do - void $ liftEffect $ runCleanupT - $ fixFRP - $ \r -> do - let _ = r `shouldHaveInferredType` (Proxy :: Proxy {}) - pure (Tuple {} unit) - - it "non-empty record" $ do - void $ liftEffect $ runCleanupT - $ fixFRP - $ \r -> do - let - _ = r `shouldHaveInferredType` - ( Proxy :: Proxy - { event :: Event Int - , dynamic :: WeakDynamic Int - } - ) - pure - ( Tuple - { event: never :: Event Int - , dynamic: pure 0 :: Dynamic Int - } - unit - ) - - it "works for Events and Dynamics" $ do - log <- liftEffect $ new [] - Tuple fire _ <- liftEffect $ runCleanupT - $ fixFRP - $ \input -> do - { event, fire } <- liftEffect newEvent - dynamic <- holdDyn 0 event - - _ <- subscribeEvent_ (append log) input.event - _ <- subscribeWeakDyn_ (append log) input.dynamic - pure (Tuple { event, dynamic } fire) - - liftEffect $ fire 1 - - log `shouldHaveValue` [ 0, 1, 1 ] diff --git a/test/node/Main.purs b/test/node/Main.purs index 01b0ad6..efbe286 100644 --- a/test/node/Main.purs +++ b/test/node/Main.purs @@ -7,19 +7,15 @@ import DynamicSpec as DynamicSpec import Effect (Effect) import Effect.Aff (launchAff_) import EventSpec as EventSpec -import FixSpec as FixSpec import RIOSpec as RIOSpec import Test.Spec.Reporter (consoleReporter) import Test.Spec.Runner (runSpec) import TraceSpec as TraceSpec -import WeakDynamicSpec as WeakDynamicSpec main :: Effect Unit main = launchAff_ $ runSpec [ consoleReporter ] do - FixSpec.spec EventSpec.spec DynamicSpec.spec - WeakDynamicSpec.spec RIOSpec.spec TraceSpec.spec AsyncSpec.spec diff --git a/test/node/WeakDynamicSpec.purs b/test/node/WeakDynamicSpec.purs deleted file mode 100644 index 4c10257..0000000 --- a/test/node/WeakDynamicSpec.purs +++ /dev/null @@ -1,68 +0,0 @@ -module WeakDynamicSpec where - -import Prelude hiding (append) - -import Control.Monad.Cleanup (execCleanupT, runCleanupT) -import Data.Either (Either(..)) -import Data.Tuple (Tuple(..)) -import Specular.FRP (holdDyn, newEvent, subscribeWeakDyn_, uniqWeakDynBy, weaken) -import Specular.FRP.WeakDynamic (subscribeWeakDyn) -import Effect.Ref (new) -import Test.Spec (Spec, describe, it) -import Test.Utils (append, clear, liftEffect, shouldHaveValue, withLeakCheck) - -spec :: Spec Unit -spec = describe "WeakDynamic" $ do - - describe "pure" $ do - it "has a value immediately" $ do - log <- liftEffect $ new [] - _ <- liftEffect $ runCleanupT - $ subscribeWeakDyn_ (\x -> append log x) - $ - pure 0 - - log `shouldHaveValue` [ 0 ] - - describe "subscribeWeakDyn" $ do - it "updates the resulting Dynamic" $ do - { event, fire } <- liftEffect newEvent - log <- liftEffect $ new [] - Tuple dyn _ <- liftEffect $ runCleanupT $ map weaken $ holdDyn 1 event - - Tuple derivedDyn _ <- liftEffect $ runCleanupT $ subscribeWeakDyn - ( \x -> - do - append log (Left x) - pure (2 * x) - ) - dyn - - _ <- liftEffect $ execCleanupT $ subscribeWeakDyn_ (\x -> append log (Right x)) derivedDyn - - liftEffect $ fire 5 - - log `shouldHaveValue` [ Left 1, Right 2, Left 5, Right 10 ] - - describe "uniqWeakDynBy" $ do - it "updates value only when it changes" $ withLeakCheck $ do - { event, fire } <- liftEffect newEvent - log <- liftEffect $ new [] - Tuple wdyn unsub1 <- liftEffect $ runCleanupT do - dyn <- holdDyn 0 event - uniqWeakDynBy eq (weaken dyn) - - unsub2 <- liftEffect $ execCleanupT $ subscribeWeakDyn_ (\x -> append log x) wdyn - log `shouldHaveValue` [ 0 ] - - clear log - liftEffect $ fire 0 - liftEffect $ fire 1 - liftEffect $ fire 2 - liftEffect $ fire 2 - - log `shouldHaveValue` [ 1, 2 ] - - -- clean up - liftEffect unsub1 - liftEffect unsub2