packages feed

poppy-codegen-1.0.0: test/Poppy/Codegen/TargetSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Poppy.Codegen.TargetSpec
  ( targetSpec,
  )
where

import Data.List (nub, sort)
import qualified Data.Text as T
import Poppy.Codegen.Run (allOutputs, schemasForTargets)
import Poppy.Codegen.Spec.Author (authorSchema)
import Poppy.Codegen.Spec.Comment (commentSchema)
import Poppy.Codegen.Spec.Editor (editorSchema)
import Poppy.Codegen.Spec.Shelf (shelfSchema)
import Poppy.Codegen.Spec.Widget (widgetSchema)
import Poppy.Codegen.Target
  ( CodegenTarget,
    GenOutput (..),
    simpleTarget,
    targetOutputs,
  )
import Poppy.Codegen.TestTarget (testTargets)
import Test.Hspec

allTargets :: [CodegenTarget]
allTargets = testTargets

schemaGoldenNames :: [FilePath]
schemaGoldenNames =
  [ "Article",
    "Author",
    "Book",
    "Chapter",
    "Comment",
    "Editor",
    "Include/Author",
    "Include/Book",
    "Include/Chapter",
    "Include/Comment",
    "Include/Editor",
    "Include/Post",
    "Include/Shelf",
    "Post",
    "PostStatus",
    "Section",
    "Shelf",
    "Tag",
    "Widget"
  ]

targetSpec :: Spec
targetSpec =
  describe "Poppy.Codegen.Target" $ do
    it "validates every schema referenced by a target" $ do
      schemasForTargets allTargets
        `shouldMatchList` [ widgetSchema,
                            shelfSchema,
                            authorSchema,
                            editorSchema,
                            commentSchema
                          ]

    it "writes table types under the output dir and clients under Client/" $ do
      let paths = sort (nub (map outputPath (allOutputs allTargets)))
      filter (not . isClientPath) paths
        `shouldBe` sort
          [ "test/Schema/Article.hs",
            "test/Schema/Author.hs",
            "test/Schema/Book.hs",
            "test/Schema/Chapter.hs",
            "test/Schema/Comment.hs",
            "test/Schema/Editor.hs",
            "test/Schema/Include/Author.hs",
            "test/Schema/Include/Book.hs",
            "test/Schema/Include/Chapter.hs",
            "test/Schema/Include/Comment.hs",
            "test/Schema/Include/Editor.hs",
            "test/Schema/Include/Post.hs",
            "test/Schema/Include/Shelf.hs",
            "test/Schema/Post.hs",
            "test/Schema/PostStatus.hs",
            "test/Schema/Section.hs",
            "test/Schema/Shelf.hs",
            "test/Schema/Tag.hs",
            "test/Schema/Widget.hs"
          ]
      filter isClientPath paths
        `shouldNotBe` []

    it "keeps generated schema modules byte-stable" $ do
      mapM_ assertGoldenStable schemaGoldenNames

    it "emits one Client per model" $ do
      outputPaths (simpleTarget "src/Schema" shelfSchema)
        `shouldMatchList` [ "src/Schema/Book.hs",
                            "src/Schema/Chapter.hs",
                            "src/Schema/Include/Book.hs",
                            "src/Schema/Include/Chapter.hs",
                            "src/Schema/Include/Shelf.hs",
                            "src/Schema/Section.hs",
                            "src/Schema/Shelf.hs",
                            "src/Schema/Tag.hs",
                            "src/Schema/Client/Book.hs",
                            "src/Schema/Client/Chapter.hs",
                            "src/Schema/Client/Section.hs",
                            "src/Schema/Client/Shelf.hs",
                            "src/Schema/Client/Tag.hs"
                          ]

    it "nests clients under the module prefix" $ do
      let target = simpleTarget "src/Schema" widgetSchema
      outputPaths target
        `shouldMatchList` [ "src/Schema/Widget.hs",
                            "src/Schema/Client/Widget.hs"
                          ]
      outputTextFor "src/Schema/Widget.hs" target
        `shouldSatisfy` ("module Schema.Widget" `T.isInfixOf`)
      outputTextFor "src/Schema/Client/Widget.hs" target
        `shouldSatisfy` ("module Schema.Client.Widget" `T.isInfixOf`)

    it "keeps nested module prefixes from the path" $ do
      outputPaths (simpleTarget "src/MyApp/Schema" widgetSchema)
        `shouldMatchList` [ "src/MyApp/Schema/Widget.hs",
                            "src/MyApp/Schema/Client/Widget.hs"
                          ]
      outputTextFor "src/MyApp/Schema/Widget.hs" (simpleTarget "src/MyApp/Schema" widgetSchema)
        `shouldSatisfy` ("module MyApp.Schema.Widget" `T.isInfixOf`)
  where
    isClientPath path = "/Client/" `T.isInfixOf` T.pack path

    assertGoldenStable name = do
      expected <- readFile ("test/Poppy/Codegen/golden/" <> name <> ".hs.golden")
      let path = "test/Schema/" <> name <> ".hs"
          actual =
            T.unpack $
              outputText $
                head
                  [ out
                    | out <- allOutputs allTargets,
                      outputPath out == path
                  ]
      actual `shouldBe` expected

    outputPaths target = map outputPath (targetOutputs target)

    outputTextFor path target =
      outputText $
        head
          [ out
            | out <- targetOutputs target,
              outputPath out == path
          ]