git.iko.soy/rowtype-yoga/purescript-yoga-react-om
on main help sign in
14 files changed+43−522
since splitsnapshots
unifiedsplit
README.md+1 spago.yaml−22 src/Yoga/React/Om.purs+42−94 src/Yoga/React/Om/Signal.purs−30 test/Test/Main.js−3 test/Test/Main.purs−14 test/Test/Yoga/React/Om/ComposableHooksSpec.purs−46 test/Test/Yoga/React/Om/ErrorChannelSpec.purs−46 test/Test/Yoga/React/Om/LayerSpec.purs−39 test/Test/Yoga/React/Om/OmComponentSpec.purs−31 test/Test/Yoga/React/Om/RefSpec.purs−43 test/Test/Yoga/React/Om/SSRSpec.purs−37 test/Test/Yoga/React/Om/SharedCtxSpec.purs−65 test/Test/Yoga/React/Om/SignalSpec.purs−52
README.mdnew file+1
@@ -0,0 +1 @@
1+
spago.yaml−22
@@ -1,16 +1,7 @@
1−workspace: {}
2−
31package:
42 name: yoga-react-om
5− publish:
6− version: 0.1.0
7− license: MIT
8− location:
9− githubOwner: rowtype-yoga
10− githubRepo: purescript-yoga-react-om
113 dependencies:
124 - effect: ">=4.0.0 <5.0.0"
13− - hyrule: ">=2.4.0 <3.0.0"
145 - indexed-monad: ">=3.0.0 <4.0.0"
156 - maybe: ">=6.0.0 <7.0.0"
167 - newtype: ">=5.0.0 <6.0.0"
@@ -18,20 +9,7 @@ package:
189 - react-basic: ">=17.0.0 <18.0.0"
1910 - react-basic-hooks: ">=8.2.0 <9.0.0"
2011 - refs: ">=6.0.0 <7.0.0"
21− - st: ">=6.2.0 <7.0.0"
2212 - tuples: ">=7.0.0 <8.0.0"
2313 - type-equality: ">=4.0.1 <5.0.0"
2414 - unsafe-coerce: ">=6.0.0 <7.0.0"
2515 - yoga-om: ">=1.4.0 <2.0.0"
26− test:
27− main: Test.Main
28− dependencies:
29− - aff
30− - react-basic-hooks
31− - react-testing-library
32− - refs
33− - spec
34− - spec-discovery
35− - yoga-om-layer
36− - yoga-react-dom
37− - spec-om
src/Yoga/React/Om.purs+42−94
@@ -3,17 +3,14 @@ module Yoga.React.Om
33 , omComponent
44 , useCtx
55 , useOm
6− , useSignal
7− , useEvent
6+ , useEffectOm
87 , UseCtx
98 , UseOm
10− , UseSignal
11− , UseEvent
9+ , UseEffectOm
1210 , liftRender
1311 , bind
1412 , discard
1513 , pure
16− -- Re-exported React hooks, lifted into OmRender
1714 , useState
1815 , useState'
1916 , useEffect
@@ -39,52 +36,40 @@ import Control.Apply.Indexed (class IxApply)
3936import Control.Bind.Indexed (class IxBind, ibind)
4037import Control.Monad.Indexed (class IxMonad)
4138import Data.Functor.Indexed (class IxFunctor)
42−import Data.Maybe (Maybe(..))
4339import Data.Newtype (class Newtype, un)
40+import Data.Either (Either(..))
41+import Data.Maybe (Maybe(..))
4442import Data.Tuple.Nested ((/\), type (/\))
45−import Control.Monad.ST.Class (liftST)
4643import Effect (Effect)
44+import Effect.Aff (error, killFiber, launchAff, launchAff_, try)
45+import Effect.Aff as Aff
4746import Effect.Class (liftEffect)
4847import Effect.Ref as Ref
4948import Effect.Unsafe (unsafePerformEffect)
50−import FRP.Event as Event
5149import React.Basic (JSX)
5250import React.Basic.Hooks (Reducer, UseState, UseEffect, UseLayoutEffect, UseRef, UseMemo, UseReducer, UseId, UseTransition, UseDeferredValue) as ReExports
5351import React.Basic.Hooks as Hooks
5452import React.Basic.Hooks.Internal (Render, unsafeHook, unsafeRenderEffect)
5553import React.Basic.Hooks.Internal as Render
56−import Yoga.React.Om.Signal (Signal)
5754import Type.Equality (class TypeEquals)
5855import Unsafe.Coerce (unsafeCoerce)
5956import Yoga.Om (Om)
6057import Yoga.Om as Om
6158
62−-- | A render context that tracks the Om context at the type level.
63−-- | At runtime this is identical to `Render` — the ctx is phantom.
64−-- | Use qualified-do with this module (e.g. `Om.do`) to compose hooks.
6559newtype OmRender :: Row Type -> Type -> Type -> Type -> Type
6660newtype OmRender ctx x y a = OmRender (Render x y a)
6761
6862derive instance Newtype (OmRender ctx x y a) _
6963
70−-- The stash: a mutable slot written by omComponent before each render,
71−-- read by useCtx/useOm during render. Safe because React renders are
72−-- synchronous and single-threaded.
7364ctxStash :: Ref.Ref (forall r. { | r })
7465ctxStash = unsafePerformEffect (Ref.new (unsafeCoerce {}))
7566
76−-- Helper: unwrap OmRender to the raw Effect
7767toEffect :: forall ctx x y a. OmRender ctx x y a -> Effect a
7868toEffect = unsafeCoerce
7969
80−-- Helper: wrap raw Effect into OmRender
8170fromEffect :: forall ctx x y a. Effect a -> OmRender ctx x y a
8271fromEffect = unsafeCoerce
8372
84−-- ============================================================================
85−-- Indexed monad instances (for qualified-do)
86−-- ============================================================================
87−
8873instance IxFunctor (OmRender ctx) where
8974 imap f m = fromEffect (map f (toEffect m))
9075
@@ -99,10 +84,6 @@ instance IxBind (OmRender ctx) where
9984
10085instance IxMonad (OmRender ctx)
10186
102−-- ============================================================================
103−-- Standard monad instances (when x ~ y, for `pure`)
104−-- ============================================================================
105−
10687instance Functor (OmRender ctx x y) where
10788 map f m = fromEffect (map f (toEffect m))
10889
@@ -123,10 +104,6 @@ instance (TypeEquals x y, Semigroup a) => Semigroup (OmRender ctx x y a) where
123104instance (TypeEquals x y, Monoid a) => Monoid (OmRender ctx x y a) where
124105 mempty = fromEffect mempty
125106
126−-- ============================================================================
127−-- Qualified-do exports
128−-- ============================================================================
129−
130107bind :: forall a b x y z ctx. OmRender ctx x y a -> (a -> OmRender ctx y z b) -> OmRender ctx x z b
131108bind = ibind
132109
@@ -136,22 +113,12 @@ discard = ibind
136113pure :: forall a x ctx. a -> OmRender ctx x x a
137114pure a = fromEffect (Prelude.pure a)
138115
139−-- ============================================================================
140−-- Lifting
141−-- ============================================================================
142−
143−-- | Lift a standard React hook into OmRender.
144116liftRender :: forall ctx x y a. Render x y a -> OmRender ctx x y a
145117liftRender = OmRender
146118
147−-- ============================================================================
148−-- Hooks
149−-- ============================================================================
150−
151119data UseCtx :: Row Type -> Type -> Type
152120data UseCtx ctx hooks
153121
154−-- | Read the full Om context record inside a component.
155122useCtx :: forall @ctx hooks. OmRender ctx hooks (UseCtx ctx hooks) { | ctx }
156123useCtx = OmRender (unsafeHook readCtx)
157124 where
@@ -161,55 +128,43 @@ useCtx = OmRender (unsafeHook readCtx)
161128data UseOm :: Row Type -> Type -> Type -> Type
162129data UseOm ctx a hooks
163130
164−-- | Run a synchronous Effect that has access to the Om context.
165131useOm
166− :: forall @ctx hooks a
167− . ({ | ctx } -> Effect a)
168− -> OmRender ctx hooks (UseOm ctx a hooks) a
169−useOm f = OmRender (unsafeHook go)
170− where
171− go :: Effect a
172− go = Prelude.do
173− ctx :: { | ctx } <- unsafeCoerce (Ref.read ctxStash)
174− f ctx
175−
176−data UseSignal :: Type -> Type -> Type
177−data UseSignal a hooks
178−
179−useSignal
180− :: forall ctx hooks a
181− . Signal a
182− -> OmRender ctx hooks (UseSignal a hooks) a
183−useSignal signal = fromEffect Prelude.do
184− initial <- signal.read
185− val /\ setVal <- toEffect (liftRender (Hooks.useState initial))
186− toEffect
187− ( liftRender
188− ( Hooks.useEffectOnce Prelude.do
189− Event.subscribe signal.event (\new -> setVal (const new)) # liftST # map liftST
190− )
191− )
192− Prelude.pure val
193−
194−data UseEvent :: Type -> Type -> Type
195−data UseEvent a hooks
196−
197−useEvent
198− :: forall ctx hooks a
199− . Event.Event a
200− -> OmRender ctx hooks (UseEvent a hooks) (Maybe a)
201−useEvent event = fromEffect Prelude.do
202− val /\ setVal <- toEffect (liftRender (Hooks.useState Nothing))
203− toEffect
204− ( liftRender
205− ( Hooks.useEffectOnce Prelude.do
206− Event.subscribe event (\new -> setVal (const (Just new))) # liftST # map liftST
207− )
208− )
209− Prelude.pure val
210−-- ============================================================================
211−-- Re-exported React hooks, lifted into OmRender
212−-- ============================================================================
132+ :: forall ctx deps a hooks
133+ . Eq deps
134+ => deps
135+ -> Om { | ctx } () a
136+ -> OmRender ctx hooks (UseOm ctx a hooks) (Maybe a)
137+useOm deps om = fromEffect Prelude.do
138+ ctx :: { | ctx } <- unsafeCoerce (Ref.read ctxStash)
139+ result /\ setResult <- toEffect (useState Nothing)
140+ toEffect $ useEffect deps Prelude.do
141+ setResult (const Nothing)
142+ fiber <- launchAff Prelude.do
143+ r <- try (Om.runOm ctx { exception: \e -> Aff.throwError e } om)
144+ liftEffect (setResult \_ -> Just r)
145+ Prelude.pure Prelude.do
146+ launchAff_ (killFiber (error "Stale request cancelled") fiber)
147+ toEffect $ liftRender $ unsafeRenderEffect case result of
148+ Just (Left err) -> Aff.throwError err
149+ Just (Right a) -> Prelude.pure (Just a)
150+ Nothing -> Prelude.pure Nothing
151+
152+data UseEffectOm :: Row Type -> Type -> Type
153+data UseEffectOm ctx hooks
154+
155+useEffectOm
156+ :: forall ctx deps hooks
157+ . Eq deps
158+ => deps
159+ -> Om { | ctx } () Unit
160+ -> OmRender ctx hooks (UseEffectOm ctx hooks) Unit
161+useEffectOm deps om = fromEffect Prelude.do
162+ ctx :: { | ctx } <- unsafeCoerce (Ref.read ctxStash)
163+ toEffect $ useEffect deps Prelude.do
164+ fiber <- launchAff Prelude.do
165+ Om.runOm ctx { exception: \_ -> Prelude.pure unit } om
166+ Prelude.pure Prelude.do
167+ launchAff_ (killFiber (error "Stale request cancelled") fiber)
213168
214169useState
215170 :: forall ctx state hooks
@@ -298,13 +253,6 @@ useDeferredValue
298253 -> OmRender ctx hooks (Hooks.UseDeferredValue a hooks) a
299254useDeferredValue a = liftRender (Hooks.useDeferredValue a)
300255
301−-- ============================================================================
302−-- Component construction
303−-- ============================================================================
304−
305−-- | Create a React component with Om-based dependency injection.
306−-- | The context is captured once at construction time and made available
307−-- | to hooks via `useCtx` and `useOm` inside qualified-do blocks.
308256omComponent
309257 :: forall @ctx @err hooks props
310258 . String
src/Yoga/React/Om/Signal.pursdeleted−30
@@ -1,30 +0,0 @@
1−module Yoga.React.Om.Signal
2− ( Signal
3− , mkSignal
4− ) where
5−
6−import Prelude
7−
8−import Control.Monad.ST.Class (liftST)
9−import Effect (Effect)
10−import Effect.Ref as Ref
11−import FRP.Event (Event)
12−import FRP.Event as Event
13−
14−type Signal a =
15− { read :: Effect a
16− , modify :: (a -> a) -> Effect Unit
17− , event :: Event a
18− }
19−
20−mkSignal :: forall a. a -> Effect (Signal a)
21−mkSignal initial = do
22− ref <- Ref.new initial
23− { event, push } <- Event.create # liftST
24− pure
25− { read: Ref.read ref
26− , modify: \f -> do
27− new <- Ref.modify f ref
28− push new
29− , event
30− }
test/Test/Main.jsdeleted−3
@@ -1,3 +0,0 @@
1−import "global-jsdom/register";
2−
3−export function setupJsdom() {}
test/Test/Main.pursdeleted−14
@@ -1,14 +0,0 @@
1−module Test.Main where
2−
3−import Prelude
4−
5−import Effect (Effect)
6−import Test.Spec.Discovery (discoverAndRunSpecs)
7−import Test.Spec.Reporter (consoleReporter)
8−
9−foreign import setupJsdom :: Effect Unit
10−
11−main :: Effect Unit
12−main = do
13− setupJsdom
14− discoverAndRunSpecs [ consoleReporter ] """Test\.Yoga\.React\.Om\..*Spec"""
test/Test/Yoga/React/Om/ComposableHooksSpec.pursdeleted−46
@@ -1,46 +0,0 @@
1−module Test.Yoga.React.Om.ComposableHooksSpec where
2−
3−import Prelude
4−
5−import Effect (Effect)
6−import Effect.Ref (Ref)
7−import Yoga.Om (ask, toOm)
8−import Yoga.Om.Ref as Ref
9−import React.TestingLibrary (cleanup, render)
10−import Yoga.Om.Test (Spec, after_, describe, it, shouldEqual, textContentShouldEqual)
11−import Yoga.React.Om (OmRender, UseOm, omComponent, useOm)
12−import Yoga.React.Om as Om
13−import Yoga.React.DOM.Internal (text)
14−
15−useCounter
16− :: forall ctx hooks
17− . OmRender (counter :: Ref Int | ctx) hooks (UseOm (counter :: Ref Int | ctx) Int hooks) Int
18−useCounter = useOm \{ counter } -> Ref.read counter
19−
20−useLogger
21− :: forall ctx hooks
22− . String
23− -> OmRender (logger :: String -> Effect Unit | ctx) hooks (UseOm (logger :: String -> Effect Unit | ctx) Unit hooks) Unit
24−useLogger msg = useOm \{ logger } -> logger msg
25−
26−mkDashboard = omComponent "Dashboard" \_ -> Om.do
27− count <- useCounter
28− useLogger ("Rendered with count: " <> show count)
29− Om.pure $ text ("Dashboard: " <> show count)
30−
31−spec :: Spec Unit
32−spec = after_ cleanup do
33− describe "composable context-slicing hooks" mkCtx do
34− it "infers ctx as union of hook requirements" do
35− { logRef } <- ask
36− dashboard <- mkDashboard
37− { findByText } <- render (dashboard unit)
38− result <- findByText "Dashboard: 99" # toOm
39− result `textContentShouldEqual` "Dashboard: 99"
40− logged <- Ref.read logRef
41− logged `shouldEqual` "Rendered with count: 99"
42− where
43− mkCtx = do
44− logRef <- Ref.new ""
45− counterRef <- Ref.new 99
46− pure { counter: counterRef, logger: (\msg -> Ref.write msg logRef), logRef }
test/Test/Yoga/React/Om/ErrorChannelSpec.pursdeleted−46
@@ -1,46 +0,0 @@
1−module Test.Yoga.React.Om.ErrorChannelSpec where
2−
3−import Prelude
4−
5−import Data.Either (Either(..))
6−import Data.Variant (match)
7−import React.Basic (JSX)
8−import Test.Spec (Spec, describe, it)
9−import Yoga.Om.Test (shouldEqual)
10−import Yoga.Om as Om
11−import Yoga.React.Om (omComponent)
12−import Yoga.React.Om as Om
13−import Yoga.React.DOM.Internal (text)
14−
15−type LoadErr r = (loadFailed :: String | r)
16−
17−spec :: Spec Unit
18−spec = describe "omComponent error channel" do
19− it "propagates typed errors from before component creation" do
20− result <- Om.runReader {} failingApp
21− case result of
22− Left err -> err # match
23− { loadFailed: \msg -> msg `shouldEqual` "worker init failed"
24− , exception: \_ -> pure unit
25− }
26− Right _ -> "should have failed" `shouldEqual` "but didn't"
27−
28− it "allows error-free construction" do
29− result <- Om.runReader {} successApp
30− case result of
31− Left _ -> "should have succeeded" `shouldEqual` "but didn't"
32− Right _ -> pure unit
33−
34−loadWorker :: forall ctx err. Om.Om { | ctx } (LoadErr err) String
35−loadWorker = Om.throw { loadFailed: "worker init failed" }
36−
37−failingApp :: forall ctx err. Om.Om { | ctx } (LoadErr err) (Unit -> JSX)
38−failingApp = do
39− workerUrl <- loadWorker
40− omComponent "Never" \_ -> Om.do
41− Om.pure $ text workerUrl
42−
43−successApp :: forall ctx err. Om.Om { | ctx } (LoadErr err) (Unit -> JSX)
44−successApp = do
45− omComponent "OK" \_ -> Om.do
46− Om.pure $ text "ok"
test/Test/Yoga/React/Om/LayerSpec.pursdeleted−39
@@ -1,39 +0,0 @@
1−module Test.Yoga.React.Om.LayerSpec where
2−
3−import Prelude
4−
5−import Effect.Ref (Ref)
6−import Yoga.Om (toOm)
7−import Yoga.Om.Ref as Ref
8−import React.Basic (JSX)
9−import React.TestingLibrary (cleanup, render)
10−import Yoga.Om.Test (Spec, after_, describe, it, textContentShouldEqual)
11−import Yoga.Om.Layer (OmLayer, makeLayer, runLayer, provide)
12−import Yoga.React.Om (omComponent, useOm)
13−import Yoga.React.Om as Om
14−import Yoga.React.DOM.Internal (text)
15−
16−counterLayer :: OmLayer () () { counter :: Ref Int }
17−counterLayer = makeLayer do
18− counter <- Ref.new 42
19− pure { counter }
20−
21−mkApp :: OmLayer (counter :: Ref Int) () { app :: Unit -> JSX }
22−mkApp = makeLayer do
23− omComponent "CounterDisplay"
24− ( \_ -> Om.do
25− n <- useOm \{ counter } -> Ref.read counter
26− Om.pure $ text ("Count: " <> show n)
27− ) <#> { app: _ }
28−
29−appLayer :: OmLayer () () { app :: Unit -> JSX }
30−appLayer = mkApp `provide` counterLayer
31−
32−spec :: Spec Unit
33−spec = after_ cleanup do
34− describe "omComponent + OmLayer" (pure {}) do
35− it "wires layers and renders from provided context" do
36− { app } <- runLayer {} appLayer
37− { findByText } <- render (app unit)
38− result <- findByText "Count: 42" # toOm
39− result `textContentShouldEqual` "Count: 42"
test/Test/Yoga/React/Om/OmComponentSpec.pursdeleted−31
@@ -1,31 +0,0 @@
1−module Test.Yoga.React.Om.OmComponentSpec where
2−
3−import Prelude
4−
5−import React.TestingLibrary (cleanup, render)
6−import Yoga.Om (toOm)
7−import Yoga.Om.Test (Spec, after_, describe, it, itWith, textContentShouldEqual)
8−import Yoga.React.Om (omComponent, useCtx)
9−import Yoga.React.Om as Om
10−import Yoga.React.DOM.Internal (text)
11−
12−mkGreeter = omComponent "Greeter" \{ name } -> Om.do
13− { greeting, magicNumber } <- useCtx
14− Om.pure $ text (greeting <> ", " <> name <> "! You are " <> show magicNumber <> ".")
15−
16−spec :: Spec Unit
17−spec = after_ cleanup do
18− describe "omComponent" (pure { greeting: "Hello", magicNumber: 42 }) do
19− it "captures context and renders it" do
20− comp <- mkGreeter
21− { findByText } <- render (comp { name: "World" })
22− result <- findByText "Hello, World! You are 42." # toOm
23− result `textContentShouldEqual` "Hello, World! You are 42."
24−
25− itWith "different context produces different output"
26− (pure { greeting: "Howdy", magicNumber: 7 })
27− do
28− comp <- mkGreeter
29− { findByText } <- render (comp { name: "Partner" })
30− result <- findByText "Howdy, Partner! You are 7." # toOm
31− result `textContentShouldEqual` "Howdy, Partner! You are 7."
test/Test/Yoga/React/Om/RefSpec.pursdeleted−43
@@ -1,43 +0,0 @@
1−module Test.Yoga.React.Om.RefSpec where
2−
3−import Prelude
4−
5−import Data.Tuple.Nested ((/\))
6−import Effect.Class (liftEffect)
7−import Effect.Ref as Ref
8−import React.Basic (fragment)
9−import React.TestingLibrary (cleanup, fireEventClick, render)
10−import Yoga.Om (toOm)
11−import Yoga.Om.Test (Spec, after_, describe, it)
12−import Yoga.React.Om (omComponent, useCtx, useState)
13−import Yoga.React.Om as Om
14−import Yoga.React.DOM.HTML.Button (button)
15−import Yoga.React.DOM.Handler (onClick)
16−import Yoga.React.DOM.Internal (text)
17−
18−spec :: Spec Unit
19−spec = after_ cleanup do
20− describe "omComponent + Ref interaction" mkCtx do
21− it "mutates a Ref from ctx and re-renders" do
22− counter <- omComponent "Counter" \_ -> Om.do
23− { counter: ctr } <- useCtx
24− count /\ setCount <- useState 0
25− Om.pure $ fragment
26− [ text ("Count: " <> show count)
27− , button
28− { onClick: onClick \_ -> do
29− n <- Ref.modify (_ + 1) ctr
30− setCount (const n)
31− }
32− "inc"
33− ]
34− { findByText } <- render (counter unit)
35− _ <- findByText "Count: 0" # toOm
36− btn <- findByText "inc" # toOm
37− fireEventClick btn
38− _ <- findByText "Count: 1" # toOm
39− pure unit
40− where
41− mkCtx = do
42− ref <- liftEffect $ Ref.new 0
43− pure { counter: ref }
test/Test/Yoga/React/Om/SSRSpec.pursdeleted−37
@@ -1,37 +0,0 @@
1−module Test.Yoga.React.Om.SSRSpec where
2−
3−import Prelude
4−
5−import Effect.Ref (Ref)
6−import Yoga.Om (Om)
7−import Yoga.Om.Ref as Ref
8−import React.Basic (JSX)
9−import Yoga.Om.Test (Spec, describe, it, itWith, shouldEqual, shouldNotEqual)
10−import Yoga.React.Om (omComponent, useOm)
11−import Yoga.React.Om as Om
12−import Yoga.React.DOM.Internal (text)
13−import Yoga.React.DOM.Server (renderToString, renderToStaticMarkup)
14−
15−spec :: Spec Unit
16−spec = describe "SSR" mkCtx do
17− it "renderToString produces HTML with context values" do
18− comp <- mkCounter
19− let html = renderToString (comp unit)
20− html `shouldNotEqual` ""
21−
22− itWith "renderToStaticMarkup produces clean HTML"
23− (mkCtxWith 7)
24− do
25− comp <- mkCounter
26− let html = renderToStaticMarkup (comp unit)
27− html `shouldEqual` "Count: 7"
28− where
29− mkCtxWith n = Ref.new n <#> { counter: _ }
30− mkCtx = mkCtxWith 42
31−
32−mkCounter
33− :: forall ctx err
34− . Om { counter :: Ref Int | ctx } err (Unit -> JSX)
35−mkCounter = omComponent "SSRCounter" \_ -> Om.do
36− n <- useOm (Ref.read <<< _.counter)
37− Om.pure $ text $ "Count: " <> show n
test/Test/Yoga/React/Om/SharedCtxSpec.pursdeleted−65
@@ -1,65 +0,0 @@
1−module Test.Yoga.React.Om.SharedCtxSpec where
2−
3−import Prelude
4−
5−import Data.Tuple.Nested ((/\))
6−import Effect (Effect)
7−import Effect.Class (liftEffect)
8−import Effect.Ref (Ref)
9−import Yoga.Om (Om, ask, toOm)
10−import Yoga.Om.Ref as Ref
11−import React.Basic (JSX, fragment)
12−import React.TestingLibrary (cleanup, fireEventClick, render)
13−import Yoga.Om.Test (Spec, after_, describe, it)
14−import Yoga.React.Om (omComponent, useCtx, useState, useEffectOnce)
15−import Yoga.React.Om as Om
16−import Yoga.React.DOM.HTML.Button (button)
17−import Yoga.React.DOM.Handler (onClick)
18−import Yoga.React.DOM.Internal (text)
19−
20−spec :: Spec Unit
21−spec = after_ cleanup do
22− describe "shared context across components" mkCtx do
23− it "two components share a Ref via Om context" do
24− { notifyRef } <- ask
25− inc /\ display <- do
26− i <- mkIncrementer
27− d <- mkDisplay
28− pure (i /\ d)
29− { findByText } <- render (fragment [ inc unit, display unit ])
30− _ <- findByText "Value: 0" # toOm
31− btn <- findByText "+1" # toOm
32− fireEventClick btn
33− liftEffect do
34− cb <- Ref.read notifyRef
35− cb
36− _ <- findByText "Value: 1" # toOm
37− pure unit
38− where
39− mkCtx = do
40− ref <- Ref.new 0
41− notifyRef <- Ref.new (pure unit :: Effect Unit)
42− pure { counter: ref, notify: (\cb -> Ref.write cb notifyRef), notifyRef }
43−
44−mkIncrementer
45− :: forall ctx err
46− . Om { counter :: Ref Int | ctx } err (Unit -> JSX)
47−mkIncrementer =
48− omComponent "Incrementer" \_props -> Om.do
49− { counter } <- useCtx
50− Om.pure $ button
51− { onClick: onClick \_ -> Ref.modify_ (_ + 1) counter }
52− "+1"
53−
54−mkDisplay
55− :: forall ctx err
56− . Om { counter :: Ref Int, notify :: (Effect Unit -> Effect Unit) | ctx } err (Unit -> JSX)
57−mkDisplay =
58− omComponent "Display" \_ ->
59− Om.do
60− { counter, notify } <- useCtx
61− n /\ setN <- useState 0
62− useEffectOnce do
63− notify (Ref.read counter >>= \v -> setN (const v))
64− pure mempty
65− Om.pure $ text ("Value: " <> show n)
test/Test/Yoga/React/Om/SignalSpec.pursdeleted−52
@@ -1,52 +0,0 @@
1−module Test.Yoga.React.Om.SignalSpec where
2−
3−import Prelude
4−
5−import Effect.Class (liftEffect)
6−import React.Basic (JSX)
7−import React.TestingLibrary (cleanup, render)
8−import Yoga.Om (Om, ask, toOm)
9−import Yoga.Om.Test (Spec, after_, describe, it, itWith, textContentShouldEqual)
10−import Yoga.React.Om (omComponent, useCtx, useSignal)
11−import Yoga.React.Om as Om
12−import Yoga.React.Om.Signal (Signal, mkSignal)
13−import Yoga.React.DOM.Internal (text)
14−
15−spec :: Spec Unit
16−spec = after_ cleanup do
17− describe "useSignal" (mkCtx 42) do
18− it "renders initial signal value" do
19− comp <- mkCounter
20− { findByText } <- render (comp unit)
21− result <- findByText "Count: 42" # toOm
22− result `textContentShouldEqual` "Count: 42"
23−
24− itWith "re-renders when signal is modified" (mkCtx 0) do
25− { count: sig } <- ask
26− comp <- mkCounter
27− { findByText } <- render (comp unit)
28− _ <- findByText "Count: 0" # toOm
29− liftEffect $ sig.modify (_ + 1)
30− result <- findByText "Count: 1" # toOm
31− result `textContentShouldEqual` "Count: 1"
32−
33− itWith "multiple signal updates" (mkCtx 0) do
34− { count: sig } <- ask
35− comp <- mkCounter
36− { findByText } <- render (comp unit)
37− _ <- findByText "Count: 0" # toOm
38− sig.modify (_ + 10) # toOm
39− _ <- findByText "Count: 10" # toOm
40− sig.modify (_ + 5) # toOm
41− result <- findByText "Count: 15" # toOm
42− result `textContentShouldEqual` "Count: 15"
43− where
44− mkCtx n = liftEffect $ mkSignal n <#> { count: _ }
45−
46−mkCounter
47− :: forall ctx err
48− . Om { count :: Signal Int | ctx } err (Unit -> JSX)
49−mkCounter = omComponent "SignalCounter" \_ -> Om.do
50− { count } <- useCtx
51− n <- useSignal count
52− Om.pure $ text ("Count: " <> show n)