mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-12-24 09:34:28 +03:00
Implemented basic code generation for Page.
This commit is contained in:
parent
d44ff4ea7f
commit
6150d01f37
@ -6,6 +6,7 @@
|
||||
"dependencies": {
|
||||
"react": "^16.8.5",
|
||||
"react-dom": "^16.8.5",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"react-scripts": "2.1.8"
|
||||
},
|
||||
"scripts": {
|
||||
|
12
stic/data/Generator/templates/react-app/src/_Page.js
vendored
Normal file
12
stic/data/Generator/templates/react-app/src/_Page.js
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{{={= =}=}}
|
||||
import React, { Component } from 'react'
|
||||
|
||||
class {= page.name =} extends Component {
|
||||
render() {
|
||||
return (
|
||||
{=& page.content =}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default {= page.name =}
|
@ -1,11 +1,13 @@
|
||||
{{={{> <}}=}}
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
import ReactDOM from 'react-dom'
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
import router from './router'
|
||||
import * as serviceWorker from './serviceWorker'
|
||||
|
||||
import './index.css'
|
||||
|
||||
|
||||
ReactDOM.render(router, document.getElementById('root'));
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
|
22
stic/data/Generator/templates/react-app/src/router.js
vendored
Normal file
22
stic/data/Generator/templates/react-app/src/router.js
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{{={= =}=}}
|
||||
import React from 'react'
|
||||
import { Route, BrowserRouter as Router } from 'react-router-dom'
|
||||
|
||||
import App from './App'
|
||||
{=# pages =}
|
||||
import {= name =} from './{= name =}'
|
||||
{=/ pages =}
|
||||
|
||||
|
||||
const router = (
|
||||
<Router>
|
||||
<div>
|
||||
<Route exact path="/" component={App} />
|
||||
{=# pages =}
|
||||
<Route exact path="{= route =}" component={ {= name =} }/>
|
||||
{=/ pages =}
|
||||
</div>
|
||||
</Router>
|
||||
)
|
||||
|
||||
export default router
|
@ -1,23 +1,20 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
module Generator.Generators
|
||||
( generateWebApp
|
||||
|
||||
-- EXPORTED ONLY FOR TESTS:
|
||||
, generatePage
|
||||
) where
|
||||
|
||||
import qualified Data.Aeson as Aeson
|
||||
import System.FilePath (FilePath, (</>))
|
||||
import Data.Aeson ((.=), object, ToJSON(..))
|
||||
import System.FilePath (FilePath, (</>), (<.>))
|
||||
|
||||
import Generator.FileDraft
|
||||
import Wasp
|
||||
|
||||
|
||||
defaultCreateTemplateFileDraft :: FilePath -> Wasp -> FileDraft
|
||||
defaultCreateTemplateFileDraft path wasp = createTemplateFileDraft path path (Aeson.toJSON wasp)
|
||||
|
||||
|
||||
type FileDraftGenerator = Wasp -> [FileDraft]
|
||||
|
||||
generateWebApp :: FileDraftGenerator
|
||||
generateWebApp wasp = concat $ map ($ wasp)
|
||||
generateWebApp :: Wasp -> [FileDraft]
|
||||
generateWebApp wasp = concatMap ($ wasp)
|
||||
[ generateReadme
|
||||
, generatePackageJson
|
||||
, generateGitignore
|
||||
@ -25,31 +22,49 @@ generateWebApp wasp = concat $ map ($ wasp)
|
||||
, generateSrcDir
|
||||
]
|
||||
|
||||
generateReadme :: FileDraftGenerator
|
||||
generateReadme wasp = [defaultCreateTemplateFileDraft "README.md" wasp]
|
||||
generateReadme :: Wasp -> [FileDraft]
|
||||
generateReadme wasp = [simpleTemplateFileDraft "README.md" wasp]
|
||||
|
||||
generatePackageJson :: FileDraftGenerator
|
||||
generatePackageJson wasp = [defaultCreateTemplateFileDraft "package.json" wasp]
|
||||
generatePackageJson :: Wasp -> [FileDraft]
|
||||
generatePackageJson wasp = [simpleTemplateFileDraft "package.json" wasp]
|
||||
|
||||
generateGitignore :: FileDraftGenerator
|
||||
generateGitignore wasp = [createTemplateFileDraft ".gitignore" "gitignore" (Aeson.toJSON wasp)]
|
||||
generateGitignore :: Wasp -> [FileDraft]
|
||||
generateGitignore wasp = [createTemplateFileDraft ".gitignore" "gitignore" (toJSON wasp)]
|
||||
|
||||
generatePublicDir :: FileDraftGenerator
|
||||
generatePublicDir :: Wasp -> [FileDraft]
|
||||
generatePublicDir wasp
|
||||
= createCopyFileDraft ("public" </> "favicon.ico") ("public" </> "favicon.ico")
|
||||
: map (\path -> defaultCreateTemplateFileDraft ("public/" </> path) wasp)
|
||||
: map (\path -> simpleTemplateFileDraft ("public/" </> path) wasp)
|
||||
[ "index.html"
|
||||
, "manifest.json"
|
||||
]
|
||||
|
||||
generateSrcDir :: FileDraftGenerator
|
||||
generateSrcDir :: Wasp -> [FileDraft]
|
||||
generateSrcDir wasp
|
||||
= (createCopyFileDraft ("src" </> "logo.png") ("src" </> "logo.png"))
|
||||
: map (\path -> defaultCreateTemplateFileDraft ("src/" </> path) wasp)
|
||||
: map (\path -> simpleTemplateFileDraft ("src/" </> path) wasp)
|
||||
[ "App.css"
|
||||
, "App.js"
|
||||
, "App.test.js"
|
||||
, "index.css"
|
||||
, "index.js"
|
||||
, "router.js"
|
||||
, "serviceWorker.js"
|
||||
]
|
||||
++ generatePages wasp
|
||||
|
||||
generatePages :: Wasp -> [FileDraft]
|
||||
generatePages wasp = generatePage wasp <$> getPages wasp
|
||||
|
||||
generatePage :: Wasp -> Page -> FileDraft
|
||||
generatePage wasp page = createTemplateFileDraft dstPath srcPath templateData
|
||||
where
|
||||
srcPath = "src" </> "_Page.js"
|
||||
dstPath = "src" </> (pageName page) <.> "js"
|
||||
templateData = object ["wasp" .= wasp, "page" .= page]
|
||||
|
||||
|
||||
-- | Creates template file draft that uses given path as both src and dst path
|
||||
-- and wasp as template data.
|
||||
simpleTemplateFileDraft :: FilePath -> Wasp -> FileDraft
|
||||
simpleTemplateFileDraft path wasp = createTemplateFileDraft path path (toJSON wasp)
|
||||
|
@ -2,7 +2,7 @@ module Parser
|
||||
( parseWasp
|
||||
) where
|
||||
|
||||
import Text.Parsec (parse, ParseError, (<|>), many1, eof)
|
||||
import Text.Parsec (ParseError, (<|>), many1, eof)
|
||||
import Text.Parsec.String (Parser)
|
||||
|
||||
import Lexer
|
||||
@ -30,7 +30,7 @@ waspParser = do
|
||||
|
||||
waspElems <- many1 waspElement
|
||||
eof
|
||||
|
||||
|
||||
-- TODO(matija): after we parsed everything, we should do semantic analysis
|
||||
-- e.g. check there is only 1 title - if not, throw a meaningful error.
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
module Parser.Common where
|
||||
|
||||
import Text.Parsec
|
||||
import Text.Parsec (ParseError, parse, many, noneOf)
|
||||
import Text.Parsec.String (Parser)
|
||||
import qualified Data.Text as T
|
||||
|
||||
|
@ -2,7 +2,7 @@ module Util.Fib (
|
||||
fibonacci
|
||||
) where
|
||||
|
||||
fibonacci :: (Num a, Ord a, Num b) => a -> b
|
||||
fibonacci :: Int -> Int
|
||||
fibonacci 0 = 0
|
||||
fibonacci 1 = 1
|
||||
fibonacci n | n > 1 = (fibonacci (n - 1)) + (fibonacci (n - 2))
|
||||
|
@ -2,17 +2,23 @@
|
||||
module Wasp
|
||||
( Wasp
|
||||
, WaspElement (..)
|
||||
, fromWaspElems
|
||||
|
||||
, App (..)
|
||||
, fromApp
|
||||
, fromWaspElems
|
||||
, getApp
|
||||
, setApp
|
||||
|
||||
, Page (..)
|
||||
, getPages
|
||||
, addPage
|
||||
) where
|
||||
|
||||
import qualified Data.Aeson as Aeson
|
||||
import Data.Aeson ((.=), object, ToJSON(..))
|
||||
|
||||
|
||||
-- * Wasp
|
||||
|
||||
data Wasp = Wasp [WaspElement] deriving (Show, Eq)
|
||||
|
||||
data WaspElement
|
||||
@ -21,11 +27,15 @@ data WaspElement
|
||||
| WaspElementEntity
|
||||
deriving (Show, Eq)
|
||||
|
||||
-- App
|
||||
fromWaspElems :: [WaspElement] -> Wasp
|
||||
fromWaspElems elems = Wasp elems
|
||||
|
||||
|
||||
-- * App
|
||||
|
||||
data App = App
|
||||
{ appName :: !String -- Identifier
|
||||
, appTitle :: !String -- Title
|
||||
, appTitle :: !String
|
||||
} deriving (Show, Eq)
|
||||
|
||||
getApp :: Wasp -> App
|
||||
@ -39,10 +49,7 @@ isAppElem WaspElementApp{} = True
|
||||
isAppElem _ = False
|
||||
|
||||
getApps :: Wasp -> [App]
|
||||
getApps (Wasp elems) = map getAppFromElem $ filter isAppElem elems
|
||||
where
|
||||
getAppFromElem (WaspElementApp app) = app
|
||||
getAppFromElem _ = error "Not an app"
|
||||
getApps (Wasp elems) = [app | (WaspElementApp app) <- elems]
|
||||
|
||||
setApp :: Wasp -> App -> Wasp
|
||||
setApp (Wasp elems) app = Wasp $ (WaspElementApp app) : (filter (not . isAppElem) elems)
|
||||
@ -50,26 +57,42 @@ setApp (Wasp elems) app = Wasp $ (WaspElementApp app) : (filter (not . isAppElem
|
||||
fromApp :: App -> Wasp
|
||||
fromApp app = Wasp [WaspElementApp app]
|
||||
|
||||
-- NOTE(martin): Here I define general transformation of App into JSON that I can then easily use
|
||||
-- as data for templates, but we will probably want to replace this in the future with the better tailored
|
||||
-- types that are exact fit for what is neeed (for example one type per template).
|
||||
instance Aeson.ToJSON App where
|
||||
toJSON app = Aeson.object
|
||||
[ "name" Aeson..= appName app
|
||||
, "title" Aeson..= appTitle app
|
||||
]
|
||||
instance Aeson.ToJSON Wasp where
|
||||
toJSON wasp = Aeson.object
|
||||
[ "app" Aeson..= getApp wasp
|
||||
]
|
||||
|
||||
fromWaspElems :: [WaspElement] -> Wasp
|
||||
fromWaspElems elems = Wasp elems
|
||||
|
||||
-- Page
|
||||
-- * Page
|
||||
|
||||
data Page = Page
|
||||
{ pageName :: !String
|
||||
{ pageName :: !String -- Identifier
|
||||
, pageRoute :: !String
|
||||
, pageContent :: !String
|
||||
} deriving (Show, Eq)
|
||||
|
||||
getPages :: Wasp -> [Page]
|
||||
getPages (Wasp elems) = [page | (WaspElementPage page) <- elems]
|
||||
|
||||
addPage :: Wasp -> Page -> Wasp
|
||||
addPage (Wasp elems) page = Wasp $ (WaspElementPage page):elems
|
||||
|
||||
|
||||
-- * ToJSON instances.
|
||||
|
||||
-- NOTE(martin): Here I define general transformation of App into JSON that I can then easily use
|
||||
-- as data for templates, but we will probably want to replace this in the future with the better tailored
|
||||
-- types that are exact fit for what is neeed, for example one type per template, which
|
||||
-- will also enable us to check via types if template data is correctly shaped.
|
||||
instance ToJSON App where
|
||||
toJSON app = object
|
||||
[ "name" .= appName app
|
||||
, "title" .= appTitle app
|
||||
]
|
||||
|
||||
instance ToJSON Page where
|
||||
toJSON page = object
|
||||
[ "name" .= pageName page
|
||||
, "route" .= pageRoute page
|
||||
, "content" .= pageContent page
|
||||
]
|
||||
instance ToJSON Wasp where
|
||||
toJSON wasp = object
|
||||
[ "app" .= getApp wasp
|
||||
, "pages" .= getPages wasp
|
||||
]
|
||||
|
@ -1,6 +1,5 @@
|
||||
module Generator.FileDraft.CopyFileDraftTest where
|
||||
|
||||
import qualified Test.Tasty
|
||||
import Test.Tasty.Hspec
|
||||
|
||||
import System.FilePath ((</>), takeDirectory)
|
||||
|
@ -1,12 +1,11 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
module Generator.FileDraft.TemplateFileDraftTest where
|
||||
|
||||
import qualified Test.Tasty
|
||||
import Test.Tasty.Hspec
|
||||
|
||||
import Data.Aeson (object, (.=))
|
||||
import System.FilePath (FilePath, (</>), takeDirectory)
|
||||
import Data.Text (Text, pack)
|
||||
import System.FilePath ((</>), takeDirectory)
|
||||
import Data.Text (Text)
|
||||
|
||||
import Generator.FileDraft
|
||||
|
||||
@ -29,7 +28,6 @@ spec_TemplateFileDraft = do
|
||||
(dstDir, dstPath, templatePath) = ("a/b", "c/d/dst.txt", "e/tmpl.txt")
|
||||
templateData = object [ "foo" .= ("bar" :: String) ]
|
||||
fileDraft = createTemplateFileDraft dstPath templatePath templateData
|
||||
expectedTemplatePath = mockTemplatesDirAbsPath </> templatePath
|
||||
expectedDstPath = dstDir </> dstPath
|
||||
mockTemplatesDirAbsPath = "mock/templates/dir"
|
||||
mockTemplateContent = "Mock template content" :: Text
|
||||
|
@ -1,11 +1,9 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
module Generator.GeneratorsTest where
|
||||
|
||||
import qualified Test.Tasty
|
||||
import Test.Tasty.Hspec
|
||||
|
||||
import System.FilePath (FilePath, (</>))
|
||||
import Data.Aeson as Aeson (object, (.=))
|
||||
import System.FilePath (FilePath, (</>), (<.>))
|
||||
|
||||
import Generator.Generators
|
||||
import Generator.FileDraft
|
||||
@ -13,16 +11,22 @@ import Generator.FileDraft.TemplateFileDraft
|
||||
import Generator.FileDraft.CopyFileDraft
|
||||
import Wasp
|
||||
|
||||
-- TODO(martin): We could define Arbitrary instance for Wasp, define properties over
|
||||
-- generator functions and then do property testing on them, that would be cool.
|
||||
|
||||
spec_Generators :: Spec
|
||||
spec_Generators = do
|
||||
let testApp = (App "TestApp" "Test App")
|
||||
let testPage = (Page "TestPage" "/test-page" "<div>Test Page</div>")
|
||||
let testWasp = (fromApp testApp) `addPage` testPage
|
||||
|
||||
describe "generateWebApp" $ do
|
||||
-- NOTE: This test does not (for now) check that content of files is correct or
|
||||
-- that they will successfully be written, it checks only that their
|
||||
-- destinations are correct.
|
||||
it "Given a simple Wasp, creates file drafts at expected destinations." $ do
|
||||
let fileDrafts = generateWebApp simpleWasp
|
||||
let expectedFileDrafts = concat $
|
||||
it "Given a simple Wasp, creates file drafts at expected destinations" $ do
|
||||
let fileDrafts = generateWebApp testWasp
|
||||
let expectedFileDraftDstPaths = concat $
|
||||
[ [ "README.md"
|
||||
, "package.json"
|
||||
, ".gitignore"
|
||||
@ -39,25 +43,28 @@ spec_Generators = do
|
||||
, "App.test.js"
|
||||
, "index.css"
|
||||
, "index.js"
|
||||
, "router.js"
|
||||
, "serviceWorker.js"
|
||||
, (pageName testPage <.> "js")
|
||||
]
|
||||
]
|
||||
mapM_
|
||||
-- NOTE(martin): I added fd to the pair here in order to have it
|
||||
-- printed when shouldBe fails, otherwise I could not know which
|
||||
-- file draft failed.
|
||||
(\fd -> (fd, existsFdWithDst fileDrafts fd) `shouldBe` (fd, True))
|
||||
expectedFileDrafts
|
||||
where
|
||||
(appName, appTitle) = ("TestApp", "Test App")
|
||||
(\dstPath -> (dstPath, existsFdWithDst fileDrafts dstPath)
|
||||
`shouldBe` (dstPath, True))
|
||||
expectedFileDraftDstPaths
|
||||
|
||||
simpleWasp :: Wasp
|
||||
simpleWasp = fromApp $ App appName appTitle
|
||||
describe "generatePage" $ do
|
||||
it "Given a simple Wasp, creates template file draft from _Page.js" $ do
|
||||
let (FileDraftTemplateFd (TemplateFileDraft _ srcPath _))
|
||||
= generatePage testWasp (head $ getPages testWasp)
|
||||
srcPath `shouldBe` "src" </> "_Page.js"
|
||||
|
||||
existsFdWithDst :: [FileDraft] -> FilePath -> Bool
|
||||
existsFdWithDst fds dstPath =
|
||||
length (filter ((== dstPath). getFileDraftDstPath) fds) == 1
|
||||
|
||||
existsFdWithDst :: [FileDraft] -> FilePath -> Bool
|
||||
existsFdWithDst fds dstPath = any ((== dstPath) . getFileDraftDstPath) fds
|
||||
|
||||
getFileDraftDstPath :: FileDraft -> FilePath
|
||||
getFileDraftDstPath (FileDraftTemplateFd fd) = templateFileDraftDstFilepath fd
|
||||
|
@ -11,9 +11,8 @@ module Generator.MockFileDraftIO
|
||||
import System.FilePath (FilePath, (</>))
|
||||
import Data.Text (Text, pack)
|
||||
import Control.Monad.State
|
||||
import Data.Aeson as Aeson
|
||||
import qualified Data.Aeson as Aeson
|
||||
|
||||
import Generator.FileDraft
|
||||
import Generator.FileDraft.FileDraftIO
|
||||
|
||||
|
||||
@ -25,7 +24,7 @@ defaultMockConfig :: MockFdIOConfig
|
||||
defaultMockConfig = MockFdIOConfig
|
||||
{ getTemplatesDirAbsPath_impl = "mock/templates/dir"
|
||||
, getTemplateFileAbsPath_impl = \path -> "mock/templates/dir" </> path
|
||||
, compileAndRenderTemplate_impl = \path json -> (pack "Mock template content")
|
||||
, compileAndRenderTemplate_impl = \_ _ -> (pack "Mock template content")
|
||||
}
|
||||
|
||||
getMockLogs :: MockFdIO a -> MockFdIOConfig -> MockFdIOLogs
|
||||
@ -58,6 +57,7 @@ instance FileDraftIO MockFdIO where
|
||||
(_, config) <- get
|
||||
return $ (compileAndRenderTemplate_impl config) path json
|
||||
|
||||
modifyLogs :: MonadState (a, b) m => (a -> a) -> m ()
|
||||
modifyLogs f = modify (\(logs, config) -> (f logs, config))
|
||||
|
||||
newtype MockFdIO a = MockFdIO { unMockFdIO :: State (MockFdIOLogs, MockFdIOConfig) a }
|
||||
@ -78,22 +78,28 @@ data MockFdIOConfig = MockFdIOConfig
|
||||
, compileAndRenderTemplate_impl :: FilePath -> Aeson.Value -> Text
|
||||
}
|
||||
|
||||
writeFileFromText_addCall :: FilePath -> Text -> MockFdIOLogs -> MockFdIOLogs
|
||||
writeFileFromText_addCall path text logs =
|
||||
logs { writeFileFromText_calls = (path, text):(writeFileFromText_calls logs) }
|
||||
|
||||
getTemplatesDirAbsPath_addCall :: MockFdIOLogs -> MockFdIOLogs
|
||||
getTemplatesDirAbsPath_addCall logs =
|
||||
logs { getTemplatesDirAbsPath_calls = ():(getTemplatesDirAbsPath_calls logs) }
|
||||
|
||||
getTemplateFileAbsPath_addCall :: FilePath -> MockFdIOLogs -> MockFdIOLogs
|
||||
getTemplateFileAbsPath_addCall path logs =
|
||||
logs { getTemplateFileAbsPath_calls = (path):(getTemplateFileAbsPath_calls logs) }
|
||||
|
||||
copyFile_addCall :: FilePath -> FilePath -> MockFdIOLogs -> MockFdIOLogs
|
||||
copyFile_addCall srcPath dstPath logs =
|
||||
logs { copyFile_calls = (srcPath, dstPath):(copyFile_calls logs) }
|
||||
|
||||
createDirectoryIfMissing_addCall :: Bool -> FilePath -> MockFdIOLogs -> MockFdIOLogs
|
||||
createDirectoryIfMissing_addCall createParents path logs =
|
||||
logs { createDirectoryIfMissing_calls =
|
||||
(createParents, path):(createDirectoryIfMissing_calls logs) }
|
||||
|
||||
compileAndRenderTemplate_addCall :: FilePath -> Aeson.Value -> MockFdIOLogs -> MockFdIOLogs
|
||||
compileAndRenderTemplate_addCall path json logs =
|
||||
logs { compileAndRenderTemplate_calls =
|
||||
(path, json):(compileAndRenderTemplate_calls logs) }
|
||||
|
@ -1,10 +1,8 @@
|
||||
module Parser.CommonTest where
|
||||
|
||||
import qualified Test.Tasty
|
||||
import Test.Tasty.Hspec
|
||||
|
||||
import Text.Parsec
|
||||
import Text.Parsec.String (Parser)
|
||||
import Data.Either
|
||||
|
||||
import Lexer
|
||||
@ -58,7 +56,7 @@ spec_parseWaspCommon = do
|
||||
describe "Parsing wasp closure" $ do
|
||||
let parseWaspClosure input = runWaspParser waspClosure input
|
||||
let closureContent = "<div>hello world</div>"
|
||||
|
||||
|
||||
it "Returns the content of closure" $ do
|
||||
parseWaspClosure ("{ " ++ closureContent ++ " }")
|
||||
`shouldBe` Right closureContent
|
||||
@ -66,6 +64,3 @@ spec_parseWaspCommon = do
|
||||
it "Removes leading and trailing spaces" $ do
|
||||
parseWaspClosure ("{ " ++ closureContent ++ " }")
|
||||
`shouldBe` Right closureContent
|
||||
|
||||
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
module Parser.PageTest where
|
||||
|
||||
import qualified Test.Tasty
|
||||
import Test.Tasty.Hspec
|
||||
|
||||
import Data.Either
|
||||
|
@ -1,6 +1,5 @@
|
||||
module Parser.ParserTest where
|
||||
|
||||
import qualified Test.Tasty
|
||||
import Test.Tasty.Hspec
|
||||
import Data.Either
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
module Util.FibTest where
|
||||
|
||||
import qualified Test.Tasty
|
||||
import Test.Tasty.Hspec
|
||||
import Test.Tasty.QuickCheck
|
||||
|
||||
@ -20,6 +19,7 @@ spec_fibonacci = do
|
||||
|
||||
-- NOTE: Most likely not the best way to write QuickCheck test, I just did this in order
|
||||
-- to get something working as an example.
|
||||
prop_fibonacci :: Property
|
||||
prop_fibonacci = forAll (choose (0, 10)) $ testFibSequence
|
||||
where
|
||||
testFibSequence :: Int -> Bool
|
||||
|
Loading…
Reference in New Issue
Block a user