on develop help sign in
Merge pull request #380 from reflex-frp/rc/0.6.0.0

Release candidate 0.6.0.0

commit873c833c3f6a167dfb7af7066ed5efb5b67eca22 authorElliot Cameron <3noch@users.noreply.github.com> dateWed 13 May 2020, 12:56 (6 years ago) parent94c2607955b591
22 files changed+511−168
cabal.project+1 dep/reflex-platform/github.json+3−3 reflex-dom-core/ChangeLog.md+46−15 reflex-dom-core/reflex-dom-core.cabal+19−15 reflex-dom-core/src/Reflex/Dom/Builder/Class.hs+1−1 reflex-dom-core/src/Reflex/Dom/Builder/Class/Events.hs+13−2 reflex-dom-core/src/Reflex/Dom/Builder/Hydratable.hs+4−4 reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs+88−41 reflex-dom-core/src/Reflex/Dom/Builder/Static.hs+12−9 reflex-dom-core/src/Reflex/Dom/Class.hs+4−7 reflex-dom-core/src/Reflex/Dom/Widget/Basic.hs−7 reflex-dom-core/src/Reflex/Dom/Widget/Input.hs+4−1 reflex-dom-core/src/Reflex/Dom/Widget/Resize.hs+4−4 reflex-dom-core/src/Reflex/Dom/Xhr.hs+8−6 reflex-dom-core/test/hydration.hs+127−28 reflex-dom/ChangeLog.md+13 reflex-dom/benchmark.hs+2−1 reflex-dom/reflex-dom.cabal+62−20 reflex-dom/src/Reflex/Dom/Internal.hs+5 release.nix+13−4 test/default.nix+10 test/reflex-dom-reexports.hs+72
cabal.projectnew file+1
@@ -0,0 +1 @@
1+packages: reflex-dom-core, reflex-dom
dep/reflex-platform/github.json+3−3
@@ -1,7 +1,7 @@
11{
22 "owner": "reflex-frp",
33 "repo": "reflex-platform",
4− "branch": "develop",
5− "rev": "e7b76dd552a10916c7d8702c11292dac4f4299ea",
6− "sha256": "0s1183arrwldcs50qhzgnv94v24n9bgq6dfq64wp0a3q2nzyvgwh"
4+ "branch": "master",
5+ "rev": "510b990d0b11f0626afbec5fe8575b5b2395391b",
6+ "sha256": "09cmahsbxr0963wq171c7j139iyzz49hramr4v9nsf684wcwkngv"
77}
reflex-dom-core/ChangeLog.md+46−15
@@ -1,44 +1,75 @@
11# Revision history for reflex-dom-core
22
3+## 0.6.0.0
4+
5+* ([#375](https://github.com/reflex-frp/reflex-dom/pull/375)) **(Breaking change)** Expose resized dimensions from `resizeDetector`, `resizeDetectorWithStyle`, and `resizeDetectorWithAttrs` from `Reflex.Dom.Widget.Resize`.
6+* ([#374](https://github.com/reflex-frp/reflex-dom/pull/374)) **(Breaking change)** Provide text clipboard data as value of `Paste` event.
7+* ([#348](https://github.com/reflex-frp/reflex-dom/pull/348)) **(Breaking change)** Make XHR response headers case insensitive by changing `_xhrResponse_headers :: Map Text Text` to `_xhrResponse_headers :: Map (CI Text) Text`.
8+* ([#225](https://github.com/reflex-frp/reflex-dom/pull/225)) **(Breaking change)** Add a functional dependency to `HasDomEvent`.
9+* ([#342](https://github.com/reflex-frp/reflex-dom/issues/342)) **(Breaking change)** The mouse wheel event is now a `WheelEventResult` rather than `()`. This provides information about the wheel's motion beyond the fact that it merely moved.
10+* ([#353](https://github.com/reflex-frp/reflex-dom/pull/353)) Support GHC 8.8.
11+* ([#358](https://github.com/reflex-frp/reflex-dom/pull/358)) Fix attribute support for explicitly namespaced elements.
12+* ([#363](https://github.com/reflex-frp/reflex-dom/pull/363)) Remove deprecation warnings for the following widgets in
13+ `Reflex.Dom.Widget.Basic`:
14+ * `Link`
15+ * `button`
16+ * `dtdd`
17+ * `linkClass`
18+ * `link`
19+ * `tabDisplay`
20+ * `tableDynAttr`
21+* ([#361](https://github.com/reflex-frp/reflex-dom/pull/361)) Fix bug in hydration causing the JavaScript to crash when dealing with unexpected HTML.
22+* ([#310](https://github.com/reflex-frp/reflex-dom/issues/310)) Fix the static rendering of which dropdown value is selected.
23+* ([#364](https://github.com/reflex-frp/reflex-dom/pull/364)) Export attributes used for controlling hydration at the element level:
24+ * "data-ssr" is now available as `Reflex.Dom.Builder.Immediate.hydratableAttribute`.
25+ * "data-hydration-skip" is now available as `Reflex.Dom.Builder.Immediate.skipHydrationAttribute`.
26+* ([#366](https://github.com/reflex-frp/reflex-dom/pull/366)) Bump bounds for `reflex` to include 0.7.
27+
328## 0.5.3
429
530* Deprecate a number of old inflexible widget helpers in `Reflex.Dom.Widget.Basic`:
6−
7− * `Link`
8− * `button`
9− * `dtdd`
10− * `linkClass`
11− * `link`
12− * `tabDisplay`
13− * `tableDynAttr`
31+ * `Link`
32+ * `button`
33+ * `dtdd`
34+ * `linkClass`
35+ * `link`
36+ * `tabDisplay`
37+ * `tableDynAttr`
1438
1539 And in `Reflex.Dom.Widget.Input`:
16−
17− * `TextInput`
18− * `TextAreaConfig`
19− * `CheckboxConfig`
20− * `FileInput`
21−
22− * Add `< 0.7` upper bound for reflex
40+ * `TextInput`
41+ * `TextAreaConfig`
42+ * `CheckboxConfig`
43+ * `FileInput`
44+* Add `< 0.7` upper bound for `reflex`.
2345
2446## 0.5.2
2547
2648* Update to use new dependent-sum/map packages and drop dependency on `*Tag` classes (e.g., `ShowTag`).
49+
2750* Update version bounds of base, containers, and stm
51+
2852* Update to use the newly split `these`/`semialign` packages. To use the pre-split `these` package, set the `split-these` flag to false.
53+
2954* Reintroduce "data-ssr": elements without this attribute are skipped during
3055 hydration.
56+
3157* Fix an issue in the hydration tests that prevented the test from finding the chromium executable
58+
3259* Relax constraints on `dyn` and `widgetHold` to match the ones in `networkView` and `networkHold` respectively
60+
3361* Fix prerender for RequesterT so that it doesn't accidentally discard a request that is made at the same moment as getPostBuild's Event fires
3462
3563## 0.5.1
3664
3765* Added support for marking elements with a "data-skip-hydration" attribute, which will cause hydration to ignore and skip over them.
66+
3867* Removed "data-ssr" attributes from statically rendered output.
3968
4069## 0.5
4170
4271* Add HydrationDomBuilderT to support hydration of statically rendered DOM nodes. See the note at the top of Reflex.Dom.Builder.Immediate.
72+
4373* As a result of the hydration changes, the Prerender class has changed, the type of `prerender` has changed and it is now a class method.
74+
4475* Add the Reflex.Dom.Xhr.FormData module to make posting formdata over xhr more convenient.
reflex-dom-core/reflex-dom-core.cabal+19−15
@@ -1,7 +1,13 @@
11Name: reflex-dom-core
2−Version: 0.5.3
2+Version: 0.6.0.0
33Synopsis: Functional Reactive Web Apps with Reflex
4−Description: Reflex-DOM is a Functional Reactive web framework based on the Reflex FRP engine
4+Description:
5+ Reflex-DOM is a Functional Reactive web framework based on the Reflex FRP engine: <https://reflex-frp.org/>.
6+ .
7+ The @reflex-dom@ package is a small wrapper around the @reflex-dom-core@ package. It pulls in the
8+ correct set of dependencies for each target platform (GHCJS, WebKitGTK, WASM, mobile, etc.).
9+ Libraries should depend on @reflex-dom-core@ and executables will usually depend on @reflex-dom@.
10+ All of @reflex-dom-core@'s modules are re-exported by @reflex-dom@.
511License: BSD3
612License-file: LICENSE
713Author: Ryan Trinkle
@@ -48,11 +54,12 @@ library
4854 hs-source-dirs: src
4955 build-depends:
5056 aeson >= 0.8 && < 1.5,
51− base >= 4.7 && < 4.13,
57+ base >= 4.7 && < 4.14,
5258 bifunctors >= 4.2 && < 6,
5359 bimap >= 0.3 && < 0.4,
54− blaze-builder,
60+ blaze-builder >= 0.4.1 && < 0.5,
5561 bytestring == 0.10.*,
62+ case-insensitive < 1.3,
5663 containers >= 0.6 && < 0.7,
5764 constraints >= 0.9 && < 0.12,
5865 contravariant >= 1.4 && < 1.6,
@@ -69,11 +76,11 @@ library
6976 lens >= 4.7 && < 5,
7077 monad-control >= 1.0.1 && < 1.1,
7178 mtl >= 2.1 && < 2.3,
72− primitive >= 0.5 && < 0.7,
73− random,
79+ primitive >= 0.5 && < 0.8,
80+ random >= 1.1 && < 1.2,
7481 ref-tf == 0.4.*,
75− reflex >= 0.6.2 && < 0.7,
76− semigroups >= 0.16 && < 0.19,
82+ reflex >= 0.6.2 && < 0.8,
83+ semigroups >= 0.16 && < 0.20,
7784 stm >= 2.4 && < 2.6,
7885 text == 1.2.*,
7986 transformers >= 0.3 && < 0.6,
@@ -84,7 +91,7 @@ library
8491 hs-source-dirs: src-ghcjs
8592 build-depends:
8693 ghcjs-base,
87− hashable == 1.2.*
94+ hashable >= 1.2 && < 1.4
8895 else
8996 hs-source-dirs: src-ghc
9097 if !os(windows)
@@ -92,7 +99,7 @@ library
9299
93100 if flag(split-these)
94101 build-depends:
95− semialign >= 1 && < 1.1,
102+ semialign >= 1 && < 1.2,
96103 these >= 1 && < 1.1
97104 else
98105 build-depends:
@@ -146,16 +153,12 @@ library
146153
147154 if flag(use-template-haskell)
148155 build-depends:
149− dependent-sum >= 0.6,
150156 dependent-sum-template >= 0.1 && < 0.2,
151− template-haskell
157+ template-haskell >= 2.12.0 && < 2.16
152158 other-extensions: TemplateHaskell
153159 cpp-options: -DUSE_TEMPLATE_HASKELL
154160 other-modules:
155161 Reflex.Dom.Builder.Class.TH
156− else
157− build-depends:
158− dependent-sum == 0.6.*
159162
160163test-suite hlint
161164 build-depends: base, hlint
@@ -166,6 +169,7 @@ test-suite hlint
166169test-suite hydration
167170 build-depends: base
168171 , aeson
172+ , async
169173 , bytestring
170174 , chrome-test-utils
171175 , constraints
reflex-dom-core/src/Reflex/Dom/Builder/Class.hs+1−1
@@ -638,7 +638,7 @@ instance (DomBuilder t m, MonadFix m, MonadHold t m, Group q, Query q, Additive
638638
639639-- * Convenience functions
640640
641−class HasDomEvent t target eventName where
641+class HasDomEvent t target eventName | target -> t where
642642 type DomEventType target eventName :: *
643643 domEvent :: EventName eventName -> target -> Event t (DomEventType target eventName)
644644
reflex-dom-core/src/Reflex/Dom/Builder/Class/Events.hs+13−2
@@ -14,6 +14,7 @@ import Data.GADT.Compare.TH
1414import Data.GADT.Compare
1515 (GOrdering(..), (:~:)(..), GEq(..), GCompare(..))
1616#endif
17+import Data.Text (Text)
1718
1819data EventTag
1920 = AbortTag
@@ -151,7 +152,7 @@ type family EventResultType (en :: EventTag) :: * where
151152 EventResultType 'BeforecopyTag = ()
152153 EventResultType 'CopyTag = ()
153154 EventResultType 'BeforepasteTag = ()
154− EventResultType 'PasteTag = ()
155+ EventResultType 'PasteTag = Maybe Text
155156 EventResultType 'ResetTag = ()
156157 EventResultType 'SearchTag = ()
157158 EventResultType 'SelectstartTag = ()
@@ -159,7 +160,17 @@ type family EventResultType (en :: EventTag) :: * where
159160 EventResultType 'TouchmoveTag = TouchEventResult
160161 EventResultType 'TouchendTag = TouchEventResult
161162 EventResultType 'TouchcancelTag = TouchEventResult
162− EventResultType 'WheelTag = ()
163+ EventResultType 'WheelTag = WheelEventResult
164+
165+data DeltaMode = DeltaPixel | DeltaLine | DeltaPage
166+ deriving (Show, Read, Eq, Ord, Bounded, Enum)
167+
168+data WheelEventResult = WheelEventResult
169+ { _wheelEventResult_deltaX :: Double
170+ , _wheelEventResult_deltaY :: Double
171+ , _wheelEventResult_deltaZ :: Double
172+ , _wheelEventResult_deltaMode :: DeltaMode
173+ } deriving (Show, Read, Eq, Ord)
163174
164175data TouchEventResult = TouchEventResult
165176 { _touchEventResult_altKey :: Bool
reflex-dom-core/src/Reflex/Dom/Builder/Hydratable.hs+4−4
@@ -21,10 +21,10 @@ import GHCJS.DOM.Types (MonadJSM (..))
2121#endif
2222import Reflex
2323import Reflex.Dom.Builder.Class
24−import Reflex.Dom.Builder.Immediate (HasDocument (..))
24+import Reflex.Dom.Builder.Immediate (HasDocument (..), hydratableAttribute)
2525import Reflex.Host.Class
2626
27−-- | A DomBuilder transformer that adds "data-ssr" to all elements such that the
27+-- | A DomBuilder transformer that adds an attribute to all elements such that the
2828-- hydration builder knows which bits of DOM were added by us, and which were
2929-- added by external scripts.
3030newtype HydratableT m a = HydratableT { runHydratableT :: m a } deriving (Functor, Applicative, Monad, MonadAtomicRef, MonadFix, MonadIO)
@@ -62,8 +62,8 @@ instance PrimMonad m => PrimMonad (HydratableT m) where
6262
6363makeHydratable :: Reflex t => ElementConfig er t m -> ElementConfig er t m
6464makeHydratable cfg = cfg
65− { _elementConfig_initialAttributes = Map.insert "data-ssr" "" $ _elementConfig_initialAttributes cfg
66− , _elementConfig_modifyAttributes = fmap (Map.delete "data-ssr") <$> _elementConfig_modifyAttributes cfg
65+ { _elementConfig_initialAttributes = Map.insert hydratableAttribute "" $ _elementConfig_initialAttributes cfg
66+ , _elementConfig_modifyAttributes = fmap (Map.delete hydratableAttribute) <$> _elementConfig_modifyAttributes cfg
6767 }
6868
6969instance PostBuild t m => PostBuild t (HydratableT m) where
reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs+88−41
@@ -107,6 +107,9 @@ module Reflex.Dom.Builder.Immediate
107107 , WindowConfig (..)
108108 , Window (..)
109109 , wrapWindow
110+ -- * Attributes for controlling hydration
111+ , hydratableAttribute
112+ , skipHydrationAttribute
110113 -- * Internal
111114 , traverseDMapWithKeyWithAdjust'
112115 , hoistTraverseWithKeyWithAdjust
@@ -137,11 +140,13 @@ import Data.IntMap.Strict (IntMap)
137140import Data.Maybe
138141import Data.Monoid ((<>))
139142import Data.Some (Some(..))
143+import Data.String (IsString)
140144import Data.Text (Text)
141145import Foreign.JavaScript.Internal.Utils
142146import Foreign.JavaScript.TH
147+import GHCJS.DOM.ClipboardEvent as ClipboardEvent
143148import GHCJS.DOM.Document (Document, createDocumentFragment, createElement, createElementNS, createTextNode, createComment)
144−import GHCJS.DOM.Element (getScrollTop, removeAttribute, removeAttributeNS, setAttribute, setAttributeNS, hasAttribute, hasAttributeNS)
149+import GHCJS.DOM.Element (getScrollTop, removeAttribute, removeAttributeNS, setAttribute, setAttributeNS, hasAttribute)
145150import GHCJS.DOM.EventM (EventM, event, on)
146151import GHCJS.DOM.KeyboardEvent as KeyboardEvent
147152import GHCJS.DOM.MouseEvent
@@ -175,6 +180,7 @@ import qualified Data.IntMap.Strict as IntMap
175180import qualified Data.Map as Map
176181import qualified Data.Text as T
177182import qualified GHCJS.DOM as DOM
183+import qualified GHCJS.DOM.DataTransfer as DataTransfer
178184import qualified GHCJS.DOM.DocumentAndElementEventHandlers as Events
179185import qualified GHCJS.DOM.DocumentOrShadowRoot as Document
180186import qualified GHCJS.DOM.Element as Element
@@ -192,6 +198,7 @@ import qualified GHCJS.DOM.TouchEvent as TouchEvent
192198import qualified GHCJS.DOM.TouchList as TouchList
193199import qualified GHCJS.DOM.Types as DOM
194200import qualified GHCJS.DOM.Window as Window
201+import qualified GHCJS.DOM.WheelEvent as WheelEvent
195202import qualified Reflex.Patch.DMap as PatchDMap
196203import qualified Reflex.Patch.DMapWithMove as PatchDMapWithMove
197204import qualified Reflex.Patch.MapWithMove as PatchMapWithMove
@@ -318,8 +325,7 @@ addHydrationStepWithSetup :: (Adjustable t m, MonadIO m) => m a -> (a -> Hydrati
318325addHydrationStepWithSetup setup f = getHydrationMode >>= \case
319326 HydrationMode_Immediate -> pure ()
320327 HydrationMode_Hydrating -> do
321− switchover <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_switchover
322− (s, _) <- lift $ runWithReplace setup $ return () <$ switchover
328+ s <- lift setup
323329 addHydrationStep (f s)
324330
325331-- | Add a hydration step
@@ -743,6 +749,14 @@ elementInternal elementTag cfg child = getHydrationMode >>= \case
743749 -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM (Element er HydrationDomSpace DomTimeline, a)
744750 #-}
745751
752+-- | An attribute which causes hydration to skip over an element completely.
753+skipHydrationAttribute :: IsString s => s
754+skipHydrationAttribute = "data-hydration-skip"
755+
756+-- | An attribute which signals that an element should be hydrated.
757+hydratableAttribute :: IsString s => s
758+hydratableAttribute = "data-ssr"
759+
746760{-# INLINE hydrateElement #-}
747761hydrateElement
748762 :: forall er t m a. (MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m)
@@ -764,18 +778,15 @@ hydrateElement elementTag cfg child = do
764778 }
765779 result <- HydrationDomBuilderT $ lift $ runReaderT (unHydrationDomBuilderT child) env'
766780 wrapResult <- liftIO newEmptyMVar
767− let skipAttr = "data-hydration-skip" :: DOM.JSString
768− ssrAttr = "data-ssr" :: DOM.JSString
781+ let -- Determine if we should skip an element. We currently skip elements for
782+ -- two reasons:
783+ -- 1) it was not produced by a static builder which supports hydration
784+ -- 2) it is explicitly marked to be skipped
769785 shouldSkip :: DOM.Element -> HydrationRunnerT t m Bool
770− shouldSkip e = case cfg ^. namespace of
771− Nothing -> do
772− skip <- hasAttribute e skipAttr
773− ssr <- hasAttribute e ssrAttr
774− pure $ skip || not ssr
775− Just ns -> do
776− skip <- hasAttributeNS e (Just ns) skipAttr
777− ssr <- hasAttributeNS e (Just ns) ssrAttr
778− pure $ skip || not ssr
786+ shouldSkip e = do
787+ skip <- hasAttribute e (skipHydrationAttribute :: DOM.JSString)
788+ hydratable <- hasAttribute e (hydratableAttribute :: DOM.JSString)
789+ pure $ skip || not hydratable
779790 childDom <- liftIO $ readIORef childDelayedRef
780791 let rawCfg = extractRawElementConfig cfg
781792 doc <- askDocument
@@ -791,7 +802,7 @@ hydrateElement elementTag cfg child = do
791802 Just node -> DOM.castTo DOM.Element node >>= \case
792803 Nothing -> go (Just node) -- this node is not an element, skip
793804 Just e -> shouldSkip e >>= \case
794− True -> go (Just node) -- this element is explicitly marked for being skipped by hydration
805+ True -> go (Just node) -- this element should be skipped by hydration
795806 False -> do
796807 t <- Element.getTagName e
797808 -- TODO: check attributes?
@@ -1277,8 +1288,8 @@ hydrateComment doc t mSetContents = do
12771288skipToAndReplaceComment
12781289 :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document)
12791290 => Text
1280− -> IORef Text
1281− -> HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text, IORef Text)
1291+ -> IORef (Maybe Text)
1292+ -> HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text, IORef (Maybe Text))
12821293skipToAndReplaceComment prefix key0Ref = getHydrationMode >>= \case
12831294 HydrationMode_Immediate -> do
12841295 -- If we're in immediate mode, we don't try to replace an existing comment,
@@ -1286,43 +1297,50 @@ skipToAndReplaceComment prefix key0Ref = getHydrationMode >>= \case
12861297 t <- textNodeImmediate $ TextNodeConfig ("" :: Text) Nothing
12871298 append $ toNode t
12881299 textNodeRef <- liftIO $ newIORef t
1289− keyRef <- liftIO $ newIORef ""
1300+ keyRef <- liftIO $ newIORef Nothing
12901301 pure (pure (), textNodeRef, keyRef)
12911302 HydrationMode_Hydrating -> do
12921303 doc <- askDocument
12931304 textNodeRef <- liftIO $ newIORef $ error "textNodeRef not yet initialized"
12941305 keyRef <- liftIO $ newIORef $ error "keyRef not yet initialized"
1295− let go key0 mLastNode = do
1296− parent <- askParent
1297− node <- maybe (Node.getFirstChildUnchecked parent) Node.getNextSiblingUnchecked mLastNode
1298− DOM.castTo DOM.Comment node >>= \case
1306+ let
1307+ go Nothing _ = do
1308+ tn <- createTextNode doc ("" :: Text)
1309+ insertAfterPreviousNode tn
1310+ HydrationRunnerT $ modify' $ \s -> s { _hydrationState_failed = True }
1311+ pure (tn, Nothing)
1312+ go (Just key0) mLastNode = do
1313+ parent <- askParent
1314+ maybe (Node.getFirstChild parent) Node.getNextSibling mLastNode >>= \case
1315+ Nothing -> go Nothing Nothing
1316+ Just node -> DOM.castTo DOM.Comment node >>= \case
12991317 Just comment -> do
1300− commentText <- Node.getTextContentUnchecked comment
1301− case T.stripPrefix (prefix <> key0) commentText of
1318+ commentText <- fromMaybe (error "Cannot get text content of comment node") <$> Node.getTextContent comment
1319+ case T.stripPrefix (prefix <> key0) commentText of -- 'key0' may be @""@ in which case we're just finding the actual key; TODO: Don't be clever.
13021320 Just key -> do
13031321 -- Replace the comment with an (invisible) text node
13041322 tn <- createTextNode doc ("" :: Text)
13051323 Node.replaceChild_ parent tn comment
1306− pure (tn, key)
1324+ pure (tn, Just key)
13071325 Nothing -> do
1308− go key0 (Just node)
1326+ go (Just key0) (Just node)
13091327 Nothing -> do
1310− go key0 (Just node)
1311− switchComment = do
1312− key0 <- liftIO $ readIORef key0Ref
1313− (tn, key) <- go key0 =<< getPreviousNode
1314− setPreviousNode $ Just $ toNode tn
1315− liftIO $ do
1316− writeIORef textNodeRef tn
1317− writeIORef keyRef key
1328+ go (Just key0) (Just node)
1329+ switchComment = do
1330+ key0 <- liftIO $ readIORef key0Ref
1331+ (tn, key) <- go key0 =<< getPreviousNode
1332+ setPreviousNode $ Just $ toNode tn
1333+ liftIO $ do
1334+ writeIORef textNodeRef tn
1335+ writeIORef keyRef key
13181336 pure (switchComment, textNodeRef, keyRef)
13191337
13201338{-# INLINABLE skipToReplaceStart #-}
1321−skipToReplaceStart :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document) => HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text, IORef Text)
1322−skipToReplaceStart = skipToAndReplaceComment "replace-start" =<< liftIO (newIORef "")
1339+skipToReplaceStart :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document) => HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text, IORef (Maybe Text))
1340+skipToReplaceStart = skipToAndReplaceComment "replace-start" =<< liftIO (newIORef $ Just "") -- TODO: Don't rely on clever usage @""@ to make this work.
13231341
13241342{-# INLINABLE skipToReplaceEnd #-}
1325−skipToReplaceEnd :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document) => IORef Text -> HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text)
1343+skipToReplaceEnd :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document) => IORef (Maybe Text) -> HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text)
13261344skipToReplaceEnd key = fmap (\(m,e,_) -> (m,e)) $ skipToAndReplaceComment "replace-end" key
13271345
13281346instance SupportsHydrationDomBuilder t m => NotReady t (HydrationDomBuilderT s t m) where
@@ -2239,7 +2257,7 @@ defaultDomEventHandler e evt = fmap (Just . EventResult) $ case evt of
22392257 Beforecopy -> return ()
22402258 Copy -> return ()
22412259 Beforepaste -> return ()
2242− Paste -> return ()
2260+ Paste -> getPasteData
22432261 Reset -> return ()
22442262 Search -> return ()
22452263 Selectstart -> return ()
@@ -2248,7 +2266,7 @@ defaultDomEventHandler e evt = fmap (Just . EventResult) $ case evt of
22482266 Touchend -> getTouchEvent
22492267 Touchcancel -> getTouchEvent
22502268 Mousewheel -> return ()
2251− Wheel -> return ()
2269+ Wheel -> getWheelEvent
22522270
22532271{-# INLINABLE defaultDomWindowEventHandler #-}
22542272defaultDomWindowEventHandler :: DOM.Window -> EventName en -> EventM DOM.Window (EventType en) (Maybe (EventResult en))
@@ -2289,7 +2307,7 @@ defaultDomWindowEventHandler w evt = fmap (Just . EventResult) $ case evt of
22892307 Beforecopy -> return ()
22902308 Copy -> return ()
22912309 Beforepaste -> return ()
2292− Paste -> return ()
2310+ Paste -> getPasteData
22932311 Reset -> return ()
22942312 Search -> return ()
22952313 Selectstart -> return ()
@@ -2298,7 +2316,7 @@ defaultDomWindowEventHandler w evt = fmap (Just . EventResult) $ case evt of
22982316 Touchend -> getTouchEvent
22992317 Touchcancel -> getTouchEvent
23002318 Mousewheel -> return ()
2301− Wheel -> return ()
2319+ Wheel -> getWheelEvent
23022320
23032321{-# INLINABLE withIsEvent #-}
23042322withIsEvent :: EventName en -> (IsEvent (EventType en) => r) -> r
@@ -2566,6 +2584,15 @@ getMouseEventCoords = do
25662584 e <- event
25672585 bisequence (getClientX e, getClientY e)
25682586
2587+{-# INLINABLE getPasteData #-}
2588+getPasteData :: EventM e ClipboardEvent (Maybe Text)
2589+getPasteData = do
2590+ e <- event
2591+ mdt <- ClipboardEvent.getClipboardData e
2592+ case mdt of
2593+ Nothing -> return Nothing
2594+ Just dt -> Just <$> DataTransfer.getData dt ("text" :: Text)
2595+
25692596{-# INLINABLE getTouchEvent #-}
25702597getTouchEvent :: EventM e TouchEvent TouchEventResult
25712598getTouchEvent = do
@@ -2607,6 +2634,26 @@ getTouchEvent = do
26072634 , _touchEventResult_touches = touches
26082635 }
26092636
2637+{-# INLINABLE getWheelEvent #-}
2638+getWheelEvent :: EventM e WheelEvent WheelEventResult
2639+getWheelEvent = do
2640+ e <- event
2641+ dx :: Double <- WheelEvent.getDeltaX e
2642+ dy :: Double <- WheelEvent.getDeltaY e
2643+ dz :: Double <- WheelEvent.getDeltaZ e
2644+ deltaMode :: Word <- WheelEvent.getDeltaMode e
2645+ return $ WheelEventResult
2646+ { _wheelEventResult_deltaX = dx
2647+ , _wheelEventResult_deltaY = dy
2648+ , _wheelEventResult_deltaZ = dz
2649+ , _wheelEventResult_deltaMode = case deltaMode of
2650+ 0 -> DeltaPixel
2651+ 1 -> DeltaLine
2652+ 2 -> DeltaPage
2653+ -- See https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/deltaMode
2654+ _ -> error "getWheelEvent: impossible encoding"
2655+ }
2656+
26102657instance MonadSample t m => MonadSample t (HydrationDomBuilderT s t m) where
26112658 {-# INLINABLE sample #-}
26122659 sample = lift . sample
reflex-dom-core/src/Reflex/Dom/Builder/Static.hs+12−9
@@ -55,7 +55,6 @@ import Reflex.PerformEvent.Class
5555import Reflex.PostBuild.Base
5656import Reflex.PostBuild.Class
5757import Reflex.TriggerEvent.Class
58−import System.Random (randomRIO)
5958
6059data StaticDomBuilderEnv t = StaticDomBuilderEnv
6160 { _staticDomBuilderEnv_shouldEscape :: Bool
@@ -64,6 +63,7 @@ data StaticDomBuilderEnv t = StaticDomBuilderEnv
6463 -- We use this to add a "selected" attribute to the appropriate "option" child element.
6564 -- This is not yet a perfect simulation of what the browser does, but it is much closer than doing nothing.
6665 -- TODO: Handle edge cases, e.g. setting to a value for which there is no option, then adding that option dynamically afterwards.
66+ , _staticDomBuilderEnv_nextRunWithReplaceKey :: IORef Int
6767 }
6868
6969newtype StaticDomBuilderT t m a = StaticDomBuilderT
@@ -164,7 +164,7 @@ instance (SupportsStaticDomBuilder t m, Monad m) => HasDocument (StaticDomBuilde
164164instance (Reflex t, Adjustable t m, MonadHold t m, SupportsStaticDomBuilder t m) => Adjustable t (StaticDomBuilderT t m) where
165165 runWithReplace a0 a' = do
166166 e <- StaticDomBuilderT ask
167− key <- replaceStart
167+ key <- replaceStart e
168168 (result0, result') <- lift $ runWithReplace (runStaticDomBuilderT a0 e) (flip runStaticDomBuilderT e <$> a')
169169 o <- hold (snd result0) $ fmapCheap snd result'
170170 StaticDomBuilderT $ modify $ (:) $ join o
@@ -174,10 +174,9 @@ instance (Reflex t, Adjustable t m, MonadHold t m, SupportsStaticDomBuilder t m)
174174 traverseDMapWithKeyWithAdjust = hoistDMapWithKeyWithAdjust traverseDMapWithKeyWithAdjust mapPatchDMap
175175 traverseDMapWithKeyWithAdjustWithMove = hoistDMapWithKeyWithAdjust traverseDMapWithKeyWithAdjustWithMove mapPatchDMapWithMove
176176
177−-- TODO remove this?
178−replaceStart :: (DomBuilder t m, MonadIO m) => m Text
179−replaceStart = do
180− str <- liftIO $ replicateM 8 $ randomRIO ('a', 'z')
177+replaceStart :: (DomBuilder t m, MonadIO m) => StaticDomBuilderEnv t -> m Text
178+replaceStart env = do
179+ str <- show <$> liftIO (atomicModifyRef (_staticDomBuilderEnv_nextRunWithReplaceKey env) $ \k -> (succ k, k))
181180 let key = "-" <> T.pack str
182181 _ <- commentNode $ def { _commentNodeConfig_initialContents = "replace-start" <> key }
183182 pure key
@@ -287,7 +286,8 @@ instance SupportsStaticDomBuilder t m => DomBuilder t (StaticDomBuilderT t m) wh
287286 es <- newFanEventWithTrigger $ \_ _ -> return (return ())
288287 StaticDomBuilderT $ do
289288 let shouldEscape = elementTag `Set.notMember` noEscapeElements
290− (result, innerHtml) <- lift $ lift $ runStaticDomBuilderT child $ StaticDomBuilderEnv shouldEscape Nothing
289+ nextRunWithReplaceKey <- asks _staticDomBuilderEnv_nextRunWithReplaceKey
290+ (result, innerHtml) <- lift $ lift $ runStaticDomBuilderT child $ StaticDomBuilderEnv shouldEscape Nothing nextRunWithReplaceKey
291291 attrs0 <- foldDyn applyMap (cfg ^. initialAttributes) (cfg ^. modifyAttributes)
292292 selectValue <- asks _staticDomBuilderEnv_selectValue
293293 let addSelectedAttr attrs sel = case Map.lookup "value" attrs of
@@ -341,7 +341,9 @@ instance SupportsStaticDomBuilder t m => DomBuilder t (StaticDomBuilderT t m) wh
341341 selectElement cfg child = do
342342 v <- holdDyn (cfg ^. selectElementConfig_initialValue) (cfg ^. selectElementConfig_setValue)
343343 (e, result) <- element "select" (_selectElementConfig_elementConfig cfg) $ do
344− (a, innerHtml) <- StaticDomBuilderT $ lift $ lift $ runStaticDomBuilderT child $ StaticDomBuilderEnv False $ Just (current v)
344+ (a, innerHtml) <- StaticDomBuilderT $ do
345+ nextRunWithReplaceKey <- asks _staticDomBuilderEnv_nextRunWithReplaceKey
346+ lift $ lift $ runStaticDomBuilderT child $ StaticDomBuilderEnv False (Just $ current v) nextRunWithReplaceKey
345347 StaticDomBuilderT $ lift $ modify $ (:) innerHtml
346348 return a
347349 let wrapped = SelectElement
@@ -363,7 +365,8 @@ renderStatic :: StaticWidget x a -> IO (a, ByteString)
363365renderStatic w = do
364366 runDomHost $ do
365367 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
366− let env0 = StaticDomBuilderEnv True Nothing
368+ nextRunWithReplaceKey <- newRef 0
369+ let env0 = StaticDomBuilderEnv True Nothing nextRunWithReplaceKey
367370 ((res, bs), FireCommand fire) <- hostPerformEventT $ runStaticDomBuilderT (runPostBuildT w postBuild) env0
368371 mPostBuildTrigger <- readRef postBuildTriggerRef
369372 forM_ mPostBuildTrigger $ \postBuildTrigger -> fire [postBuildTrigger :=> Identity ()] $ return ()
reflex-dom-core/src/Reflex/Dom/Class.hs+4−7
@@ -4,8 +4,7 @@ module Reflex.Dom.Class ( module Reflex.Dom.Class
44 , module Web.KeyCode
55 ) where
66
7−import Data.Map (Map)
8−import qualified Data.Map as Map
7+import Control.Lens
98import Reflex.Class
109import Web.KeyCode
1110
@@ -13,11 +12,9 @@ import Foreign.JavaScript.TH
1312import Reflex.PerformEvent.Class
1413import Reflex.PostBuild.Class
1514
16−import Prelude hiding (concat, mapM, mapM_, sequence)
17−
18−-- | Alias for Data.Map.singleton
19−(=:) :: k -> a -> Map k a
20−(=:) = Map.singleton
15+-- | Previously an alias for 'Data.Map.singleton', but now generalised to 'At'
16+(=:) :: (At m, Monoid m) => Index m -> IxValue m -> m
17+k =: a = at k ?~ a $ mempty
2118infixr 7 =: -- Ought to bind tighter than <>, which is infixr 6
2219
2320{-# DEPRECATED keycodeEnter "Instead of `x == keycodeEnter`, use `keyCodeLookup x == Enter`" #-}
reflex-dom-core/src/Reflex/Dom/Widget/Basic.hs−7
@@ -138,7 +138,6 @@ dynComment t = do
138138display :: (PostBuild t m, DomBuilder t m, Show a) => Dynamic t a -> m ()
139139display = dynText . fmap (T.pack . show)
140140
141−{-# DEPRECATED button "Use 'elAttr'' in combination with 'domEvent' instead" #-}
142141button :: DomBuilder t m => Text -> m (Event t ())
143142button t = do
144143 (e, _) <- element "button" def $ text t
@@ -272,25 +271,21 @@ dynamicAttributesToModifyAttributesWithInitial attrs0 d = do
272271 return $ if Map.null p then Nothing else Just p
273272 return modificationsNeeded
274273
275−{-# DEPRECATED Link "Will be removed when 'linkClass' and 'link' are removed. Follow those functions' deprecation instructions." #-}
276274newtype Link t
277275 = Link { _link_clicked :: Event t ()
278276 }
279277
280−{-# DEPRECATED linkClass "Use 'elAttr'' in combination with 'domEvent' for just clicks. Use 'routeLink' for Obelisk navigation" #-}
281278linkClass :: DomBuilder t m => Text -> Text -> m (Link t)
282279linkClass s c = do
283280 (l,_) <- elAttr' "a" ("class" =: c) $ text s
284281 return $ Link $ domEvent Click l
285282
286−{-# DEPRECATED link "Use 'elAttr'' in combination with 'domEvent' for just clicks. Use 'routeLink' for Obelisk navigation" #-}
287283link :: DomBuilder t m => Text -> m (Link t)
288284link s = linkClass s ""
289285
290286divClass :: forall t m a. DomBuilder t m => Text -> m a -> m a
291287divClass = elClass "div"
292288
293−{-# DEPRECATED dtdd "Use an application specific widget generating function" #-}
294289dtdd :: forall t m a. DomBuilder t m => Text -> m a -> m a
295290dtdd h w = do
296291 el "dt" $ text h
@@ -301,7 +296,6 @@ blank = return ()
301296
302297-- TODO: Move to an example project.
303298-- | A widget to display a table with static columns and dynamic rows.
304−{-# DEPRECATED tableDynAttr "Use an application specific widget generating function" #-}
305299tableDynAttr :: forall t m r k v. (Ord k, DomBuilder t m, MonadHold t m, PostBuild t m, MonadFix m)
306300 => Text -- ^ Class applied to <table> element
307301 -> [(Text, k -> Dynamic t r -> m v)] -- ^ Columns of (header, row key -> row value -> child widget)
@@ -321,7 +315,6 @@ tableDynAttr klass cols dRows rowAttrs = elAttr "div" (Map.singleton "style" "zo
321315-- | A widget to construct a tabbed view that shows only one of its child widgets at a time.
322316-- Creates a header bar containing a <ul> with one <li> per child; clicking a <li> displays
323317-- the corresponding child and hides all others.
324−{-# DEPRECATED tabDisplay "Use an application specific widget generating function" #-}
325318tabDisplay :: forall t m k. (MonadFix m, DomBuilder t m, MonadHold t m, PostBuild t m, Ord k)
326319 => Text -- ^ Class applied to <ul> element
327320 -> Text -- ^ Class applied to currently active <li> element
reflex-dom-core/src/Reflex/Dom/Widget/Input.hs+4−1
@@ -437,9 +437,12 @@ dropdown k0 options (DropdownConfig setK attrs) = do
437437 let xs = fmap (\(ix, (k, v)) -> ((ix, k), ((ix, k), v))) $ zip [0::Int ..] $ Map.toList os
438438 in (Map.fromList $ map snd xs, Bimap.fromList $ map fst xs)
439439 modifyAttrs <- dynamicAttributesToModifyAttributes attrs
440+ postBuild <- getPostBuild
441+ let setSelection = attachPromptlyDynWithMaybe (flip Bimap.lookupR) ixKeys $
442+ leftmost [setK, k0 <$ postBuild]
440443 let cfg = def
441444 & selectElementConfig_elementConfig . elementConfig_modifyAttributes .~ fmap mapKeysToAttributeName modifyAttrs
442− & selectElementConfig_setValue .~ fmap (T.pack . show) (attachPromptlyDynWithMaybe (flip Bimap.lookupR) ixKeys setK)
445+ & selectElementConfig_setValue .~ fmap (T.pack . show) setSelection
443446 (eRaw, _) <- selectElement cfg $ listWithKey indexedOptions $ \(ix, k) v -> do
444447 let optionAttrs = fmap (\dk -> "value" =: T.pack (show ix) <> if dk == k then "selected" =: "selected" else mempty) defaultKey
445448 elDynAttr "option" optionAttrs $ dynText v
reflex-dom-core/src/Reflex/Dom/Widget/Resize.hs+4−4
@@ -37,19 +37,19 @@ import qualified GHCJS.DOM.Types as DOM
3737-- This function can cause strange scrollbars to appear in some circumstances.
3838-- These can be hidden with pseudo selectors, for example, in webkit browsers:
3939-- .wrapper *::-webkit-scrollbar { width: 0px; background: transparent; }
40−resizeDetector :: (MonadJSM m, DomBuilder t m, PostBuild t m, TriggerEvent t m, PerformEvent t m, MonadHold t m, DomBuilderSpace m ~ GhcjsDomSpace, MonadJSM (Performable m), MonadFix m) => m a -> m (Event t (), a)
40+resizeDetector :: (MonadJSM m, DomBuilder t m, PostBuild t m, TriggerEvent t m, PerformEvent t m, MonadHold t m, DomBuilderSpace m ~ GhcjsDomSpace, MonadJSM (Performable m), MonadFix m) => m a -> m (Event t (Maybe Double, Maybe Double), a)
4141resizeDetector = resizeDetectorWithStyle ""
4242
4343resizeDetectorWithStyle :: (MonadJSM m, DomBuilder t m, PostBuild t m, TriggerEvent t m, PerformEvent t m, MonadHold t m, DomBuilderSpace m ~ GhcjsDomSpace, MonadJSM (Performable m), MonadFix m)
4444 => Text -- ^ A css style string. Warning: It should not contain the "position" style attribute.
4545 -> m a -- ^ The embedded widget
46− -> m (Event t (), a) -- ^ An 'Event' that fires on resize, and the result of the embedded widget
46+ -> m (Event t (Maybe Double, Maybe Double), a) -- ^ An 'Event' that fires on resize, and the result of the embedded widget
4747resizeDetectorWithStyle styleString = resizeDetectorWithAttrs ("style" =: styleString)
4848
4949resizeDetectorWithAttrs :: (MonadJSM m, DomBuilder t m, PostBuild t m, TriggerEvent t m, PerformEvent t m, MonadHold t m, DomBuilderSpace m ~ GhcjsDomSpace, MonadJSM (Performable m), MonadFix m)
5050 => Map Text Text -- ^ A map of attributes. Warning: It should not modify the "position" style attribute.
5151 -> m a -- ^ The embedded widget
52− -> m (Event t (), a) -- ^ An 'Event' that fires on resize, and the result of the embedded widget
52+ -> m (Event t (Maybe Double, Maybe Double), a) -- ^ An 'Event' that fires on resize, and the result of the embedded widget
5353resizeDetectorWithAttrs attrs w = do
5454 let childStyle = "position: absolute; left: 0; top: 0;"
5555 containerAttrs = "style" =: "position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;"
@@ -91,4 +91,4 @@ resizeDetectorWithAttrs attrs w = do
9191 size0 <- performEvent $ fmap (const $ liftJSM reset) pb
9292 rec resize <- performEventAsync $ fmap (\d cb -> (liftIO . cb) =<< liftJSM (resetIfChanged d)) $ tag (current dimensions) $ leftmost [expandScroll, shrinkScroll]
9393 dimensions <- holdDyn (Nothing, Nothing) $ leftmost [ size0, fmapMaybe id resize ]
94− return (fmapMaybe void resize, w')
94+ return (updated dimensions, w')
reflex-dom-core/src/Reflex/Dom/Xhr.hs+8−6
@@ -159,6 +159,8 @@ import Data.Aeson.Text
159159import Data.Aeson.Encode
160160#endif
161161import qualified Data.ByteString.Lazy as BL
162+import Data.CaseInsensitive (CI)
163+import qualified Data.CaseInsensitive as CI
162164import Data.Default
163165import qualified Data.List as L
164166import Data.Map (Map)
@@ -198,12 +200,12 @@ data XhrResponse
198200 , _xhrResponse_statusText :: Text
199201 , _xhrResponse_response :: Maybe XhrResponseBody
200202 , _xhrResponse_responseText :: Maybe Text
201− , _xhrResponse_headers :: Map Text Text
203+ , _xhrResponse_headers :: Map (CI Text) Text
202204 }
203205 deriving (Typeable)
204206
205207data XhrResponseHeaders =
206− OnlyHeaders (Set.Set Text) -- ^ Parse a subset of headers from the XHR Response
208+ OnlyHeaders (Set.Set (CI Text)) -- ^ Parse a subset of headers from the XHR Response
207209 | AllHeaders -- ^ Parse all headers from the XHR Response
208210 deriving (Show, Read, Eq, Ord, Typeable)
209211
@@ -275,7 +277,7 @@ newXMLHttpRequestWithError req cb = do
275277 AllHeaders -> parseAllHeadersString <$>
276278 xmlHttpRequestGetAllResponseHeaders xhr
277279 OnlyHeaders xs -> traverse (xmlHttpRequestGetResponseHeader xhr)
278− (Map.fromSet id xs)
280+ (Map.fromSet CI.original xs)
279281 _ <- liftJSM $ cb $ Right
280282 XhrResponse { _xhrResponse_status = status
281283 , _xhrResponse_statusText = statusText
@@ -288,10 +290,10 @@ newXMLHttpRequestWithError req cb = do
288290 return ()
289291 return xhr
290292
291−parseAllHeadersString :: Text -> Map Text Text
293+parseAllHeadersString :: Text -> Map (CI Text) Text
292294parseAllHeadersString s = Map.fromList $ fmap (stripBoth . T.span (/=':')) $
293295 L.dropWhileEnd T.null $ T.splitOn (T.pack "\r\n") s
294− where stripBoth (txt1, txt2) = (T.strip txt1, T.strip $ T.drop 1 txt2)
296+ where stripBoth (txt1, txt2) = (CI.mk $ T.strip txt1, T.strip $ T.drop 1 txt2)
295297
296298newXMLHttpRequest :: (HasJSContext m, MonadJSM m, IsXhrPayload a) => XhrRequest a -> (XhrResponse -> JSM ()) -> m XMLHttpRequest
297299newXMLHttpRequest req cb = newXMLHttpRequestWithError req $ mapM_ cb
@@ -444,7 +446,7 @@ xhrResponse_responseText :: Lens' XhrResponse (Maybe Text)
444446xhrResponse_responseText f (XhrResponse x1 x2 x3 x4 x5) = (\y -> XhrResponse x1 x2 x3 y x5) <$> f x4
445447{-# INLINE xhrResponse_responseText #-}
446448
447−xhrResponse_headers :: Lens' XhrResponse (Map Text Text)
449+xhrResponse_headers :: Lens' XhrResponse (Map (CI Text) Text)
448450xhrResponse_headers f (XhrResponse x1 x2 x3 x4 x5) = (\y -> XhrResponse x1 x2 x3 x4 y) <$> f x5
449451{-# INLINE xhrResponse_headers #-}
450452
reflex-dom-core/test/hydration.hs+127−28
@@ -1,13 +1,9 @@
1−{-# OPTIONS_GHC -fno-warn-orphans #-}
21{-# LANGUAGE BangPatterns #-}
3−{-# LANGUAGE StandaloneDeriving #-}
4−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5−{-# LANGUAGE UndecidableInstances #-}
62{-# LANGUAGE ConstraintKinds #-}
7−{-# LANGUAGE CPP #-}
83{-# LANGUAGE FlexibleContexts #-}
94{-# LANGUAGE FlexibleInstances #-}
105{-# LANGUAGE GADTs #-}
6+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
117{-# LANGUAGE LambdaCase #-}
128{-# LANGUAGE MultiParamTypeClasses #-}
139{-# LANGUAGE OverloadedStrings #-}
@@ -15,13 +11,19 @@
1511{-# LANGUAGE RankNTypes #-}
1612{-# LANGUAGE RecursiveDo #-}
1713{-# LANGUAGE ScopedTypeVariables #-}
14+{-# LANGUAGE StandaloneDeriving #-}
1815{-# LANGUAGE TemplateHaskell #-}
1916{-# LANGUAGE TupleSections #-}
2017{-# LANGUAGE TypeApplications #-}
2118{-# LANGUAGE TypeFamilies #-}
19+{-# LANGUAGE UndecidableInstances #-}
20+
21+{-# OPTIONS_GHC -fno-warn-orphans #-}
2222
2323import Prelude hiding (fail)
2424import Control.Concurrent
25+import qualified Control.Concurrent.Async as Async
26+import Control.Lens.Operators
2527import Control.Monad hiding (fail)
2628import Control.Monad.Catch
2729import Control.Monad.Fail
@@ -31,12 +33,13 @@ import Control.Monad.Ref
3133import Data.Constraint.Extras
3234import Data.Constraint.Extras.TH
3335import Data.Dependent.Map (DMap)
34−import Data.Dependent.Sum (DSum(..), (==>), EqTag(..), ShowTag(..))
36+import Data.Dependent.Sum (DSum(..), (==>))
3537import Data.Functor.Identity
3638import Data.Functor.Misc
3739import Data.GADT.Compare.TH
3840import Data.GADT.Show.TH
3941import Data.IORef (IORef)
42+import Data.List (sort)
4043import Data.Maybe
4144import Data.Proxy
4245import Data.Text (Text)
@@ -47,6 +50,7 @@ import Network.Socket
4750import Network.Wai
4851import Network.WebSockets
4952import Reflex.Dom.Core
53+import Reflex.Dom.Widget.Input (dropdown)
5054import Reflex.Patch.DMapWithMove
5155import System.Directory
5256import System.Environment
@@ -55,14 +59,13 @@ import System.IO.Silently
5559import System.IO.Temp
5660import System.Process
5761import System.Which (staticWhich)
58−import qualified Test.HUnit as HUnit (assertEqual, assertFailure)
62+import qualified Test.HUnit as HUnit
5963import qualified Test.Hspec as H
6064import qualified Test.Hspec.Core.Spec as H
6165import Test.Hspec (xit)
6266import Test.Hspec.WebDriver hiding (runWD, click, uploadFile, WD)
6367import qualified Test.Hspec.WebDriver as WD
6468import Test.WebDriver (WD(..))
65−import Test.WebDriver.Exceptions (ServerError(..))
6669
6770import qualified Data.ByteString.Lazy as LBS
6871import qualified Data.Dependent.Map as DMap
@@ -78,6 +81,9 @@ import qualified Test.WebDriver.Capabilities as WD
7881import Test.Util.ChromeFlags
7982import Test.Util.UnshareNetwork
8083
84+-- ORPHAN: https://github.com/kallisti-dev/hs-webdriver/pull/167
85+deriving instance MonadMask WD
86+
8187chromium :: FilePath
8288chromium = $(staticWhich "chromium")
8389
@@ -93,11 +99,14 @@ instance MonadRef WD where
9399 writeRef r = WD . writeRef r
94100
95101assertEqual :: (MonadIO m, Eq a, Show a) => String -> a -> a -> m ()
96−assertEqual a b = liftIO . HUnit.assertEqual a b
102+assertEqual msg a b = liftIO $ HUnit.assertEqual msg a b
97103
98104assertFailure :: MonadIO m => String -> m ()
99105assertFailure = liftIO . HUnit.assertFailure
100106
107+assertBool :: (MonadIO m) => String -> Bool -> m ()
108+assertBool msg bool = liftIO $ HUnit.assertBool msg bool
109+
101110chromeConfig :: Text -> [Text] -> WD.WDConfig
102111chromeConfig fp flags = WD.useBrowser (WD.chrome { WD.chromeBinary = Just $ T.unpack fp, WD.chromeOptions = T.unpack <$> flags }) WD.defaultConfig
103112
@@ -770,6 +779,15 @@ tests withDebugging wdConfig caps _selenium = do
770779 prerender_ (pure ()) (liftIO $ trigger "Client")
771780 textNode $ TextNodeConfig "Initial" $ Just e
772781
782+ describe "namespaces" $ session' $ do
783+ it "dyn can be nested in namespaced widget" $ runWD $ do
784+ testWidget (pure ()) (checkTextInTag "svg" "one") $ do
785+ let svgRootCfg = def
786+ & (elementConfig_namespace ?~ "http://www.w3.org/2000/svg")
787+ & (elementConfig_initialAttributes .~ ("width" =: "100%" <> "height" =: "100%" <> "viewBox" =: "0 0 1536 2048"))
788+ void $ element "svg" svgRootCfg $ do
789+ dyn_ $ text "one" <$ pure ()
790+
773791 describe "runWithReplace" $ session' $ do
774792 it "works" $ runWD $ do
775793 replaceChan :: Chan Text <- liftIO newChan
@@ -914,6 +932,58 @@ tests withDebugging wdConfig caps _selenium = do
914932 , el "span" . text <$> replace
915933 ]
916934
935+ let checkInnerHtml t x = findElemWithRetry (WD.ByTag t) >>= (`attr` "innerHTML") >>= (`shouldBe` Just x)
936+ it "removes bracketing comments" $ runWD $ do
937+ replaceChan :: Chan () <- liftIO newChan
938+ let
939+ preSwitchover = checkInnerHtml "div" "before|<!--replace-start-0-->inner1<!--replace-end-0-->|after"
940+ check () = do
941+ liftIO $ writeChan replaceChan () -- trigger creation of p tag
942+ _ <- findElemWithRetry $ WD.ByTag "p" -- wait till p tag is created
943+ checkInnerHtml "div" "before|<p>inner2</p>|after"
944+ testWidget' preSwitchover check $ do
945+ replace <- triggerEventWithChan replaceChan
946+ el "div" $ do
947+ text "before|"
948+ _ <- runWithReplace (text "inner1") $ el "p" (text "inner2") <$ replace
949+ text "|after"
950+ it "ignores extra ending bracketing comment" $ runWD $ do
951+ replaceChan :: Chan () <- liftIO newChan
952+ let
953+ preSwitchover = checkInnerHtml "div" "before|<!--replace-start-0-->inner1<!--replace-end-0--><!--replace-end-0-->|after"
954+ check () = do
955+ liftIO $ writeChan replaceChan () -- trigger creation of p tag
956+ _ <- findElemWithRetry $ WD.ByTag "p" -- wait till p tag is created
957+ checkInnerHtml "div" "before|inner2|after"
958+ testWidget' preSwitchover check $ do
959+ replace <- triggerEventWithChan replaceChan
960+ el "div" $ do
961+ text "before|"
962+ _ <- runWithReplace (text "inner1" *> comment "replace-end-0") $ text "inner2" <$ replace
963+ text "|after"
964+ void $ runWithReplace blank $ el "p" blank <$ replace -- Signal tag for end of test
965+ it "ignores missing ending bracketing comments" $ runWD $ do
966+ replaceChan :: Chan () <- liftIO newChan
967+ let
968+ preSwitchover = do
969+ checkInnerHtml "div" "before|<!--replace-start-0-->inner1<!--replace-end-0-->|after"
970+ divEl <- findElemWithRetry (WD.ByTag "div")
971+ let wrongHtml = "<!--replace-start-0-->inner1"
972+ actualHtml :: String <- WD.executeJS
973+ [WD.JSArg divEl, WD.JSArg wrongHtml]
974+ "arguments[0].innerHTML = arguments[1]; return arguments[0].innerHTML"
975+ actualHtml `shouldBe` wrongHtml
976+ check () = do
977+ liftIO $ writeChan replaceChan () -- trigger creation of p tag
978+ _ <- findElemWithRetry $ WD.ByTag "p" -- wait till p tag is created
979+ checkInnerHtml "div" "before|<p>inner2</p>|after"
980+ testWidget' preSwitchover check $ do
981+ replace <- triggerEventWithChan replaceChan
982+ el "div" $ do
983+ text "before|"
984+ _ <- runWithReplace (text "inner1") $ el "p" (text "inner2") <$ replace
985+ text "|after"
986+
917987 describe "traverseDMapWithKeyWithAdjust" $ session' $ do
918988 let widget :: DomBuilder t m => DKey a -> Identity a -> m (Identity a)
919989 widget k (Identity v) = elAttr "li" ("id" =: textKey k) $ do
@@ -1230,6 +1300,29 @@ tests withDebugging wdConfig caps _selenium = do
12301300 el "ol" $ text "inner"
12311301 text "after"
12321302
1303+ -- TODO: This test presupposes the exact set of labels that "dropdown" places in the "value" fields to distinguish options.
1304+ -- This dependence on internal implementation details is undesirable in a test case, but seems fairly tricky to avoid.
1305+ -- It seems expedient for the time being to expect this test case to be updated, should those implementation details ever change.
1306+ describe "dropdown" $ session' $ do
1307+ let doTest expectedOpts (initialValue :: Text) = do
1308+ let doCheck = do
1309+ es <- findElemsWithRetry $ WD.ByTag "option"
1310+ opts <- mapM fetchElement es
1311+ assertEqual "missing/extra/incorrect option element(s)" expectedOpts (sort opts)
1312+ testWidget doCheck doCheck $ do
1313+ void $ dropdown initialValue (constDyn (("aa" :: Text) =: "aaa" <> "bb" =: "bbb")) def
1314+ fetchElement e = do
1315+ val <- WD.attr e "value"
1316+ sel <- WD.attr e "selected"
1317+ return (fromMaybe "" val, isJust sel)
1318+ -- The "aa" test case is important, but a good test implementation probably needs to avoid selenium, because HTML parsers will insert a "selected" attribute on the first "option" tag if no selected attributes are present; thus as written, this erroneously succeeds on the old implementation (but properly implemented, should fail)
1319+ -- Thus, it would appear that we do actually need a HTML5 or maybe XML parser for this test suite.
1320+ xit "statically renders initial values (on aa)" $ runWD $ do
1321+ doTest [("0",True),("1",False)] "aa"
1322+ -- These tests are currently marked "pending" (by using "xit" instead of "it") because this test has a high chance of non-deterministically failing, which is a problem elsewhere in this test suite as well
1323+ xit "statically renders initial values (on bb)" $ runWD $ do
1324+ doTest [("0",False),("1",True)] "bb"
1325+
12331326data Selenium = Selenium
12341327 { _selenium_portNumber :: PortNumber
12351328 , _selenium_stopServer :: IO ()
@@ -1297,6 +1390,9 @@ checkTextInId i expected = do
12971390findElemWithRetry :: Selector -> WD WD.Element
12981391findElemWithRetry = withRetry . WD.findElem
12991392
1393+findElemsWithRetry :: Selector -> WD [WD.Element]
1394+findElemsWithRetry = withRetry . WD.findElems
1395+
13001396getBody :: WD WD.Element
13011397getBody = WD.findElem $ WD.ByTag "body"
13021398
@@ -1305,8 +1401,8 @@ withRetry a = wait 300
13051401 where wait :: Int -> WD a
13061402 wait 0 = a
13071403 wait n = try a >>= \case
1308− Left (_ :: SomeException) -> do
1309− liftIO $ threadDelay 100000
1404+ Left (e :: SomeException) -> do
1405+ liftIO $ putStrLn ("(retrying due to " <> show e <> ")") *> threadDelay 100000
13101406 wait $ n - 1
13111407 Right v -> return v
13121408
@@ -1388,23 +1484,26 @@ testWidgetDebug' withDebugging beforeJS afterSwitchover bodyWidget = do
13881484 ]
13891485 -- hSilence to get rid of ConnectionClosed logs
13901486 silenceIfDebug = if withDebugging then id else hSilence [stderr]
1391− jsaddleWarp = forkIO $ silenceIfDebug $ Warp.runSettings settings application
1392− jsaddleTid <- liftIO jsaddleWarp
1393− putStrLnDebug "taking waitJSaddle"
1394− liftIO $ takeMVar waitJSaddle
1395− putStrLnDebug "opening page"
1396− WD.openPage $ "http://localhost:" <> show jsaddlePort
1397− putStrLnDebug "running beforeJS"
1398− a <- beforeJS
1399− putStrLnDebug "putting waitBeforeJS"
1400− liftIO $ putMVar waitBeforeJS ()
1401− putStrLnDebug "taking waitUntilSwitchover"
1402− liftIO $ takeMVar waitUntilSwitchover
1403− putStrLnDebug "running afterSwitchover"
1404− b <- afterSwitchover a
1405− putStrLnDebug "killing jsaddle thread"
1406− liftIO $ killThread jsaddleTid
1407− return b
1487+ jsaddleWarp = silenceIfDebug $ Warp.runSettings settings application
1488+ withAsync' jsaddleWarp $ do
1489+ putStrLnDebug "taking waitJSaddle"
1490+ liftIO $ takeMVar waitJSaddle
1491+ putStrLnDebug "opening page"
1492+ WD.openPage $ "http://localhost:" <> show jsaddlePort
1493+ putStrLnDebug "running beforeJS"
1494+ a <- beforeJS
1495+ putStrLnDebug "putting waitBeforeJS"
1496+ liftIO $ putMVar waitBeforeJS ()
1497+ putStrLnDebug "taking waitUntilSwitchover"
1498+ liftIO $ takeMVar waitUntilSwitchover
1499+ putStrLnDebug "running afterSwitchover"
1500+ afterSwitchover a
1501+
1502+withAsync' :: (MonadIO m, MonadMask m) => IO a -> m b -> m b
1503+withAsync' f g = bracket
1504+ (liftIO $ Async.async f)
1505+ (liftIO . Async.uninterruptibleCancel)
1506+ (const g)
14081507
14091508data Key2 a where
14101509 Key2_Int :: Int -> Key2 Int
reflex-dom/ChangeLog.md+13
@@ -1,5 +1,17 @@
11# Revision history for reflex-dom
22
3+## 0.6.0.0
4+
5+* ([#379](https://github.com/reflex-frp/reflex-dom/pull/379)) Re-export all modules from `reflex-dom-core`. The newly re-exported modules are:
6+ * `Foreign.JavaScript.Utils`
7+ * `Reflex.Dom.Builder.Hydratable`
8+ * `Reflex.Dom.Modals.Class`
9+ * `Reflex.Dom.Prerender`
10+ * `Reflex.Dom.Time`
11+ * `Reflex.Dom.WebSocket.Query`
12+ * `Reflex.Dom.Xhr.FormData`
13+* ([#366](https://github.com/reflex-frp/reflex-dom/pull/366)) Bump bounds for `reflex` to include 0.7.
14+
315## 0.5.3
416
517* On Android, enable prompting the user for geolocation
@@ -18,4 +30,5 @@
1830## 0.5
1931
2032* Re-export new hydration widget "mainWidget" functions.
33+
2134* The use-warp flag now properly takes precedence on macOS.
reflex-dom/benchmark.hs+2−1
@@ -15,8 +15,9 @@ import qualified Data.Text as T
1515import Reflex.Dom
1616import System.Random
1717import qualified Data.Map as Map
18−import Data.Dependent.Map (DMap (..), DSum (..))
18+import Data.Dependent.Map (DMap (..))
1919import qualified Data.Dependent.Map as DMap
20+import Data.Dependent.Sum (DSum (..))
2021import Data.Functor.Misc
2122import Data.Functor.Identity
2223import Data.Functor.Compose
reflex-dom/reflex-dom.cabal+62−20
@@ -1,10 +1,15 @@
11Name: reflex-dom
2−Version: 0.5.3
2+Version: 0.6.0.0
33Synopsis: Functional Reactive Web Apps with Reflex
44Description:
5− Reflex-DOM is a Functional Reactive web framework based on the Reflex FRP engine.
5+ Reflex-DOM is a Functional Reactive web framework based on the Reflex FRP engine: <https://reflex-frp.org/>.
66 .
7− For hackage documentation, please see: <https://hackage.haskell.org/package/reflex-dom-core>.
7+ The @reflex-dom@ package is a small wrapper around the @reflex-dom-core@ package. It pulls in the
8+ correct set of dependencies for each target platform (GHCJS, WebKitGTK, WASM, mobile, etc.).
9+ Libraries should depend on @reflex-dom-core@ and executables will usually depend on @reflex-dom@.
10+ All of @reflex-dom-core@'s modules are re-exported by @reflex-dom@.
11+ .
12+ For Hackage documentation, please see: <https://hackage.haskell.org/package/reflex-dom-core>.
813License: BSD3
914License-file: LICENSE
1015Author: Ryan Trinkle
@@ -47,49 +52,56 @@ library
4752 hs-source-dirs: src-android
4853 other-modules: Reflex.Dom.Android.MainWidget
4954 build-depends:
50− aeson,
55+ aeson >= 1.4 && < 1.5,
5156 android-activity == 0.1.*,
5257 data-default == 0.7.*,
53− jsaddle
58+ jsaddle >= 0.9.6 && < 0.10
5459 c-sources: cbits/MainWidget.c
5560 include-dirs: cbits/include
5661 install-includes: MainWidget.h
5762 cpp-options: -DANDROID
5863 build-tools: hsc2hs
5964 build-depends:
60− base >= 4.7 && < 4.13,
65+ base >= 4.7 && < 4.14,
6166 bytestring == 0.10.*,
62− reflex >= 0.6.2 && < 0.7,
63− reflex-dom-core >=0.5.2 && < 0.6,
67+ reflex >= 0.6.2 && < 0.8,
68+ reflex-dom-core == 0.6.0.0,
6469 text == 1.2.*
6570 if !impl(ghcjs)
6671 if flag(use-warp) && (os(linux) || os(osx))
6772 build-depends:
68− jsaddle,
69− jsaddle-warp
73+ jsaddle >= 0.9.6 && < 0.10,
74+ jsaddle-warp >= 0.9.6 && < 0.10
7075 else
7176 if os(osx) || os(ios)
7277 build-depends:
7378 data-default == 0.7.*,
74− jsaddle,
75− jsaddle-wkwebview
79+ jsaddle >= 0.9.6 && < 0.10,
80+ jsaddle-wkwebview >= 0.9.6 && < 0.10
7681 else
77− if flag(webkit2gtk)
82+ if arch(wasm32)
7883 build-depends:
79− jsaddle-webkit2gtk
84+ jsaddle >= 0.9.6 && < 0.10,
85+ jsaddle-wasm >= 0.1 && < 0.2
8086 else
81− if !os(android)
87+ if flag(webkit2gtk)
8288 build-depends:
83− jsaddle-webkit2gtk
89+ jsaddle-webkit2gtk >= 0.9.6 && < 0.10
90+ else
91+ if !os(android)
92+ build-depends:
93+ jsaddle-webkit2gtk >= 0.9.6 && < 0.10
8494
8595 exposed-modules:
8696 Reflex.Dom
8797 Reflex.Dom.Internal
8898 reexported-modules:
89− Foreign.JavaScript.TH
90− , Foreign.JavaScript.Orphans
99+ Foreign.JavaScript.Orphans
100+ , Foreign.JavaScript.TH
101+ , Foreign.JavaScript.Utils
91102 , Reflex.Dom.Builder.Class
92103 , Reflex.Dom.Builder.Class.Events
104+ , Reflex.Dom.Builder.Hydratable
93105 , Reflex.Dom.Builder.Immediate
94106 , Reflex.Dom.Builder.InputDisabled
95107 , Reflex.Dom.Builder.Static
@@ -97,14 +109,19 @@ library
97109 , Reflex.Dom.Core
98110 , Reflex.Dom.Location
99111 , Reflex.Dom.Main
112+ , Reflex.Dom.Modals.Class
100113 , Reflex.Dom.Old
114+ , Reflex.Dom.Prerender
115+ , Reflex.Dom.Time
101116 , Reflex.Dom.WebSocket
117+ , Reflex.Dom.WebSocket.Query
102118 , Reflex.Dom.Widget
103119 , Reflex.Dom.Widget.Basic
104120 , Reflex.Dom.Widget.Input
105121 , Reflex.Dom.Widget.Lazy
106122 , Reflex.Dom.Widget.Resize
107123 , Reflex.Dom.Xhr
124+ , Reflex.Dom.Xhr.FormData
108125 ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2 -ferror-spans
109126 if flag(expose-all-unfoldings)
110127 ghc-options: -fexpose-all-unfoldings
@@ -136,7 +153,19 @@ executable sortableList
136153 default-language: Haskell2010
137154
138155executable benchmark
139− build-depends: base, reflex, reflex-dom, text, prim-uniq, dependent-map, containers, transformers, mtl, ghcjs-dom, ghc-prim, random, dependent-sum
156+ build-depends: base
157+ , containers
158+ , dependent-map
159+ , dependent-sum
160+ , ghc-prim
161+ , ghcjs-dom
162+ , mtl
163+ , prim-uniq
164+ , random
165+ , reflex
166+ , reflex-dom
167+ , text
168+ , transformers
140169 hs-source-dirs: .
141170 main-is: benchmark.hs
142171 ghc-options: -O2 -fspecialise-aggressively
@@ -145,7 +174,20 @@ executable benchmark
145174 default-language: Haskell2010
146175
147176executable krausest
148− build-depends: base, reflex, reflex-dom, text, prim-uniq, dependent-map, containers, transformers, mtl, ghcjs-dom, ghc-prim, random, dependent-sum, vector
177+ build-depends: base
178+ , containers
179+ , dependent-map
180+ , dependent-sum
181+ , ghc-prim
182+ , ghcjs-dom
183+ , mtl
184+ , prim-uniq
185+ , random
186+ , reflex
187+ , reflex-dom
188+ , text
189+ , transformers
190+ , vector
149191 hs-source-dirs: benchmarks
150192 main-is: krausest.hs
151193 ghc-options: -O2 -fspecialise-aggressively
reflex-dom/src/Reflex/Dom/Internal.hs+5
@@ -80,6 +80,11 @@ run jsm = do
8080 startMainWidget a startPage jsm
8181 }
8282 forever $ threadDelay 1000000000
83+#elif defined(wasm32_HOST_ARCH)
84+import qualified Language.Javascript.JSaddle.Wasm as Wasm (run)
85+import Language.Javascript.JSaddle (JSM)
86+run :: JSM () -> IO ()
87+run = Wasm.run 0
8388#else
8489import Language.Javascript.JSaddle.WebKitGTK (run)
8590#endif
release.nix+13−4
@@ -1,12 +1,14 @@
11{ reflex-platform-fun ? import ./dep/reflex-platform
2+, supportedSystems ? ["x86_64-linux" "x86_64-darwin"]
23}:
34
45let
56 native-reflex-platform = reflex-platform-fun {};
67 inherit (native-reflex-platform.nixpkgs) lib;
7− systems = ["x86_64-linux" "x86_64-darwin"];
88
9− perPlatform = lib.genAttrs systems (system: let
9+ tests = system: import ./test { pkgs = (reflex-platform-fun { inherit system; }).nixpkgs; };
10+
11+ perPlatform = lib.genAttrs supportedSystems (system: let
1012 reflex-platform = reflex-platform-fun { inherit system; };
1113 compilers = [
1214 "ghc"
@@ -25,15 +27,22 @@ let
2527 (self: super: {
2628 _dep = super._dep // {
2729 reflex-dom = builtins.filterSource (path: type: !(builtins.elem (baseNameOf path) [
28− "release.nix"
2930 ".git"
3031 "dist"
32+ "dist-newstyle"
33+ ]) && !(builtins.elem path [
34+ ./CONTRIBUTING.md
35+ ./FAQ.md
36+ ./Quickref.md
37+ ./README.md
38+ ./release.nix
39+ ./test
3140 ])) ./.;
3241 };
3342 })
3443 ];
3544 };
36− all = {
45+ all = tests system // {
3746 inherit (reflex-platform.${ghc})
3847 reflex-dom-core
3948 reflex-dom
test/default.nixnew file+10
@@ -0,0 +1,10 @@
1+{ pkgs ? (import ../dep/reflex-platform {}).nixpkgs }:
2+{
3+ test-reflex-dom-reexports = pkgs.runCommand "test-reflex-dom-reexports" {} (
4+ let
5+ ghc = pkgs.haskellPackages.ghcWithPackages (p: [p.Cabal]);
6+ in ''
7+ ${ghc}/bin/runhaskell ${./reflex-dom-reexports.hs} ${../reflex-dom/reflex-dom.cabal} ${../reflex-dom-core/reflex-dom-core.cabal}
8+ touch "$out"
9+ '');
10+}
test/reflex-dom-reexports.hsnew file+72
@@ -0,0 +1,72 @@
1+-- This tests that reflex-dom re-exports all of reflex-dom-core's modules.
2+-- Without this test they easily drift.
3+module Main where
4+
5+import Control.Monad (when)
6+import Data.List (intercalate)
7+import Data.Maybe (fromMaybe, isNothing, mapMaybe)
8+import qualified Data.Set as Set
9+import Distribution.Types.PackageName (mkPackageName)
10+import Distribution.Compiler (CompilerFlavor (GHC))
11+import Distribution.ModuleName (ModuleName, components)
12+import Distribution.PackageDescription.Parsec (parseGenericPackageDescription)
13+import qualified Distribution.Parsec.Common as Dist
14+import Distribution.Parsec.ParseResult (runParseResult)
15+import qualified Distribution.System as Dist
16+import Distribution.Types.BuildInfo (buildable, defaultExtensions, defaultLanguage, hsSourceDirs, options)
17+import Distribution.Types.CondTree (simplifyCondTree)
18+import Distribution.Types.GenericPackageDescription (ConfVar (Arch, Impl, OS), condLibrary)
19+import Distribution.Types.Library (exposedModules, libBuildInfo, reexportedModules)
20+import Distribution.Types.ModuleReexport (ModuleReexport, moduleReexportOriginalName, moduleReexportOriginalPackage)
21+import Distribution.Utils.Generic (toUTF8BS, readUTF8File)
22+import System.Environment (getArgs)
23+import qualified System.Info
24+
25+main :: IO ()
26+main = do
27+ [reflexDomFile, reflexDomCoreFile] <- getArgs
28+ (_, reflexDomReexports) <- parseCabalExports reflexDomFile
29+ (reflexDomCoreExports', _) <- parseCabalExports reflexDomCoreFile
30+
31+ let
32+ reflexDomCoreExports = Set.fromList reflexDomCoreExports'
33+ reflexDomCorePackageName = mkPackageName "reflex-dom-core"
34+ reflexDomReexportsFromCore
35+ = Set.fromList
36+ $ mapMaybe (\x -> if let origPackage = moduleReexportOriginalPackage x
37+ in isNothing origPackage || origPackage == Just reflexDomCorePackageName
38+ then Just $ moduleReexportOriginalName x
39+ else Nothing
40+ )
41+ reflexDomReexports
42+
43+ when (reflexDomCoreExports /= reflexDomReexportsFromCore) $ do
44+ error $ intercalate "\n\t"
45+ $ "reflex-dom does not re-export the following modules from reflex-dom-core:"
46+ : map
47+ (intercalate "." . components)
48+ (Set.toAscList $ reflexDomCoreExports `Set.difference` reflexDomReexportsFromCore)
49+
50+ putStrLn "Test passed."
51+
52+parseCabalExports :: FilePath -> IO ([ModuleName], [ModuleReexport])
53+parseCabalExports file = do
54+ contents <- readUTF8File file
55+ let
56+ (warnings, result) = runParseResult $ parseGenericPackageDescription $ toUTF8BS contents
57+ osConfVar = case System.Info.os of
58+ "linux" -> Just Dist.Linux
59+ "darwin" -> Just Dist.OSX
60+ _ -> error "Unrecognized System.Info.os"
61+ archConfVar = Just Dist.X86_64
62+ evalConfVar v = Right $ case v of
63+ OS osVar -> Just osVar == osConfVar
64+ Arch archVar -> Just archVar == archConfVar
65+ Impl GHC _ -> True
66+ _ -> False
67+ pure $ case condLibrary <$> result of
68+ Right (Just condLib) ->
69+ let (_, lib) = simplifyCondTree evalConfVar condLib
70+ in (exposedModules lib, reexportedModules lib)
71+ Right Nothing -> error $ "Haskell package has no library component: " <> file
72+ Left (_, errors) -> error $ "Failed to parse " <> file <> ":\n" <> unlines (map show errors)