packages feed

hslua-1.2.0: test/Foreign/Lua/ModuleTests.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
{-|
Module      : Foreign.Lua.ModuleTests
Copyright   : © 2019-2020 Albert Krewinkel
License     : MIT
Maintainer  : Albert Krewinkel <albert+hslua@zeitkraut.de>
Stability   : alpha
Portability : Requires GHC 8 or later.

Tests creating and loading of modules with Haskell.
-}
module Foreign.Lua.ModuleTests (tests) where

import Foreign.Lua (Lua)
import Foreign.Lua.Call hiding (render)
import Foreign.Lua.Module
import Foreign.Lua.Peek (peekIntegral)
import Foreign.Lua.Push (pushIntegral)
import Test.HsLua.Util ((=:), pushLuaExpr, shouldBeResultOf)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit ((@=?))

import qualified Data.Text as T
import qualified Foreign.Lua as Lua

-- | Specifications for Attributes parsing functions.
tests :: TestTree
tests = testGroup "Module"
  [ testGroup "requirehs"
    [ "pushes module to stack" =:
      1 `shouldBeResultOf` do
        Lua.openlibs
        old <- Lua.gettop
        requirehs "foo" (Lua.pushnumber 5.0)
        new <- Lua.gettop
        return (new - old)

    , "module can be loaded with `require`" =:
      let testModule = "string as a module" :: String
      in testModule `shouldBeResultOf` do
        Lua.openlibs
        requirehs "test.module" (Lua.push testModule)
        pushLuaExpr "require 'test.module'"
        Lua.peek Lua.stackTop
    ]

  , testGroup "preloadhs"
    [ "does not modify the stack" =:
      0 `shouldBeResultOf` do
        Lua.openlibs
        old <- Lua.gettop
        preloadhs "foo" (1 <$ Lua.pushnumber 5.0)
        new <- Lua.gettop
        return (new - old)

    , "module can be loaded with `require`" =:
      let testModule = "string as a module" :: String
      in testModule `shouldBeResultOf` do
        Lua.openlibs
        preloadhs "test.module" (1 <$ Lua.push testModule)
        pushLuaExpr "require 'test.module'"
        Lua.peek Lua.stackTop
    ]

  , testGroup "creation helpers"
    [ "create produces a table" =:
      Lua.TypeTable `shouldBeResultOf` do
        create
        Lua.ltype Lua.stackTop

    , "addfield modifies table" =:
      Lua.Integer 23 `shouldBeResultOf` do
        create
        addfield "field_name" (23 :: Int)
        Lua.getfield Lua.stackTop "field_name"
        Lua.peek Lua.stackTop

    , "addfunction modifies table" =:
      Lua.Integer 5 `shouldBeResultOf` do
        create
        addfunction "minus18" (return . subtract 18 :: Int -> Lua Int)
        Lua.getfield Lua.stackTop "minus18"
        Lua.pushinteger 23
        Lua.call 1 1
        Lua.peek Lua.stackTop
    ]
  , testGroup "module type"
    [ "register module" =:
      1 `shouldBeResultOf` do
        Lua.openlibs
        old <- Lua.gettop
        registerModule mymath
        new <- Lua.gettop
        return (new - old)

    , "call module function" =:
      Right 24 `shouldBeResultOf` do
        Lua.openlibs
        registerModule mymath
        _ <- Lua.dostring $ mconcat
             [ "local mymath = require 'mymath'\n"
             , "return mymath.factorial(4)"
             ]
        peekIntegral @Integer Lua.stackTop
    ]
  , testGroup "documentation"
    [ "module docs" =:
      (T.intercalate "\n"
        [ "# mymath"
        , ""
        , "A math module."
        , ""
        , "## Functions"
        , ""
        , "### factorial (n)"
        , ""
        , "Parameters:"
        , ""
        , "n"
        , ":   number for which the factorial is computed (integer)"
        , ""
        , "Returns:"
        , ""
        , " - factorial (integer)\n"
        ] @=?
        render mymath)
    ]
  ]

mymath :: Module
mymath = Module
  { moduleName = "mymath"
  , moduleDescription = "A math module."
  , moduleFields = []
  , moduleFunctions = [ ("factorial", factorial)]
  }

factorial :: HaskellFunction
factorial = toHsFnPrecursor (\n -> product [1..n])
  <#> factorialParam
  =#> factorialResult

factorialParam :: Parameter Integer
factorialParam =
  parameter (peekIntegral @Integer) "integer"
    "n"
    "number for which the factorial is computed"

factorialResult :: FunctionResults Integer
factorialResult =
  functionResult (pushIntegral @Integer) "integer" "factorial"