{-# LANGUAGE NamedFieldPuns #-}
{-# OPTIONS_GHC -Wno-missing-export-lists #-}
module Test.MockCat.Internal.Message where

import Data.List (intercalate, maximumBy, elemIndex)
import Data.Ord (comparing)
import Data.Char (isLower)
import Data.Text (pack, replace, unpack)
import Test.MockCat.Internal.Types

message :: Show a => Maybe MockName -> a -> a -> String
message :: forall a. Show a => Maybe String -> a -> a -> String
message Maybe String
name a
expected a
actual =
  let expectedStr :: String
expectedStr = String -> String
formatStr (a -> String
forall a. Show a => a -> String
show a
expected)
      actualStr :: String
actualStr = String -> String
formatStr (a -> String
forall a. Show a => a -> String
show a
actual)
      diffLine :: String
diffLine = String
"            " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String -> String -> String
diffPointer String
expectedStr String
actualStr
      mainMessage :: String
mainMessage = String
"function" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Maybe String -> String
mockNameLabel Maybe String
name String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
" was not called with the expected arguments."
   in case String -> String -> [Difference]
structuralDiff String
expectedStr String
actualStr of
        [] ->
           String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
"\n"
             [ String
mainMessage,
               String
"  expected: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
expectedStr,
               String
"   but got: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
actualStr,
               String
diffLine
             ]
        [Difference]
ds ->
           let diffMessages :: String
diffMessages = [Difference] -> String
formatDifferences [Difference]
ds
            in String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
"\n"
                 [ String
mainMessage,
                   String
diffMessages,
                   String
"",
                   String
"Full context:",
                   String
"  expected: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
expectedStr,
                   String
"   but got: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
actualStr,
                   String
diffLine
                 ]

diffPointer :: String -> String -> String
diffPointer :: String -> String -> String
diffPointer String
expected String
actual =
  let commonPrefixLen :: Int
commonPrefixLen = [Bool] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([Bool] -> Int) -> [Bool] -> Int
forall a b. (a -> b) -> a -> b
$ (Bool -> Bool) -> [Bool] -> [Bool]
forall a. (a -> Bool) -> [a] -> [a]
takeWhile Bool -> Bool
forall a. a -> a
id ([Bool] -> [Bool]) -> [Bool] -> [Bool]
forall a b. (a -> b) -> a -> b
$ (Char -> Char -> Bool) -> String -> String -> [Bool]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
(==) String
expected String
actual
      diffLen :: Int
diffLen = Int -> Int -> Int
forall a. Ord a => a -> a -> a
max (String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
expected) (String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
actual) Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
commonPrefixLen
   in Int -> Char -> String
forall a. Int -> a -> [a]
replicate Int
commonPrefixLen Char
' ' String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Int -> Char -> String
forall a. Int -> a -> [a]
replicate Int
diffLen Char
'^'

mockNameLabel :: Maybe MockName -> String
mockNameLabel :: Maybe String -> String
mockNameLabel = String -> (String -> String) -> Maybe String -> String
forall b a. b -> (a -> b) -> Maybe a -> b
maybe String
forall a. Monoid a => a
mempty (String
" " String -> String -> String
forall a. Semigroup a => a -> a -> a
<>) (Maybe String -> String)
-> (Maybe String -> Maybe String) -> Maybe String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Maybe String -> Maybe String
enclose String
"`"

enclose :: String -> Maybe String -> Maybe String
enclose :: String -> Maybe String -> Maybe String
enclose String
e = (String -> String) -> Maybe String -> Maybe String
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\String
v -> String
e String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
v String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
e)

-- Normalize a show-produced string for consistency in diffs
formatStr :: String -> String
formatStr :: String -> String
formatStr String
s =
  case String
s of
    [] -> String
s
    Char
c:String
cs ->
      case String -> String
forall a. [a] -> [a]
reverse String
cs of
        [] -> String -> String
formatInner String
s
        Char
l:String
ls ->
          let inner :: String
inner = String -> String
forall a. [a] -> [a]
reverse String
ls
          in case (Char
c, Char
l) of
               (Char
'(', Char
')') -> String
"(" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String -> String
formatInner String
inner String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
")"
               (Char
'[', Char
')') -> String
"[" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String -> String
formatInner String
inner String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"]" -- Preserving the weird legacy case
               (Char
'[', Char
']') -> String
"[" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String -> String
formatInner String
inner String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"]"
               (Char
'{', Char
'}') -> String
"{" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String -> String
formatInner String
inner String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"}"
               (Char, Char)
_          -> String -> String
formatInner String
s
  where
    formatInner :: String -> String
formatInner String
inner =
      let tokens :: [String]
tokens = (String -> String) -> [String] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (String -> String
trim (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> String
quoteToken (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> String
trim) (String -> [String]
splitByComma String
inner)
       in String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
", " [String]
tokens

-- Helper: quote a token if it looks like an unquoted alpha token
quoteToken :: String -> String
quoteToken :: String -> String
quoteToken String
s = case String
s of
  [] -> String
s
  Char
'"':String
_ -> String
s
  Char
'(':String
_ -> String
s
  Char
'[':String
_ -> String
s
  Char
c:String
_
    | (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Char -> Bool
isSpecial String
s -> String
s
    | Char -> Bool
isLower Char
c -> Char
'"' Char -> String -> String
forall a. a -> [a] -> [a]
: String
s String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"\""
    | Bool
otherwise -> String
s
  where
    isSpecial :: Char -> Bool
isSpecial Char
c = Char
c Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
"{}= "

verifyFailedMessage :: Show a => Maybe MockName -> InvocationList a -> a -> VerifyFailed
verifyFailedMessage :: forall a.
Show a =>
Maybe String -> InvocationList a -> a -> VerifyFailed
verifyFailedMessage Maybe String
name InvocationList a
invocationList a
expected =
  let expectedStr :: String
expectedStr = String -> String
formatStr (a -> String
forall a. Show a => a -> String
show a
expected)
      actualStr :: String
actualStr = InvocationList a -> String
forall a. Show a => InvocationList a -> String
formatInvocationList InvocationList a
invocationList
      diffLine :: String
diffLine = String
"            " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String -> String -> String
diffPointer String
expectedStr String
actualStr
      mainMessage :: String
mainMessage = String
"function" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Maybe String -> String
mockNameLabel Maybe String
name String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
" was not called with the expected arguments."
   in String -> VerifyFailed
VerifyFailed (String -> VerifyFailed) -> String -> VerifyFailed
forall a b. (a -> b) -> a -> b
$ case String -> String -> [Difference]
structuralDiff String
expectedStr String
actualStr of
        [] ->
           String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
"\n"
             [ String
mainMessage,
               String
"  expected: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
expectedStr,
               String
"   but got: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
actualStr,
               String
diffLine
             ]
        [Difference]
ds ->
           let diffMessages :: String
diffMessages = [Difference] -> String
formatDifferences [Difference]
ds
            in String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
"\n"
                 [ String
mainMessage,
                   String
diffMessages,
                   String
"",
                   String
"Full context:",
                   String
"  expected: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
expectedStr,
                   String
"   but got: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
actualStr,
                   String
diffLine
                 ]

data Difference = Difference
  { Difference -> String
diffPath :: String,
    Difference -> String
diffExpected :: String,
    Difference -> String
diffActual :: String
  }
  deriving (Int -> Difference -> String -> String
[Difference] -> String -> String
Difference -> String
(Int -> Difference -> String -> String)
-> (Difference -> String)
-> ([Difference] -> String -> String)
-> Show Difference
forall a.
(Int -> a -> String -> String)
-> (a -> String) -> ([a] -> String -> String) -> Show a
$cshowsPrec :: Int -> Difference -> String -> String
showsPrec :: Int -> Difference -> String -> String
$cshow :: Difference -> String
show :: Difference -> String
$cshowList :: [Difference] -> String -> String
showList :: [Difference] -> String -> String
Show, Difference -> Difference -> Bool
(Difference -> Difference -> Bool)
-> (Difference -> Difference -> Bool) -> Eq Difference
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Difference -> Difference -> Bool
== :: Difference -> Difference -> Bool
$c/= :: Difference -> Difference -> Bool
/= :: Difference -> Difference -> Bool
Eq)

formatDifferences :: [Difference] -> String
formatDifferences :: [Difference] -> String
formatDifferences [Difference
d] =
  let label :: String
label = if String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null (Difference -> String
diffPath Difference
d) then String
"Specific difference:" else String
"Specific difference in `" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Difference -> String
diffPath Difference
d String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"`:"
   in String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
"\n"
        [ String
"  " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
label,
          String
"    expected: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Difference -> String
diffExpected Difference
d,
          String
"     but got: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Difference -> String
diffActual Difference
d,
          String
"              " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String -> String -> String
diffPointer (Difference -> String
diffExpected Difference
d) (Difference -> String
diffActual Difference
d)
        ]
formatDifferences [Difference]
ds =
  String
"  Specific differences:\n" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
"\n" ((Difference -> String) -> [Difference] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Difference -> String
formatDiff [Difference]
ds)
  where
    formatDiff :: Difference -> String
formatDiff Difference
d =
      let pathLabel :: String
pathLabel = if String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null (Difference -> String
diffPath Difference
d) then String
"root" else String
"`" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Difference -> String
diffPath Difference
d String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"`"
       in String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
"\n"
            [ String
"    - " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
pathLabel String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
":",
              String
"        expected: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Difference -> String
diffExpected Difference
d,
              String
"         but got: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Difference -> String
diffActual Difference
d
            ]

structuralDiff :: String -> String -> [Difference]
structuralDiff :: String -> String -> [Difference]
structuralDiff = String -> String -> String -> [Difference]
structuralDiff' String
""

structuralDiff' :: String -> String -> String -> [Difference]
structuralDiff' :: String -> String -> String -> [Difference]
structuralDiff' String
path String
expected String
actual
  | String -> Bool
isList String
expected Bool -> Bool -> Bool
&& String -> Bool
isList String
actual = String -> String -> String -> [Difference]
diffLists String
path String
expected String
actual
  | String -> Bool
isRecord String
expected Bool -> Bool -> Bool
&& String -> Bool
isRecord String
actual = String -> String -> String -> [Difference]
diffRecords String
path String
expected String
actual
  | Bool
otherwise = []

diffLists :: String -> String -> String -> [Difference]
diffLists :: String -> String -> String -> [Difference]
diffLists String
path String
expected String
actual =
  let items1 :: [String]
items1 = String -> [String]
extractListItems String
expected
      items2 :: [String]
items2 = String -> [String]
extractListItems String
actual
      -- We need to track indices for lists
      indexedMismatches :: [(Int, (String, String))]
indexedMismatches = ((Int, (String, String)) -> Bool)
-> [(Int, (String, String))] -> [(Int, (String, String))]
forall a. (a -> Bool) -> [a] -> [a]
filter (\(Int
_, (String
i1, String
i2)) -> String
i1 String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
i2) ([Int] -> [(String, String)] -> [(Int, (String, String))]
forall a b. [a] -> [b] -> [(a, b)]
zip [Int
0 :: Int ..] ([String] -> [String] -> [(String, String)]
forall a b. [a] -> [b] -> [(a, b)]
zip [String]
items1 [String]
items2))
   in ((Int, (String, String)) -> [Difference])
-> [(Int, (String, String))] -> [Difference]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\(Int
idx, (String
i1, String
i2)) ->
        let newPath :: String
newPath = String
path String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"[" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Int -> String
forall a. Show a => a -> String
show Int
idx String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"]"
            nested :: [Difference]
nested = String -> String -> String -> [Difference]
structuralDiff' String
newPath String
i1 String
i2
         in if [Difference] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Difference]
nested
              then [String -> String -> String -> Difference
Difference String
newPath String
i1 String
i2]
              else [Difference]
nested
      ) [(Int, (String, String))]
indexedMismatches

diffRecords :: String -> String -> String -> [Difference]
diffRecords :: String -> String -> String -> [Difference]
diffRecords String
path String
expected String
actual =
  let fields1 :: [String]
fields1 = String -> [String]
extractFields String
expected
      fields2 :: [String]
fields2 = String -> [String]
extractFields String
actual
      mismatches :: [(String, String)]
mismatches = ((String, String) -> Bool)
-> [(String, String)] -> [(String, String)]
forall a. (a -> Bool) -> [a] -> [a]
filter (\(String
f1, String
f2) -> String
f1 String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
f2 Bool -> Bool -> Bool
&& String -> Bool
isField String
f1 Bool -> Bool -> Bool
&& String -> Bool
isField String
f2) ([String] -> [String] -> [(String, String)]
forall a b. [a] -> [b] -> [(a, b)]
zip [String]
fields1 [String]
fields2)
   in ((String, String) -> [Difference])
-> [(String, String)] -> [Difference]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\(String
f1, String
f2) ->
        let fieldName :: String
fieldName = String -> String
getFieldName String
f1
            val1 :: String
val1 = String -> String
getFieldValue String
f1
            val2 :: String
val2 = String -> String
getFieldValue String
f2
            newPath :: String
newPath = if String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
path then String
fieldName else String
path String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"." String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
fieldName
            nested :: [Difference]
nested = String -> String -> String -> [Difference]
structuralDiff' String
newPath String
val1 String
val2
         in if [Difference] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Difference]
nested
              then [String -> String -> String -> Difference
Difference String
newPath String
val1 String
val2]
              else [Difference]
nested
      ) [(String, String)]
mismatches

-- utilities for message formatting
trim :: String -> String
trim :: String -> String
trim = String -> String
f (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> String
f
  where
    f :: String -> String
f = String -> String
forall a. [a] -> [a]
reverse (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
' ')

splitByComma :: String -> [String]
splitByComma :: String -> [String]
splitByComma = Int -> Int -> Int -> String -> String -> [String]
forall {a} {a} {a}.
(Num a, Num a, Num a, Ord a, Ord a, Ord a) =>
a -> a -> a -> String -> String -> [String]
go (Int
0 :: Int) (Int
0 :: Int) (Int
0 :: Int) String
""
  where
    go :: a -> a -> a -> String -> String -> [String]
go a
_ a
_ a
_ String
acc [] = [String -> String
trim (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ String -> String
forall a. [a] -> [a]
reverse String
acc | Bool -> Bool
not (String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
acc)]
    go a
p a
l a
b String
acc (Char
c : String
cs)
      | Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'(' = a -> a -> a -> String -> String -> [String]
go (a
p a -> a -> a
forall a. Num a => a -> a -> a
+ a
1) a
l a
b (Char
c Char -> String -> String
forall a. a -> [a] -> [a]
: String
acc) String
cs
      | Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
')' = a -> a -> a -> String -> String -> [String]
go (a -> a -> a
forall a. Ord a => a -> a -> a
max a
0 (a
p a -> a -> a
forall a. Num a => a -> a -> a
- a
1)) a
l a
b (Char
c Char -> String -> String
forall a. a -> [a] -> [a]
: String
acc) String
cs
      | Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'[' = a -> a -> a -> String -> String -> [String]
go a
p (a
l a -> a -> a
forall a. Num a => a -> a -> a
+ a
1) a
b (Char
c Char -> String -> String
forall a. a -> [a] -> [a]
: String
acc) String
cs
      | Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
']' = a -> a -> a -> String -> String -> [String]
go a
p (a -> a -> a
forall a. Ord a => a -> a -> a
max a
0 (a
l a -> a -> a
forall a. Num a => a -> a -> a
- a
1)) a
b (Char
c Char -> String -> String
forall a. a -> [a] -> [a]
: String
acc) String
cs
      | Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'{' = a -> a -> a -> String -> String -> [String]
go a
p a
l (a
b a -> a -> a
forall a. Num a => a -> a -> a
+ a
1) (Char
c Char -> String -> String
forall a. a -> [a] -> [a]
: String
acc) String
cs
      | Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'}' = a -> a -> a -> String -> String -> [String]
go a
p a
l (a -> a -> a
forall a. Ord a => a -> a -> a
max a
0 (a
b a -> a -> a
forall a. Num a => a -> a -> a
- a
1)) (Char
c Char -> String -> String
forall a. a -> [a] -> [a]
: String
acc) String
cs
      | Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
',' Bool -> Bool -> Bool
&& a
p a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
0 Bool -> Bool -> Bool
&& a
l a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
0 Bool -> Bool -> Bool
&& a
b a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
0 = String -> String
trim (String -> String
forall a. [a] -> [a]
reverse String
acc) String -> [String] -> [String]
forall a. a -> [a] -> [a]
: a -> a -> a -> String -> String -> [String]
go a
0 a
0 a
0 String
"" String
cs
      | Bool
otherwise = a -> a -> a -> String -> String -> [String]
go a
p a
l a
b (Char
c Char -> String -> String
forall a. a -> [a] -> [a]
: String
acc) String
cs

formatInvocationList :: Show a => InvocationList a -> String
formatInvocationList :: forall a. Show a => InvocationList a -> String
formatInvocationList InvocationList a
invocationList = case InvocationList a
invocationList of
  [] -> String
"Function was never called"
  [a
x] -> String -> String
formatStr (a -> String
forall a. Show a => a -> String
show a
x)
  InvocationList a
_ -> String
"[" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
", " ((a -> String) -> InvocationList a -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (String -> String
formatStr (String -> String) -> (a -> String) -> a -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> String
forall a. Show a => a -> String
show) InvocationList a
invocationList) String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"]"

_replace :: Show a => String -> a -> String
_replace :: forall a. Show a => String -> a -> String
_replace String
r a
s = Text -> String
unpack (Text -> String) -> Text -> String
forall a b. (a -> b) -> a -> b
$ HasCallStack => Text -> Text -> Text -> Text
Text -> Text -> Text -> Text
replace (String -> Text
pack String
r) (String -> Text
pack String
"") (String -> Text
pack (a -> String
forall a. Show a => a -> String
show a
s))

messageForMultiMock :: Show a => Maybe MockName -> [a] -> a -> String
messageForMultiMock :: forall a. Show a => Maybe String -> [a] -> a -> String
messageForMultiMock Maybe String
name [a]
expecteds a
actual =
  let expectedStrs :: [String]
expectedStrs = (a -> String) -> [a] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (String -> String
formatStr (String -> String) -> (a -> String) -> a -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> String
forall a. Show a => a -> String
show) [a]
expecteds
      actualStr :: String
actualStr = String -> String
formatStr (a -> String
forall a. Show a => a -> String
show a
actual)
      nearest :: String
nearest = String -> [String] -> String
chooseNearest String
actualStr [String]
expectedStrs
   in String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate
        String
"\n"
        [ String
"function" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Maybe String -> String
mockNameLabel Maybe String
name String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
" was not called with the expected arguments.",
          String
"  expected one of the following:",
          String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
"\n" ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ (String
"    " String -> String -> String
forall a. Semigroup a => a -> a -> a
<>) (String -> String) -> [String] -> [String]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [String]
expectedStrs,
          String
"  but got:",
          String
"    " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
actualStr,
          String
"    " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String -> String -> String
diffPointer String
nearest String
actualStr
        ]

chooseNearest :: String -> [String] -> String
chooseNearest :: String -> [String] -> String
chooseNearest String
_ [] = String
""
chooseNearest String
actual [String]
expectations =
  let commonPrefixLen :: [b] -> [b] -> Int
commonPrefixLen [b]
s1 [b]
s2 = [Bool] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([Bool] -> Int) -> [Bool] -> Int
forall a b. (a -> b) -> a -> b
$ (Bool -> Bool) -> [Bool] -> [Bool]
forall a. (a -> Bool) -> [a] -> [a]
takeWhile Bool -> Bool
forall a. a -> a
id ([Bool] -> [Bool]) -> [Bool] -> [Bool]
forall a b. (a -> b) -> a -> b
$ (b -> b -> Bool) -> [b] -> [b] -> [Bool]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith b -> b -> Bool
forall a. Eq a => a -> a -> Bool
(==) [b]
s1 [b]
s2
      scores :: [(Int, String)]
scores = (String -> (Int, String)) -> [String] -> [(Int, String)]
forall a b. (a -> b) -> [a] -> [b]
map (\String
e -> (String -> String -> Int
forall {b}. Eq b => [b] -> [b] -> Int
commonPrefixLen String
actual String
e, String
e)) [String]
expectations
   in (Int, String) -> String
forall a b. (a, b) -> b
snd ((Int, String) -> String) -> (Int, String) -> String
forall a b. (a -> b) -> a -> b
$ ((Int, String) -> (Int, String) -> Ordering)
-> [(Int, String)] -> (Int, String)
forall (t :: * -> *) a.
Foldable t =>
(a -> a -> Ordering) -> t a -> a
maximumBy (((Int, String) -> Int)
-> (Int, String) -> (Int, String) -> Ordering
forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing (Int, String) -> Int
forall a b. (a, b) -> a
fst) [(Int, String)]
scores


isRecord :: String -> Bool
isRecord :: String -> Bool
isRecord String
s = Char
'{' Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
s Bool -> Bool -> Bool
&& Char
'}' Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
s

extractFields :: String -> [String]
extractFields :: String -> [String]
extractFields String
s = [String] -> (String -> [String]) -> Maybe String -> [String]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] String -> [String]
splitByComma (Char -> Char -> String -> Maybe String
extractInner Char
'{' Char
'}' String
s)

extractInner :: Char -> Char -> String -> Maybe String
extractInner :: Char -> Char -> String -> Maybe String
extractInner Char
open Char
close String
s =
  case (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
open) String
s of
    (Char
x:String
rest) | Char
x Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
open -> String -> Maybe String
forall a. a -> Maybe a
Just (Int -> String -> String
takeBalanced (Int
1 :: Int) String
rest)
    String
_ -> Maybe String
forall a. Maybe a
Nothing
  where
    takeBalanced :: Int -> String -> String
    takeBalanced :: Int -> String -> String
takeBalanced Int
0 String
_ = String
""
    takeBalanced Int
_ [] = String
""
    takeBalanced Int
n (Char
c:String
cs)
      | Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
open = Char
c Char -> String -> String
forall a. a -> [a] -> [a]
: Int -> String -> String
takeBalanced (Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1) String
cs
      | Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
close = if Int
n Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
1 then String
"" else Char
c Char -> String -> String
forall a. a -> [a] -> [a]
: Int -> String -> String
takeBalanced (Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1) String
cs
      | Bool
otherwise = Char
c Char -> String -> String
forall a. a -> [a] -> [a]
: Int -> String -> String
takeBalanced Int
n String
cs

isField :: String -> Bool
isField :: String -> Bool
isField = (Char
'=' Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem`)

getFieldName :: String -> String
getFieldName :: String -> String
getFieldName = String -> String
trim (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'=')

getFieldValue :: String -> String
getFieldValue :: String -> String
getFieldValue = String -> String
trim (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> String -> String
forall a. Int -> [a] -> [a]
drop Int
1 (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'=')

isList :: String -> Bool
isList :: String -> Bool
isList String
s = case String
s of
  Char
'[':String
cs -> case String -> String
forall a. [a] -> [a]
reverse String
cs of
              Char
']':String
_ -> Bool
True
              String
_ -> Bool
False
  String
_ -> Bool
False

extractListItems :: String -> [String]
extractListItems :: String -> [String]
extractListItems String
s = [String] -> (String -> [String]) -> Maybe String -> [String]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] String -> [String]
splitByComma (Char -> Char -> String -> Maybe String
extractInner Char
'[' Char
']' String
s)

listMismatchIndex :: [String] -> [String] -> Maybe Int
listMismatchIndex :: [String] -> [String] -> Maybe Int
listMismatchIndex [String]
s1 [String]
s2 = Bool -> [Bool] -> Maybe Int
forall a. Eq a => a -> [a] -> Maybe Int
elemIndex Bool
False ((String -> String -> Bool) -> [String] -> [String] -> [Bool]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith String -> String -> Bool
forall a. Eq a => a -> a -> Bool
(==) [String]
s1 [String]
s2)

verifyOrderFailedMesssage :: Show a => VerifyOrderResult a -> String
verifyOrderFailedMesssage :: forall a. Show a => VerifyOrderResult a -> String
verifyOrderFailedMesssage VerifyOrderResult {Int
index :: Int
index :: forall a. VerifyOrderResult a -> Int
index, a
calledValue :: a
calledValue :: forall a. VerifyOrderResult a -> a
calledValue, a
expectedValue :: a
expectedValue :: forall a. VerifyOrderResult a -> a
expectedValue} =
  let callIndex :: String
callIndex = Int -> String
showHumanReadable (Int
index Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)
      expectedStr :: String
expectedStr = String -> String
formatStr (a -> String
forall a. Show a => a -> String
show a
expectedValue)
      actualStr :: String
actualStr = String -> String
formatStr (a -> String
forall a. Show a => a -> String
show a
calledValue)
      prefix :: String
prefix = String
"   but got " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
callIndex String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
" call: "
      spaces :: String
spaces = Int -> Char -> String
forall a. Int -> a -> [a]
replicate (String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
prefix) Char
' '
   in String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate
        String
"\n"
        [ String
"  expected " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
callIndex String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
" call: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
expectedStr,
          String
prefix String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
actualStr,
          String
spaces String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String -> String -> String
diffPointer String
expectedStr String
actualStr
        ]
  where
    showHumanReadable :: Int -> String
    showHumanReadable :: Int -> String
showHumanReadable Int
1 = String
"1st"
    showHumanReadable Int
2 = String
"2nd"
    showHumanReadable Int
3 = String
"3rd"
    showHumanReadable Int
n = Int -> String
forall a. Show a => a -> String
show Int
n String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"th"