packages feed

sectile-0.1.0.0: test/Data/Sectile/TypesSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Data.Sectile.TypesSpec (spec) where

import Control.Monad.State (evalState, execState)
import qualified Data.Aeson as Aeson
import qualified Data.ByteString.Builder as B
import qualified Data.ByteString.Lazy as BSL
import qualified Data.HashMap.Strict as HashMap
import Data.Sectile.Tmux (ChunkStyle (..), noStyle)
import Data.Sectile.Types
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Test.Hspec

spec :: Spec
spec = do
  describe "Env state helpers" $ do
    let emptyEnv = Env noStyle HashMap.empty

    it "currentStyle gets style" $ do
      evalState currentStyle emptyEnv `shouldBe` noStyle

    it "updateStyle modifies and returns new style" $ do
      let newStyle = noStyle {chunkStyleItalic = Just True}
      let res = evalState (updateStyle (const newStyle)) emptyEnv
      res `shouldBe` newStyle

    it "currentBindings gets bindings" $ do
      evalState currentBindings emptyEnv `shouldBe` HashMap.empty

    it "updateBindings modifies and returns new bindings" $ do
      let newBindings = HashMap.singleton "key" (Aeson.String "value")
      let res = evalState (updateBindings (const newBindings)) emptyEnv
      res `shouldBe` newBindings

    it "appendBindings adds bindings" $ do
      let initialEnv = Env noStyle (HashMap.singleton "k1" (Aeson.String "v1"))
      let newBindings = HashMap.singleton "k2" (Aeson.String "v2")
      let res = evalState (appendBindings newBindings) initialEnv
      res `shouldBe` HashMap.fromList [("k1", Aeson.String "v1"), ("k2", Aeson.String "v2")]

    it "scopeBindings prefixes child bindings and preserves parent bindings" $ do
      let initialEnv = Env noStyle (HashMap.singleton "parent" (Aeson.String "pv"))
      let action = do
            _ <- appendBindings (HashMap.singleton "child" (Aeson.String "cv"))
            pure ("result" :: String)
      let finalEnv = execState (scopeBindings "test" action) initialEnv
      bindings finalEnv
        `shouldBe` HashMap.fromList
          [ ("parent", Aeson.String "pv"),
            ("test.child", Aeson.String "cv")
          ]

  describe "Binding helpers" $ do
    it "unitBindings generates correctly for KB" $ do
      let bnds = unitBindings "B" "disk" 2048
      bnds
        `shouldBe` HashMap.fromList
          [ ("disk", Aeson.String "2.0KiB"),
            ("disk.raw", Aeson.Number 2048),
            ("disk.value.full", Aeson.String "2.0"),
            ("disk.value.round", Aeson.String "2"),
            ("disk.unit.full", Aeson.String "KiB"),
            ("disk.unit.base", Aeson.String "B"),
            ("disk.unit.prefix", Aeson.String "Ki")
          ]

    it "percentBindings generates correctly" $ do
      let bnds = percentBindings "usage" 0.426
      bnds
        `shouldBe` HashMap.fromList
          [ ("usage", Aeson.String "42.6%"),
            ("usage.raw", Aeson.Number (realToFrac (0.426 :: Double))),
            ("usage.absolute", Aeson.String "0.43"),
            ("usage.percent.full", Aeson.String "42.6"),
            ("usage.percent.round", Aeson.String "43")
          ]

  describe "bindingsDetail" $ do
    it "renders nothing for empty bindings" $ do
      renderDetail (bindingsDetail HashMap.empty) `shouldBe` []

    it "flattens nested objects into sorted dot paths" $ do
      let bnds =
            HashMap.fromList
              [ ("_inner", Aeson.toJSON (HashMap.fromList [("raw" :: T.Text, Aeson.String "hello"), ("style", Aeson.toJSON (HashMap.fromList [("raw" :: T.Text, Aeson.String "")]))])),
                ("top", Aeson.String "v")
              ]
      renderDetail (bindingsDetail bnds)
        `shouldBe` [ "Bindings:",
                     "  _inner.raw = hello",
                     "  _inner.style.raw = ",
                     "  top = v"
                   ]

    it "renders JSON scalar bindings via Aeson encoding" $ do
      let bnds = HashMap.singleton "clock.raw" (Aeson.Number 85)
      renderDetail (bindingsDetail bnds) `shouldBe` ["Bindings:", "  clock.raw = 85"]

renderDetail :: [Detail B.Builder] -> [T.Text]
renderDetail = concatMap (go 0)
  where
    go lvl (DetailPlain b) = [T.replicate (2 * lvl) " " <> T.decodeUtf8 (BSL.toStrict (B.toLazyByteString b))]
    go lvl (DetailNested d) = go (lvl + 1) d
    go lvl (DetailList ds) = concatMap (go lvl) ds