packages feed

yesod-core-1.7.0.0: test/Route/FocusLeafConsSpec.hs

{-# LANGUAGE TemplateHaskell #-}
-- | This module pins the constructor set of the generated @FocusNestR@
-- datatype exactly, so escalate an incomplete 'describeFocus' to an error.
{-# OPTIONS_GHC -Werror=incomplete-patterns #-}

-- | Regression test for 'mkRouteConsOpts' in focus (module-split) mode.
--
-- Previously the focused branch dropped every 'ResourceLeaf' among the
-- target's children (returning @([], [])@ for leaves), so the focused
-- datatype was generated without its leaf constructors. Here we splice a
-- datatype built directly from 'mkRouteConsOpts'' focused output, then assert
-- its constructor set is /exactly/ what we expect via a total 'case':
--
--   * a /missing/ constructor fails the pattern reference (a name that isn't
--     in scope), and
--   * an /extra/ constructor makes 'describeFocus' non-exhaustive, which
--     @-Werror=incomplete-patterns@ (set above) turns into a compile error.
--
-- So this module fails to build unless @FocusNestR@ has precisely the two leaf
-- constructors @FocusIndexR@ and @FocusShowR Int@.
module Route.FocusLeafConsSpec (spec) where

import Test.Hspec
import Language.Haskell.TH
import Yesod.Routes.TH.Types
import Yesod.Routes.TH.RenderRoute
    ( mkRouteConsOpts
    , defaultOpts
    , setFocusOnNestedRoute
    )

data App = App

-- | A nested route @FocusNestR@ whose children are two leaves. We build the
-- focused datatype from 'mkRouteConsOpts' and declare it by hand from the
-- returned constructors.
$(do
    let leaf name pieces =
            ResourceLeaf (Resource name pieces (Methods Nothing ["GET"]) [] True)
        parent name pieces =
            ResourceParent name True mempty pieces
        routes =
            [ leaf "FocusHomeR" []
            , parent "FocusNestR" []
                [ leaf "FocusIndexR" []
                , leaf "FocusShowR" [Dynamic (ConT ''Int)]
                ]
            ]
    (cons, _decs) <-
        mkRouteConsOpts
            (setFocusOnNestedRoute "FocusNestR" defaultOpts)
            []
            NoTyArgs
            (ConT ''App)
            routes
    pure [DataD [] (mkName "FocusNestR") [] Nothing cons [DerivClause Nothing [ConT ''Show]]])

-- | Total over every constructor of the generated @FocusNestR@. Naming each
-- constructor exactly once is what makes this a complete-set assertion: see
-- the module header for how missing/extra constructors are each rejected at
-- compile time.
describeFocus :: FocusNestR -> String
describeFocus r = case r of
    FocusIndexR  -> "FocusIndexR"
    FocusShowR n -> "FocusShowR " ++ show n

spec :: Spec
spec = describe "mkRouteConsOpts (focus mode)" $
    it "generates exactly the focused route's leaf constructors" $ do
        -- The real assertion is at compile time (see 'describeFocus'); these
        -- just exercise the generated constructors.
        describeFocus FocusIndexR    `shouldBe` "FocusIndexR"
        describeFocus (FocusShowR 7) `shouldBe` "FocusShowR 7"