packages feed

seihou-core-0.4.0.0: test/Seihou/Interaction/ConfirmSpec.hs

module Seihou.Interaction.ConfirmSpec (tests) where

import Data.Map.Strict qualified as Map
import Effectful
import Seihou.Composition.Instance (primaryInstance)
import Seihou.Core.Types
import Seihou.Effect.ConsolePure
import Seihou.Interaction.Confirm (confirmDefaults)
import Test.Hspec
import Test.Tasty
import Test.Tasty.Hspec (testSpec)

tests :: IO TestTree
tests = testSpec "Seihou.Interaction.Confirm" spec

mkModule :: ModuleName -> [VarDecl] -> [Prompt] -> Module
mkModule name vars prompts =
  Module
    { name = name,
      version = Nothing,
      description = Nothing,
      vars = vars,
      exports = [],
      prompts = prompts,
      steps = [],
      commands = [],
      dependencies = [],
      removal = Nothing,
      migrations = []
    }

mkTextVar :: VarName -> Maybe VarValue -> VarDecl
mkTextVar name defVal =
  VarDecl
    { name = name,
      type_ = VTText,
      default_ = defVal,
      description = Nothing,
      required = True,
      validation = Nothing
    }

mkIntVar :: VarName -> Maybe VarValue -> VarDecl
mkIntVar name defVal =
  VarDecl
    { name = name,
      type_ = VTInt,
      default_ = defVal,
      description = Nothing,
      required = True,
      validation = Nothing
    }

mkResolved :: VarDecl -> VarValue -> VarSource -> ResolvedVar
mkResolved decl val src =
  ResolvedVar {value = val, source = src, decl = decl}

spec :: Spec
spec = do
  describe "confirmDefaults" $ do
    it "does nothing when the flag activates but no variables have default sources" $ do
      let decl = mkTextVar "project.name" (Just (VText "my-project"))
          m = mkModule "base" [decl] []
          resolved =
            Map.singleton
              (primaryInstance "base")
              (Map.singleton "project.name" (mkResolved decl (VText "my-app") FromCLI))
      (result, st) <-
        runEff $
          runConsolePure [] $
            confirmDefaults [(primaryInstance m.name, m, "/fake/base")] resolved
      result `shouldBe` resolved
      st.consoleOutputs `shouldSatisfy` all (/= "Confirm default values:")

    it "prompts for FromDefault variables and accepts Enter as keeping the default" $ do
      let decl = mkTextVar "project.version" (Just (VText "0.1.0.0"))
          m = mkModule "base" [decl] []
          resolved =
            Map.singleton
              (primaryInstance "base")
              (Map.singleton "project.version" (mkResolved decl (VText "0.1.0.0") FromDefault))
      (result, st) <-
        runEff $
          runConsolePure [""] $
            confirmDefaults [(primaryInstance m.name, m, "/fake/base")] resolved
      let rv = (result Map.! primaryInstance "base") Map.! "project.version"
      rv.value `shouldBe` VText "0.1.0.0"
      rv.source `shouldBe` FromDefault
      st.consoleOutputs `shouldSatisfy` any (== "Confirm default values:")
      st.consoleOutputs `shouldSatisfy` any (== "project.version [0.1.0.0]:")

    it "replaces the value and marks source as FromPrompt when user types a new value" $ do
      let decl = mkTextVar "project.version" (Just (VText "0.1.0.0"))
          m = mkModule "base" [decl] []
          resolved =
            Map.singleton
              (primaryInstance "base")
              (Map.singleton "project.version" (mkResolved decl (VText "0.1.0.0") FromDefault))
      (result, _st) <-
        runEff $
          runConsolePure ["1.0.0"] $
            confirmDefaults [(primaryInstance m.name, m, "/fake/base")] resolved
      let rv = (result Map.! primaryInstance "base") Map.! "project.version"
      rv.value `shouldBe` VText "1.0.0"
      rv.source `shouldBe` FromPrompt

    it "retries on invalid input and keeps the default on final failure" $ do
      let decl = mkIntVar "retry.count" (Just (VInt 42))
          m = mkModule "base" [decl] []
          resolved =
            Map.singleton
              (primaryInstance "base")
              (Map.singleton "retry.count" (mkResolved decl (VInt 42) FromDefault))
      (result, _st) <-
        runEff $
          runConsolePure ["not-an-int", "still-bad", "nope"] $
            confirmDefaults [(primaryInstance m.name, m, "/fake/base")] resolved
      let rv = (result Map.! primaryInstance "base") Map.! "retry.count"
      rv.value `shouldBe` VInt 42
      rv.source `shouldBe` FromDefault

    it "prompts for FromParent variables" $ do
      let decl = mkTextVar "skill.name" (Just (VText "exec-plan"))
          m = mkModule "child" [decl] []
          resolved =
            Map.singleton
              (primaryInstance "child")
              ( Map.singleton
                  "skill.name"
                  (mkResolved decl (VText "exec-plan") (FromParent "parent"))
              )
      (result, _st) <-
        runEff $
          runConsolePure ["override"] $
            confirmDefaults [(primaryInstance m.name, m, "/fake/child")] resolved
      let rv = (result Map.! primaryInstance "child") Map.! "skill.name"
      rv.value `shouldBe` VText "override"
      rv.source `shouldBe` FromPrompt

    it "is a no-op in non-interactive mode" $ do
      let decl = mkTextVar "project.version" (Just (VText "0.1.0.0"))
          m = mkModule "base" [decl] []
          resolved =
            Map.singleton
              (primaryInstance "base")
              (Map.singleton "project.version" (mkResolved decl (VText "0.1.0.0") FromDefault))
      (result, st) <-
        runEff $
          runConsolePureNonInteractive $
            confirmDefaults [(primaryInstance m.name, m, "/fake/base")] resolved
      result `shouldBe` resolved
      st.consoleOutputs `shouldBe` []

    it "uses authored Prompt text when available" $ do
      let decl = mkTextVar "license" (Just (VText "MIT"))
          prompt =
            Prompt
              { var = "license",
                text = "Choose a license",
                condition = Nothing,
                choices = Nothing
              }
          m = mkModule "base" [decl] [prompt]
          resolved =
            Map.singleton
              (primaryInstance "base")
              (Map.singleton "license" (mkResolved decl (VText "MIT") FromDefault))
      (_result, st) <-
        runEff $
          runConsolePure [""] $
            confirmDefaults [(primaryInstance m.name, m, "/fake/base")] resolved
      st.consoleOutputs `shouldSatisfy` any (== "Choose a license [MIT]:")