1
+ module Test.Spec.React18HooksSpec where
2
+
3
+ import Prelude
4
+
5
+ import Control.Monad.Rec.Class (forever )
6
+ import Data.Array as Array
7
+ import Data.Foldable (for_ , traverse_ )
8
+ import Data.Maybe (fromMaybe )
9
+ import Data.Monoid (guard , power )
10
+ import Data.String as String
11
+ import Data.Tuple.Nested ((/\))
12
+ import Effect.Aff (Milliseconds (..), apathize , delay , launchAff_ )
13
+ import Effect.Class (liftEffect )
14
+ import Effect.Ref as Ref
15
+ import Foreign.Object as Object
16
+ import React.Basic (fragment )
17
+ import React.Basic.DOM as R
18
+ import React.Basic.DOM.Events (targetValue )
19
+ import React.Basic.Events (handler , handler_ )
20
+ import React.Basic.Hooks (reactComponent )
21
+ import React.Basic.Hooks as Hooks
22
+ import React.TestingLibrary (cleanup , fireEventClick , renderComponent , typeText )
23
+ import Test.Spec (Spec , after_ , before , describe , it )
24
+ import Test.Spec.Assertions (shouldNotEqual )
25
+ import Test.Spec.Assertions.DOM (textContentShouldEqual )
26
+ import Web.DOM.Element (getAttribute )
27
+ import Web.HTML.HTMLElement as HTMLElement
28
+
29
+ spec ∷ Spec Unit
30
+ spec =
31
+ after_ cleanup do
32
+ before setup do
33
+ describe " React 18 hooks" do
34
+ it " useId works" \{ useId } -> do
35
+ { findByTestId } <- renderComponent useId {}
36
+ elem <- findByTestId " use-id"
37
+ idʔ <- getAttribute " id" (HTMLElement .toElement elem) # liftEffect
38
+ let id = idʔ # fromMaybe " "
39
+ id `shouldNotEqual` " "
40
+ elem `textContentShouldEqual` id
41
+
42
+ it " useTransition works" \{ useTransition } -> do
43
+ { findByText } <- renderComponent useTransition {}
44
+ elem <- findByText " 0"
45
+ fireEventClick elem
46
+ elem `textContentShouldEqual` " 1"
47
+
48
+ it " useDeferredValue hopefully works" \{ useDeferredValue } -> do
49
+ { findByTestId } <- renderComponent useDeferredValue {}
50
+ spanElem <- findByTestId " span"
51
+ spanElem `textContentShouldEqual` " 0"
52
+ findByTestId " input" >>= typeText (power " text" 100 )
53
+ spanElem `textContentShouldEqual` " 400"
54
+
55
+ it " useSyncExternalStore" \{ useSyncExternalStore } -> do
56
+ { findByTestId } <- renderComponent useSyncExternalStore {}
57
+ spanElem <- findByTestId " span"
58
+ spanElem `textContentShouldEqual` " 0"
59
+ delay (350.0 # Milliseconds )
60
+ spanElem `textContentShouldEqual` " 3"
61
+
62
+ it " useInsertionEffect works" \{ useInsertionEffect } -> do
63
+ { findByText } <- renderComponent useInsertionEffect {}
64
+ void $ findByText " insertion-done"
65
+
66
+ where
67
+ setup = liftEffect ado
68
+
69
+ useId <-
70
+ reactComponent " UseIDExample" \(_ :: { } ) -> Hooks.do
71
+ id <- Hooks .useId
72
+ pure $ R .div
73
+ { id
74
+ , _data: Object .singleton " testid" " use-id"
75
+ , children: [ R .text id ]
76
+ }
77
+
78
+ useTransition <-
79
+ reactComponent " UseTransitionExample" \(_ :: { } ) -> Hooks.do
80
+ isPending /\ startTransition <- Hooks .useTransition
81
+ count /\ setCount <- Hooks .useState 0
82
+ let handleClick = startTransition do setCount (_ + 1 )
83
+ pure $ R .div
84
+ { children:
85
+ [ guard isPending (R .text " Pending" )
86
+ , R .button
87
+ { onClick: handler_ handleClick
88
+ , children: [ R .text (show count) ]
89
+ }
90
+ ]
91
+ }
92
+
93
+ useDeferredValue <-
94
+ reactComponent " UseDeferredValueExample" \(_ :: { } ) -> Hooks.do
95
+ text /\ setText <- Hooks .useState' " "
96
+ textLength <- Hooks .useDeferredValue (String .length text)
97
+ pure $ fragment
98
+ [ R .input
99
+ { onChange: handler targetValue (traverse_ setText)
100
+ , _data: Object .singleton " testid" " input"
101
+ }
102
+ , R .span
103
+ { _data: Object .singleton " testid" " span"
104
+ , children: [ R .text (show textLength) ]
105
+ }
106
+ ]
107
+
108
+ useInsertionEffect <-
109
+ reactComponent " UseInsertionEffectExample" \(_ :: { } ) -> Hooks.do
110
+ text /\ setText <- Hooks .useState' " waiting"
111
+ Hooks .useInsertionEffect unit do
112
+ setText " insertion-done"
113
+ mempty
114
+ pure $ R .span_ [ R .text text ]
115
+
116
+ useSyncExternalStore <- do
117
+ { subscribe, getSnapshot, getServerSnapshot } <- do
118
+ subscribersRef <- Ref .new []
119
+ intRef <- Ref .new 0
120
+ -- Update the intRef every 100ms.
121
+ launchAff_ $ apathize $ forever do
122
+ delay (100.0 # Milliseconds )
123
+ intRef # Ref .modify_ (_ + 1 ) # liftEffect
124
+ subscribers <- subscribersRef # Ref .read # liftEffect
125
+ liftEffect $ for_ subscribers identity
126
+
127
+ pure
128
+ { subscribe: \callback -> do
129
+ subscribersRef # Ref .modify_ (Array .cons callback)
130
+ pure $
131
+ subscribersRef # Ref .modify_ (Array .drop 1 )
132
+ , getSnapshot: Ref .read intRef
133
+ , getServerSnapshot: Ref .read intRef
134
+ }
135
+
136
+ reactComponent " UseSyncExternalStoreExample" \(_ :: { } ) -> Hooks.do
137
+ number <- Hooks .useSyncExternalStore
138
+ subscribe
139
+ getSnapshot
140
+ getServerSnapshot
141
+ pure $ R .span { _data: Object .singleton " testid" " span" , children: [ R .text (show number) ] }
142
+
143
+ in { useId, useTransition, useDeferredValue, useInsertionEffect, useSyncExternalStore }
0 commit comments