module Test.Hspec.Core.Formatters.V2Spec (spec) where

import           Prelude ()
import           Helper
import qualified Control.Exception as E

import qualified Test.Hspec.Core.Spec as H
import qualified Test.Hspec.Core.Spec as Spec
import qualified Test.Hspec.Core.Runner as H
import           Test.Hspec.Core.Format
import           Test.Hspec.Core.Formatters.V2

testSpec :: H.Spec
testSpec = do
  H.describe "Example" $ do
    H.it "success"    (H.Result "" Spec.Success)
    H.it "fail 1"     (H.Result "" $ Spec.Failure Nothing $ H.Reason "fail message")
    H.it "pending"    (H.pendingWith "pending message")
    H.it "fail 2"     (H.Result "" $ Spec.Failure Nothing H.NoReason)
    H.it "exceptions" (undefined :: Spec.Result)
    H.it "fail 3"     (H.Result "" $ Spec.Failure Nothing H.NoReason)


formatConfig :: FormatConfig
formatConfig = FormatConfig {
  formatConfigUseColor = False
, formatConfigOutputUnicode = True
, formatConfigUseDiff = True
, formatConfigPrettyPrint = True
, formatConfigPrintTimes = False
, formatConfigHtmlOutput = False
, formatConfigPrintCpuTime = False
, formatConfigUsedSeed = 0
, formatConfigExpectedTotalCount = 0
}

runSpecWith :: Formatter -> H.Spec -> IO [String]
runSpecWith formatter = captureLines . H.hspecWithResult H.defaultConfig {H.configFormat = Just $ formatterToFormat formatter}

spec :: Spec
spec = do
  describe "indentChunks" $ do
    context "with Original" $ do
      it "does not indent single-line input" $ do
        indentChunks "  " [Original "foo"] `shouldBe` [PlainChunk "foo"]

      it "indents multi-line input" $ do
        indentChunks "  " [Original "foo\nbar\nbaz\n"] `shouldBe` [PlainChunk "foo\n  bar\n  baz\n  "]

    context "with Modified" $ do
      it "returns the empty list on empty input" $ do
        indentChunks "  " [Modified ""] `shouldBe` []

      it "does not indent single-line input" $ do
        indentChunks "  " [Modified "foo"] `shouldBe` [ColorChunk "foo"]

      it "indents multi-line input" $ do
        indentChunks "  " [Modified "foo\nbar\nbaz\n"] `shouldBe` [ColorChunk "foo", PlainChunk "\n  ", ColorChunk "bar", PlainChunk "\n  ", ColorChunk "baz", PlainChunk "\n  "]

      it "colorizes whitespace-only input" $ do
        indentChunks "  " [Modified "  "] `shouldBe` [ColorChunk "  "]

      it "colorizes whitespace-only lines" $ do
        indentChunks "  " [Modified "foo\n  \n"] `shouldBe` [ColorChunk "foo", PlainChunk "\n  ", ColorChunk "  ", PlainChunk "\n  "]

      it "colorizes whitespace at the end of the input" $ do
        indentChunks "  " [Modified "foo\n  "] `shouldBe` [ColorChunk "foo", PlainChunk "\n  ", ColorChunk "  "]

      it "splits off whitespace-only segments at the end of a line so that they get colorized" $ do
        indentChunks "  " [Modified "foo  \n"] `shouldBe` [ColorChunk "foo", ColorChunk "  ", PlainChunk "\n  "]

      context "with empty lines" $ do
        it "colorizes indentation" $ do
          indentChunks "  " [Original "foo", Modified "\n\n", Original "bar"] `shouldBe` [PlainChunk "foo", PlainChunk "\n", ColorChunk "  ", PlainChunk "\n", ColorChunk "  ", PlainChunk "bar"]

  describe "progress" $ do
    let item = ItemDone ([], "") . Item Nothing 0 ""
    describe "formatterItemDone" $ do
      it "marks succeeding examples with ." $ do
        formatter <- formatterToFormat progress formatConfig
        captureLines (formatter $ item Success)
          `shouldReturn` ["."]

      it "marks failing examples with F" $ do
        formatter <- formatterToFormat progress formatConfig
        captureLines (formatter . item $ Failure Nothing NoReason)
          `shouldReturn` ["F"]

      it "marks pending examples with ." $ do
        formatter <- formatterToFormat progress formatConfig
        captureLines (formatter . item $ Pending Nothing Nothing)
          `shouldReturn` ["."]

  describe "checks" $ do
    let
      formatter = checks
      config = H.defaultConfig { H.configFormat = Just $ formatterToFormat formatter }

    it "" $ do
      r <- captureLines . H.hspecWithResult config $ do
        H.it "foo" True
      normalizeSummary r `shouldBe` [
          ""
        , "foo [✔]"
        , ""
        , "Finished in 0.0000 seconds"
        , "1 example, 0 failures"
        ]

    it "" $ do
      r <- captureLines . H.hspecWithResult config { H.configUnicodeMode = H.UnicodeNever } $ do
        H.it "foo" True
      normalizeSummary r `shouldBe` [
          ""
        , "foo [v]"
        , ""
        , "Finished in 0.0000 seconds"
        , "1 example, 0 failures"
        ]

  describe "specdoc" $ do

    let runSpec = runSpecWith specdoc

    it "displays a header for each thing being described" $ do
      _:x:_ <- runSpec testSpec
      x `shouldBe` "Example"

    it "displays one row for each behavior" $ do
      r <- runSpec $ do
        H.describe "List as a Monoid" $ do
          H.describe "mappend" $ do
            H.it "is associative" True
          H.describe "mempty" $ do
            H.it "is a left identity" True
            H.it "is a right identity" True
        H.describe "Maybe as a Monoid" $ do
          H.describe "mappend" $ do
            H.it "is associative" True
          H.describe "mempty" $ do
            H.it "is a left identity" True
            H.it "is a right identity" True
      normalizeSummary r `shouldBe` [
          ""
        , "List as a Monoid"
        , "  mappend"
        , "    is associative"
        , "  mempty"
        , "    is a left identity"
        , "    is a right identity"
        , "Maybe as a Monoid"
        , "  mappend"
        , "    is associative"
        , "  mempty"
        , "    is a left identity"
        , "    is a right identity"
        , ""
        , "Finished in 0.0000 seconds"
        , "6 examples, 0 failures"
        ]

    it "outputs an empty line at the beginning (even for non-nested specs)" $ do
      r <- runSpec $ do
        H.it "example 1" True
        H.it "example 2" True
      normalizeSummary r `shouldBe` [
          ""
        , "example 1"
        , "example 2"
        , ""
        , "Finished in 0.0000 seconds"
        , "2 examples, 0 failures"
        ]

    it "displays a row for each successful, failed, or pending example" $ do
      r <- runSpec testSpec
      r `shouldSatisfy` any (== "  fail 1 FAILED [1]")
      r `shouldSatisfy` any (== "  success")

    it "displays a '#' with an additional message for pending examples" $ do
      r <- runSpec testSpec
      r `shouldSatisfy` any (== "    # PENDING: pending message")

    context "with an empty group" $ do
      it "omits that group from the report" $ do
        r <- runSpec $ do
          H.describe "foo" $ do
            H.it "example 1" True
          H.describe "bar" $ do
            return ()
          H.describe "baz" $ do
            H.it "example 2" True

        normalizeSummary r `shouldBe` [
            ""
          , "foo"
          , "  example 1"
          , "baz"
          , "  example 2"
          , ""
          , "Finished in 0.0000 seconds"
          , "2 examples, 0 failures"
          ]

    describe "formatterDone" $ do
      it "recovers unicode from ExpectedButGot" $ do
        formatter <- formatterToFormat failed_examples formatConfig { formatConfigOutputUnicode = True }
        _ <- formatter .  ItemDone ([], "") . Item Nothing 0 "" $ Failure Nothing $ ExpectedButGot Nothing (show "\955") (show "\956")
        (fmap normalizeSummary . captureLines) (formatter $ Done []) `shouldReturn` [
            ""
          , "Failures:"
          , ""
          , "  1) "
          , "       expected: \"λ\""
          , "        but got: \"μ\""
          , ""
          , "  To rerun use: --match \"//\""
          , ""
          , "Randomized with seed 0"
          , ""
          , "Finished in 0.0000 seconds"
          , "1 example, 1 failure"
          ]

      context "when actual/expected contain newlines" $ do
        it "adds indentation" $ do
          formatter <- formatterToFormat failed_examples formatConfig
          _ <- formatter .  ItemDone ([], "") . Item Nothing 0 "" $ Failure Nothing $ ExpectedButGot Nothing "first\nsecond\nthird" "first\ntwo\nthird"
          (fmap normalizeSummary . captureLines) (formatter $ Done []) `shouldReturn` [
              ""
            , "Failures:"
            , ""
            , "  1) "
            , "       expected: first"
            , "                 second"
            , "                 third"
            , "        but got: first"
            , "                 two"
            , "                 third"
            , ""
            , "  To rerun use: --match \"//\""
            , ""
            , "Randomized with seed 0"
            , ""
            , "Finished in 0.0000 seconds"
            , "1 example, 1 failure"
            ]

      context "without failures" $ do
        it "shows summary in green if there are no failures" $ do
          formatter <- formatterToFormat failed_examples formatConfig
          _ <- formatter .  ItemDone ([], "") . Item Nothing 0 "" $ Success
          (fmap normalizeSummary . captureLines) (formatter $ Done []) `shouldReturn` [
              ""
            , "Finished in 0.0000 seconds"
            , "1 example, 0 failures"
            ]

      context "with pending examples" $ do
        it "shows summary in yellow if there are pending examples" $ do
          formatter <- formatterToFormat failed_examples formatConfig
          _ <- formatter .  ItemDone ([], "") . Item Nothing 0 "" $ Pending Nothing Nothing
          (fmap normalizeSummary . captureLines) (formatter $ Done []) `shouldReturn` [
              ""
            , "Finished in 0.0000 seconds"
            , "1 example, 0 failures, 1 pending"
            ]

    context "same as failed_examples" $ do
      failed_examplesSpec specdoc

  describe "getExpectedTotalCount" $ do
    let formatter = silent { formatterStarted = fmap show getExpectedTotalCount >>= writeLine }
        runSpec = runSpecWith formatter
    it "returns the total number of spec items" $ do
      result:_ <- runSpec testSpec
      result `shouldBe` "6"

failed_examplesSpec :: Formatter -> Spec
failed_examplesSpec formatter = do
  let runSpec = runSpecWith formatter

  context "displays a detailed list of failures" $ do
    it "prints all requirements that are not met" $ do
      r <- runSpec testSpec
      r `shouldSatisfy` any (== "  1) Example fail 1")

    it "prints the exception type for requirements that fail due to an uncaught exception" $ do
      r <- runSpec $ do
        H.it "foobar" (E.throw (E.ErrorCall "baz") :: Bool)
      r `shouldContain` [
          "  1) foobar"
        , "       uncaught exception: ErrorCall"
        , "       baz"
        ]

    it "prints all descriptions when a nested requirement fails" $ do
      r <- runSpec $
        H.describe "foo" $ do
          H.describe "bar" $ do
            H.it "baz" False
      r `shouldSatisfy` any (== "  1) foo.bar baz")


    context "when a failed example has a source location" $ do
      it "includes that source location above the error message" $ do
        let loc = H.Location "test/FooSpec.hs" 23 4
            addLoc e = e {Spec.itemLocation = Just loc}
        r <- runSpec $ H.mapSpecItem_ addLoc $ do
          H.it "foo" False
        r `shouldContain` ["  test/FooSpec.hs:23:4: ", "  1) foo"]
