packages feed

sandwich 0.1.0.10 → 0.1.0.11

raw patch · 19 files changed

+384/−95 lines, 19 filesdep −lensPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies removed: lens

API changes (from Hackage documentation)

+ Test.Sandwich.Golden: golden :: (MonadIO m, MonadThrow m, Eq str, Show str) => Golden str -> Free (SpecCommand context m) ()
+ Test.Sandwich.Golden: goldenActualFile :: Golden a -> Maybe FilePath
+ Test.Sandwich.Golden: goldenFailFirstTime :: Golden a -> Bool
+ Test.Sandwich.Golden: goldenFile :: Golden a -> FilePath
+ Test.Sandwich.Golden: goldenJSON :: (ToJSON a, FromJSON a) => String -> a -> Golden a
+ Test.Sandwich.Golden: goldenOutput :: Golden a -> a
+ Test.Sandwich.Golden: goldenReadFromFile :: Golden a -> FilePath -> IO a
+ Test.Sandwich.Golden: goldenShowable :: (Show a, Read a) => String -> a -> Golden a
+ Test.Sandwich.Golden: goldenString :: String -> String -> Golden String
+ Test.Sandwich.Golden: goldenText :: String -> Text -> Golden Text
+ Test.Sandwich.Golden: goldenWriteToFile :: Golden a -> FilePath -> a -> IO ()
+ Test.Sandwich.Golden: mkGolden :: (FilePath -> a -> IO ()) -> (FilePath -> IO a) -> String -> a -> Golden a
+ Test.Sandwich.Misc: [optGoldenOptions] :: CommandLineOptions a -> CommandLineGoldenOptions
+ Test.Sandwich.Misc: [optPrintGoldenFlags] :: CommandLineOptions a -> Maybe Bool
- Test.Sandwich.Internal: [RunNodeAfter] :: {runNodeCommon :: RunNodeCommonWithStatus s l t, runNodeChildren :: [RunNodeWithStatus context s l t], runNodeAfter :: ExampleT context IO ()} -> RunNodeWithStatus context s l t
+ Test.Sandwich.Internal: [RunNodeAfter] :: RunNodeCommonWithStatus s l t -> [RunNodeWithStatus context s l t] -> ExampleT context IO () -> RunNodeWithStatus context s l t
- Test.Sandwich.Internal: [RunNodeAround] :: {runNodeCommon :: RunNodeCommonWithStatus s l t, runNodeChildren :: [RunNodeWithStatus context s l t], runNodeActionWith :: ExampleT context IO [Result] -> ExampleT context IO ()} -> RunNodeWithStatus context s l t
+ Test.Sandwich.Internal: [RunNodeAround] :: RunNodeCommonWithStatus s l t -> [RunNodeWithStatus context s l t] -> (ExampleT context IO [Result] -> ExampleT context IO ()) -> RunNodeWithStatus context s l t
- Test.Sandwich.Internal: [RunNodeBefore] :: {runNodeCommon :: RunNodeCommonWithStatus s l t, runNodeChildren :: [RunNodeWithStatus context s l t], runNodeBefore :: ExampleT context IO ()} -> RunNodeWithStatus context s l t
+ Test.Sandwich.Internal: [RunNodeBefore] :: RunNodeCommonWithStatus s l t -> [RunNodeWithStatus context s l t] -> ExampleT context IO () -> RunNodeWithStatus context s l t
- Test.Sandwich.Internal: [RunNodeDescribe] :: {runNodeCommon :: RunNodeCommonWithStatus s l t, runNodeChildren :: [RunNodeWithStatus context s l t]} -> RunNodeWithStatus context s l t
+ Test.Sandwich.Internal: [RunNodeDescribe] :: RunNodeCommonWithStatus s l t -> [RunNodeWithStatus context s l t] -> RunNodeWithStatus context s l t
- Test.Sandwich.Internal: [RunNodeIntroduceWith] :: {runNodeCommon :: RunNodeCommonWithStatus s l t, runNodeChildrenAugmented :: [RunNodeWithStatus (LabelValue lab intro :> context) s l t], runNodeIntroduceAction :: (intro -> ExampleT context IO [Result]) -> ExampleT context IO ()} -> RunNodeWithStatus context s l t
+ Test.Sandwich.Internal: [RunNodeIntroduceWith] :: RunNodeCommonWithStatus s l t -> [RunNodeWithStatus (LabelValue lab intro :> context) s l t] -> ((intro -> ExampleT context IO [Result]) -> ExampleT context IO ()) -> RunNodeWithStatus context s l t
- Test.Sandwich.Internal: [RunNodeIntroduce] :: Typeable intro => {runNodeCommon :: RunNodeCommonWithStatus s l t, runNodeChildrenAugmented :: [RunNodeWithStatus (LabelValue lab intro :> context) s l t], runNodeAlloc :: ExampleT context IO intro, runNodeCleanup :: intro -> ExampleT context IO ()} -> RunNodeWithStatus context s l t
+ Test.Sandwich.Internal: [RunNodeIntroduce] :: Typeable intro => RunNodeCommonWithStatus s l t -> [RunNodeWithStatus (LabelValue lab intro :> context) s l t] -> ExampleT context IO intro -> (intro -> ExampleT context IO ()) -> RunNodeWithStatus context s l t
- Test.Sandwich.Internal: [RunNodeIt] :: {runNodeCommon :: RunNodeCommonWithStatus s l t, runNodeExample :: ExampleT context IO ()} -> RunNodeWithStatus context s l t
+ Test.Sandwich.Internal: [RunNodeIt] :: RunNodeCommonWithStatus s l t -> ExampleT context IO () -> RunNodeWithStatus context s l t
- Test.Sandwich.Internal: [RunNodeParallel] :: {runNodeCommon :: RunNodeCommonWithStatus s l t, runNodeChildren :: [RunNodeWithStatus context s l t]} -> RunNodeWithStatus context s l t
+ Test.Sandwich.Internal: [RunNodeParallel] :: RunNodeCommonWithStatus s l t -> [RunNodeWithStatus context s l t] -> RunNodeWithStatus context s l t
- Test.Sandwich.Misc: CommandLineOptions :: FormatterType -> Maybe LogLevel -> Maybe Int -> Maybe String -> Int -> Maybe String -> Maybe Bool -> Maybe FilePath -> Maybe Bool -> Maybe Bool -> Maybe Bool -> Maybe Bool -> Maybe Bool -> Maybe IndividualTestModule -> CommandLineQuickCheckOptions -> CommandLineHedgehogOptions -> CommandLineSlackOptions -> CommandLineWebdriverOptions -> a -> CommandLineOptions a
+ Test.Sandwich.Misc: CommandLineOptions :: FormatterType -> Maybe LogLevel -> Maybe Int -> Maybe String -> Int -> Maybe String -> Maybe Bool -> Maybe FilePath -> Maybe Bool -> Maybe Bool -> Maybe Bool -> Maybe Bool -> Maybe Bool -> Maybe Bool -> Maybe IndividualTestModule -> CommandLineGoldenOptions -> CommandLineQuickCheckOptions -> CommandLineHedgehogOptions -> CommandLineSlackOptions -> CommandLineWebdriverOptions -> a -> CommandLineOptions a

Files

CHANGELOG.md view
@@ -2,6 +2,10 @@  ## Unreleased changes +## 0.1.0.11++* Initial release Test.Sandwich.Golden for golden testing.+* Support Brick 1.x in addition to 0.x.  ## 0.1.0.10 
LICENSE view
@@ -1,4 +1,4 @@-Copyright Tom McLaughlin (c) 2021+Copyright Tom McLaughlin (c) 2022  All rights reserved. 
sandwich.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 933d39e891051a78e2b2f7d00c6879128eb9fce0b3c31eb6f21cb663e0643f14+-- hash: eea544fa3b581852af314541fcc7874b15f46d425b1632e09e36eeaf60de11b3  name:           sandwich-version:        0.1.0.10+version:        0.1.0.11 synopsis:       Yet another test framework for Haskell description:    Please see the <https://codedownio.github.io/sandwich documentation>. category:       Testing@@ -15,7 +15,7 @@ bug-reports:    https://github.com/codedownio/sandwich/issues author:         Tom McLaughlin maintainer:     tom@codedown.io-copyright:      2021 Tom McLaughlin+copyright:      2022 Tom McLaughlin license:        BSD3 license-file:   LICENSE build-type:     Simple@@ -31,6 +31,7 @@       Test.Sandwich       Test.Sandwich.Contexts       Test.Sandwich.Expectations+      Test.Sandwich.Golden       Test.Sandwich.Logging       Test.Sandwich.Misc       Test.Sandwich.Nodes@@ -67,6 +68,7 @@       Test.Sandwich.Formatters.TerminalUI.Keys       Test.Sandwich.Formatters.TerminalUI.OpenInEditor       Test.Sandwich.Formatters.TerminalUI.Types+      Test.Sandwich.Golden.Update       Test.Sandwich.Internal.Formatters       Test.Sandwich.Internal.Running       Test.Sandwich.Interpreters.FilterTree@@ -87,6 +89,7 @@       Test.Sandwich.Types.RunTree       Test.Sandwich.Types.Spec       Test.Sandwich.Types.TestTimer+      Test.Sandwich.Types.TestTimer.LensRules       Test.Sandwich.Util       Paths_sandwich   hs-source-dirs:@@ -106,7 +109,6 @@     , filepath     , free     , haskell-src-exts-    , lens     , lifted-async     , microlens     , microlens-th@@ -152,7 +154,6 @@     , filepath     , free     , haskell-src-exts-    , lens     , lifted-async     , microlens     , microlens-th@@ -199,7 +200,6 @@     , filepath     , free     , haskell-src-exts-    , lens     , lifted-async     , microlens     , microlens-th@@ -251,7 +251,6 @@     , filepath     , free     , haskell-src-exts-    , lens     , lifted-async     , microlens     , microlens-th@@ -304,7 +303,6 @@     , filepath     , free     , haskell-src-exts-    , lens     , lifted-async     , microlens     , microlens-th
src/Test/Sandwich.hs view
@@ -95,6 +95,7 @@ import Test.Sandwich.Contexts import Test.Sandwich.Expectations import Test.Sandwich.Formatters.Common.Count+import Test.Sandwich.Golden.Update import Test.Sandwich.Internal.Running import Test.Sandwich.Interpreters.FilterTreeModule import Test.Sandwich.Interpreters.RunTree@@ -129,12 +130,15 @@   (clo, individualTestParser, modulesAndShorthands) <- parseCommandLineArgs' userOptionsParser spec   (options, repeatCount) <- liftIO $ addOptionsFromArgs baseOptions clo -  if | optPrintQuickCheckFlags clo == Just True -> do+  if | optPrintGoldenFlags clo == Just True -> do          void $ withArgs ["--help"] $-           OA.execParser quickCheckOptionsWithInfo+           OA.execParser goldenOptionsWithInfo      | optPrintHedgehogFlags clo == Just True -> do          void $ withArgs ["--help"] $            OA.execParser hedgehogOptionsWithInfo+     | optPrintQuickCheckFlags clo == Just True -> do+         void $ withArgs ["--help"] $+           OA.execParser quickCheckOptionsWithInfo      | optPrintSlackFlags clo == Just True -> do          void $ withArgs ["--help"] $            OA.execParser slackOptionsWithInfo@@ -146,6 +150,8 @@            OA.execParser $ OA.info (individualTestParser mempty <**> helper) $              fullDesc <> header "Pass one of these flags to run an individual test module."                       <> progDesc "If a module has a \"*\" next to its name, then we detected that it has its own main function. If you pass the option name suffixed by -main then we'll just directly invoke the main function."+     | optUpdateGolden (optGoldenOptions clo) == Just True -> do+         updateGolden (optGoldenDir (optGoldenOptions clo))      | otherwise -> do          -- Awkward, but we need a specific context type to call countItNodes          let totalTests = countItNodes (spec :: SpecFree (LabelValue "commandLineOptions" (CommandLineOptions a) :> BaseContext) IO ())
src/Test/Sandwich/ArgParsing.hs view
@@ -47,11 +47,11 @@     <> header "Sandwich test runner"   ) -quickCheckOptionsWithInfo :: ParserInfo CommandLineQuickCheckOptions-quickCheckOptionsWithInfo = OA.info (commandLineQuickCheckOptions mempty <**> helper)+goldenOptionsWithInfo :: ParserInfo CommandLineGoldenOptions+goldenOptionsWithInfo = OA.info (commandLineGoldenOptions mempty <**> helper)   (     briefDesc-    <> header "Special options used by sandwich-quickcheck.\n\nIf a flag is passed, it will override the value in the QuickCheck option configured in the code."+    <> header "Special options used for golden testing."   )  hedgehogOptionsWithInfo :: ParserInfo CommandLineHedgehogOptions@@ -61,6 +61,13 @@     <> header "Special options used by sandwich-hedgehog.\n\nIf a flag is passed, it will override the value in the Hedgehog option configured in the code."   ) +quickCheckOptionsWithInfo :: ParserInfo CommandLineQuickCheckOptions+quickCheckOptionsWithInfo = OA.info (commandLineQuickCheckOptions mempty <**> helper)+  (+    briefDesc+    <> header "Special options used by sandwich-quickcheck.\n\nIf a flag is passed, it will override the value in the QuickCheck option configured in the code."+  )+ slackOptionsWithInfo :: ParserInfo CommandLineSlackOptions slackOptionsWithInfo = OA.info (commandLineSlackOptions mempty <**> helper)   (@@ -88,6 +95,7 @@   <*> optional (strOption (long "markdown-summary" <> help "File path to write a Markdown summary of the results." <> metavar "STRING"))    <*> optional (flag False True (long "list-tests" <> help "List individual test modules"))+  <*> optional (flag False True (long "print-golden-flags" <> help "Print the additional golden testing flags"))   <*> optional (flag False True (long "print-quickcheck-flags" <> help "Print the additional QuickCheck flags"))   <*> optional (flag False True (long "print-hedgehog-flags" <> help "Print the additional Hedgehog flags"))   <*> optional (flag False True (long "print-slack-flags" <> help "Print the additional Slack flags"))@@ -95,6 +103,7 @@    <*> individualTestParser +  <*> commandLineGoldenOptions internal   <*> commandLineQuickCheckOptions internal   <*> commandLineHedgehogOptions internal   <*> commandLineSlackOptions internal@@ -135,6 +144,11 @@   flag' Current (long "current" <> help "Open browser in current display (default)" <> maybeInternal)   <|> flag' Headless (long "headless" <> help "Open browser in headless mode" <> maybeInternal)   <|> flag Current Xvfb (long "xvfb" <> help "Open browser in Xvfb session" <> maybeInternal)++commandLineGoldenOptions :: (forall f a. Mod f a) -> Parser CommandLineGoldenOptions+commandLineGoldenOptions maybeInternal = CommandLineGoldenOptions+  <$> optional (flag False True (long "golden-update" <> help "Update your golden files" <> maybeInternal))+  <*> optional (strOption (long "golden-dir" <> help "The directory where golden results are stored (defaults to \".golden\")" <> metavar "STRING" <> maybeInternal))  commandLineQuickCheckOptions :: (forall f a. Mod f a) -> Parser CommandLineQuickCheckOptions commandLineQuickCheckOptions maybeInternal = CommandLineQuickCheckOptions
src/Test/Sandwich/Formatters/MarkdownSummary.hs view
@@ -72,11 +72,12 @@   fixedTree <- liftIO $ atomically $ mapM fixRunTree rts   let failed = countWhere isFailedItBlock fixedTree   let pending = countWhere isPendingItBlock fixedTree+  let succeeded = countWhere isSuccessItBlock fixedTree    liftIO $ withFile markdownSummaryPath AppendMode $ \h -> do     if | failed == 0 -> do            whenJust markdownSummarySuccessIcon (liftIO . (hPutStr h) . T.unpack)-           hPutStr h [i|All tests passed in #{timeDiff}.|]+           hPutStr h [i|All #{succeeded} tests passed in #{timeDiff}.|]        | otherwise -> do            whenJust markdownSummaryFailureIcon (liftIO . (hPutStr h) . T.unpack)            hPutStr h [i|#{failed} failed of #{total} in #{timeDiff}.|]
src/Test/Sandwich/Formatters/Print/CallStacks.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE RecordWildCards #-}--- |  module Test.Sandwich.Formatters.Print.CallStacks where 
src/Test/Sandwich/Formatters/Print/Color.hs view
@@ -1,4 +1,3 @@--- |  module Test.Sandwich.Formatters.Print.Color where 
src/Test/Sandwich/Formatters/Print/Logs.hs view
@@ -2,7 +2,6 @@ {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE LambdaCase #-}--- |  module Test.Sandwich.Formatters.Print.Logs where 
src/Test/Sandwich/Formatters/Print/Printing.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE NamedFieldPuns #-}+ -- | Utility functions for printing  module Test.Sandwich.Formatters.Print.Printing where@@ -31,12 +32,12 @@ pRedLn msg = printIndentedWithColor (Just (SetColor Foreground Dull Red)) (msg <> "\n") -- Tried solarizedRed here but it was too orange  printIndentedWithColor maybeColor msg = do-  (PrintFormatter {..}, indent, h) <- ask+  (PrintFormatter {}, indent, h) <- ask   liftIO $ hPutStr h $ L.replicate indent ' '   printWithColor maybeColor msg  printWithColor maybeColor msg = do-  (PrintFormatter {..}, _, h) <- ask+  (PrintFormatter {printFormatterUseColor}, _, h) <- ask   when (printFormatterUseColor) $ whenJust maybeColor $ \color -> liftIO $ setSGR [color]   liftIO $ hPutStr h msg   when (printFormatterUseColor) $ whenJust maybeColor $ \_ -> liftIO $ setSGR [Reset]
src/Test/Sandwich/Formatters/TerminalUI.hs view
@@ -7,6 +7,7 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE CPP #-}  module Test.Sandwich.Formatters.TerminalUI (   -- | The terminal UI formatter produces an interactive UI for running tests and inspecting their results.@@ -142,12 +143,36 @@ app = App {   appDraw = drawUI   , appChooseCursor = showFirstCursor+#if MIN_VERSION_brick(1,0,0)+  , appHandleEvent = \event -> get >>= \s -> appEvent s event+  , appStartEvent = return ()+#else   , appHandleEvent = appEvent   , appStartEvent = return+#endif   , appAttrMap = const mainAttrMap   } +#if MIN_VERSION_brick(1,0,0)+continue :: AppState -> EventM ClickableName AppState ()+continue = put++continueNoChange :: AppState -> EventM ClickableName AppState ()+continueNoChange _ = return ()++doHalt _ = halt+#else+continueNoChange :: AppState -> EventM ClickableName (Next AppState)+continueNoChange = continue++doHalt = halt+#endif++#if MIN_VERSION_brick(1,0,0)+appEvent :: AppState -> BrickEvent ClickableName AppEvent -> EventM ClickableName AppState ()+#else appEvent :: AppState -> BrickEvent ClickableName AppEvent -> EventM ClickableName (Next AppState)+#endif appEvent s (AppEvent (RunTreeUpdated newTree)) = do   now <- liftIO getCurrentTime   continue $ s@@ -170,10 +195,10 @@  appEvent s (MouseDown (ListRow _i) V.BScrollUp _ _) = do   vScrollBy (viewportScroll MainList) (-1)-  continue s+  continueNoChange s appEvent s (MouseDown (ListRow _i) V.BScrollDown _ _) = do   vScrollBy (viewportScroll MainList) 1-  continue s+  continueNoChange s appEvent s (MouseDown (ListRow i) V.BLeft _ _) = do   continue (s & appMainList %~ (listMoveTo i)) appEvent s (VtyEvent e) =@@ -207,29 +232,29 @@      -- Scrolling in toggled items     -- Wanted to make these uniformly Ctrl+whatever, but Ctrl+PageUp/PageDown was causing it to get KEsc and exit (?)-    V.EvKey V.KUp [V.MCtrl] -> withScroll s $ flip vScrollBy (-1)-    V.EvKey (V.KChar 'p') [V.MCtrl] -> withScroll s $ flip vScrollBy (-1)-    V.EvKey V.KDown [V.MCtrl] -> withScroll s $ flip vScrollBy 1-    V.EvKey (V.KChar 'n') [V.MCtrl] -> withScroll s $ flip vScrollBy 1-    V.EvKey (V.KChar 'v') [V.MMeta] -> withScroll s $ flip vScrollPage Up-    V.EvKey (V.KChar 'v') [V.MCtrl] -> withScroll s $ flip vScrollPage Down-    V.EvKey V.KHome [V.MCtrl] -> withScroll s vScrollToBeginning-    V.EvKey V.KEnd [V.MCtrl] -> withScroll s vScrollToEnd+    V.EvKey V.KUp [V.MCtrl] -> withScroll s $ \vp -> vScrollBy vp (-1)+    V.EvKey (V.KChar 'p') [V.MCtrl] -> withScroll s $ \vp -> vScrollBy vp (-1)+    V.EvKey V.KDown [V.MCtrl] -> withScroll s $ \vp -> vScrollBy vp 1+    V.EvKey (V.KChar 'n') [V.MCtrl] -> withScroll s $ \vp -> vScrollBy vp 1+    V.EvKey (V.KChar 'v') [V.MMeta] -> withScroll s $ \vp -> vScrollPage vp Up+    V.EvKey (V.KChar 'v') [V.MCtrl] -> withScroll s $ \vp -> vScrollPage vp Down+    V.EvKey V.KHome [V.MCtrl] -> withScroll s $ \vp -> vScrollToBeginning vp+    V.EvKey V.KEnd [V.MCtrl] -> withScroll s $ \vp -> vScrollToEnd vp      -- Column 2     V.EvKey c [] | c == cancelAllKey -> do       liftIO $ mapM_ cancelNode (s ^. appRunTreeBase)       continue s-    V.EvKey c [] | c == cancelSelectedKey -> withContinueS $ do+    V.EvKey c [] | c == cancelSelectedKey -> withContinueS s $ do       whenJust (listSelectedElement (s ^. appMainList)) $ \(_, MainListElem {..}) -> liftIO $         (readTVarIO $ runTreeStatus node) >>= \case           Running {..} -> cancel statusAsync           _ -> return ()-    V.EvKey c [] | c == runAllKey -> withContinueS $ do+    V.EvKey c [] | c == runAllKey -> withContinueS s $ do       when (all (not . isRunning . runTreeStatus . runNodeCommon) (s ^. appRunTree)) $ liftIO $ do         mapM_ clearRecursively (s ^. appRunTreeBase)         void $ async $ void $ runNodesSequentially (s ^. appRunTreeBase) (s ^. appBaseContext)-    V.EvKey c [] | c == runSelectedKey -> withContinueS $+    V.EvKey c [] | c == runSelectedKey -> withContinueS s $       whenJust (listSelectedElement (s ^. appMainList)) $ \(_, MainListElem {..}) -> case status of         Running {} -> return ()         _ -> do@@ -244,18 +269,18 @@               -- Start a run for all affected nodes               let bc = (s ^. appBaseContext) { baseContextOnlyRunIds = Just allIds }               void $ liftIO $ async $ void $ runNodesSequentially (s ^. appRunTreeBase) bc-    V.EvKey c [] | c == clearSelectedKey -> withContinueS $ do+    V.EvKey c [] | c == clearSelectedKey -> withContinueS s $ do       whenJust (listSelectedElement (s ^. appMainList)) $ \(_, MainListElem {..}) -> case status of         Running {} -> return ()         _ -> case findRunNodeChildrenById ident (s ^. appRunTree) of           Nothing -> return ()           Just childIds -> liftIO $ mapM_ (clearRecursivelyWhere (\x -> runTreeId x `S.member` childIds)) (s ^. appRunTreeBase)-    V.EvKey c [] | c == clearAllKey -> withContinueS $ do+    V.EvKey c [] | c == clearAllKey -> withContinueS s $ do       liftIO $ mapM_ clearRecursively (s ^. appRunTreeBase)-    V.EvKey c [] | c == openSelectedFolderInFileExplorer -> withContinueS $ do+    V.EvKey c [] | c == openSelectedFolderInFileExplorer -> withContinueS s $ do       whenJust (listSelectedElement (s ^. appMainList)) $ \(_i, MainListElem {folderPath}) ->         whenJust folderPath $ liftIO . openFileExplorerFolderPortable-    V.EvKey c [] | c == openTestRootKey -> withContinueS $+    V.EvKey c [] | c == openTestRootKey -> withContinueS s $       whenJust (baseContextRunRoot (s ^. appBaseContext)) $ liftIO . openFileExplorerFolderPortable     V.EvKey c [] | c == openTestInEditorKey -> case listSelectedElement (s ^. appMainList) of       Just (_i, MainListElem {node=(runTreeLoc -> Just loc)}) -> openSrcLoc s loc@@ -282,7 +307,8 @@      -- Column 3     V.EvKey c [] | c == cycleVisibilityThresholdKey -> do-      let newVisibilityThreshold =  case [(i, x) | (i, x) <- zip [0..] (s ^. appVisibilityThresholdSteps), x > s ^. appVisibilityThreshold] of+      let newVisibilityThreshold =  case [(i, x) | (i, x) <- zip [0..] (s ^. appVisibilityThresholdSteps)+                                                 , x > s ^. appVisibilityThreshold] of             [] -> 0             xs -> minimum $ fmap snd xs       continue $ s@@ -298,16 +324,24 @@       -- Cancel everything and wait for cleanups       liftIO $ mapM_ cancelNode (s ^. appRunTreeBase)       forM_ (s ^. appRunTreeBase) (liftIO . waitForTree)-      halt s+      doHalt s     V.EvKey c [] | c == debugKey -> continue (s & appLogLevel ?~ LevelDebug)     V.EvKey c [] | c == infoKey -> continue (s & appLogLevel ?~ LevelInfo)     V.EvKey c [] | c == warnKey -> continue (s & appLogLevel ?~ LevelWarn)     V.EvKey c [] | c == errorKey -> continue (s & appLogLevel ?~ LevelError) +#if MIN_VERSION_brick(1,0,0)+    ev -> zoom appMainList $ handleListEvent ev+#else     ev -> handleEventLensed s appMainList handleListEvent ev >>= continue+#endif -  where withContinueS action = action >> continue s+  where withContinueS s action = action >> continue s+#if MIN_VERSION_brick(1,0,0)+appEvent _ _ = return ()+#else appEvent s _ = continue s+#endif  modifyToggled s f = case listSelectedElement (s ^. appMainList) of   Nothing -> continue s@@ -387,6 +421,11 @@ findRunNodeChildrenById' ident (RunNodeIntroduceWith {..}) = findRunNodeChildrenById ident runNodeChildrenAugmented findRunNodeChildrenById' ident node = findRunNodeChildrenById ident (runNodeChildren node) +#if MIN_VERSION_brick(1,0,0)+withScroll :: AppState -> (forall s. ViewportScroll ClickableName -> EventM n s ()) -> EventM n AppState ()+#else+withScroll :: AppState -> (ViewportScroll ClickableName -> EventM n ()) -> EventM n (Next AppState)+#endif withScroll s action = do   case listSelectedElement (s ^. appMainList) of     Nothing -> return ()@@ -394,7 +433,9 @@       let scroll = viewportScroll (InnerViewport [i|viewport_#{ident}|])       action scroll +#if !MIN_VERSION_brick(1,0,0)   continue s+#endif  openSrcLoc s loc' = do   -- Try to make the file path in the SrcLoc absolute
src/Test/Sandwich/Formatters/TerminalUI/AttrMap.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}--- |+{-# LANGUAGE CPP #-}  module Test.Sandwich.Formatters.TerminalUI.AttrMap where @@ -9,7 +9,17 @@ import Test.Sandwich.Types.RunTree import Test.Sandwich.Types.Spec +#if MIN_VERSION_brick(1,0,0)+mkAttrName :: String -> AttrName+mkAttrName = attrName+#else+import Data.String +mkAttrName :: String -> AttrName+mkAttrName = fromString+#endif++ mainAttrMap :: AttrMap mainAttrMap = attrMap V.defAttr [   -- (listAttr, V.white `on` V.blue)@@ -80,46 +90,46 @@ -- selectedAttr = "list_line_selected"  visibilityThresholdNotSelectedAttr :: AttrName-visibilityThresholdNotSelectedAttr = "visibility_threshold_not_selected"+visibilityThresholdNotSelectedAttr = mkAttrName "visibility_threshold_not_selected"  visibilityThresholdSelectedAttr :: AttrName-visibilityThresholdSelectedAttr = "visibility_threshold_selected"+visibilityThresholdSelectedAttr = mkAttrName "visibility_threshold_selected"  runningAttr :: AttrName-runningAttr = "running"+runningAttr = mkAttrName "running"  notStartedAttr :: AttrName-notStartedAttr = "not_started"+notStartedAttr = mkAttrName "not_started"  pendingAttr :: AttrName-pendingAttr = "pending"+pendingAttr = mkAttrName "pending"  totalAttr :: AttrName-totalAttr = "total"+totalAttr = mkAttrName "total"  successAttr :: AttrName-successAttr = "success"+successAttr = mkAttrName "success"  failureAttr :: AttrName-failureAttr = "failure"+failureAttr = mkAttrName "failure"  toggleMarkerAttr :: AttrName-toggleMarkerAttr = "toggleMarker"+toggleMarkerAttr = mkAttrName "toggleMarker"  openMarkerAttr :: AttrName-openMarkerAttr = "openMarker"+openMarkerAttr = mkAttrName "openMarker"  visibilityThresholdIndicatorAttr :: AttrName-visibilityThresholdIndicatorAttr = "visibilityThresholdIndicator"+visibilityThresholdIndicatorAttr = mkAttrName "visibilityThresholdIndicator"  visibilityThresholdIndicatorMutedAttr :: AttrName-visibilityThresholdIndicatorMutedAttr = "visibilityThresholdMutedIndicator"+visibilityThresholdIndicatorMutedAttr = mkAttrName "visibilityThresholdMutedIndicator"  hotkeyAttr, disabledHotkeyAttr, hotkeyMessageAttr, disabledHotkeyMessageAttr :: AttrName-hotkeyAttr = "hotkey"-disabledHotkeyAttr = "disableHotkey"-hotkeyMessageAttr = "hotkeyMessage"-disabledHotkeyMessageAttr = "disabledHotkeyMessage"+hotkeyAttr = mkAttrName "hotkey"+disabledHotkeyAttr = mkAttrName "disableHotkey"+hotkeyMessageAttr = mkAttrName "hotkeyMessage"+disabledHotkeyMessageAttr = mkAttrName "disabledHotkeyMessage"  chooseAttr :: Status -> AttrName chooseAttr NotStarted = notStartedAttr@@ -133,47 +143,47 @@ -- * Logging and callstacks  debugAttr, infoAttr, warnAttr, errorAttr, otherAttr :: AttrName-debugAttr = "log_debug"-infoAttr = "log_info"-warnAttr = "log_warn"-errorAttr = "log_error"-otherAttr = "log_other"+debugAttr = attrName"log_debug"+infoAttr = attrName"log_info"+warnAttr = attrName"log_warn"+errorAttr = attrName"log_error"+otherAttr = mkAttrName "log_other"  logTimestampAttr :: AttrName-logTimestampAttr = "log_timestamp"+logTimestampAttr = mkAttrName "log_timestamp"  logFilenameAttr, logModuleAttr, logPackageAttr, logLineAttr, logChAttr :: AttrName-logFilenameAttr = "logFilename"-logModuleAttr = "logModule"-logPackageAttr = "logPackage"-logLineAttr = "logLine"-logChAttr = "logCh"-logFunctionAttr = "logFunction"+logFilenameAttr = mkAttrName "logFilename"+logModuleAttr = mkAttrName "logModule"+logPackageAttr = mkAttrName "logPackage"+logLineAttr = mkAttrName "logLine"+logChAttr = mkAttrName "logCh"+logFunctionAttr = mkAttrName "logFunction"  -- * Exceptions and pretty printing  expectedAttr, sawAttr :: AttrName-expectedAttr = "expected"-sawAttr = "saw"+expectedAttr = mkAttrName "expected"+sawAttr = mkAttrName "saw"  integerAttr, timeAttr, dateAttr, stringAttr, charAttr, floatAttr, quoteAttr, slashAttr, negAttr :: AttrName listBracketAttr, tupleBracketAttr, braceAttr, ellipsesAttr, recordNameAttr, fieldNameAttr, constructorNameAttr :: AttrName-integerAttr = "integer"-floatAttr = "float"-charAttr = "char"-stringAttr = "string"-dateAttr = "date"-timeAttr = "time"-quoteAttr = "quote"-slashAttr = "slash"-negAttr = "neg"-listBracketAttr = "listBracket"-tupleBracketAttr = "tupleBracket"-braceAttr = "brace"-ellipsesAttr = "ellipses"-recordNameAttr = "recordName"-fieldNameAttr = "fieldName"-constructorNameAttr = "fieldName"+integerAttr = mkAttrName "integer"+floatAttr = mkAttrName "float"+charAttr = mkAttrName "char"+stringAttr = mkAttrName "string"+dateAttr = mkAttrName "date"+timeAttr = mkAttrName "time"+quoteAttr = mkAttrName "quote"+slashAttr = mkAttrName "slash"+negAttr = mkAttrName "neg"+listBracketAttr = mkAttrName "listBracket"+tupleBracketAttr = mkAttrName "tupleBracket"+braceAttr = mkAttrName "brace"+ellipsesAttr = mkAttrName "ellipses"+recordNameAttr = mkAttrName "recordName"+fieldNameAttr = mkAttrName "fieldName"+constructorNameAttr = mkAttrName "fieldName"  -- * Colors 
+ src/Test/Sandwich/Golden.hs view
@@ -0,0 +1,118 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE LambdaCase #-}++-- This module is based on Test.Hspec.Golden from hspec-golden-0.2.0.0, which is MIT licensed.++module Test.Sandwich.Golden (+  -- * Main test function+  golden++  -- * Built-in Goldens.+  , goldenText+  , goldenString+  , goldenJSON+  , goldenShowable+  , mkGolden++  -- * Parameters for a 'Golden'.+  , goldenOutput+  , goldenWriteToFile+  , goldenReadFromFile+  , goldenFile+  , goldenActualFile+  , goldenFailFirstTime+  ) where++import Control.Exception.Safe+import Control.Monad+import Control.Monad.Free+import Control.Monad.IO.Class+import Data.Aeson as A+import qualified Data.ByteString.Lazy as BL+import Data.String.Interpolate+import qualified Data.Text as T+import qualified Data.Text.IO as T+import GHC.Stack+import System.Directory+import System.FilePath+import Test.Sandwich+import Test.Sandwich.Golden.Update+import Test.Sandwich.Types.Spec+++data Golden a = Golden {+  -- | Name+  goldenName :: String+  -- | Expected output.+  , goldenOutput :: a+  -- | How to write into the golden file the file.+  , goldenWriteToFile :: FilePath -> a -> IO ()+  -- | How to read the file.+  , goldenReadFromFile :: FilePath -> IO a+  -- | Where to read/write the golden file for this test.+  , goldenFile :: FilePath+  -- | Where to save the actual file for this test. If it is @Nothing@ then no file is written.+  , goldenActualFile :: Maybe FilePath+  -- | Whether to record a failure the first time this test is run.+  , goldenFailFirstTime :: Bool+  }+++-- | Make your own 'Golden' constructor by providing 'goldenWriteToFile' and 'goldenReadFromFile'.+mkGolden :: (FilePath -> a -> IO ()) -> (FilePath -> IO a) -> String -> a -> Golden a+mkGolden goldenWriteToFile goldenReadFromFile name output = Golden {+  goldenName = name+  , goldenOutput = output+  , goldenWriteToFile = goldenWriteToFile+  , goldenReadFromFile = goldenReadFromFile+  , goldenFile = defaultDirGoldenTest </> name </> "golden"+  , goldenActualFile = Just (defaultDirGoldenTest </> name </> "actual")+  , goldenFailFirstTime = False+  }++-- | Golden for a 'T.Text'.+goldenText :: String -> T.Text -> Golden T.Text+goldenText = mkGolden T.writeFile T.readFile++-- | Golden for a 'String'.+goldenString :: String -> String -> Golden String+goldenString = mkGolden writeFile readFile++-- | Golden for an Aeson value ('ToJSON'/'FromJSON').+goldenJSON :: (A.ToJSON a, A.FromJSON a) => String -> a -> Golden a+goldenJSON = mkGolden (\f x -> BL.writeFile f $ A.encode x) $ \f ->+  eitherDecodeFileStrict' f >>= \case+    Left err -> expectationFailure [i|Failed to decode JSON value in #{f}: #{err}|]+    Right x -> return x++-- | Golden for a general 'Show'/'Read' type.+goldenShowable :: (Show a, Read a) => String -> a -> Golden a+goldenShowable = mkGolden (\f x -> writeFile f (show x)) ((read <$>) . readFile)++-- | Runs a Golden test.++golden :: (MonadIO m, MonadThrow m, Eq str, Show str) => Golden str -> Free (SpecCommand context m) ()+golden (Golden {..}) = it goldenName $ do+  let goldenTestDir = takeDirectory goldenFile+  liftIO $ createDirectoryIfMissing True goldenTestDir+  goldenFileExist <- liftIO $ doesFileExist goldenFile++  case goldenActualFile of+    Nothing -> return ()+    Just actual -> do+      -- It is recommended to always write the actual file+      let actualDir = takeDirectory actual+      liftIO $ createDirectoryIfMissing True actualDir+      liftIO $ goldenWriteToFile actual goldenOutput++  if not goldenFileExist+    then do+        liftIO $ goldenWriteToFile goldenFile goldenOutput+        when goldenFailFirstTime $ expectationFailure [i|Failed due to first execution and goldenFailFirstTime=True.|]+    else do+       liftIO (goldenReadFromFile goldenFile) >>= \case+         x | x == goldenOutput -> return ()+         x -> throwIO $ ExpectedButGot (Just callStack) (SEB x) (SEB goldenOutput)
+ src/Test/Sandwich/Golden/Update.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE LambdaCase #-}++{-| This module is based on hgold from hspec-golden-0.2.0.0, which is MIT licensed -}++module Test.Sandwich.Golden.Update (+  updateGolden+  , defaultDirGoldenTest+  ) where++import Control.Exception.Safe+import Control.Monad+import Data.Maybe+import Data.String.Interpolate+import System.Console.ANSI+import System.Directory+import System.Environment+++defaultDirGoldenTest :: FilePath+defaultDirGoldenTest = ".golden"++updateGolden :: Maybe FilePath -> IO ()+updateGolden (fromMaybe defaultDirGoldenTest -> dir) = do+  enableColor <- lookupEnv "NO_COLOR" >>= \case+    Nothing -> return EnableColor+    Just _ -> return DisableColor++  putStrLnColor enableColor green "Replacing golden with actual..."+  go enableColor dir+  putStrLnColor enableColor green "Done!"++  where+    go enableColor dir = listDirectory dir >>= mapM_ (processEntry enableColor)++    processEntry enableColor (((dir ++ "/") ++) -> entryInDir) = do+      isDir <- doesDirectoryExist entryInDir+      when isDir $ do+        mvActualToGolden enableColor entryInDir+        go enableColor entryInDir++mvActualToGolden :: EnableColor -> FilePath -> IO ()+mvActualToGolden enableColor testPath = do+  let actualFilePath = testPath ++ "/actual"+  let goldenFilePath = testPath ++ "/golden"++  exists <- doesFileExist actualFilePath+  when exists $ do+    putStr [i|  #{goldenFilePath}|]+    putStrColor enableColor magenta " <-- "+    putStrLnColor enableColor red [i|#{actualFilePath}|]++    renameFile actualFilePath goldenFilePath++green, red, magenta :: SGR+green = SetColor Foreground Dull Green+red = SetColor Foreground Dull Red+magenta = SetColor Foreground Dull Magenta++putStrColor EnableColor color s = bracket_ (setSGR [color]) (setSGR [Reset]) (putStr s)+putStrColor DisableColor _ s = putStr s++putStrLnColor EnableColor color s = bracket_ (setSGR [color]) (setSGR [Reset]) (putStrLn s)+putStrLnColor DisableColor _ s = putStrLn s++data EnableColor = EnableColor | DisableColor
src/Test/Sandwich/Internal/Running.hs view
@@ -145,6 +145,8 @@   , "fixed-root"   , "list-tests" +  , "print-golden-flags"+  , "print-hedgehog-flags"   , "print-quickcheck-flags"   , "print-slack-flags"   , "print-webdriver-flags"
src/Test/Sandwich/TestTimer.hs view
@@ -13,7 +13,6 @@  import Control.Concurrent import Control.Exception.Safe-import Control.Lens import Control.Monad.IO.Class import Control.Monad.Reader import Control.Monad.Trans.State@@ -25,6 +24,7 @@ import qualified Data.Text as T import qualified Data.Text.IO as T import Data.Time.Clock.POSIX+import Lens.Micro import System.Directory import System.FilePath import System.IO
src/Test/Sandwich/Types/ArgParsing.hs view
@@ -54,6 +54,7 @@   , optMarkdownSummaryPath :: Maybe FilePath    , optListAvailableTests :: Maybe Bool+  , optPrintGoldenFlags :: Maybe Bool   , optPrintQuickCheckFlags :: Maybe Bool   , optPrintHedgehogFlags :: Maybe Bool   , optPrintSlackFlags :: Maybe Bool@@ -61,6 +62,7 @@    , optIndividualTestModule :: Maybe IndividualTestModule +  , optGoldenOptions :: CommandLineGoldenOptions   , optQuickCheckOptions :: CommandLineQuickCheckOptions   , optHedgehogOptions :: CommandLineHedgehogOptions   , optSlackOptions :: CommandLineSlackOptions@@ -75,6 +77,13 @@ instance Show IndividualTestModule where   show (IndividualTestModuleName moduleName) = moduleName   show (IndividualTestMainFn _) = "<main function>"++-- * golden options++data CommandLineGoldenOptions = CommandLineGoldenOptions {+  optUpdateGolden :: Maybe Bool+  , optGoldenDir :: Maybe FilePath+  } deriving Show  -- * sandwich-quickcheck options 
src/Test/Sandwich/Types/TestTimer.hs view
@@ -13,15 +13,16 @@ module Test.Sandwich.Types.TestTimer where  import Control.Concurrent-import Control.Lens import Data.Aeson as A import Data.Aeson.TH as A import qualified Data.List as L import Data.Sequence import qualified Data.Text as T import Data.Time.Clock.POSIX+import Lens.Micro.TH import System.IO import Test.Sandwich.Types.Spec+import Test.Sandwich.Types.TestTimer.LensRules (testTimerLensRules)   -- * SpeedScope types@@ -33,7 +34,7 @@                  A.fieldLabelModifier = L.drop 1                  , A.sumEncoding = A.UntaggedValue                  }) ''SpeedScopeFrame)-$(makeFieldsNoPrefix ''SpeedScopeFrame)+$(makeLensesWith testTimerLensRules ''SpeedScopeFrame)  data SpeedScopeShared = SpeedScopeShared {   _frames :: Seq SpeedScopeFrame@@ -42,7 +43,7 @@                  A.fieldLabelModifier = L.drop 1                  , A.sumEncoding = A.UntaggedValue                  }) ''SpeedScopeShared)-$(makeFieldsNoPrefix ''SpeedScopeShared)+$(makeLensesWith testTimerLensRules ''SpeedScopeShared)  data SpeedScopeEventType = SpeedScopeEventTypeOpen | SpeedScopeEventTypeClose   deriving (Show, Eq)@@ -62,7 +63,7 @@                      _ -> L.drop 1 x                  , A.sumEncoding = A.UntaggedValue                  }) ''SpeedScopeEvent)-$(makeFieldsNoPrefix ''SpeedScopeEvent)+$(makeLensesWith testTimerLensRules ''SpeedScopeEvent)  data SpeedScopeProfile = SpeedScopeProfile {   _typ :: T.Text@@ -78,7 +79,7 @@                      _ -> L.drop 1 x                  , A.sumEncoding = A.UntaggedValue                  }) ''SpeedScopeProfile)-$(makeFieldsNoPrefix ''SpeedScopeProfile)+$(makeLensesWith testTimerLensRules ''SpeedScopeProfile)  data SpeedScopeFile = SpeedScopeFile {   _exporter :: T.Text@@ -94,7 +95,7 @@                      _ -> L.drop 1 x                  , A.sumEncoding = A.UntaggedValue                  }) ''SpeedScopeFile)-$(makeFieldsNoPrefix ''SpeedScopeFile)+$(makeLensesWith testTimerLensRules ''SpeedScopeFile)  emptySpeedScopeFile =   SpeedScopeFile {
+ src/Test/Sandwich/Types/TestTimer/LensRules.hs view
@@ -0,0 +1,20 @@++module Test.Sandwich.Types.TestTimer.LensRules (+  testTimerLensRules+  ) where++import Data.Char (toLower, toUpper)+import Language.Haskell.TH+import Lens.Micro+import Lens.Micro.TH+++testTimerLensRules :: LensRules+testTimerLensRules = camelCaseFields+  & lensField .~ underscoreNoPrefixNamer++underscoreNoPrefixNamer :: Name -> [Name] -> Name -> [DefName]+underscoreNoPrefixNamer _ _ n =+  case nameBase n of+    '_':x:xs -> [MethodName (mkName ("Has" ++ (toUpper x:xs))) (mkName (toLower x:xs))]+    _        -> []