Commit 9e872a96 authored by James Laver's avatar James Laver

v0.1.0

parent 953a4679
......@@ -20,13 +20,12 @@
"pulp": "^12.4.0",
"purescript": "^0.12.5",
"purs-loader": "^3.2.0",
"react-testing-library": "^5.9.0",
"react-testing-library": "^6.1.2",
"spago": "^0.7.5"
},
"dependencies": {
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-testing-library": "^6.1.2"
"react-dom": "^16.8.6"
},
"eslintConfig": {
"extends": "react-app"
......
......@@ -123,6 +123,11 @@ let additions =
, "spec", "spec-mocha", "unsafe-coerce" ]
"https://github.com/irresponsible/purescript-dom-simple"
"master"
, ffi-simple =
mkPackage
[ "functions", "nullable", "prelude" ]
"https://github.com/irresponsible/purescript-ffi-simple"
"v0.1.2"
, spec-mocha =
mkPackage
[ "console", "foldable-traversable", "exceptions", "spec" ]
......
......@@ -6,10 +6,12 @@
[ "console"
, "dom-simple"
, "effect"
, "ffi-simple"
, "functions"
, "newtype"
, "nullable"
, "prelude"
, "refs"
, "spec"
, "spec-mocha"
, "unsafe-coerce" ]
......
module Reactix.DOM.Raw
(LeafFactory, TreeFactory
, button, div, div', i, i', p, p', span, span'
(LeafFactory, ElementFactory
, button, div, div', hr
, i, i', p, p', span, span'
, text) where
import Reactix.React (Element, createDOMElement)
import Reactix.React (Element, createElement)
import Unsafe.Coerce (unsafeCoerce)
createLeafDOMElement :: forall props. String -> Record props -> Element
createLeafDOMElement e p = createDOMElement e p []
createLeafElement :: forall props. String -> Record props -> Element
createLeafElement e p = createElement e p []
-- A factory function for a DOM element with no children
type LeafFactory = forall props. Record props -> Element
-- A factory function for a DOM element with children
type TreeFactory = forall props. Record props -> Array Element -> Element
type ElementFactory = forall props. Record props -> Array Element -> Element
text :: String -> Element
text = unsafeCoerce
button :: TreeFactory
button = createDOMElement "button"
button :: ElementFactory
button = createElement "button"
div :: TreeFactory
div = createDOMElement "div"
div :: ElementFactory
div = createElement "div"
div' :: LeafFactory
div' = createLeafDOMElement "div"
div' = createLeafElement "div"
i :: TreeFactory
i = createDOMElement "i"
hr :: LeafFactory
hr = createLeafElement "hr"
i :: ElementFactory
i = createElement "i"
i' :: LeafFactory
i' = createLeafDOMElement "i"
i' = createLeafElement "i"
p :: TreeFactory
p = createDOMElement "p"
p :: ElementFactory
p = createElement "p"
p' :: LeafFactory
p' = createLeafDOMElement "p"
p' = createLeafElement "p"
span :: TreeFactory
span = createDOMElement "span"
span :: ElementFactory
span = createElement "span"
span' :: LeafFactory
span' = createLeafDOMElement "span"
span' = createLeafElement "span"
......@@ -4,12 +4,6 @@ var React = require("react");
function _simple(prop) {
return function() { return React[prop].apply(React, arguments); };
}
function _tuple(prop) {
return function(ctor) {
const r = React[prop].apply(React, Array.prototype.slice.call(arguments, 1));
return ctor(r[0])(r[1]);
}
}
function _memo(prop) {
return function() {
var args = Array.prototype.slice.call(arguments);
......@@ -17,16 +11,13 @@ function _memo(prop) {
return React[prop].apply(React, args);
}
}
exports._tuple = function tuple(ctor, v) { return ctor(v[0])(v[1]); };
exports._useContext = _simple('useContext');
exports._useDebugValue = _simple('useDebugValue');
exports._useDebugValuePrime = _simple('useDebugValue');
// exports._useImperativeHandle = _simple('useImperativeHandle');
exports._useState = _tuple('useState');
exports._useReducer = _tuple('useReducer');
exports._useRef = function(ctor, value) {
const r = React.useRef(value);
const set = function(v) { r.current = v; };
......@@ -40,16 +31,3 @@ exports._useMemo3 = _memo('useMemo');
exports._useMemo4 = _memo('useMemo');
exports._useMemo5 = _memo('useMemo');
exports._useEffect = _simple('useEffect');
exports._useEffect1 = _memo('useEffect');
exports._useEffect2 = _memo('useEffect');
exports._useEffect3 = _memo('useEffect');
exports._useEffect4 = _memo('useEffect');
exports._useEffect5 = _memo('useEffect');
exports._useLayoutEffect = _simple('useLayoutEffect');
exports._useLayoutEffect1 = _memo('useLayoutEffect');
exports._useLayoutEffect2 = _memo('useLayoutEffect');
exports._useLayoutEffect3 = _memo('useLayoutEffect');
exports._useLayoutEffect4 = _memo('useLayoutEffect');
exports._useLayoutEffect5 = _memo('useLayoutEffect');
This diff is collapsed.
'use strict';
exports.react = require("react");
exports.reactDOM = require('react-dom');
var React = require("react");
var react_dom = require('react-dom');
function _simple(prop) {
return function() { return React[prop].apply(React, arguments); };
}
exports._isValid = _simple('isValidElement');
exports._children = function(c) { return React.Children.toArray(c); };
exports._memo = _simple('memo');
exports._memoPrime = _simple('memo');
exports._createRef = function() { return React.createRef(); };
exports._deref = function(r) { return r.current; };
// https://reactjs.org/docs/react-api.html#reactforwardref
// exports._forwardRef = _simple('forwardRef');
// creating and cloning elements
exports._cloneElement = _simple('cloneElement');
function _createElement(elem, props, children) {
var c = children.slice();
c.unshift(props);
c.unshift(elem);
return React.createElement.apply(React, c);
}
exports._createElement = _createElement;
exports._createFragment = function(children) {
return _createElement(React.Fragment, {}, children);
};
exports._contextProvider = function(c) { return c.Provider; };
exports._contextConsumer = function(c) { return c.Consumer; };
exports._createContext = function(ctor, val) {
var c = React.createContext(val);
return {provider: c.Provider, consumer: c.Consumer, context: c};
};
exports._render = function(a,b) { return react_dom.render(a,b); };
exports._named = function(name, f) { Object.defineProperty(f, 'name', {value: name, writable: false}); return f; };
This diff is collapsed.
-- | https://reactjs.org/docs/events.html
module Reactix.SyntheticEvent where
import Prelude
import DOM.Simple as DOM
import Effect ( Effect )
import Effect.Uncurried ( EffectFn1, runEffectFn1 )
import FFI.Simple ( (..), (...) )
class IsSyntheticEvent e
foreign import data NativeEvent :: Type
foreign import data MouseEvent :: Type
foreign import data KeyboardEvent :: Type
instance keyboardEventIsSyntheticEvent :: IsSyntheticEvent KeyboardEvent
instance mouseEventIsSyntheticEvent :: IsSyntheticEvent MouseEvent
bubbles :: forall e. IsSyntheticEvent e => e -> Boolean
bubbles e = e .. "bubbles"
cancelable :: forall e. IsSyntheticEvent e => e -> Boolean
cancelable e = e .. "cancelable"
isTrusted :: forall e. IsSyntheticEvent e => e -> Boolean
isTrusted e = e .. "isTrusted"
defaultPrevented :: forall e. IsSyntheticEvent e => e -> Boolean
defaultPrevented e = e .. "defaultPrevented"
eventPhase :: forall e. IsSyntheticEvent e => e -> Number
eventPhase e = e .. "eventPhase"
timestamp :: forall e. IsSyntheticEvent e => e -> Number
timestamp e = e .. "timeStamp"
type' :: forall e. IsSyntheticEvent e => e -> String
type' e = e .. "type"
-- target :: forall e. IsSyntheticEvent e => e -> NativeEventTarget
-- target e = e .. "target"
-- currentTarget :: forall e. IsSyntheticEvent e => e -> NativeEventTarget
-- currentTarget e = e .. "currentTarget"
-- nativeEvent :: forall e. IsSyntheticEvent e => e -> NativeEvent
-- nativeEvent e = e .. "nativeEvent"
stopPropagation :: forall e. IsSyntheticEvent e => e -> Effect Unit
stopPropagation e = e ... "stopPropagation" $ []
preventDefault :: forall e. IsSyntheticEvent e => e -> Effect Unit
preventDefault e = e ... "preventDefault" $ []
isPropagationStopped :: forall e. IsSyntheticEvent e => e -> Effect Unit
isPropagationStopped e = e ... "isPropagationStopped" $ []
isDefaultPrevented :: forall e. IsSyntheticEvent e => e -> Effect Unit
isDefaultPrevented e = e ... "isDefaultPrevented" $ []
-- Events with Modifier keys
-- | This class is used to access information about modifier keys for
-- | supported events
class HasModifierKeys e
instance mouseEventHasModifierKeys :: HasModifierKeys MouseEvent
instance keyboardEventHasModifierKeys :: HasModifierKeys KeyboardEvent
-- instance touchEventHasModifierKeys :: HasModifierKeys TouchEvent
altKey :: forall e. HasModifierKeys e => e -> Boolean
altKey e = e .. "altKey"
ctrlKey :: forall e. HasModifierKeys e => e -> Boolean
ctrlKey e = e .. "ctrlKey"
shiftKey :: forall e. HasModifierKeys e => e -> Boolean
shiftKey e = e .. "shiftKey"
metaKey :: forall e. HasModifierKeys e => e -> Boolean
metaKey e = e .. "metaKey"
getModifierState :: forall e. HasModifierKeys e => e -> String -> Boolean
getModifierState e s = e ... "getModifierState" $ [ s ]
-- Keyboard Events
key :: KeyboardEvent -> String
key e = e .. "key"
which :: KeyboardEvent -> Number
which e = e .. "which"
charCode :: KeyboardEvent -> Int
charCode e = e .. "charCode"
keyCode :: KeyboardEvent -> Number
keyCode e = e .. "keyCode"
locale :: KeyboardEvent -> String
locale e = e .. "locale"
location :: KeyboardEvent -> Number
location e = e .. "location"
repeat :: KeyboardEvent -> Boolean
repeat e = e .. "repeat"
button :: MouseEvent -> Number
button e = e .. "button"
buttons :: MouseEvent -> Number
buttons e = e .. "buttons"
-- relatedTarget :: MouseEvent -> NativeEventTarget
-- relatedTarget e = e .. "relatedTarget"
clientX :: MouseEvent -> Number
clientX e = e .. "clientX"
clientY :: MouseEvent -> Number
clientY e = e .. "clientY"
pageX :: MouseEvent -> Number
pageX e = e .. "pageX"
pageY :: MouseEvent -> Number
pageY e = e .. "pageY"
screenX :: MouseEvent -> Number
screenX e = e .. "screenX"
screenY :: MouseEvent -> Number
screenY e = e .. "screenY"
-- foreign import data TouchEvent :: Type
-- changedTouches :: TouchEvent -> NativeTouchList
-- targetTouches :: TouchEvent -> NativeTouchList
-- touches :: TouchEvent -> NativeTouchList
-- foreign import data NativeDataTransfer :: Type
-- foreign import data NativeAbstractView :: Type
-- foreign import data NativeTouchList :: Type
-- foreign import data AnimationEvent :: Type
-- animationName :: AnimationEvent -> String
-- pseudoElement :: AnimationEvent -> String
-- elapsedTime :: AnimationEvent -> Number
-- foreign import data ClipboardEvent :: Type
-- clipboardData :: ClipboardEvent -> NativeDataTransfer
-- foreign import data CompositionEvent :: Type
-- data' :: SyntheticCompositionEvent -> String
-- foreign import data FocusEvent :: Type
-- relatedTarget :: FocusEvent -> NativeEventTarget
-- foreign import data TransitionEvent :: Type
-- propertyName :: TransitionEvent -> String
-- pseudoElement :: TransitionEvent -> String
-- elapsedTime :: TransitionEvent -> Number
-- foreign import data UIEvent :: Type
-- detail :: UIEvent -> Number
-- view :: UIEvent -> NativeAbstractView
-- foreign import data WheelEvent :: Type
-- deltaMode :: WheelEvent -> Number
-- deltaX :: WheelEvent -> Number
-- deltaY :: WheelEvent -> Number
-- deltaZ :: WheelEvent -> Number
'use strict';
const utils = require('react-dom/test-utils');
const test = require('react-testing-library');
function spread(obj,prop) {
return function() {
return obj[prop].apply(obj, Array.from(arguments));
};
}
exports._act = function(e) {
var ret;
utils.act(function() { ret = e(); });
return ret;
};
exports._render = spread(test,'render');
exports._fireEvent = function(name, obj) {
test.fireEvent[name].call(test.fireEvent, obj);
};
'use strict';
exports.testUtils = require('react-dom/test-utils');
exports.testingLibrary = require('react-testing-library');
......@@ -3,36 +3,48 @@ module Reactix.Test
, render
, Rendered
, fireEvent, fireClick
, cleanup
) where
import Prelude ( Unit )
import Prelude
import Effect ( Effect )
import Effect.Uncurried ( EffectFn1, runEffectFn1, EffectFn2, runEffectFn2 )
import Data.Function.Uncurried ( Fn2, runFn2 )
import DOM.Simple as DOM
import Reactix.React ( Element )
import Reactix.React ( react, Element )
import FFI.Simple ( (..), (...), delay )
import DOM.Simple.Console
foreign import data TestUtils :: Type
foreign import testUtils :: TestUtils
foreign import data Testing :: Type
foreign import testingLibrary :: Testing
type Rendered =
{ getByText :: EffectFn1 String Element
, getByTestId :: EffectFn1 String Element
{ getByText :: String -> Effect Element
, getByTestId :: String -> Effect Element
, container :: DOM.Element
, asFragment :: Effect DOM.Fragment }
render :: Element -> Effect Rendered
render = runEffectFn1 _render
foreign import _render :: EffectFn1 Element Rendered
render e = pure $ raw { getByText=getByText, getByTestId=getByTestId }
where getByText = runEffectFn1 raw.getByText
getByTestId = runEffectFn1 raw.getByTestId
raw = testingLibrary ... "render" $ [e]
-- | Make react behave more predictably in tests
act :: forall t. Effect t -> Effect t
act = runEffectFn1 _act
foreign import _act :: forall t. EffectFn1 (Effect t) t
act :: forall t. (Unit -> Effect t) -> Effect t
act f = testUtils ... "act" $ [ delay f ]
fireClick :: DOM.Element -> Effect Unit
fireClick = fireEvent "click"
fireEvent :: String -> DOM.Element -> Effect Unit
fireEvent = runEffectFn2 _fireEvent
fireEvent ev el = pure $ (testingLibrary .. "fireEvent") ... ev $ [el]
cleanup :: Effect Unit
cleanup = delay $ \_ -> pure $ testingLibrary ... "cleanup" $ []
foreign import _fireEvent :: EffectFn2 String DOM.Element Unit
......@@ -8,35 +8,35 @@ import Data.Traversable ( traverse, traverse_, sequence_ )
import Data.Tuple ( Tuple(..) )
import Data.Tuple.Nested ( (/\) )
import Effect ( Effect )
import Effect.Aff ( Aff )
import Effect.Class ( liftEffect )
import Effect.Ref as Ref
import Effect.Uncurried ( EffectFn1, mkEffectFn1, runEffectFn1 )
-- import Effect.Aff (launchAff_)
import Test.Spec ( Spec, describe, it )
import Test.Spec.Assertions ( shouldEqual )
-- import Test.Spec.QuickCheck (quickCheck')
import DOM.Simple.Console ( log2 )
import DOM.Simple as DOM
import DOM.Simple.Document as Document
import DOM.Simple.Element as Element
import DOM.Simple.Event as Event
import Reactix as R
import Reactix.Test as RT
import Reactix.DOM.Raw ( button, div, i, text )
import Reactix.Hooks ( useState )
import Reactix.Hooks ( useState, useEffect, useLayoutEffect )
staticTest :: Spec Unit
staticTest =
describe "Basic DOM rendering" do
it "Simple elements" do
describe "Basic DOM rendering" $ do
it "Simple elements" $ do
root <- liftEffect $ RT.render elem
(Element.name <$> Element.children root.container) `shouldEqual` ["I"]
it "Fragments" do
children /\ count <- liftEffect $
do let root = Document.createElement "div"
RT.act $ R.render frag root
pure $ Tuple (Element.children root) (Element.childCount root)
count `shouldEqual` 2
let children = Element.children root.container
(Element.name <$> children) `shouldEqual` ["I"]
(Element.innerHTML <$> children) `shouldEqual` ["hello world"]
it "Fragments" $ do
root <- liftEffect $ RT.render $ frag
Element.childCount root.container `shouldEqual` 2
let children = Element.children root.container
A.length children `shouldEqual` 2
(Element.name <$> children) `shouldEqual` ["I", "I"]
(Element.innerHTML <$> children) `shouldEqual` ["hello","world"]
......@@ -46,28 +46,26 @@ staticTest =
type CounterProps = ( count :: Int )
counterCpt :: R.Component CounterProps
counterCpt = R.hooksLeaf "Counter" cpt
counterCpt = R.hooksComponent "Counter" cpt
where
cpt :: forall m. R.MonadHooks m => Record CounterProps -> m R.Element
cpt {count} = do
y /\ setY <- useState $ pure count
cpt {count} _ = do
y /\ setY <- useState $ \_ -> pure count
pure $ div { className: "counter" }
[ button { type: "button", onClick: onclick setY (y + 1) } [ text "++" ]
, div {} [ text (show y) ] ]
onclick set to = mkEffectFn1 $ \e -> do
runEffectFn1 set to
onclick set to = mkEffectFn1 $ \e -> runEffectFn1 set to
counterTest :: Spec Unit
counterTest =
describe "Counter" do
it "Works for plain components" $ do
let counter = R.createLeaf counterCpt {count: 0}
let counter = R.createElement counterCpt {count: 0} []
liftEffect (RT.render counter) >>= test
it "Works for memoised components" $ do
let counter = R.createLeaf (R.memo counterCpt (==)) {count: 0}
let counter = R.createElement (R.memo counterCpt (==)) {count: 0} []
liftEffect (RT.render counter) >>= test
it "works for memo'ised components" $ do
let counter = R.createLeaf (R.memo' counterCpt) {count: 0}
let counter = R.createElement (R.memo' counterCpt) {count: 0} []
liftEffect (RT.render counter) >>= test
where
test root = do
......@@ -86,12 +84,105 @@ counterTest =
A.length children4 `shouldEqual` 2
(Element.innerHTML <$> children4) `shouldEqual` ["++", "2"]
listTest :: Spec Unit
listTest = pure unit
data EffectorState = Fresh | Initialised | Done
derive instance eqEffectorState :: Eq EffectorState
instance showEffectorState :: Show EffectorState where
show Fresh = "fresh"
show Initialised = "initialised"
show Done = "done"
type EffectorProps = ( stateRef :: Ref.Ref EffectorState )
effectorCpt :: R.Component EffectorProps
effectorCpt = R.hooksComponent "Effector" cpt
where cpt {stateRef} _ = do
useEffect $ \_ -> do
Ref.write Initialised stateRef
pure $ \_ -> Ref.write Done stateRef
pure $ div {} []
-- TODO: test it's firing at the right time
effectorTest :: Spec Unit
effectorTest =
describe "Effector" do
it "Works for plain components" $
test $ effectorCpt
it "works for memo'ised components" $
test $ R.memo' effectorCpt
where
test :: forall cpt. R.IsComponent cpt EffectorProps (Array R.Element) => cpt -> Aff Unit
test cpt = do
ref <- liftEffect $ Ref.new Fresh
let effector = R.createElement cpt {stateRef: ref} []
root <- liftEffect (RT.render effector)
state <- liftEffect $ Ref.read ref
state `shouldEqual` Initialised
liftEffect $ RT.cleanup
state' <- liftEffect $ Ref.read ref
state' `shouldEqual` Done
layoutEffectorCpt :: R.Component EffectorProps
layoutEffectorCpt = R.hooksComponent "LayoutEffector" cpt
where cpt {stateRef} _ = do
useLayoutEffect $ \_ -> do
Ref.write Initialised stateRef
pure $ \_ -> Ref.write Done stateRef
pure $ div {} []
-- TODO: test it's firing at the right time
layoutEffectorTest :: Spec Unit
layoutEffectorTest =
describe "LayoutEffector" do
it "Works for plain components" $
test $ layoutEffectorCpt
it "works for memo'ised components" $
test $ R.memo' layoutEffectorCpt
where
test :: forall cpt. R.IsComponent cpt EffectorProps (Array R.Element) => cpt -> Aff Unit
test cpt = do
ref <- liftEffect $ Ref.new Fresh
let effector = R.createElement cpt {stateRef: ref} []
root <- liftEffect (RT.render effector)
state <- liftEffect $ Ref.read ref
state `shouldEqual` Initialised
liftEffect $ RT.cleanup
state' <- liftEffect $ Ref.read ref
state' `shouldEqual` Done
type ContextProps = ()
-- contextualCpt :: R.Component ContextProps
-- contextualCpt = R.hooksComponent "Contextual" cpt
-- where cpt {stateRef} _ = do
-- useEffect $ \_ -> do
-- Ref.write Initialised stateRef
-- pure $ \_ -> Ref.write Done stateRef
-- pure $ div {} []
-- contextTest :: Spec Unit
-- contextTest =
-- describe "Context" do
-- it "Works for plain components" $
-- test $ contextualCpt
-- where test cpt = pure unit
-- reducerTest :: Spec Unit
-- memoTest :: Spec Unit
-- refTest :: Spec Unit
-- imperativeHandleTest :: Spec Unit
-- debugValueTest :: Spec Unit
-- listTest :: Spec Unit
-- listTest = pure unit
spec :: Spec Unit
spec = sequence_
[ staticTest
, counterTest
, listTest
, effectorTest
, layoutEffectorTest
]
-- , listTest
-- ]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment