on develop help sign in
Test and fixes for hydration immediate mode inputElement
commit9c49db44d9b46cee34899ab269b13b0f6ad491c7 authorTom Smalley <tfsmalley@gmail.com> dateTue 4 Dec 2018, 15:06 (8 years ago) parent49216ac
2 files changed+219−109
reflex-dom-core/src/Reflex/Dom/Builder/Hydration.hs+10−5 reflex-dom-core/test/hydration.hs+209−104
reflex-dom-core/src/Reflex/Dom/Builder/Hydration.hs+10−5
@@ -109,6 +109,7 @@ import qualified GHCJS.DOM.DocumentAndElementEventHandlers as Events
109109import GHCJS.DOM.EventM (EventM, event, on)
110110import qualified GHCJS.DOM.EventM as DOM
111111import qualified GHCJS.DOM.FileList as FileList
112+import qualified GHCJS.DOM.DocumentOrShadowRoot as Document
112113import qualified GHCJS.DOM.HTMLInputElement as Input
113114import qualified GHCJS.DOM.HTMLSelectElement as Select
114115import qualified GHCJS.DOM.HTMLTextAreaElement as TextArea
@@ -210,9 +211,11 @@ addHydrationStepWithSetup setup f = getHydrationMode >>= \case
210211 HydrationDomBuilderT $ modify' (>> f s)
211212
212213-- | Add a hydration step
213−addHydrationStep :: MonadIO m => HydrationRunnerT t m () -> HydrationDomBuilderT t m ()
214+addHydrationStep :: (MonadIO m, Ref m ~ IORef, MonadRef m, PerformEvent t m, MonadFix m, MonadReflexCreateTrigger t m, MonadJSM m, MonadJSM (Performable m)) => HydrationRunnerT t m () -> HydrationDomBuilderT t m ()
214215addHydrationStep m = getHydrationMode >>= \case
215− HydrationMode_Immediate -> pure ()
216+ HydrationMode_Immediate -> do
217+ events <- askEvents
218+ lift $ runHydrationRunnerT m Nothing undefined events
216219 HydrationMode_Hydrating -> HydrationDomBuilderT $ modify' (>> m)
217220
218221-- | A monad for DomBuilder which just gets the results of children and pushes
@@ -552,7 +555,7 @@ hydrateTextNode t = do
552555
553556
554557{-# INLINABLE commentNodeInternal #-}
555−commentNodeInternal :: (MonadJSM m, MonadFix m, ToDOMString contents, Reflex t, Adjustable t m) => contents -> Maybe (Event t contents) -> HydrationDomBuilderT t m DOM.Comment
558+commentNodeInternal :: (Ref m ~ IORef, MonadRef m, PerformEvent t m, MonadReflexCreateTrigger t m, MonadJSM (Performable m), MonadJSM m, MonadFix m, ToDOMString contents, Reflex t, Adjustable t m) => contents -> Maybe (Event t contents) -> HydrationDomBuilderT t m DOM.Comment
556559commentNodeInternal !t mSetContents = getHydrationMode >>= \case
557560 HydrationMode_Immediate -> do
558561 n <- makeNodeInternal (askDocument >>= \doc -> createComment doc t)
@@ -659,6 +662,8 @@ instance (SupportsHydrationDomBuilder t m) => DomBuilder t (HydrationDomBuilderT
659662
660663 (fileChange, triggerFileChange) <- newTriggerEvent
661664
665+ doc <- askDocument
666+
662667 -- Expected initial value from config
663668 let v0 = _inputElementConfig_initialValue cfg
664669
@@ -682,11 +687,11 @@ instance (SupportsHydrationDomBuilder t m) => DomBuilder t (HydrationDomBuilderT
682687 v <- getValue -- We get the value after setting it in case the browser has mucked with it somehow
683688 liftIO $ triggerChangeBySetValue v
684689
685− -- TODO look at initial focus at hydration time?
686690 let focusChange' = leftmost
687691 [ False <$ Reflex.select (_element_events e) (WrapArg Blur)
688692 , True <$ Reflex.select (_element_events e) (WrapArg Focus)
689693 ]
694+ liftIO . triggerFocusChange =<< Node.isSameNode (toNode domElement) . fmap toNode =<< Document.getActiveElement doc
690695 requestDomAction_ $ liftIO . triggerFocusChange <$> focusChange'
691696
692697 Input.setChecked domInputElement $ _inputElementConfig_initialChecked cfg
@@ -712,7 +717,7 @@ instance (SupportsHydrationDomBuilder t m) => DomBuilder t (HydrationDomBuilderT
712717 ]
713718 checked <- holdUniqDyn checked'
714719
715− let initialFocus = False --TODO: Is this correct?
720+ let initialFocus = False -- Assume it isn't focused, but we update the actual focus state at switchover
716721 hasFocus <- holdDyn initialFocus focusChange
717722
718723 v <- holdDyn v0 $ leftmost
reflex-dom-core/test/hydration.hs+209−104
@@ -220,113 +220,217 @@ main = hspec $ parallel $ do
220220 pure ()
221221
222222 describe "inputElement" $ parallel $ do
223− it "doesn't wipe user input when switching over" $ do
224− inputRef <- newIORef ""
225− testWidget'
226− (do
227− e <- WD.findElem $ WD.ByTag "input"
228− WD.sendKeys "hello world" e
229− pure e)
230− (\e -> do
231− t <- WD.attr e "value"
232− liftIO $ t `shouldBe` Just "hello world"
233− WD.click <=< WD.findElem $ WD.ByTag "button"
234− input <- liftIO $ readIORef inputRef
235− liftIO $ input `shouldBe` "hello world"
236− ) $ do
237− e <- inputElement def
238− click <- button "save"
239− performEvent_ $ liftIO . writeIORef inputRef <$> tag (current (value e)) click
240− it "captures user input after switchover" $ do
241− inputRef <- newIORef ""
242− let checkValue = do
243− WD.sendKeys "hello world" <=< WD.findElem $ WD.ByTag "input"
223+ describe "hydration" $ do
224+ it "doesn't wipe user input when switching over" $ do
225+ inputRef <- newIORef ""
226+ testWidget'
227+ (do
228+ e <- WD.findElem $ WD.ByTag "input"
229+ WD.sendKeys "hello world" e
230+ pure e)
231+ (\e -> do
232+ t <- WD.attr e "value"
233+ liftIO $ t `shouldBe` Just "hello world"
244234 WD.click <=< WD.findElem $ WD.ByTag "button"
245235 input <- liftIO $ readIORef inputRef
246236 liftIO $ input `shouldBe` "hello world"
247− testWidget (pure ()) checkValue $ do
248− e <- inputElement def
249− click <- button "save"
250− performEvent_ $ liftIO . writeIORef inputRef <$> tag (current (value e)) click
251− it "sets focus appropriately" $ do
252− focusRef <- newIORef False
253− let checkValue = do
254− liftIO $ readIORef focusRef >>= flip shouldBe False
255− e <- WD.findElem $ WD.ByTag "input"
256− WD.moveToCenter e
257− WD.click e
258− liftIO $ threadDelay 100000
259− liftIO $ readIORef focusRef >>= flip shouldBe True
260− testWidget (pure ()) checkValue $ do
261− e <- inputElement def
262− performEvent_ $ liftIO . writeIORef focusRef <$> updated (_inputElement_hasFocus e)
263− it "sets value appropriately" $ do
264− valueByUIRef <- newIORef ""
265− valueRef <- newIORef ""
266− setValueChan :: Chan Text <- newChan
267− let checkValue = do
268− liftIO $ readIORef valueByUIRef >>= flip shouldBe ""
269− liftIO $ readIORef valueRef >>= flip shouldBe ""
270− e <- WD.findElem $ WD.ByTag "input"
271− WD.sendKeys "hello" e
272− liftIO $ do
273− threadDelay 100000
274− readIORef valueByUIRef >>= flip shouldBe "hello"
275− readIORef valueRef >>= flip shouldBe "hello"
276− writeChan setValueChan "world"
277− threadDelay 100000
278− readIORef valueByUIRef >>= flip shouldBe "hello"
279− readIORef valueRef >>= flip shouldBe "world"
280− testWidget (pure ()) checkValue $ do
281− (setValue, triggerSetValue) <- newTriggerEvent
282− prerender (pure ()) $ liftIO $ void $ forkIO $ forever $ do
283− triggerSetValue =<< readChan setValueChan
284− e <- inputElement $ def & inputElementConfig_setValue .~ setValue
285− performEvent_ $ liftIO . writeIORef valueByUIRef <$> _inputElement_input e
286− performEvent_ $ liftIO . writeIORef valueRef <$> updated (value e)
287− it "sets checked appropriately" $ do
288− checkedByUIRef <- newIORef False
289− checkedRef <- newIORef False
290− setCheckedChan <- newChan
291− let checkValue = do
292− liftIO $ readIORef checkedByUIRef >>= flip shouldBe False
293− liftIO $ readIORef checkedRef >>= flip shouldBe False
294− e <- WD.findElem $ WD.ByTag "input"
295− WD.moveToCenter e
296− WD.click e
297− liftIO $ do
298− threadDelay 100000
299− readIORef checkedByUIRef >>= flip shouldBe True
300− readIORef checkedRef >>= flip shouldBe True
301− writeChan setCheckedChan False
302− threadDelay 100000
303− readIORef checkedByUIRef >>= flip shouldBe True
304− readIORef checkedRef >>= flip shouldBe False
305− testWidget (pure ()) checkValue $ do
306− (setChecked, triggerSetChecked) <- newTriggerEvent
307− prerender (pure ()) $ liftIO $ void $ forkIO $ forever $ do
308− triggerSetChecked =<< readChan setCheckedChan
309− e <- inputElement $ def
310− & initialAttributes .~ "type" =: "checkbox"
311− & inputElementConfig_setChecked .~ setChecked
312− performEvent_ $ liftIO . writeIORef checkedByUIRef <$> _inputElement_checkedChange e
313− performEvent_ $ liftIO . writeIORef checkedRef <$> updated (_inputElement_checked e)
314− it "captures file uploads" $ do
315− filesRef :: IORef [Text] <- newIORef []
316− let uploadFile = do
317− e <- WD.findElem $ WD.ByTag "input"
318− path <- liftIO $ writeSystemTempFile "testFile" "file contents"
319− WD.sendKeys (T.pack path) e
320− WD.click <=< WD.findElem $ WD.ByTag "button"
321− liftIO $ removeFile path
322− input <- liftIO $ readIORef filesRef
323− liftIO $ input `shouldBe` [T.pack $ FilePath.takeFileName path]
324− testWidget (pure ()) uploadFile $ do
325− e <- inputElement $ def & initialAttributes .~ "type" =: "file"
326− click <- button "save"
327− prerender (pure ()) $ performEvent_ $ ffor (tag (current (_inputElement_files e)) click) $ \fs -> do
328− names <- liftJSM $ traverse File.getName fs
329− liftIO $ writeIORef filesRef names
237+ ) $ do
238+ e <- inputElement def
239+ click <- button "save"
240+ performEvent_ $ liftIO . writeIORef inputRef <$> tag (current (value e)) click
241+ it "captures user input after switchover" $ do
242+ inputRef <- newIORef ""
243+ let checkValue = do
244+ WD.sendKeys "hello world" <=< WD.findElem $ WD.ByTag "input"
245+ WD.click <=< WD.findElem $ WD.ByTag "button"
246+ input <- liftIO $ readIORef inputRef
247+ liftIO $ input `shouldBe` "hello world"
248+ testWidget (pure ()) checkValue $ do
249+ e <- inputElement def
250+ click <- button "save"
251+ performEvent_ $ liftIO . writeIORef inputRef <$> tag (current (value e)) click
252+ it "sets focus appropriately" $ do
253+ focusRef <- newIORef False
254+ let checkValue = do
255+ liftIO $ readIORef focusRef >>= flip shouldBe False
256+ e <- WD.findElem $ WD.ByTag "input"
257+ WD.click e
258+ liftIO $ threadDelay 100000
259+ liftIO $ readIORef focusRef >>= flip shouldBe True
260+ testWidget (pure ()) checkValue $ do
261+ e <- inputElement def
262+ performEvent_ $ liftIO . writeIORef focusRef <$> updated (_inputElement_hasFocus e)
263+ it "sets focus when focus occurs before hydration" $ do
264+ focusRef <- newIORef False
265+ let setup = do
266+ e <- WD.findElem $ WD.ByTag "input"
267+ WD.click e
268+ hasFocus <- (== e) <$> WD.activeElem
269+ liftIO $ do
270+ hasFocus `shouldBe` True
271+ readIORef focusRef >>= flip shouldBe False
272+ check = liftIO $ readIORef focusRef >>= flip shouldBe True
273+ testWidget setup check $ do
274+ e <- inputElement def
275+ performEvent_ $ liftIO . writeIORef focusRef <$> updated (_inputElement_hasFocus e)
276+ it "sets value appropriately" $ do
277+ valueByUIRef <- newIORef ""
278+ valueRef <- newIORef ""
279+ setValueChan :: Chan Text <- newChan
280+ let checkValue = do
281+ liftIO $ readIORef valueByUIRef >>= flip shouldBe ""
282+ liftIO $ readIORef valueRef >>= flip shouldBe ""
283+ e <- WD.findElem $ WD.ByTag "input"
284+ WD.sendKeys "hello" e
285+ liftIO $ do
286+ threadDelay 100000
287+ readIORef valueByUIRef >>= flip shouldBe "hello"
288+ readIORef valueRef >>= flip shouldBe "hello"
289+ writeChan setValueChan "world"
290+ threadDelay 100000
291+ readIORef valueByUIRef >>= flip shouldBe "hello"
292+ readIORef valueRef >>= flip shouldBe "world"
293+ testWidget (pure ()) checkValue $ do
294+ (setValue, triggerSetValue) <- newTriggerEvent
295+ prerender (pure ()) $ liftIO $ void $ forkIO $ forever $ do
296+ triggerSetValue =<< readChan setValueChan
297+ e <- inputElement $ def & inputElementConfig_setValue .~ setValue
298+ performEvent_ $ liftIO . writeIORef valueByUIRef <$> _inputElement_input e
299+ performEvent_ $ liftIO . writeIORef valueRef <$> updated (value e)
300+ it "sets checked appropriately" $ do
301+ checkedByUIRef <- newIORef False
302+ checkedRef <- newIORef False
303+ setCheckedChan <- newChan
304+ let checkValue = do
305+ liftIO $ readIORef checkedByUIRef >>= flip shouldBe False
306+ liftIO $ readIORef checkedRef >>= flip shouldBe False
307+ e <- WD.findElem $ WD.ByTag "input"
308+ WD.moveToCenter e
309+ WD.click e
310+ liftIO $ do
311+ threadDelay 100000
312+ readIORef checkedByUIRef >>= flip shouldBe True
313+ readIORef checkedRef >>= flip shouldBe True
314+ writeChan setCheckedChan False
315+ threadDelay 100000
316+ readIORef checkedByUIRef >>= flip shouldBe True
317+ readIORef checkedRef >>= flip shouldBe False
318+ testWidget (pure ()) checkValue $ do
319+ (setChecked, triggerSetChecked) <- newTriggerEvent
320+ prerender (pure ()) $ liftIO $ void $ forkIO $ forever $ do
321+ triggerSetChecked =<< readChan setCheckedChan
322+ e <- inputElement $ def
323+ & initialAttributes .~ "type" =: "checkbox"
324+ & inputElementConfig_setChecked .~ setChecked
325+ performEvent_ $ liftIO . writeIORef checkedByUIRef <$> _inputElement_checkedChange e
326+ performEvent_ $ liftIO . writeIORef checkedRef <$> updated (_inputElement_checked e)
327+ it "captures file uploads" $ do
328+ filesRef :: IORef [Text] <- newIORef []
329+ let uploadFile = do
330+ e <- WD.findElem $ WD.ByTag "input"
331+ path <- liftIO $ writeSystemTempFile "testFile" "file contents"
332+ WD.sendKeys (T.pack path) e
333+ WD.click <=< WD.findElem $ WD.ByTag "button"
334+ liftIO $ removeFile path
335+ input <- liftIO $ readIORef filesRef
336+ liftIO $ input `shouldBe` [T.pack $ FilePath.takeFileName path]
337+ testWidget (pure ()) uploadFile $ do
338+ e <- inputElement $ def & initialAttributes .~ "type" =: "file"
339+ click <- button "save"
340+ prerender (pure ()) $ performEvent_ $ ffor (tag (current (_inputElement_files e)) click) $ \fs -> do
341+ names <- liftJSM $ traverse File.getName fs
342+ liftIO $ writeIORef filesRef names
343+
344+ describe "hydration/immediate" $ do
345+ it "captures user input after switchover" $ do
346+ inputRef :: IORef Text <- newIORef ""
347+ let checkValue = do
348+ WD.sendKeys "hello world" <=< WD.findElem $ WD.ByTag "input"
349+ WD.click <=< WD.findElem $ WD.ByTag "button"
350+ input <- liftIO $ readIORef inputRef
351+ liftIO $ input `shouldBe` "hello world"
352+ testWidget (pure ()) checkValue $ prerender (pure ()) $ do
353+ e <- inputElement def
354+ click <- button "save"
355+ performEvent_ $ liftIO . writeIORef inputRef <$> tag (current (value e)) click
356+ it "sets focus appropriately" $ do
357+ focusRef <- newIORef False
358+ let checkValue = do
359+ liftIO $ readIORef focusRef >>= flip shouldBe False
360+ e <- WD.findElem $ WD.ByTag "input"
361+ WD.click e
362+ liftIO $ threadDelay 100000
363+ liftIO $ readIORef focusRef >>= flip shouldBe True
364+ testWidget (pure ()) checkValue $ prerender (pure ()) $ do
365+ e <- inputElement def
366+ performEvent_ $ liftIO . writeIORef focusRef <$> updated (_inputElement_hasFocus e)
367+ it "sets value appropriately" $ do
368+ valueByUIRef :: IORef Text <- newIORef ""
369+ valueRef :: IORef Text <- newIORef ""
370+ setValueChan :: Chan Text <- newChan
371+ let checkValue = do
372+ liftIO $ readIORef valueByUIRef >>= flip shouldBe ""
373+ liftIO $ readIORef valueRef >>= flip shouldBe ""
374+ e <- WD.findElem $ WD.ByTag "input"
375+ WD.sendKeys "hello" e
376+ liftIO $ do
377+ threadDelay 100000
378+ readIORef valueByUIRef >>= flip shouldBe "hello"
379+ readIORef valueRef >>= flip shouldBe "hello"
380+ writeChan setValueChan "world"
381+ threadDelay 100000
382+ readIORef valueByUIRef >>= flip shouldBe "hello"
383+ readIORef valueRef >>= flip shouldBe "world"
384+ testWidget (pure ()) checkValue $ prerender (pure ()) $ do
385+ (setValue, triggerSetValue) <- newTriggerEvent
386+ prerender (pure ()) $ liftIO $ void $ forkIO $ forever $ do
387+ triggerSetValue =<< readChan setValueChan
388+ e <- inputElement $ def & inputElementConfig_setValue .~ setValue
389+ performEvent_ $ liftIO . writeIORef valueByUIRef <$> _inputElement_input e
390+ performEvent_ $ liftIO . writeIORef valueRef <$> updated (value e)
391+ it "sets checked appropriately" $ do
392+ checkedByUIRef <- newIORef False
393+ checkedRef <- newIORef False
394+ setCheckedChan <- newChan
395+ let checkValue = do
396+ liftIO $ readIORef checkedByUIRef >>= flip shouldBe False
397+ liftIO $ readIORef checkedRef >>= flip shouldBe False
398+ e <- WD.findElem $ WD.ByTag "input"
399+ WD.moveToCenter e
400+ WD.click e
401+ liftIO $ do
402+ threadDelay 100000
403+ readIORef checkedByUIRef >>= flip shouldBe True
404+ readIORef checkedRef >>= flip shouldBe True
405+ writeChan setCheckedChan False
406+ threadDelay 100000
407+ readIORef checkedByUIRef >>= flip shouldBe True
408+ readIORef checkedRef >>= flip shouldBe False
409+ testWidget (pure ()) checkValue $ prerender (pure ()) $ do
410+ (setChecked, triggerSetChecked) <- newTriggerEvent
411+ prerender (pure ()) $ liftIO $ void $ forkIO $ forever $ do
412+ triggerSetChecked =<< readChan setCheckedChan
413+ e <- inputElement $ def
414+ & initialAttributes .~ "type" =: "checkbox"
415+ & inputElementConfig_setChecked .~ setChecked
416+ performEvent_ $ liftIO . writeIORef checkedByUIRef <$> _inputElement_checkedChange e
417+ performEvent_ $ liftIO . writeIORef checkedRef <$> updated (_inputElement_checked e)
418+ it "captures file uploads" $ do
419+ filesRef :: IORef [Text] <- newIORef []
420+ let uploadFile = do
421+ e <- WD.findElem $ WD.ByTag "input"
422+ path <- liftIO $ writeSystemTempFile "testFile" "file contents"
423+ WD.sendKeys (T.pack path) e
424+ WD.click <=< WD.findElem $ WD.ByTag "button"
425+ liftIO $ removeFile path
426+ input <- liftIO $ readIORef filesRef
427+ liftIO $ input `shouldBe` [T.pack $ FilePath.takeFileName path]
428+ testWidget (pure ()) uploadFile $ prerender (pure ()) $ do
429+ e <- inputElement $ def & initialAttributes .~ "type" =: "file"
430+ click <- button "save"
431+ prerender (pure ()) $ performEvent_ $ ffor (tag (current (_inputElement_files e)) click) $ \fs -> do
432+ names <- liftJSM $ traverse File.getName fs
433+ liftIO $ writeIORef filesRef names
330434
331435 describe "prerender" $ parallel $ do
332436 it "works in simple case" $ do
@@ -444,4 +548,5 @@ testWidget' beforeJS afterSwitchover bodyWidget = maybe (error "test timed out")
444548 a <- beforeJS
445549 liftIO $ putMVar waitBeforeJS ()
446550 liftIO $ takeMVar waitUntilSwitchover
551+ liftIO $ threadDelay 100000 -- wait a bit
447552 afterSwitchover a