packages feed

hgg-dataframe-0.1.0.0: test/Spec.hs

{-# LANGUAGE OverloadedStrings #-}
module Main where

import           Graphics.Hgg.Backend.SVG (renderBound)
import           Graphics.Hgg.DataFrame   (dfResolver, plotDF)
import           Graphics.Hgg.Frame       (PlotData (..), (|>>))
import           Graphics.Hgg.Spec        (ColData (..), layer, scatter)
import           Data.List                (isInfixOf)
import           Data.Map.Strict          (Map)
import qualified Data.Map.Strict          as M
import           Data.Text                (Text)
import qualified Data.Vector              as V
import qualified DataFrame.Internal.Column    as DF
import qualified DataFrame.Internal.DataFrame as DF
import           System.Directory         (getTemporaryDirectory, removeFile)
import           System.FilePath          ((</>))
import           Test.Hspec

main :: IO ()
main = hspec $ do
  describe "dfResolver" $ do
    it "Double 列を resolve" $ do
      let df = DF.fromNamedColumns
                 [ ("x", DF.fromList ([1.0, 2.0, 3.0] :: [Double])) ]
          r  = dfResolver df
      case r "x" of
        Just (NumData v) -> V.toList v `shouldBe` [1.0, 2.0, 3.0]
        _                -> expectationFailure "expected NumData"
    it "Int 列を resolve (= Double に変換)" $ do
      let df = DF.fromNamedColumns
                 [ ("n", DF.fromList ([10, 20, 30] :: [Int])) ]
          r  = dfResolver df
      case r "n" of
        Just (NumData v) -> V.toList v `shouldBe` [10.0, 20.0, 30.0]
        _                -> expectationFailure "expected NumData"
    it "存在しない列は Nothing" $ do
      let df = DF.fromNamedColumns
                 [ ("x", DF.fromList ([1.0, 2.0] :: [Double])) ]
          r  = dfResolver df
      r "nope" `shouldBe` Nothing
    it "nullable (Maybe Int) 列を resolve (NA → NaN・長さ保持)" $ do
      let df = DF.fromNamedColumns
                 [ ("m", DF.fromList ([Just 1, Nothing, Just 3] :: [Maybe Int])) ]
          r  = dfResolver df
      case r "m" of
        Just (NumData v) -> do
          length (V.toList v) `shouldBe` 3
          (V.toList v !! 0, V.toList v !! 2) `shouldBe` (1.0, 3.0)
          isNaN (V.toList v !! 1) `shouldBe` True   -- NA は NaN
        _ -> expectationFailure "expected NumData for Maybe Int column"
    it "nullable (Maybe Double) 列を resolve" $ do
      let df = DF.fromNamedColumns
                 [ ("d", DF.fromList ([Just 1.5, Nothing] :: [Maybe Double])) ]
          r  = dfResolver df
      case r "d" of
        Just (NumData v) -> do
          V.toList v !! 0 `shouldBe` 1.5
          isNaN (V.toList v !! 1) `shouldBe` True
        _ -> expectationFailure "expected NumData for Maybe Double column"

  -- Phase 61: safeColumnAs を columnAsVector 版へ置換した回帰 (§0 実測 4 ケース +
  -- end-to-end)。 とくに空 DataFrame は columnAsVector が Left でなく純粋例外を
  -- 投げる唯一の入力 (dataframe-operations Operations/Core.hs:825) なので、
  -- 「Nothing が返り例外が漏れない」 ことが本 Phase の要。
  describe "dfResolver 回帰 (Phase 61 = columnAsVector 化)" $ do
    it "型不一致: Text 列は TxtData として resolve (数値型 4 試行が例外を投げず Nothing で通過)" $ do
      let df = DF.fromNamedColumns
                 [ ("s", DF.fromList (["a", "b"] :: [Text])) ]
          r  = dfResolver df
      case r "s" of
        Just (TxtData v) -> V.toList v `shouldBe` ["a", "b"]
        _                -> expectationFailure "expected TxtData"
    it "空 DataFrame は Nothing (EmptyDataSetException を透過させない)" $ do
      let r = dfResolver DF.empty
      r "x" `shouldBe` Nothing
    it "空 DataFrame を plotDF に渡して例外なく SVG が書ける (end-to-end)" $ do
      tmp <- getTemporaryDirectory
      let path = tmp </> "hgg-df-empty-test.svg"
      plotDF path DF.empty (layer (scatter "x" "y"))
      svg <- readFile path
      ("<svg" `isInfixOf` svg) `shouldBe` True   -- 空の図が出る = 従来挙動
      removeFile path

  describe "PlotData DataFrame instance (Phase 14 A4)" $ do
    let df = DF.fromNamedColumns
               [ ("x", DF.fromList ([1.0, 2.0, 3.0] :: [Double]))
               , ("y", DF.fromList ([4.0, 5.0, 6.0] :: [Double])) ]
    it "columnNames は全列" $
      columnNames df `shouldBe` ["x", "y"]
    it "nrows は行数 (dataframeDimensions の fst)" $
      nrows df `shouldBe` 3
    it "Map と DataFrame で同一 SVG (同データ、 spec-2 §7)" $ do
      let mapDF :: Map Text ColData
          mapDF = M.fromList
            [ ("x", NumData (V.fromList [1.0, 2.0, 3.0]))
            , ("y", NumData (V.fromList [4.0, 5.0, 6.0])) ]
      renderBound (df    |>> layer (scatter "x" "y"))
        `shouldBe`
        renderBound (mapDF |>> layer (scatter "x" "y"))