packages feed

nirum-0.2.0: test/Nirum/PackageSpec.hs

{-# LANGUAGE OverloadedLists #-}
module Nirum.PackageSpec where

import Data.Either (isRight)
import qualified Data.Map.Strict as M
import System.FilePath ((</>))
import Test.Hspec.Meta

import Nirum.Constructs.Annotation (empty)
import Nirum.Constructs.Module (Module(Module), coreModulePath)
import Nirum.Constructs.ModulePath (ModulePath)
import Nirum.Constructs.TypeDeclaration ( JsonType(String)
                                        , PrimitiveTypeIdentifier(Text)
                                        , Type(Alias, PrimitiveType)
                                        , TypeDeclaration ( Import
                                                          , TypeDeclaration
                                                          )
                                        )
import Nirum.Package ( BoundModule(boundPackage, modulePath)
                     , ImportError ( CircularImportError
                                   , MissingImportError
                                   , MissingModulePathError
                                   )
                     , Package
                     , PackageError (ImportError)
                     , TypeLookup(Imported, Local, Missing)
                     , docs
                     , lookupType
                     , makePackage
                     , resolveBoundModule
                     , resolveModule
                     , scanModules
                     , scanPackage
                     , types
                     )
import Nirum.Parser (parseFile)

createPackage :: M.Map ModulePath Module -> Package
createPackage modules' =
    case makePackage modules' of
        Right pkg -> pkg
        Left e -> error $ "errored: " ++ show e

validPackage :: Package
validPackage =
    createPackage [ (["foo", "bar"], Module [] $ Just "foo.bar")
                  , (["foo", "baz"], Module [] $ Just "foo.baz")
                  , (["foo"],        Module [] $ Just "foo")
                  , (["qux"],        Module [] $ Just "qux")
                  , ( ["abc"]
                    , Module [TypeDeclaration "a" (Alias "text") empty]
                             Nothing
                    )
                  , ( ["xyz"]
                    , Module [ Import ["abc"] "a" empty
                             , TypeDeclaration "x" (Alias "text") empty
                             ] Nothing
                    )
                  ]

missingImportsModules :: M.Map ModulePath Module
missingImportsModules =
    [ ( ["foo"]
      , Module [ Import ["foo", "bar"] "xyz" empty -- MissingModulePathError
               , Import ["foo", "bar"] "zzz" empty -- MissingModulePathError
               , Import ["baz"] "qux" empty
               ] Nothing
      )
    , ( ["baz"]
      , Module [ TypeDeclaration "qux" (Alias "text") empty ] Nothing
      )
    , (["qux"], Module [ Import ["foo"] "abc" empty -- MissingImportError
                       , Import ["foo"] "def" empty -- MissingImportError
                       ] Nothing)
    ]

circularImportsModules :: M.Map ModulePath Module
circularImportsModules =
    [ (["asdf"], Module [ Import ["asdf"] "foo" empty
                        , TypeDeclaration "bar" (Alias "text") empty
                        ] Nothing)
    , (["abc", "def"], Module [ Import ["abc", "ghi"] "bar" empty
                              , TypeDeclaration
                                    "foo" (Alias "text") empty
                              ] Nothing)
    , (["abc", "ghi"], Module [ Import ["abc", "xyz"] "baz" empty
                              , TypeDeclaration
                                    "bar" (Alias "text") empty
                              ] Nothing)
    , (["abc", "xyz"], Module [ Import ["abc", "def"] "foo" empty
                              , TypeDeclaration
                                    "baz" (Alias "text") empty
                              ] Nothing)
    ]

spec :: Spec
spec = do
    describe "Package" $ do
        specify "resolveModule" $ do
            resolveModule ["foo"] validPackage `shouldBe`
                Just (Module [] $ Just "foo")
            resolveModule ["foo", "bar"] validPackage `shouldBe`
                Just (Module [] $ Just "foo.bar")
            resolveModule ["qux"] validPackage `shouldBe`
                Just (Module [] $ Just "qux")
            resolveModule ["baz"] validPackage `shouldBe` Nothing
        specify "resolveBoundModule" $ do
            let Just bm = resolveBoundModule ["foo"] validPackage
            boundPackage bm `shouldBe` validPackage
            modulePath bm `shouldBe` ["foo"]
            resolveBoundModule ["baz"] validPackage `shouldBe` Nothing
        specify "detectMissingImports" $
            makePackage missingImportsModules `shouldBe`
                Left [ MissingModulePathError ["foo"] ["foo", "bar"]
                     , MissingImportError ["qux"] ["foo"] "abc"
                     , MissingImportError ["qux"] ["foo"] "def"
                     ]
        specify "detectCircularImports" $
            makePackage circularImportsModules `shouldBe`
                Left [ CircularImportError [["asdf"], ["asdf"]]
                     , MissingImportError ["asdf"] ["asdf"] "foo"
                     , CircularImportError [ ["abc", "def"]
                                           , ["abc", "ghi"]
                                           , ["abc", "xyz"]
                                           , ["abc", "def"]
                                           ]
                     , CircularImportError [ ["abc", "ghi"]
                                           , ["abc", "xyz"]
                                           , ["abc", "def"]
                                           , ["abc", "ghi"]
                                           ]
                     , CircularImportError [ ["abc", "xyz"]
                                           , ["abc", "def"]
                                           , ["abc", "ghi"]
                                           , ["abc", "xyz"]
                                           ]
                     ]
        specify "scanPackage" $ do
            let path = "." </> "examples"
            package' <- scanPackage path
            package' `shouldSatisfy` isRight
            let Right package = package'
            Right builtinsM <- parseFile (path </> "builtins.nrm")
            Right productM <- parseFile (path </> "product.nrm")
            Right shapesM <- parseFile (path </> "shapes.nrm")
            Right countriesM <- parseFile (path </> "countries.nrm")
            Right addressM <- parseFile (path </> "address.nrm")
            Right pdfServiceM <- parseFile (path </> "pdf-service.nrm")
            let modules = [ (["builtins"], builtinsM)
                          , (["product"], productM)
                          , (["shapes"], shapesM)
                          , (["countries"], countriesM)
                          , (["address"], addressM)
                          , (["pdf-service"], pdfServiceM)
                          ] :: M.Map ModulePath Module
            package `shouldBe` createPackage modules
            Left error' <- scanPackage $ "." </> "test" </> "import_error"
            error' `shouldBe`
                ImportError [MissingModulePathError ["import_error"] ["foo"]]
        specify "scanModules" $ do
            let path = "." </> "examples"
            mods <- scanModules "."
            mods `shouldBe`
                [ (["examples", "builtins"], path </> "builtins.nrm")
                , (["examples", "product"], path </> "product.nrm")
                , (["examples", "shapes"], path </> "shapes.nrm")
                , (["examples", "countries"], path </> "countries.nrm")
                , (["examples", "address"], path </> "address.nrm")
                , (["examples", "pdf-service"], path </> "pdf-service.nrm")
                , ( ["test", "import_error", "import_error"]
                  , "." </> "test" </> "import_error" </> "import_error.nrm"
                  )
                ]
            mods' <- scanModules path
            mods' `shouldBe` [ (["builtins"], path </> "builtins.nrm")
                             , (["product"], path </> "product.nrm")
                             , (["shapes"], path </> "shapes.nrm")
                             , (["countries"], path </> "countries.nrm")
                             , (["address"], path </> "address.nrm")
                             , (["pdf-service"], path </> "pdf-service.nrm")
                             ]
    describe "BoundModule" $ do
        let Just bm = resolveBoundModule ["foo", "bar"] validPackage
            Just abc = resolveBoundModule ["abc"] validPackage
            Just xyz = resolveBoundModule ["xyz"] validPackage
        specify "docs" $ do
            docs bm `shouldBe` Just "foo.bar"
            let Just bm' = resolveBoundModule ["foo"] validPackage
            docs bm' `shouldBe` Just "foo"
        specify "types" $ do
            types bm `shouldBe` []
            types abc `shouldBe` [TypeDeclaration "a" (Alias "text") empty]
            types xyz `shouldBe` [ Import ["abc"] "a" empty
                                 , TypeDeclaration "x" (Alias "text") empty
                                 ]
        specify "lookupType" $ do
            lookupType "a" bm `shouldBe` Missing
            lookupType "a" abc `shouldBe` Local (Alias "text")
            lookupType "a" xyz `shouldBe` Imported ["abc"] (Alias "text")
            lookupType "x" bm `shouldBe` Missing
            lookupType "x" abc `shouldBe` Missing
            lookupType "x" xyz `shouldBe` Local (Alias "text")
            lookupType "text" bm `shouldBe`
                Imported coreModulePath (PrimitiveType Text String)
            lookupType "text" abc `shouldBe` lookupType "text" bm
            lookupType "text" xyz `shouldBe` lookupType "text" bm