packages feed

hspec-yesod 0.2.1.1 → 0.3.0.0

raw patch · 12 files changed

+379/−109 lines, 12 filesdep ~yesod-corePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: yesod-core

API changes (from Hackage documentation)

- Test.Hspec.Yesod: TestApp :: site -> Middleware -> TestApp site
- Test.Hspec.Yesod: [testAppMiddleware] :: TestApp site -> Middleware
- Test.Hspec.Yesod: [testAppSite] :: TestApp site -> site
- Test.Hspec.Yesod: [yedCreateApplication] :: YesodExampleData site -> !site -> Middleware -> IO Application
- Test.Hspec.Yesod: data TestApp site
- Test.Hspec.Yesod: mkTestApp :: site -> TestApp site
+ Test.Hspec.Yesod: getRunnerEnv :: Yesod site => YesodExample site (YesodRunnerEnv site)
+ Test.Hspec.Yesod: setUrlNested :: (RedirectUrl site (WithParentArgs url), Yesod site) => ParentArgs url -> url -> RequestBuilderFor (WithParentArgs url) site ()
+ Test.Hspec.Yesod: type RequestBuilderFor url site = SIO RequestBuilderData url site
+ Test.Hspec.Yesod.Internal: [rbdUrl] :: RequestBuilderData url site -> Maybe url
+ Test.Hspec.Yesod.Internal: voidRequestBuilderUrl :: RequestBuilderData url site -> RequestBuilderData () site
- Test.Hspec.Yesod: YesodExampleData :: !site -> Middleware -> IO Application -> !Middleware -> !site -> !Cookies -> !Maybe (RequestBuilderData site) -> !Maybe SResponse -> !IO () -> YesodExampleData site
+ Test.Hspec.Yesod: YesodExampleData :: !Middleware -> !site -> !Cookies -> !Maybe (RequestBuilderData () site) -> !Maybe SResponse -> !IO () -> YesodExampleData site
- Test.Hspec.Yesod: [yedRequest] :: YesodExampleData site -> !Maybe (RequestBuilderData site)
+ Test.Hspec.Yesod: [yedRequest] :: YesodExampleData site -> !Maybe (RequestBuilderData () site)
- Test.Hspec.Yesod: addBasicAuthHeader :: CI ByteString -> CI ByteString -> RequestBuilder site ()
+ Test.Hspec.Yesod: addBasicAuthHeader :: CI ByteString -> CI ByteString -> RequestBuilderFor url site ()
- Test.Hspec.Yesod: addFile :: Text -> FilePath -> Text -> RequestBuilder site ()
+ Test.Hspec.Yesod: addFile :: Text -> FilePath -> Text -> RequestBuilderFor url site ()
- Test.Hspec.Yesod: addGetParam :: Text -> Text -> RequestBuilder site ()
+ Test.Hspec.Yesod: addGetParam :: Text -> Text -> RequestBuilderFor url site ()
- Test.Hspec.Yesod: addPostParam :: Text -> Text -> RequestBuilder site ()
+ Test.Hspec.Yesod: addPostParam :: Text -> Text -> RequestBuilderFor url site ()
- Test.Hspec.Yesod: addRequestHeader :: Header -> RequestBuilder site ()
+ Test.Hspec.Yesod: addRequestHeader :: Header -> RequestBuilderFor url site ()
- Test.Hspec.Yesod: addToken :: HasCallStack => RequestBuilder site ()
+ Test.Hspec.Yesod: addToken :: HasCallStack => RequestBuilderFor url site ()
- Test.Hspec.Yesod: addTokenFromCookie :: HasCallStack => RequestBuilder site ()
+ Test.Hspec.Yesod: addTokenFromCookie :: HasCallStack => RequestBuilderFor url site ()
- Test.Hspec.Yesod: addTokenFromCookieNamedToHeaderNamed :: HasCallStack => ByteString -> CI ByteString -> RequestBuilder site ()
+ Test.Hspec.Yesod: addTokenFromCookieNamedToHeaderNamed :: HasCallStack => ByteString -> CI ByteString -> RequestBuilderFor url site ()
- Test.Hspec.Yesod: addToken_ :: HasCallStack => Query -> RequestBuilder site ()
+ Test.Hspec.Yesod: addToken_ :: HasCallStack => Query -> RequestBuilderFor url site ()
- Test.Hspec.Yesod: byLabelContain :: Text -> Text -> RequestBuilder site ()
+ Test.Hspec.Yesod: byLabelContain :: Text -> Text -> RequestBuilderFor url site ()
- Test.Hspec.Yesod: byLabelExact :: Text -> Text -> RequestBuilder site ()
+ Test.Hspec.Yesod: byLabelExact :: Text -> Text -> RequestBuilderFor url site ()
- Test.Hspec.Yesod: byLabelPrefix :: Text -> Text -> RequestBuilder site ()
+ Test.Hspec.Yesod: byLabelPrefix :: Text -> Text -> RequestBuilderFor url site ()
- Test.Hspec.Yesod: byLabelSuffix :: Text -> Text -> RequestBuilder site ()
+ Test.Hspec.Yesod: byLabelSuffix :: Text -> Text -> RequestBuilderFor url site ()
- Test.Hspec.Yesod: clickOn :: (HasCallStack, Yesod site) => Query -> YesodExample site ()
+ Test.Hspec.Yesod: clickOn :: (HasCallStack, UrlToDispatch Text site, YesodDispatch site) => Query -> YesodExample site ()
- Test.Hspec.Yesod: fileByLabelContain :: Text -> FilePath -> Text -> RequestBuilder site ()
+ Test.Hspec.Yesod: fileByLabelContain :: Text -> FilePath -> Text -> RequestBuilderFor url site ()
- Test.Hspec.Yesod: fileByLabelExact :: Text -> FilePath -> Text -> RequestBuilder site ()
+ Test.Hspec.Yesod: fileByLabelExact :: Text -> FilePath -> Text -> RequestBuilderFor url site ()
- Test.Hspec.Yesod: fileByLabelPrefix :: Text -> FilePath -> Text -> RequestBuilder site ()
+ Test.Hspec.Yesod: fileByLabelPrefix :: Text -> FilePath -> Text -> RequestBuilderFor url site ()
- Test.Hspec.Yesod: fileByLabelSuffix :: Text -> FilePath -> Text -> RequestBuilder site ()
+ Test.Hspec.Yesod: fileByLabelSuffix :: Text -> FilePath -> Text -> RequestBuilderFor url site ()
- Test.Hspec.Yesod: followRedirect :: Yesod site => YesodExample site (Either Text Text)
+ Test.Hspec.Yesod: followRedirect :: (UrlToDispatch Text site, YesodDispatch site) => YesodExample site (Either Text Text)
- Test.Hspec.Yesod: formatRequestBuilderDataForDebugging :: RequestBuilderData site -> Text
+ Test.Hspec.Yesod: formatRequestBuilderDataForDebugging :: RequestBuilderData url site -> Text
- Test.Hspec.Yesod: get :: (Yesod site, RedirectUrl site url) => url -> YesodExample site ()
+ Test.Hspec.Yesod: get :: (Yesod site, UrlToDispatch url site) => url -> YesodExample site ()
- Test.Hspec.Yesod: getLatestRequest :: YesodExample site (Maybe (RequestBuilderData site))
+ Test.Hspec.Yesod: getLatestRequest :: YesodExample site (Maybe (RequestBuilderData () site))
- Test.Hspec.Yesod: getRequestCookies :: HasCallStack => RequestBuilder site Cookies
+ Test.Hspec.Yesod: getRequestCookies :: HasCallStack => RequestBuilderFor url site Cookies
- Test.Hspec.Yesod: performMethod :: (Yesod site, RedirectUrl site url) => ByteString -> url -> YesodExample site ()
+ Test.Hspec.Yesod: performMethod :: (Yesod site, UrlToDispatch url site) => ByteString -> url -> YesodExample site ()
- Test.Hspec.Yesod: post :: (Yesod site, RedirectUrl site url) => url -> YesodExample site ()
+ Test.Hspec.Yesod: post :: (Yesod site, UrlToDispatch url site) => url -> YesodExample site ()
- Test.Hspec.Yesod: postBody :: (Yesod site, RedirectUrl site url) => url -> ByteString -> YesodExample site ()
+ Test.Hspec.Yesod: postBody :: (Yesod site, UrlToDispatch url site) => url -> ByteString -> YesodExample site ()
- Test.Hspec.Yesod: request :: RequestBuilder site () -> YesodExample site ()
+ Test.Hspec.Yesod: request :: (UrlToDispatch url site, Yesod site, HasCallStack) => RequestBuilderFor url site () -> YesodExample site ()
- Test.Hspec.Yesod: requireLatestRequest :: YesodExample site (RequestBuilderData site)
+ Test.Hspec.Yesod: requireLatestRequest :: HasCallStack => YesodExample site (RequestBuilderData () site)
- Test.Hspec.Yesod: setMethod :: Method -> RequestBuilder site ()
+ Test.Hspec.Yesod: setMethod :: Method -> RequestBuilderFor url site ()
- Test.Hspec.Yesod: setRequestBody :: ByteString -> RequestBuilder site ()
+ Test.Hspec.Yesod: setRequestBody :: ByteString -> RequestBuilderFor url site ()
- Test.Hspec.Yesod: setUrl :: (Yesod site, RedirectUrl site url) => url -> RequestBuilder site ()
+ Test.Hspec.Yesod: setUrl :: (Yesod site, RedirectUrl site url) => url -> RequestBuilderFor url site ()
- Test.Hspec.Yesod: siteToYesodExampleData :: YesodDispatch site => site -> YesodExampleData site
+ Test.Hspec.Yesod: siteToYesodExampleData :: site -> YesodExampleData site
- Test.Hspec.Yesod: type RequestBuilder site = SIO RequestBuilderData site
+ Test.Hspec.Yesod: type RequestBuilder site = RequestBuilderFor Route site site
- Test.Hspec.Yesod: yesodSpec :: YesodDispatch site => site -> YesodSpec site -> Spec
+ Test.Hspec.Yesod: yesodSpec :: site -> YesodSpec site -> Spec
- Test.Hspec.Yesod: yesodSpecWithSiteGenerator :: YesodDispatch site => IO site -> YesodSpec site -> Spec
+ Test.Hspec.Yesod: yesodSpecWithSiteGenerator :: IO site -> YesodSpec site -> Spec
- Test.Hspec.Yesod: yesodSpecWithSiteGeneratorAndArgument :: YesodDispatch site => (a -> IO site) -> YesodSpec site -> SpecWith a
+ Test.Hspec.Yesod: yesodSpecWithSiteGeneratorAndArgument :: (a -> IO site) -> YesodSpec site -> SpecWith a
- Test.Hspec.Yesod.Internal: RequestBuilderData :: RBDPostData -> Maybe SResponse -> Method -> site -> [Text] -> Query -> RequestHeaders -> RequestBuilderData site
+ Test.Hspec.Yesod.Internal: RequestBuilderData :: RBDPostData -> Maybe SResponse -> Method -> site -> Maybe url -> [Text] -> Query -> RequestHeaders -> RequestBuilderData url site
- Test.Hspec.Yesod.Internal: [rbdGets] :: RequestBuilderData site -> Query
+ Test.Hspec.Yesod.Internal: [rbdGets] :: RequestBuilderData url site -> Query
- Test.Hspec.Yesod.Internal: [rbdHeaders] :: RequestBuilderData site -> RequestHeaders
+ Test.Hspec.Yesod.Internal: [rbdHeaders] :: RequestBuilderData url site -> RequestHeaders
- Test.Hspec.Yesod.Internal: [rbdMethod] :: RequestBuilderData site -> Method
+ Test.Hspec.Yesod.Internal: [rbdMethod] :: RequestBuilderData url site -> Method
- Test.Hspec.Yesod.Internal: [rbdPath] :: RequestBuilderData site -> [Text]
+ Test.Hspec.Yesod.Internal: [rbdPath] :: RequestBuilderData url site -> [Text]
- Test.Hspec.Yesod.Internal: [rbdPostData] :: RequestBuilderData site -> RBDPostData
+ Test.Hspec.Yesod.Internal: [rbdPostData] :: RequestBuilderData url site -> RBDPostData
- Test.Hspec.Yesod.Internal: [rbdResponse] :: RequestBuilderData site -> Maybe SResponse
+ Test.Hspec.Yesod.Internal: [rbdResponse] :: RequestBuilderData url site -> Maybe SResponse
- Test.Hspec.Yesod.Internal: [rbdSite] :: RequestBuilderData site -> site
+ Test.Hspec.Yesod.Internal: [rbdSite] :: RequestBuilderData url site -> site
- Test.Hspec.Yesod.Internal: data RequestBuilderData site
+ Test.Hspec.Yesod.Internal: data RequestBuilderData url site

Files

ChangeLog.md view
@@ -1,5 +1,35 @@ # ChangeLog for hspec-yesod +## 0.3.0.0++- [#7](https://github.com/parsonsmatt/hspec-yesod/pull/7)+    - Require `yesod-core >= 1.7.0.0`.+    - You can now test a subset of routes without depending on the full+      `YesodDispatch`, which can dramatically cut test compile times. `setUrl`+      and the new `setUrlNested` accept route fragments via `RequestBuilderFor`,+      which carries the route-fragment type.+    - **Breaking:** `RequestBuilderData` gained a `url` type parameter+      (`RequestBuilderData url site`); `getLatestRequest`/`requireLatestRequest`+      return `RequestBuilderData () site`.+    - **Breaking:** `get`, `post`, `postBody`, `performMethod`, and `request`+      now require `UrlToDispatch url site` (from `yesod-core`) instead of+      `RedirectUrl site url`.+    - **Breaking:** a request must now set a URL explicitly; previously an unset+      URL defaulted to `/`, now `request` fails with an error.+    - **Breaking:** removed `TestApp`, `mkTestApp`, and the+      `yedCreateApplication` field of `YesodExampleData`.+    - Added `getRunnerEnv`, which builds a `YesodRunnerEnv` for the current+      `yedSite` via `mkYesodRunnerEnv`. It is built fresh per request so it+      always reflects per-request foundation changes.+    - The `YesodDispatch site` constraint is no longer required by `yesodSpec`,+      `yesodSpecWithSiteGenerator`, `yesodSpecWithSiteGeneratorAndArgument`, or+      `siteToYesodExampleData`.+    - WAI middlewares are now applied in the request builder.+    - Add a `CallStack` to `requireLatestRequest`.+    - Assertion failures now use `withFrozenCallStack`, and the debug formatters+      decode request bytes leniently, so a failure message can no longer be+      masked by a `UnicodeException`.+ ## 0.2.1.1  - [#8](https://github.com/parsonsmatt/hspec-yesod/pull/8)
hspec-yesod.cabal view
@@ -1,5 +1,5 @@ name:               hspec-yesod-version:            0.2.1.1+version:            0.3.0.0 license:            MIT license-file:       LICENSE author:             Nubis <nubis@woobiz.com.ar>, Matt Parsons <parsonsmatt@gmail.com>@@ -41,7 +41,7 @@                    , wai-extra                    , xml-conduit               >= 1.0                    , xml-types                 >= 0.3-                   , yesod-core                >= 1.6.17+                   , yesod-core                >= 1.7.0.0                    , yesod-test                       exposed-modules: Test.Hspec.Yesod, Test.Hspec.Yesod.Internal@@ -52,6 +52,15 @@     default-language: Haskell2010     type: exitcode-stdio-1.0     main-is: main.hs+    other-modules:+       NestedRouteDispatchSpec+       NestedRouteDispatchSpec.Resources+       NestedRouteDispatchSpec.Foo.Route+       NestedRouteDispatchSpec.Foo.Handler+       NestedRouteDispatchSpec.Foo.HandlerSpec+       NestedRouteDispatchSpec.YesodData+       NestedRouteDispatchSpec.YesodDispatch+     hs-source-dirs: test     build-depends:          base                           , yesod-test
src/Test/Hspec/Yesod.hs view
@@ -123,9 +123,7 @@     , yesodSpecWithSiteGeneratorAndArgument     , YesodExample     , YesodExampleData(..)-    , TestApp (..)     , YSpec-    , mkTestApp     , ydescribe     , yit @@ -172,8 +170,10 @@     , addFile     , setRequestBody     , RequestBuilder+    , RequestBuilderFor     , SIO     , setUrl+    , setUrlNested     , clickOn      -- *** Adding fields by label@@ -222,6 +222,7 @@      -- * Grab information     , getTestYesod+    , getRunnerEnv     , getLatestRequest     , requireLatestRequest     , formatRequestBuilderDataForDebugging@@ -248,6 +249,7 @@ import qualified Data.Text as T import qualified Data.Text.Encoding as TE import qualified Data.Text.Encoding.Error as TErr+import GHC.Stack (withFrozenCallStack) import qualified Data.ByteString.Lazy.Char8 as BSL8 import qualified Test.HUnit as HUnit import qualified Network.HTTP.Types as H@@ -262,6 +264,7 @@ import System.IO import Yesod.Core.Unsafe (runFakeHandler) import Yesod.Core+import Yesod.Core.Types (YesodRunnerEnv) import qualified Data.Text.Lazy as TL import Data.Text.Lazy.Encoding (encodeUtf8, decodeUtf8, decodeUtf8With) import Text.XML.Cursor hiding (element)@@ -290,11 +293,10 @@ -- -- Since 1.2.4 data YesodExampleData site = YesodExampleData-    { yedCreateApplication :: !(site -> Middleware -> IO Application)-    , yedMiddleware :: !Middleware+    { yedMiddleware :: !Middleware     , yedSite :: !site     , yedCookies :: !Cookies-    , yedRequest :: !(Maybe (RequestBuilderData site))+    , yedRequest :: !(Maybe (RequestBuilderData () site))     , yedResponse :: !(Maybe SResponse)     , yedTestCleanup :: !(IO ())     }@@ -325,19 +327,35 @@ getTestYesod :: YesodExample site site getTestYesod = fmap yedSite MS.get +-- | Build a 'YesodRunnerEnv' for the current test's 'yedSite' via+-- 'mkYesodRunnerEnv'.+--+-- The environment is built fresh on each call so that it always reflects the+-- current site, including any per-request foundation changes that downstream+-- harnesses make by updating 'yedSite' directly. 'mkYesodRunnerEnv' spawns a+-- few @auto-update@ worker threads (for the logger date, session date, and+-- max-expires caches); when an environment is no longer referenced those+-- workers block and the RTS reaps them (@BlockedIndefinitelyOnMVar@). Their+-- lifecycle is a @yesod-core@ concern, not something this library tries to+-- pool around.+getRunnerEnv :: Yesod site => YesodExample site (YesodRunnerEnv site)+getRunnerEnv = do+    currentSite <- MS.gets yedSite+    liftIO $ mkYesodRunnerEnv currentSite+ -- | Get the most recently provided request value, if available. -- -- This is intended to allow assertions to add context on the request in their error messages. -- Currently the contents of RequestBuilderData are only accessible via Test.Hspec.Yesod.Internal and formatRequestBuilderDataForDebugging. -- -- @since 0.2.0-getLatestRequest :: YesodExample site (Maybe (RequestBuilderData site))+getLatestRequest :: YesodExample site (Maybe (RequestBuilderData () site)) getLatestRequest = fmap yedRequest MS.get  -- | Like 'getLatestRequest', but throws an error if no request has been made. -- -- @since 0.2.0-requireLatestRequest :: YesodExample site (RequestBuilderData site)+requireLatestRequest :: HasCallStack => YesodExample site (RequestBuilderData () site) requireLatestRequest = do   mRequest <- getLatestRequest   case mRequest of@@ -353,22 +371,30 @@ -- | The 'RequestBuilder' state monad constructs a URL encoded string of arguments -- to send with your requests. Some of the functions that run on it use the current -- response to analyze the forms that the server is expecting to receive.-type RequestBuilder site = YT.SIO.SIO (RequestBuilderData site)+--+-- This type requires 'YesodDispatch' which may take a long time to+-- compile by depending on all the modules in your web application. To+-- reduce dependency load, see 'RequestBuilderFor'.+type RequestBuilder site = RequestBuilderFor (Route site) site +-- | A 'RequestBuilderFor' is a more general form of a 'RequestBuilder'+-- that allows you to target nested route fragments.+--+-- @since 0.3.0.0+type RequestBuilderFor url site = YT.SIO.SIO (RequestBuilderData url site)+ -- | Start describing a Tests suite keeping cookies and a reference to the tested 'Application' -- and 'ConnectionPool' ydescribe :: String -> YesodSpec site -> YesodSpec site ydescribe = describe -yesodSpec :: YesodDispatch site-          => site+yesodSpec :: site           -> YesodSpec site           -> Spec yesodSpec site =     before $ do         pure YesodExampleData-            { yedCreateApplication = \finalSite middleware -> middleware <$> toWaiAppPlain finalSite-            , yedMiddleware = id+            { yedMiddleware = id             , yedSite = site             , yedCookies = M.empty             , yedRequest = Nothing@@ -379,8 +405,7 @@ -- | Same as yesodSpec, but instead of taking already built site it -- takes an action which produces site for each test. yesodSpecWithSiteGenerator-    :: YesodDispatch site-    => IO site+    :: IO site     -> YesodSpec site     -> Spec yesodSpecWithSiteGenerator getSiteAction =@@ -391,16 +416,14 @@ -- -- @since 1.6.4 yesodSpecWithSiteGeneratorAndArgument-    :: YesodDispatch site-    => (a -> IO site)+    :: (a -> IO site)     -> YesodSpec site     -> SpecWith a yesodSpecWithSiteGeneratorAndArgument getSiteAction =     beforeWith $ \a -> do         site <- getSiteAction a         pure YesodExampleData-            { yedCreateApplication = \finalSite middleware -> middleware <$> toWaiAppPlain finalSite-            , yedMiddleware = id+            { yedMiddleware = id             , yedSite = site             , yedCookies = M.empty             , yedRequest = Nothing@@ -656,7 +679,7 @@ statusIs number = do   latestRequest <- requireLatestRequest   withResponse $ \resp@(SResponse status _ _) -> do-    +     liftIO $ flip HUnit.assertBool (H.statusCode status == number) $ unlines       [ "Expected status: " <> show number       , "But status was: " <> (show $ H.statusCode status)@@ -677,7 +700,7 @@     in previewFinal  -getRequestBodyPreview :: RequestBuilderData site -> T.Text+getRequestBodyPreview :: RequestBuilderData url site -> T.Text getRequestBodyPreview RequestBuilderData{..} =     let mRequestContentType = lookup hContentType rbdHeaders         isRequestUTF8ContentType = maybe False YT.Internal.contentTypeHeaderIsUtf8 mRequestContentType@@ -692,7 +715,7 @@         getRequestTextPreview :: BSL8.ByteString -> T.Text         getRequestTextPreview body =           let characterLimit = 1024-              textBody = TL.toStrict $ decodeUtf8 body+              textBody = TL.toStrict $ decodeUtf8With TErr.lenientDecode body           in if T.length textBody < characterLimit                 then textBody                 else T.take characterLimit textBody <> "... (use `rbdPostData` from `yedRequest` to see complete request body)"@@ -942,7 +965,7 @@ -- > {-# LANGUAGE OverloadedStrings #-} -- > post $ do -- >   addPostParam "key" "value"-addPostParam :: T.Text -> T.Text -> RequestBuilder site ()+addPostParam :: T.Text -> T.Text -> RequestBuilderFor url site () addPostParam name value =   YT.SIO.modifySIO $ \rbd -> rbd { rbdPostData = (addPostData (rbdPostData rbd)) }   where addPostData (BinaryPostData _) = error "Trying to add post param to binary content."@@ -956,7 +979,7 @@ -- > {-# LANGUAGE OverloadedStrings #-} -- > request $ do -- >   addGetParam "key" "value" -- Adds ?key=value to the URL-addGetParam :: T.Text -> T.Text -> RequestBuilder site ()+addGetParam :: T.Text -> T.Text -> RequestBuilderFor url site () addGetParam name value = YT.SIO.modifySIO $ \rbd -> rbd     { rbdGets = (TE.encodeUtf8 name, Just $ TE.encodeUtf8 value)               : rbdGets rbd@@ -973,7 +996,7 @@ addFile :: T.Text -- ^ The parameter name for the file.         -> FilePath -- ^ The path to the file.         -> T.Text -- ^ The MIME type of the file, e.g. "image/png".-        -> RequestBuilder site ()+        -> RequestBuilderFor url site () addFile name path mimetype = do   contents <- liftIO $ BSL8.readFile path   YT.SIO.modifySIO $ \rbd -> rbd { rbdPostData = (addPostData (rbdPostData rbd) contents) }@@ -983,7 +1006,7 @@  -- | -- This looks up the name of a field based on the contents of the label pointing to it.-genericNameFromLabel :: HasCallStack => (T.Text -> T.Text -> Bool) -> T.Text -> RequestBuilder site T.Text+genericNameFromLabel :: HasCallStack => (T.Text -> T.Text -> Bool) -> T.Text -> RequestBuilderFor url site T.Text genericNameFromLabel match label = do   mres <- fmap rbdResponse YT.SIO.getSIO   res <-@@ -1025,7 +1048,7 @@ byLabelWithMatch :: (T.Text -> T.Text -> Bool) -- ^ The matching method which is used to find labels (i.e. exact, contains)                  -> T.Text                     -- ^ The text contained in the @\<label>@.                  -> T.Text                     -- ^ The value to set the parameter to.-                 -> RequestBuilder site ()+                 -> RequestBuilderFor url site () byLabelWithMatch match label value = do   name <- genericNameFromLabel match label   addPostParam name value@@ -1059,7 +1082,7 @@ -- @since 1.5.9 byLabelExact :: T.Text -- ^ The text in the @\<label>@.              -> T.Text -- ^ The value to set the parameter to.-             -> RequestBuilder site ()+             -> RequestBuilderFor url site () byLabelExact = byLabelWithMatch (==)  -- |@@ -1070,7 +1093,7 @@ -- @since 1.6.2 byLabelContain :: T.Text -- ^ The text in the @\<label>@.                -> T.Text -- ^ The value to set the parameter to.-               -> RequestBuilder site ()+               -> RequestBuilderFor url site () byLabelContain = byLabelWithMatch T.isInfixOf  -- |@@ -1081,7 +1104,7 @@ -- @since 1.6.2 byLabelPrefix :: T.Text -- ^ The text in the @\<label>@.               -> T.Text -- ^ The value to set the parameter to.-              -> RequestBuilder site ()+              -> RequestBuilderFor url site () byLabelPrefix = byLabelWithMatch T.isPrefixOf  -- |@@ -1092,14 +1115,14 @@ -- @since 1.6.2 byLabelSuffix :: T.Text -- ^ The text in the @\<label>@.               -> T.Text -- ^ The value to set the parameter to.-              -> RequestBuilder site ()+              -> RequestBuilderFor url site () byLabelSuffix = byLabelWithMatch T.isSuffixOf  fileByLabelWithMatch  :: (T.Text -> T.Text -> Bool) -- ^ The matching method which is used to find labels (i.e. exact, contains)                       -> T.Text                     -- ^ The text contained in the @\<label>@.                       -> FilePath                   -- ^ The path to the file.                       -> T.Text                     -- ^ The MIME type of the file, e.g. "image/png".-                      -> RequestBuilder site ()+                      -> RequestBuilderFor url site () fileByLabelWithMatch match label path mime = do   name <- genericNameFromLabel match label   addFile name path mime@@ -1131,7 +1154,7 @@ fileByLabelExact :: T.Text -- ^ The text contained in the @\<label>@.                  -> FilePath -- ^ The path to the file.                  -> T.Text -- ^ The MIME type of the file, e.g. "image/png".-                 -> RequestBuilder site ()+                 -> RequestBuilderFor url site () fileByLabelExact = fileByLabelWithMatch (==)  -- |@@ -1143,7 +1166,7 @@ fileByLabelContain :: T.Text -- ^ The text contained in the @\<label>@.                    -> FilePath -- ^ The path to the file.                    -> T.Text -- ^ The MIME type of the file, e.g. "image/png".-                   -> RequestBuilder site ()+                   -> RequestBuilderFor url site () fileByLabelContain = fileByLabelWithMatch T.isInfixOf  -- |@@ -1155,7 +1178,7 @@ fileByLabelPrefix :: T.Text -- ^ The text contained in the @\<label>@.                   -> FilePath -- ^ The path to the file.                   -> T.Text -- ^ The MIME type of the file, e.g. "image/png".-                  -> RequestBuilder site ()+                  -> RequestBuilderFor url site () fileByLabelPrefix = fileByLabelWithMatch T.isPrefixOf  -- |@@ -1167,7 +1190,7 @@ fileByLabelSuffix :: T.Text -- ^ The text contained in the @\<label>@.                   -> FilePath -- ^ The path to the file.                   -> T.Text -- ^ The MIME type of the file, e.g. "image/png".-                  -> RequestBuilder site ()+                  -> RequestBuilderFor url site () fileByLabelSuffix = fileByLabelWithMatch T.isSuffixOf  -- | Lookups the hidden input named "_token" and adds its value to the params.@@ -1177,12 +1200,15 @@ -- -- > request $ do -- >   addToken_ "#formID"-addToken_ :: HasCallStack => Query -> RequestBuilder site ()+addToken_ :: HasCallStack => Query -> RequestBuilderFor url site () addToken_ scope = do   matches <- htmlQuery' rbdResponse ["Tried to get CSRF token with addToken'"] $ scope <> " input[name=_token][type=hidden][value]"   case matches of     [] -> failure $ "No CSRF token found in the current page"-    element:[] -> addPostParam "_token" $ head $ attribute "value" $ parseHTML element+    element:[] ->+      case attribute "value" $ parseHTML element of+        value:_ -> addPostParam "_token" value+        [] -> failure "Found a CSRF token element, but it had no value attribute"     _ -> failure $ "More than one CSRF token found in the page"  -- | For responses that display a single form, just lookup the only CSRF token available.@@ -1191,7 +1217,7 @@ -- -- > request $ do -- >   addToken-addToken :: HasCallStack => RequestBuilder site ()+addToken :: HasCallStack => RequestBuilderFor url site () addToken = addToken_ ""  -- | Calls 'addTokenFromCookieNamedToHeaderNamed' with the 'defaultCsrfCookieName' and 'defaultCsrfHeaderName'.@@ -1204,7 +1230,7 @@ -- >   addTokenFromCookie -- -- Since 1.4.3.2-addTokenFromCookie :: HasCallStack => RequestBuilder site ()+addTokenFromCookie :: HasCallStack => RequestBuilderFor url site () addTokenFromCookie = addTokenFromCookieNamedToHeaderNamed defaultCsrfCookieName defaultCsrfHeaderName  -- | Looks up the CSRF token stored in the cookie with the given name and adds it to the request headers. An error is thrown if the cookie can't be found.@@ -1223,7 +1249,7 @@ addTokenFromCookieNamedToHeaderNamed :: HasCallStack                                      => ByteString -- ^ The name of the cookie                                      -> CI ByteString -- ^ The name of the header-                                     -> RequestBuilder site ()+                                     -> RequestBuilderFor url site () addTokenFromCookieNamedToHeaderNamed cookieName headerName = do   cookies <- getRequestCookies   case M.lookup cookieName cookies of@@ -1244,7 +1270,7 @@ -- >   liftIO $ putStrLn $ "Cookies are: " ++ show cookies -- -- Since 1.4.3.2-getRequestCookies :: HasCallStack => RequestBuilder site Cookies+getRequestCookies :: HasCallStack => RequestBuilderFor url site Cookies getRequestCookies = do   requestBuilderData <- YT.SIO.getSIO   headers <- case simpleHeaders Control.Applicative.<$> rbdResponse requestBuilderData of@@ -1259,7 +1285,7 @@ -- ==== __Examples__ -- -- > post HomeR-post :: (Yesod site, RedirectUrl site url)+post :: (Yesod site, UrlToDispatch url site)      => url      -> YesodExample site () post = performMethod "POST"@@ -1272,7 +1298,7 @@ -- -- > import Data.Aeson -- > postBody HomeR (encode $ object ["age" .= (1 :: Integer)])-postBody :: (Yesod site, RedirectUrl site url)+postBody :: (Yesod site, UrlToDispatch url site)          => url          -> BSL8.ByteString          -> YesodExample site ()@@ -1288,7 +1314,7 @@ -- > get HomeR -- -- > get ("http://google.com" :: Text)-get :: (Yesod site, RedirectUrl site url)+get :: (Yesod site, UrlToDispatch url site)     => url     -> YesodExample site () get = performMethod "GET"@@ -1301,7 +1327,7 @@ -- -- > performMethod "DELETE" HomeR performMethod-    :: (Yesod site, RedirectUrl site url)+    :: (Yesod site, UrlToDispatch url site)     => ByteString     -> url     -> YesodExample site ()@@ -1318,7 +1344,7 @@ -- > get HomeR -- > followRedirect followRedirect-    :: (Yesod site)+    :: (UrlToDispatch T.Text site, YesodDispatch site)     => YesodExample site (Either T.Text T.Text) -- ^ 'Left' with an error message if not a redirect, 'Right' with the redirected URL if it was followRedirect = do   mr <- getResponse@@ -1366,7 +1392,7 @@ -- > import Network.HTTP.Types.Method -- > request $ do -- >   setMethod methodPut-setMethod :: H.Method -> RequestBuilder site ()+setMethod :: H.Method -> RequestBuilderFor url site () setMethod m = YT.SIO.modifySIO $ \rbd -> rbd { rbdMethod = m }  -- | Sets the URL used by the request.@@ -1380,7 +1406,7 @@ -- >   setUrl ("http://google.com/" :: Text) setUrl :: (Yesod site, RedirectUrl site url)        => url-       -> RequestBuilder site ()+       -> RequestBuilderFor url site () setUrl url' = do     site <- fmap rbdSite YT.SIO.getSIO     eurl <- Yesod.Core.Unsafe.runFakeHandler@@ -1388,7 +1414,7 @@         (const $ error "Test.Hspec.Yesod: No logger available")         site         (toTextUrl url')-    url <- either (error . show) return eurl+    url <- either (\err -> failure $ "setUrl: failed to render the URL: " <> T.pack (show err)) return eurl     let (urlPath, urlQuery) = T.break (== '?') url     YT.SIO.modifySIO $ \rbd -> rbd         { rbdPath =@@ -1397,9 +1423,20 @@                 ("https:":_:rest) -> rest                 x -> x         , rbdGets = rbdGets rbd ++ H.parseQuery (TE.encodeUtf8 urlQuery)+        , rbdUrl = Just url'         } +-- | Set the URL of the request, specialized to a nested route fragment.+--+-- @since 0.3.0.0+setUrlNested+    :: (RedirectUrl site (WithParentArgs url), Yesod site)+    => ParentArgs url+    -> url+    -> RequestBuilderFor (WithParentArgs url) site ()+setUrlNested parentArgs url = setUrl (WithParentArgs parentArgs url) + -- | Click on a link defined by a CSS query -- -- ==== __ Examples__@@ -1408,7 +1445,7 @@ -- > clickOn "a#idofthelink" -- -- @since 1.5.7-clickOn :: (HasCallStack, Yesod site) => Query -> YesodExample site ()+clickOn :: (HasCallStack, UrlToDispatch T.Text site, YesodDispatch site) => Query -> YesodExample site () clickOn query = do   withResponse' yedResponse ["Tried to invoke clickOn in order to read HTML of a previous response."] $ \ res ->     case YT.CSS.findAttributeBySelector (simpleBody res) query "href" of@@ -1428,7 +1465,7 @@ -- > import Data.Aeson -- > request $ do -- >   setRequestBody $ encode $ object ["age" .= (1 :: Integer)]-setRequestBody :: BSL8.ByteString -> RequestBuilder site ()+setRequestBody :: BSL8.ByteString -> RequestBuilderFor url site () setRequestBody body = YT.SIO.modifySIO $ \rbd -> rbd { rbdPostData = BinaryPostData body }  -- | Adds the given header to the request; see "Network.HTTP.Types.Header" for creating 'Header's.@@ -1438,7 +1475,7 @@ -- > import Network.HTTP.Types.Header -- > request $ do -- >   addRequestHeader (hUserAgent, "Chrome/41.0.2228.0")-addRequestHeader :: H.Header -> RequestBuilder site ()+addRequestHeader :: H.Header -> RequestBuilderFor url site () addRequestHeader header = YT.SIO.modifySIO $ \rbd -> rbd     { rbdHeaders = header : rbdHeaders rbd     }@@ -1453,28 +1490,22 @@ -- @since 1.6.7 addBasicAuthHeader :: CI ByteString -- ^ Username                    -> CI ByteString -- ^ Password-                   -> RequestBuilder site ()+                   -> RequestBuilderFor url site () addBasicAuthHeader username password =   let credentials = convertToBase Base64 $ CI.original $ username <> ":" <> password   in addRequestHeader ("Authorization", "Basic " <> credentials) -yesodExampleDataToApplication :: YesodExampleData site -> IO Application-yesodExampleDataToApplication YesodExampleData {..} =-    yedCreateApplication yedSite yedMiddleware--mkApplication :: YesodExample site Application-mkApplication = do-    liftIO . yesodExampleDataToApplication =<< MS.get- -- | Provides a helpful summary of the request, meant to be used in assertion functions -- -- Currently formats as METHOD PATH?QUERY, e.g. GET /foo/bar?q=true -- The exact format is subject to change. -- -- @since 0.2.0-formatRequestBuilderDataForDebugging :: RequestBuilderData site -> T.Text+formatRequestBuilderDataForDebugging :: RequestBuilderData url site -> T.Text formatRequestBuilderDataForDebugging RequestBuilderData{..} =-    (TE.decodeUtf8 rbdMethod) <> " " <> getEncodedPath rbdPath <> (TE.decodeUtf8 $ H.renderQuery True rbdGets)+    lenient rbdMethod <> " " <> getEncodedPath rbdPath <> lenient (H.renderQuery True rbdGets)+  where+    lenient = TE.decodeUtf8With TErr.lenientDecode  getEncodedPath :: [T.Text] -> T.Text getEncodedPath pathSegments =@@ -1496,13 +1527,14 @@ -- >   setMethod "PUT" -- >   setUrl NameR request-    :: RequestBuilder site ()+    :: (UrlToDispatch url site, Yesod site, HasCallStack)+    => RequestBuilderFor url site ()     -> YesodExample site () request reqBuilder = do-    app <- mkApplication     site <- MS.gets yedSite     mRes <- MS.gets yedResponse     oldCookies <- MS.gets yedCookies+    middleware <- MS.gets yedMiddleware      rbd@RequestBuilderData {..} <- liftIO $ execSIO reqBuilder RequestBuilderData       { rbdPostData = MultipleItemsPostData []@@ -1512,29 +1544,33 @@       , rbdPath = []       , rbdGets = []       , rbdHeaders = []+      , rbdUrl = Nothing       }+     let path = getEncodedPath rbdPath +    yre <- getRunnerEnv+    appNoMiddleware <- mkApplicationFor yre rbd+    let app = middleware appNoMiddleware+     -- expire cookies and filter them for the current path. TODO: support max age     currentUtc <- liftIO getCurrentTime     let cookies = M.filter (checkCookieTime currentUtc) oldCookies         cookiesForPath = M.filter (checkCookiePath path) cookies -    let req = case rbdPostData of-          MultipleItemsPostData x ->-            if DL.any isFile x-            then (multipart x)-            else singlepart-          BinaryPostData _ -> singlepart-          where singlepart = makeSinglepart cookiesForPath rbdPostData rbdMethod rbdHeaders path rbdGets-                multipart x = makeMultipart cookiesForPath x rbdMethod rbdHeaders path rbdGets-    -- let maker = case rbdPostData of-    --       MultipleItemsPostData x ->-    --         if DL.any isFile x-    --         then makeMultipart-    --         else makeSinglepart-    --       BinaryPostData _ -> makeSinglepart-    -- let req = maker cookiesForPath rbdPostData rbdMethod rbdHeaders path rbdGets+    let req =+            case rbdPostData of+                MultipleItemsPostData x ->+                    if DL.any isFile x+                    then (multipart x)+                    else singlepart+                BinaryPostData _ -> singlepart+          where+            singlepart =+                makeSinglepart cookiesForPath rbdPostData rbdMethod rbdHeaders path rbdGets+            multipart x =+                makeMultipart cookiesForPath x rbdMethod rbdHeaders path rbdGets+     response <- liftIO $ runSession (srequest req         { simpleRequest = (simpleRequest req)             { httpVersion = H.http11@@ -1542,7 +1578,11 @@         }) app     let newCookies = parseSetCookies $ simpleHeaders response         cookies' = M.fromList [(Cookie.setCookieName c, c) | c <- newCookies] `M.union` cookies-    modify $ \e -> e { yedCookies = cookies', yedRequest = Just rbd, yedResponse = Just response }+    modify $ \e -> e+        { yedCookies = cookies'+        , yedRequest = Just $ voidRequestBuilderUrl rbd+        , yedResponse = Just response+        }   where     isFile (ReqFilePart _ _ _ _) = True     isFile _ = False@@ -1633,37 +1673,52 @@       , queryString = urlQuery       } +-- | Build a WAI 'Application' for the route stored in the request builder,+-- dispatching against the given (cached) 'YesodRunnerEnv'.+--+-- The @url@ type determines the dispatch target: a full 'Route' dispatches+-- against the whole site, while a nested route fragment dispatches against+-- just that fragment (see 'setUrlNested'). A request with no URL set is an+-- error.+mkApplicationFor+    :: (MonadIO m, UrlToDispatch url site, HasCallStack)+    => YesodRunnerEnv site+    -> RequestBuilderData url site+    -> m Application+mkApplicationFor yre rbd =+    case rbdUrl rbd of+        Nothing -> liftIO $ do+            let msg = mconcat+                    [ "The test tried to make a request, but no URL was specified.\n"+                    , "Previously, the library would assume an empty URL to be the default / route.\n"+                    , "Now, you must explicitly set a route in a request."+                    ] +            failure msg+        Just url ->+            pure $ urlToDispatch url yre++ parseSetCookies :: [H.Header] -> [Cookie.SetCookie] parseSetCookies headers = map (Cookie.parseSetCookie . snd) $ DL.filter (("Set-Cookie"==) . fst) $ headers --- Yes, just a shortcut+-- Yes, just a shortcut. 'withFrozenCallStack' so the assertion is blamed on+-- the caller of 'failure' rather than on this line. failure :: (HasCallStack, MonadIO a) => T.Text -> a b-failure reason = (liftIO $ HUnit.assertFailure $ T.unpack reason) >> error ""--data TestApp site = TestApp-    { testAppSite :: site-    , testAppMiddleware :: Middleware-    }--mkTestApp :: site -> TestApp site-mkTestApp site = TestApp-    { testAppSite = site-    , testAppMiddleware = id-    }+failure reason = withFrozenCallStack $ do+    _ <- liftIO $ HUnit.assertFailure $ T.unpack reason+    error "Test.Hspec.Yesod.failure: assertFailure returned instead of throwing"  type YSpec site = SpecWith (YesodExampleData site)  -- | This creates a minimal 'YesodExampleData' for a given @site@. No -- middlewares are applied. siteToYesodExampleData-    :: (YesodDispatch site)-    => site+    :: site     -> YesodExampleData site siteToYesodExampleData site =     YesodExampleData-        { yedCreateApplication = \site' middleware -> middleware <$> toWaiAppPlain site'-        , yedMiddleware = id+        { yedMiddleware = id         , yedSite = site         , yedCookies = M.empty         , yedRequest = Nothing
src/Test/Hspec/Yesod/Internal.hs view
@@ -5,6 +5,7 @@     ( RequestBuilderData(..)     , RBDPostData(..)     , RequestPart(..)+    , voidRequestBuilderUrl     ) where  import qualified Data.Text as T@@ -12,15 +13,22 @@ import qualified Network.HTTP.Types as H import Network.Wai.Test hiding (assertHeader, assertNoHeader, request) -data RequestBuilderData site = RequestBuilderData+data RequestBuilderData url site = RequestBuilderData     { rbdPostData :: RBDPostData     , rbdResponse :: (Maybe SResponse)     , rbdMethod :: H.Method     , rbdSite :: site+    , rbdUrl :: Maybe url     , rbdPath :: [T.Text]     , rbdGets :: H.Query     , rbdHeaders :: H.RequestHeaders     }++voidRequestBuilderUrl :: RequestBuilderData url site -> RequestBuilderData () site+voidRequestBuilderUrl rbd =+    rbd+        { rbdUrl = () <$ rbdUrl rbd+        }  data RBDPostData = MultipleItemsPostData [RequestPart]                  | BinaryPostData BSL8.ByteString
+ test/NestedRouteDispatchSpec.hs view
@@ -0,0 +1,39 @@+-- | This module hierarchy has the example on how to split routes up.+--+-- First, "NestedRouteDispatchSpec.Resources" defines the @resources@ value+-- with 'parseRoutesNoCheck'. This gives us the @['ResourceTree' 'String']@+-- that we need in order to generate the rest of the route information. The+-- 'App' datatype is also defined there, along with the 'RouteOpts' that+-- we'll use for the rest of the code generation.+--+-- Second, "NestedRouteDispatchSpec.Foo.Route" imports the @resources@+-- above and calls @'mkYesodDataOpts' (mkRouteOpts (Just "FooR")) "App"+-- resources@. This generates the code for the nested route fragment+-- @FooR@.+--+-- Third, "NestedRouteDispatchSpec.Foo.Handler" imports the @resources@ and+-- the @FooR@ route datatype, and creates the 'YesodDispatchNested'+-- instance. The handlers for the datatype are defined here as well.+--+-- A developer could combine the prior two modules: defining both the route+-- datatype and the handlers for that datatype in the same location. This+-- would eliminate the need to have an orphan instance in the @Handler@+-- module. However, this would require you to know about the implementation+-- of the handlers in order to reference the route fragments, which is not+-- ideal from a module graph compilation perspective.+--+-- Fourth, in "NestedRouteDispatchSpec.YesodData", we define the+-- 'mkYesodDataOpts' for the entire 'App'. Then we can provide the+-- @instance Yesod App@, which is required for running tests. This also+-- gives us instances of 'ToParentRoute' since this is where the full+-- @'Route' App@ is fully defined. This module needs to import the 'FooR'+-- type, otherwise it will be regenerated and you'll get weird errors.+--+-- Finally, we have "NestedRouteDispatchSpec.Foo.HandlerSpec". This module+-- writes a test against the 'FooIndexR' route. This module depends on the+-- @instance Yesod App@ and the specific handlers, but it does not depend+-- on any other handlers in order for the test to work.+--+-- With this feature set, we can now write tests that only depend on the+-- handlers we actually need to call!+module NestedRouteDispatchSpec where
+ test/NestedRouteDispatchSpec/Foo/Handler.hs view
@@ -0,0 +1,24 @@+{-# language TemplateHaskell #-}+{-# language OverloadedStrings #-}+{-# language ViewPatterns #-}++{-# OPTIONS_GHC -Wno-orphans #-}++module NestedRouteDispatchSpec.Foo.Handler where++import NestedRouteDispatchSpec.Foo.Route+import NestedRouteDispatchSpec.Resources+import qualified Data.Text as Text+import Data.Text (Text)+import Yesod.Core++mkYesodDispatchOpts (nestDefaultOptsFor "FooR") "App" resources++getFooIndexR :: Int -> HandlerFor App Text+getFooIndexR i = pure $ "getFooIndexR: " <> Text.pack (show i)++getFooEditR :: Int -> HandlerFor App Text+getFooEditR i = pure $ "getFooEditR: " <> Text.pack (show i)++getFooShowR :: Int -> Int -> HandlerFor App Text+getFooShowR i j = pure $ "getFooShowR: " <> Text.pack (show (i, j))
+ test/NestedRouteDispatchSpec/Foo/HandlerSpec.hs view
@@ -0,0 +1,19 @@+module NestedRouteDispatchSpec.Foo.HandlerSpec where++import NestedRouteDispatchSpec.Foo.Handler () -- need YesodDispatchNested FooR instance+import NestedRouteDispatchSpec.Resources (App(..))+import NestedRouteDispatchSpec.Foo.Route (FooR(..))+import NestedRouteDispatchSpec.YesodData () -- need Yesod App instance+import Yesod.Core (WithParentArgs(..))+import Test.Hspec.Yesod (request, bodyEquals, siteToYesodExampleData, setUrl, setUrlNested)+import Test.Hspec (Spec, before, it)++spec :: Spec+spec = do+    before (pure (siteToYesodExampleData App)) $ do+        it "lets me dispatch a nested route fragment via setUrl" $ do+            request $ setUrl (WithParentArgs 1 FooIndexR)+            bodyEquals "getFooIndexR: 1"+        it "lets me dispatch a nested route fragment via setUrlNested" $ do+            request $ setUrlNested 1 FooIndexR+            bodyEquals "getFooIndexR: 1"
+ test/NestedRouteDispatchSpec/Foo/Route.hs view
@@ -0,0 +1,11 @@+{-# language TemplateHaskell #-}+{-# language ViewPatterns #-}+{-# language TypeFamilies #-}+{-# language OverloadedStrings #-}++module NestedRouteDispatchSpec.Foo.Route where++import NestedRouteDispatchSpec.Resources+import Yesod.Core++mkYesodDataOpts (nestDefaultOptsFor "FooR") "App" resources
+ test/NestedRouteDispatchSpec/Resources.hs view
@@ -0,0 +1,28 @@+{-# language QuasiQuotes #-}++module NestedRouteDispatchSpec.Resources where++import Yesod.Core+import Yesod.Routes.TH.Types++data App = App++resources :: [ResourceTree String]+resources = [parseRoutesNoCheck|++/   HomeR GET++/foo/#Int   FooR:+    /       FooIndexR   GET+    /edit   FooEditR    GET+    /#Int   FooShowR    GET++|]++nestDefaultOptsFor :: String -> RouteOpts+nestDefaultOptsFor target =+    setFocusOnNestedRoute target nestDefaultOpts++nestDefaultOpts :: RouteOpts+nestDefaultOpts =+    setNestedRouteFallthrough True defaultOpts
+ test/NestedRouteDispatchSpec/YesodData.hs view
@@ -0,0 +1,16 @@+{-# language TemplateHaskell #-}+{-# language ViewPatterns #-}+{-# language TypeFamilies #-}+{-# language OverloadedStrings #-}++{-# OPTIONS_GHC -Wno-orphans #-}++module NestedRouteDispatchSpec.YesodData where++import NestedRouteDispatchSpec.Foo.Route (FooR(..))+import NestedRouteDispatchSpec.Resources+import Yesod.Core++mkYesodDataOpts nestDefaultOpts "App" resources++instance Yesod App
+ test/NestedRouteDispatchSpec/YesodDispatch.hs view
@@ -0,0 +1,26 @@+{-# language TemplateHaskell #-}+{-# language OverloadedStrings #-}+{-# language ViewPatterns #-}++{-# OPTIONS_GHC -Wno-orphans #-}++-- | This module is not imported by any spec on purpose. It builds the full+-- @YesodDispatch App@ instance (via 'mkYesodDispatchOpts' with+-- 'nestDefaultOpts'), and is kept in the test suite's @other-modules@ purely+-- to verify that the full application dispatch still compiles alongside the+-- nested route-fragment dispatch. Handler specs depend only on the cheap+-- @YesodDispatchNested FooR@ fragment; the real application still wants this+-- full @YesodDispatch@.+module NestedRouteDispatchSpec.YesodDispatch where++import NestedRouteDispatchSpec.Foo.Handler () -- needed for the yesod dispatch instance in scope+import NestedRouteDispatchSpec.Foo.Route (FooR(..))+import NestedRouteDispatchSpec.Resources+import NestedRouteDispatchSpec.YesodData+import Yesod.Core+import Data.Text (Text)++mkYesodDispatchOpts nestDefaultOpts "App" resources++getHomeR :: HandlerFor App Text+getHomeR = pure "HomeR"
test/main.hs view
@@ -1,5 +1,5 @@ -- Ignore warnings about using deprecated byLabel/fileByLabel functions-{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}+{-# OPTIONS_GHC -Wno-deprecations #-}  {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-}@@ -47,6 +47,8 @@ import qualified Data.Text as T import Yesod.Test.Internal (contentTypeHeaderIsUtf8) +import qualified NestedRouteDispatchSpec.Foo.HandlerSpec+ parseQuery_ :: Text -> [[SelectorGroup]] parseQuery_ = either error id . parseQuery @@ -67,6 +69,7 @@  main :: IO () main = hspec $ do+    describe "NestedRouteDispatchSpec.Foo.HandlerSpec" NestedRouteDispatchSpec.Foo.HandlerSpec.spec     describe "CSS selector parsing" $ do         it "elements" $ parseQuery_ "strong" @?= [[DeepChildren [ByTagName "strong"]]]         it "child elements" $ parseQuery_ "strong > i" @?= [[DeepChildren [ByTagName "strong"], DirectChildren [ByTagName "i"]]]@@ -456,9 +459,11 @@             statusIs 201              loc <- getLocation-            liftIO $ assertBool "expected location to be available" $ isRight loc-            let (Right (ResourceR t)) = loc-            liftIO $ assertBool "expected location header to contain post param" $ t == "bar"+            case loc of+                Right (ResourceR t) ->+                    liftIO $ assertBool "expected location header to contain post param" $ t == "bar"+                other ->+                    liftIO $ assertFailure $ "expected a Right ResourceR location, got: " <> show other          yit "returns a Left when no redirect was returned" $ do             get HomeR@@ -497,11 +502,11 @@         yit "checks for valid content-type" $ do             get ("get-json-wrong-content-type" :: Text)             statusIs 200-            (requireJSONResponse :: YesodExample site [Integer]) `liftedShouldThrow` (\(e :: SomeException) -> True)+            (requireJSONResponse :: YesodExample site [Integer]) `liftedShouldThrow` (\(_ :: SomeException) -> True)         yit "checks for valid JSON parse" $ do             get ("get-json-response" :: Text)             statusIs 200-            (requireJSONResponse :: YesodExample site [Text]) `liftedShouldThrow` (\(e :: SomeException) -> True)+            (requireJSONResponse :: YesodExample site [Text]) `liftedShouldThrow` (\(_ :: SomeException) -> True)  instance RenderMessage LiteApp FormMessage where     renderMessage _ _ = defaultFormMessage@@ -649,8 +654,8 @@  getIntegerR :: Handler Text getIntegerR = do-    app <- getYesod-    pure $ T.pack $ show (routedAppInteger app)+    app' <- getYesod+    pure $ T.pack $ show (routedAppInteger app')   -- infix Copied from HSpec's version