miso-0.20.0.0: tests/Main.hs
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NamedFieldPuns #-}
module Main where
import Control.Exception hiding (assert)
import Control.Monad
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.State
import Data.Aeson hiding (Result(..))
import qualified Data.HashMap.Strict as H
import Data.Scientific
import qualified Data.Vector as V
import Debug.Trace
import GHCJS.Marshal
import GHCJS.Types
import qualified JavaScript.Object.Internal as OI
import System.IO.Unsafe
import Test.QuickCheck hiding (total)
import Test.QuickCheck.Instances
import Test.QuickCheck.Monadic
import Miso
import Miso.FFI
instance Arbitrary Value where
arbitrary = sized sizedArbitraryValue
sizedArbitraryValue :: Int -> Gen Value
sizedArbitraryValue n
| n <= 0 = oneof [pure Null, bool, number, string]
| otherwise = resize n' $ oneof [pure Null, bool, string, number, array, object']
where
n' = n `div` 2
bool = Bool <$> arbitrary
number = Number <$> arbitrary
string = String <$> arbitrary
array = Array <$> arbitrary
object' = Object <$> arbitrary
compareValue :: Value -> Value -> Bool
compareValue (Object x) (Object y) = and $ zipWith compareValue (H.elems x) (H.elems y)
compareValue (Array x) (Array y) = and $ zipWith compareValue (V.toList x) (V.toList y)
compareValue (String x) (String y) = x == y
compareValue (Bool x) (Bool y) = x == y
compareValue Null Null = True
compareValue (Number x) (Number y) = closeEnough x y
compareValue _ _ = False
closeEnough x y
= let d = max (abs x) (abs y)
relDiff = if (d == 0.0) then d else abs (x - y) / d
in relDiff <= 0.00001
main :: IO ()
main = runTests $ do
storageTests
roundTripJSVal
storageTests :: TestM ()
storageTests = do
it "should write to and read from local storage" $ do
let obj = object [ "foo" .= ("bar" :: String) ]
setLocalStorage "foo" obj
Right r <- getLocalStorage "foo"
r `shouldBe` obj
it "should write to and read from session storage" $ do
let obj = object [ "foo" .= ("bar" :: String) ]
setSessionStorage "foo" obj
Right r <- getLocalStorage "foo"
r `shouldBe` obj
roundTripJSVal :: TestM ()
roundTripJSVal =
it "Should round trip JSVal" $ do
propTest iso_prop
roundTrip
:: Value
-> IO Bool
roundTrip x = do
Just y <- jsvalToValue =<< toJSVal x
pure $ compareValue x y == True
iso_prop :: Value -> Property
iso_prop = monadicIO . run . roundTrip
propTest :: Testable prop => prop -> IO (Bool, String)
propTest prop = do
r <- flip quickCheckWithResult prop stdArgs { chatty = False }
pure $ case r of
Success { output = o } -> (True, o)
GaveUp { output = o } -> (False, o)
Failure { output = o } -> (False, o)
NoExpectedFailure { output = o } -> (False, o)
InsufficientCoverage { output = o } -> (False, o)
foreign import javascript unsafe "window.global_test_results = $1;"
writeToGlobalObject :: JSVal -> IO ()
runTests :: TestM () -> IO ()
runTests t = do
results <- toJSVal =<< toResult <$> execStateT t []
consoleLog results
writeToGlobalObject results
where
toResult :: [Test] -> TestResult
toResult xs = TestResult failed' passed' total' duration' xs
where
passed' = length (filter result xs)
failed' = length (filter (not . result) xs)
total' = length xs
duration' = sum (map duration xs)
instance ToJSVal TestResult where
toJSVal t = do
o@(OI.Object j) <- OI.create
set "passed" (passed t) o
set "failed" (failed t) o
set "total" (total t) o
set "duration" (duration' t) o
set "tests" (tests t) o
pure j
instance ToJSVal Test where
toJSVal t = do
o@(OI.Object j) <- OI.create
set "name" (name t) o
set "result" (result t) o
set "message" (message t) o
set "duration" (duration t) o
pure j
it :: String -> IO (Bool, String) -> TestM ()
it name test = do
(result, msg, time) <- liftIO $ (do
((x,msg),t) <- clock test
pure (x,msg,t))
`catch` (\(e :: SomeException) ->
pure (False, show e, 0))
modify (Test name result msg time:)
data TestResult
= TestResult
{ failed :: Int
, passed :: Int
, total :: Int
, duration' :: Double
, tests :: [Test]
} deriving (Show, Eq)
data Test
= Test
{ name :: String
, result :: Bool
, message :: String
, duration :: Double
} deriving (Show, Eq)
type TestM a = StateT [Test] IO a
shouldBe :: (Show a, Eq a, Applicative f) => a -> a -> f (Bool, String)
shouldBe x y =
pure $
if x == y
then (True, mempty)
else (False, "Expecting: " ++ show y ++ " but got: " ++ show x)
infix 0 `shouldBe`
-- | Measure in seconds
clock :: IO a -> IO (a, Double)
clock action = do
start <- now
x <- action
stop <- now
pure (x, (stop - start) / 1000)