{-# LANGUAGE OverloadedStrings #-} module Main where import Data.Aeson (Value (..)) import qualified Data.Aeson as Aeson import qualified Data.Text as T import LCJF (prettyLeadingComma) import Test.Hspec main :: IO () main = hspec spec spec :: Spec spec = do describe "prettyLeadingComma" $ do describe "primitive values" $ do it "formats null" $ do prettyLeadingComma Null `shouldBe` "null" it "formats true" $ do prettyLeadingComma (Bool True) `shouldBe` "true" it "formats false" $ do prettyLeadingComma (Bool False) `shouldBe` "false" it "formats integers" $ do prettyLeadingComma (Number 42) `shouldBe` "42" it "formats negative integers" $ do prettyLeadingComma (Number (-7)) `shouldBe` "-7" it "formats decimals" $ do prettyLeadingComma (Number 3.14) `shouldBe` "3.14" it "formats strings" $ do prettyLeadingComma (String "hello") `shouldBe` "\"hello\"" describe "string escaping" $ do it "escapes double quotes" $ do prettyLeadingComma (String "say \"hi\"") `shouldBe` "\"say \\\"hi\\\"\"" it "escapes backslashes" $ do prettyLeadingComma (String "a\\b") `shouldBe` "\"a\\\\b\"" it "escapes newlines" $ do prettyLeadingComma (String "line1\nline2") `shouldBe` "\"line1\\nline2\"" it "escapes tabs" $ do prettyLeadingComma (String "col1\tcol2") `shouldBe` "\"col1\\tcol2\"" it "escapes carriage returns" $ do prettyLeadingComma (String "a\rb") `shouldBe` "\"a\\rb\"" describe "empty containers" $ do it "formats empty array with spaces" $ do prettyLeadingComma (toJSON ([] :: [Int])) `shouldBe` "[ ]" it "formats empty object with spaces" $ do prettyLeadingComma (Aeson.object []) `shouldBe` "{ }" describe "arrays" $ do it "formats single-element array" $ do let result = prettyLeadingComma (toJSON [1 :: Int]) T.lines result `shouldBe` ["[ 1", "]"] it "formats multi-element array with leading commas" $ do let result = prettyLeadingComma (toJSON [1 :: Int, 2, 3]) T.lines result `shouldBe` ["[ 1", ", 2", ", 3", "]"] it "formats array of strings" $ do let result = prettyLeadingComma (toJSON ["a" :: T.Text, "b"]) T.lines result `shouldBe` ["[ \"a\"", ", \"b\"", "]"] describe "objects" $ do it "formats single-field object" $ do let result = prettyLeadingComma (Aeson.object [("name", String "test")]) T.lines result `shouldBe` ["{ \"name\": \"test\"", "}"] it "formats multi-field object with leading commas" $ do let result = prettyLeadingComma ( Aeson.object [ ("a", Number 1) , ("b", Number 2) ] ) T.lines result `shouldBe` ["{ \"a\": 1", ", \"b\": 2", "}"] it "sorts object keys alphabetically" $ do let result = prettyLeadingComma ( Aeson.object [ ("zebra", Number 1) , ("apple", Number 2) ] ) T.lines result `shouldBe` ["{ \"apple\": 2", ", \"zebra\": 1", "}"] describe "nested structures" $ do it "formats nested array on new line" $ do let result = prettyLeadingComma ( Aeson.object [("items", toJSON [1 :: Int, 2])] ) T.lines result `shouldBe` [ "{ \"items\":" , " [ 1" , " , 2" , " ]" , "}" ] it "formats nested object on new line" $ do let result = prettyLeadingComma ( Aeson.object [("nested", Aeson.object [("x", Number 1)])] ) T.lines result `shouldBe` [ "{ \"nested\":" , " { \"x\": 1" , " }" , "}" ] it "formats empty nested containers inline" $ do let result = prettyLeadingComma ( Aeson.object [ ("arr", toJSON ([] :: [Int])) , ("obj", Aeson.object []) ] ) T.lines result `shouldBe` [ "{ \"arr\": [ ]" , ", \"obj\": { }" , "}" ] it "formats deeply nested structures" $ do let result = prettyLeadingComma ( Aeson.object [ ( "outer" , Aeson.object [("inner", toJSON [1 :: Int, 2])] ) ] ) T.lines result `shouldBe` [ "{ \"outer\":" , " { \"inner\":" , " [ 1" , " , 2" , " ]" , " }" , "}" ] describe "complex example" $ do it "formats the example from requirements" $ do let input = Aeson.object [ ("field_one", String "string") , ("field_two", Null) , ("field_three", toJSON [5 :: Int, 248, 15187519]) , ("field_four", Aeson.object []) ] T.lines (prettyLeadingComma input) `shouldBe` [ "{ \"field_four\": { }" , ", \"field_one\": \"string\"" , ", \"field_three\":" , " [ 5" , " , 248" , " , 15187519" , " ]" , ", \"field_two\": null" , "}" ] it "handles mixed value types in array" $ do let result = prettyLeadingComma ( toJSON [ toJSON (1 :: Int) , toJSON ("text" :: T.Text) , toJSON True , Null ] ) T.lines result `shouldBe` [ "[ 1" , ", \"text\"" , ", true" , ", null" , "]" ] toJSON :: (Aeson.ToJSON a) => a -> Value toJSON = Aeson.toJSON