diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
 
@@ -5,6 +6,9 @@
 
 import qualified Control.Monad.Fail as Fail
 import qualified Data.Aeson as Aeson
+#ifdef VERSION_attoparsec_aeson
+import qualified Data.Aeson.Parser as Aeson
+#endif
 import qualified Data.Attoparsec.ByteString as Parsec
 import qualified Data.ByteString as ByteString
 import qualified Data.ByteString.Lazy as ByteString.Lazy
@@ -41,8 +45,8 @@
           ( Options.long "context-file"
               <> Options.metavar "PATH"
               <> Options.help
-                "Path of a file containing a JSON object which is used \
-                \as the template context"
+                ("Path of a file containing a JSON object which is used " <>
+                 "as the template context")
           )
       )
     <*> Options.optional
@@ -63,26 +67,32 @@
   where
     usage =
       Pretty.vcat
-        [ Pretty.empty,
+        [ Pretty.emptyDoc,
           "The --context-file and --context-json options are processed as follows:",
           Pretty.indent 2 "1.)"
             Pretty.<+> ( Pretty.align
-                           ( "Both are provided"
-                               Pretty.<$$> "Both objects are merged into one with the keys of \
-                                           \--context-json taking precedence over those in the \
-                                           \file provided by --context-file."
+                           ( Pretty.vcat
+                               [ "Both are provided",
+                                 "Both objects are merged into one with the keys of " <>
+                                 "--context-json taking precedence over those in the " <>
+                                 "file provided by --context-file."
+                               ]
                            )
                        ),
           Pretty.indent 2 "2.)"
             Pretty.<+> ( Pretty.align
-                           ( "None of them are provided"
-                               Pretty.<$$> "The JSON object is read from STDIN."
+                           ( Pretty.vcat
+                               [ "None of them are provided",
+                                 "The JSON object is read from STDIN."
+                               ]
                            )
                        ),
           Pretty.indent 2 "3.)"
             Pretty.<+> ( Pretty.align
-                           ( "One of them is provided"
-                               Pretty.<$$> "The JSON object is read from the supplied option."
+                           ( Pretty.vcat
+                               [ "One of them is provided",
+                                 "The JSON object is read from the supplied option."
+                               ]
                            )
                        )
         ]
diff --git a/ede.cabal b/ede.cabal
--- a/ede.cabal
+++ b/ede.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.2
 name:               ede
-version:            0.3.3.0
+version:            0.3.4.0
 synopsis:
   Templating language with similar syntax and features to Liquid or Jinja2.
 
@@ -9,7 +9,7 @@
 license-file:       LICENSE
 author:             Brendan Hay
 maintainer:         Brendan Hay <brendan.g.hay@gmail.com>
-copyright:          Copyright (c) 2013-2020 Brendan Hay
+copyright:          Copyright (c) 2013-2022 Brendan Hay
 stability:          Experimental
 category:           Text, Template, Web
 build-type:         Simple
@@ -45,6 +45,9 @@
   type:     git
   location: git://github.com/brendanhay/ede.git
 
+flag AttoparsecAeson
+  description: Build attoparsec-aeson.
+
 common base
   default-language: Haskell2010
   ghc-options:
@@ -95,15 +98,22 @@
   main-is:        Main.hs
   ghc-options:    -threaded -rtsopts -with-rtsopts=-A128m
   build-depends:
-    , aeson                        >=1.4
     , attoparsec                   >=0.13
     , bytestring                   >=0.10.4
     , ede
-    , optparse-applicative         >=0.11
-    , prettyprinter                >= 1.7
+    , optparse-applicative         >=0.18
+    , prettyprinter                >=1.7
     , prettyprinter-ansi-terminal  >=1.1
     , text                         >=1.2
     , unordered-containers         >=0.2.3
+
+  if flag(AttoparsecAeson)
+    build-depends:
+      , aeson                      >=2.2
+      , attoparsec-aeson           >=2.2
+  else
+    build-depends:
+      , aeson                      >=1.4
 
 test-suite tests
   import:         base
diff --git a/lib/Text/EDE.hs b/lib/Text/EDE.hs
--- a/lib/Text/EDE.hs
+++ b/lib/Text/EDE.hs
@@ -3,7 +3,7 @@
 
 -- |
 -- Module      : Text.EDE
--- Copyright   : (c) 2013-2020 Brendan Hay <brendan.g.hay@gmail.com>
+-- Copyright   : (c) 2013-2022 Brendan Hay <brendan.g.hay@gmail.com>
 -- License     : This Source Code Form is subject to the terms of
 --               the Mozilla Public License, v. 2.0.
 --               A copy of the MPL can be found in the LICENSE file or
@@ -109,6 +109,9 @@
 
     -- ** Let Expressions
     -- $let
+
+    -- ** Set
+    -- $set
   )
 where
 
@@ -211,14 +214,15 @@
   -- | Strict 'ByteString' template definition.
   ByteString ->
   m (Result Template)
-parseWith config f name =
-  result failure resolve
-    . Parser.runParser config name
+parseWith config f name input =
+  case Parser.runParser config name input of
+    Success (u, is', es', bs) -> do
+      is <- Foldable.foldrM include (Success (HashMap.singleton name u)) (HashMap.toList is')
+      es <- Foldable.foldrM extends (Success mempty) (HashMap.toList es')
+      pure (Template name u <$> is <*> es <*> pure bs)
+    Failure err ->
+      failure err
   where
-    resolve (u, is) =
-      Foldable.foldrM include (Success (HashMap.singleton name u)) (HashMap.toList is)
-        >>= result failure (success . Template name u)
-
     -- Presuming self is always in self's includes, see singleton above.
     -- FIXME: utilise the list of deltas for failures
     include (_, _) (Failure err) = failure err
@@ -226,6 +230,11 @@
       f config key delta
         >>= result failure (success . mappend ss . _tmplIncl)
 
+    extends (_, _) (Failure err) = failure err
+    extends (key, delta :| _) (Success ss) =
+      f config key delta
+        >>= result failure (success . mappend ss . _tmplIncl)
+
 -- | 'HashMap' resolver for @include@ expressions.
 --
 -- The 'identifier' component of the @include@ expression is treated as a lookup
@@ -288,8 +297,12 @@
   -- | Bindings to make available in the environment.
   HashMap Text Value ->
   Result Text.Lazy.Text
-renderWith fs (Template _ u ts) =
-  fmap Text.Builder.toLazyText . Eval.render ts fs u
+renderWith fs t =
+  fmap Text.Builder.toLazyText .
+    Eval.render
+      (_tmplIncl t <> _tmplExtends t)
+      fs
+      (_tmplExp t)
 
 -- | /See:/ 'parse'
 eitherParse :: ByteString -> Either String Template
@@ -696,3 +709,55 @@
 -- > {{ var }}
 -- > ...
 -- > {% endlet %}
+
+-- $set
+--
+-- You can also bind an identifier to whole templates which will be available within
+-- the following expression scope. The identifier will be available in subsequent template.
+--
+-- For example:
+--
+-- > {% set var %}
+-- >   ...
+-- > {% endset %}
+-- > ...
+-- > {{ var }}
+
+-- $block
+--
+-- Blocks are used for inheritance and act as both placeholders and replacements at the same time: The most powerful
+-- part of @ED-E@ is template inheritance. Template inheritance allows you to build a base "skeleton" template that
+-- contains all the common elements of your site and defines blocks that child templates can override.
+--
+-- Base template:
+--
+-- > <!doctype html>
+-- > {% block head %}
+-- > <title>{% block title %}{% endblock %}</title>
+-- > {% endblock %}
+-- > {% block body %}{% endblock %}
+--
+-- Child template:
+--
+-- > {% extends "base.html" %}
+-- > {% block title %}Index{% endblock %}
+-- > {% block head %}
+-- >  {{ super }}
+-- >  <style type="text/css">
+-- >   .important { color: #336699; }
+-- > </style>
+-- > {% endblock %}
+-- > {% block body %}
+-- >  <h1>Index</h1>
+-- >  <p class="important">
+-- >    Welcome to my awesome homepage.
+-- >  </p>
+-- > {% endblock %}
+--
+-- The @{% extends %}@ tag is the key here. It tells the template engine that this template "extends" another template.
+-- When the template system evaluates this template, it first locates the parent. The extends tag should be the first tag in the template.
+--
+-- As you can see it's also possible to render the contents of the parent block by calling super(). You can’t define multiple {% block %}
+-- tags with the same name in the same template. This limitation exists because a block tag works in “both” directions. That is, a block
+-- tag doesn’t just provide a placeholder to fill - it also defines the content that fills the placeholder in the parent. If there were two
+-- similarly-named {% block %} tags in a template, that template's parent wouldn’t know which one of the blocks’ content to use.
diff --git a/lib/Text/EDE/Filters.hs b/lib/Text/EDE/Filters.hs
--- a/lib/Text/EDE/Filters.hs
+++ b/lib/Text/EDE/Filters.hs
@@ -1,6 +1,6 @@
 -- |
 -- Module      : Text.EDE.Filters
--- Copyright   : (c) 2013-2020 Brendan Hay <brendan.g.hay@gmail.com>
+-- Copyright   : (c) 2013-2022 Brendan Hay <brendan.g.hay@gmail.com>
 -- License     : This Source Code Form is subject to the terms of
 --               the Mozilla Public License, v. 2.0.
 --               A copy of the MPL can be found in the LICENSE file or
diff --git a/lib/Text/EDE/Internal/AST.hs b/lib/Text/EDE/Internal/AST.hs
--- a/lib/Text/EDE/Internal/AST.hs
+++ b/lib/Text/EDE/Internal/AST.hs
@@ -3,7 +3,7 @@
 
 -- |
 -- Module      : Text.EDE.Internal.AST
--- Copyright   : (c) 2013-2020 Brendan Hay <brendan.g.hay@gmail.com>
+-- Copyright   : (c) 2013-2022 Brendan Hay <brendan.g.hay@gmail.com>
 -- License     : This Source Code Form is subject to the terms of
 --               the Mozilla Public License, v. 2.0.
 --               A copy of the MPL can be found in the LICENSE file or
@@ -56,6 +56,21 @@
 elet :: Maybe (Id, Exp a) -> Exp a -> Exp a
 elet m e = maybe e (\(i, b) -> Comonad.extract b :< ELet i b e) m
 {-# INLINEABLE elet #-}
+
+eset :: Id -> Exp a -> Exp a -> Exp a
+eset i e k =
+  elet (Just (i, e)) k
+{-# INLINEABLE eset #-}
+
+eblock :: Id -> Exp a -> Exp a
+eblock i e =
+  Comonad.extract e :< EBlock i e
+{-# INLINEABLE eblock #-}
+
+eOverrideBlock :: Id -> Exp a -> Exp a -> Exp a
+eOverrideBlock i b e =
+  Comonad.extract b :< EOverrideBlock i b e
+{-# INLINEABLE eOverrideBlock #-}
 
 ecase ::
   Exp a ->
diff --git a/lib/Text/EDE/Internal/Eval.hs b/lib/Text/EDE/Internal/Eval.hs
--- a/lib/Text/EDE/Internal/Eval.hs
+++ b/lib/Text/EDE/Internal/Eval.hs
@@ -5,7 +5,7 @@
 
 -- |
 -- Module      : Text.EDE.Internal.Eval
--- Copyright   : (c) 2013-2020 Brendan Hay <brendan.g.hay@gmail.com>
+-- Copyright   : (c) 2013-2022 Brendan Hay <brendan.g.hay@gmail.com>
 -- License     : This Source Code Form is subject to the terms of
 --               the Mozilla Public License, v. 2.0.
 --               A copy of the MPL can be found in the LICENSE file or
@@ -49,9 +49,11 @@
 import qualified Text.Trifecta.Delta as Trifecta.Delta
 
 data Env = Env
-  { _templates :: HashMap Id (Exp Delta),
+  { _extends :: Bool,
+    _templates :: HashMap Id (Exp Delta),
     _quoted :: HashMap Id Term,
-    _values :: HashMap Id Value
+    _values :: HashMap Id Value,
+    _blocks :: HashMap Id (Exp Delta)
   }
 
 type Context = ReaderT Env Result
@@ -63,7 +65,7 @@
   HashMap Id Value ->
   Result Builder
 render ts fs e o =
-  Reader.runReaderT (eval e >>= nf) (Env ts (stdlib <> fs) o)
+  Reader.runReaderT (eval e >>= nf) (Env False ts (stdlib <> fs) o mempty)
   where
     nf (TVal v) = build (Trifecta.Delta.delta e) v
     nf _ =
@@ -85,6 +87,26 @@
   x <- eval a
   y <- eval b
   binding d x y
+eval (d :< EOverrideBlock i b e) =
+  setExtended True $ do
+    q <- HashMap.lookup i <$> Reader.asks _blocks
+    case q of
+      Nothing ->
+        bindBlock (HashMap.insert i b) (eval e)
+      Just eb ->
+        bindBlock (HashMap.insert i (d :< ELet "super" b eb)) (eval e)
+eval (_ :< EBlock i b) = do
+  extends <- Reader.asks _extends
+  q <- HashMap.lookup i <$> Reader.asks _blocks
+  if extends then
+    pure (qprim (String mempty))
+  else do
+    x <- eval b
+    v <- lift (unquote i 0 x)
+    maybe
+      (pure x)
+      (bind (HashMap.insert "super" v) . eval)
+      q
 eval (_ :< ELet k rhs bdy) = do
   q <- eval rhs
   v <- lift (unquote k 0 q)
@@ -164,18 +186,41 @@
 eval (d :< EIncl i) = do
   ts <- Reader.asks _templates
   case HashMap.lookup i ts of
-    Just e -> eval e
+    Just e ->
+      -- Don't inherit any blocks declared so far.
+      setExtended False $
+        bindBlock (\_ -> mempty) (eval e)
     Nothing ->
       throwError d $
         "template"
           <+> bold (pp i)
           <+> "is not in scope:"
           <+> PP.brackets (pp (Text.intercalate "," $ HashMap.keys ts))
+eval (d :< EExt i) = do
+  ts <- Reader.asks _templates
+  case HashMap.lookup i ts of
+    Just e ->
+      setExtended False (eval e)
+    Nothing ->
+      throwError d $
+        "template"
+          <+> bold (pp i)
+          <+> "is not in scope:"
+          <+> PP.brackets (pp (Text.intercalate "," $ HashMap.keys ts))
 {-# INLINEABLE eval #-}
 
 bind :: (HashMap Text Value -> HashMap Text Value) -> Context a -> Context a
 bind f = Reader.withReaderT (\x -> x {_values = f (_values x)})
 {-# INLINEABLE bind #-}
+
+bindBlock :: (HashMap Id (Exp Delta) -> HashMap Id (Exp Delta)) -> Context a -> Context a
+bindBlock f = Reader.withReaderT (\x -> x {_blocks = f (_blocks x)})
+{-# INLINEABLE bindBlock #-}
+
+setExtended :: Bool -> Context a -> Context a
+setExtended extends =
+  Reader.withReaderT (\x -> x { _extends = extends })
+{-# INLINEABLE setExtended #-}
 
 variable :: Delta -> Var -> Context Value
 variable d (Var is) =
diff --git a/lib/Text/EDE/Internal/Filters.hs b/lib/Text/EDE/Internal/Filters.hs
--- a/lib/Text/EDE/Internal/Filters.hs
+++ b/lib/Text/EDE/Internal/Filters.hs
@@ -8,7 +8,7 @@
 
 -- |
 -- Module      : Text.EDE.Internal.Filters
--- Copyright   : (c) 2013-2020 Brendan Hay <brendan.g.hay@gmail.com>
+-- Copyright   : (c) 2013-2022 Brendan Hay <brendan.g.hay@gmail.com>
 -- License     : This Source Code Form is subject to the terms of
 --               the Mozilla Public License, v. 2.0.
 --               A copy of the MPL can be found in the LICENSE file or
diff --git a/lib/Text/EDE/Internal/Parser.hs b/lib/Text/EDE/Internal/Parser.hs
--- a/lib/Text/EDE/Internal/Parser.hs
+++ b/lib/Text/EDE/Internal/Parser.hs
@@ -11,7 +11,7 @@
 
 -- |
 -- Module      : Text.EDE.Internal.Parser
--- Copyright   : (c) 2013-2020 Brendan Hay <brendan.g.hay@gmail.com>
+-- Copyright   : (c) 2013-2022 Brendan Hay <brendan.g.hay@gmail.com>
 -- License     : This Source Code Form is subject to the terms of
 --               the Mozilla Public License, v. 2.0.
 --               A copy of the MPL can be found in the LICENSE file or
@@ -35,7 +35,6 @@
 import qualified Control.Monad.State.Strict as State
 import Control.Monad.Trans (lift)
 import Data.Aeson.Types (Value (..))
-import qualified Data.Bifunctor as Bifunctor
 import Data.ByteString (ByteString)
 import qualified Data.Char as Char
 import Data.HashMap.Strict (HashMap)
@@ -64,7 +63,9 @@
 
 data Env = Env
   { _settings :: !Syntax,
-    _includes :: HashMap Text (NonEmpty Delta)
+    _includes :: HashMap Text (NonEmpty Delta),
+    _extends :: HashMap Text (NonEmpty Delta),
+    _blocks :: HashMap Text (Exp Delta)
   }
 
 $(Lens.makeLenses ''Env)
@@ -120,15 +121,25 @@
   Syntax ->
   Text ->
   ByteString ->
-  Result (Exp Delta, HashMap Text (NonEmpty Delta))
+  Result (Exp Delta, HashMap Text (NonEmpty Delta), HashMap Text (NonEmpty Delta), HashMap Text (Exp Delta))
 runParser o n = res . Trifecta.parseByteString (runEDE run) pos
   where
-    run = State.runStateT (pragma *> document <* Trifecta.eof) (Env o mempty)
+    run = State.runStateT (pragma *> document <* Trifecta.eof) (Env o mempty mempty mempty)
 
     pos = Trifecta.Delta.Directed (Text.Encoding.encodeUtf8 n) 0 0 0 0
 
     res = \case
-      Trifecta.Success x -> Success (Bifunctor.second _includes x)
+      Trifecta.Success (x, env) ->
+        if null (_extends env) then
+          Success (x, _includes env, _extends env, _blocks env)
+        else
+          -- In case the template extends another template we arrange the contained
+          -- blocks in a way that make it easy to evaluate the "inheritance" mechanism.
+          let
+            x' =
+              HashMap.foldrWithKey eOverrideBlock x (_blocks env)
+          in
+            Success (x', _includes env, _extends env, _blocks env)
       Trifecta.Failure e -> Failure (Trifecta._errDoc e)
 
 pragma :: Parser m => m ()
@@ -184,6 +195,9 @@
       cases,
       loop,
       include,
+      set,
+      block',
+      extends',
       binding,
       raw,
       comment
@@ -257,6 +271,13 @@
         (,) <$> (keyword "with" *> identifier)
           <*> (Trifecta.symbol "=" *> term)
 
+extends' :: Parser m => m (Exp Delta)
+extends' = block "extends" $ do
+  d <- Trifecta.position
+  k <- Trifecta.stringLiteral
+  extends %= HashMap.insertWith (<>) k (d :| [])
+  pure (d :< EExt k)
+
 binding :: Parser m => m (Exp Delta)
 binding =
   elet . Just
@@ -267,6 +288,22 @@
       )
     <*> document
     <* exit "endlet"
+
+set :: Parser m => m (Exp Delta)
+set =
+  eset
+  <$> block "set" identifier
+  <*> document
+  <* exit "endset"
+  <*> document
+
+block' :: Parser m => m (Exp Delta)
+block' = do
+  k <- block "block" identifier
+  e <- document
+  _ <- exit "endblock"
+  blocks %= HashMap.insert k e
+  pure (eblock k e)
 
 raw :: Parser m => m (Exp Delta)
 raw = ann (ELit <$> body)
diff --git a/lib/Text/EDE/Internal/Quoting.hs b/lib/Text/EDE/Internal/Quoting.hs
--- a/lib/Text/EDE/Internal/Quoting.hs
+++ b/lib/Text/EDE/Internal/Quoting.hs
@@ -8,7 +8,7 @@
 
 -- |
 -- Module      : Text.EDE.Internal.Quoting
--- Copyright   : (c) 2013-2020 Brendan Hay <brendan.g.hay@gmail.com>
+-- Copyright   : (c) 2013-2022 Brendan Hay <brendan.g.hay@gmail.com>
 -- License     : This Source Code Form is subject to the terms of
 --               the Mozilla Public License, v. 2.0.
 --               A copy of the MPL can be found in the LICENSE file or
diff --git a/lib/Text/EDE/Internal/Syntax.hs b/lib/Text/EDE/Internal/Syntax.hs
--- a/lib/Text/EDE/Internal/Syntax.hs
+++ b/lib/Text/EDE/Internal/Syntax.hs
@@ -2,7 +2,7 @@
 
 -- |
 -- Module      : Text.EDE.Internal.Syntax
--- Copyright   : (c) 2013-2020 Brendan Hay <brendan.g.hay@gmail.com>
+-- Copyright   : (c) 2013-2022 Brendan Hay <brendan.g.hay@gmail.com>
 -- License     : This Source Code Form is subject to the terms of
 --               the Mozilla Public License, v. 2.0.
 --               A copy of the MPL can be found in the LICENSE file or
@@ -104,7 +104,11 @@
       "_",
       ".",
       "true",
-      "false"
+      "false",
+      "set",
+      "endset",
+      "block",
+      "endblock"
     ]
 
 pragmaStyle :: TokenParsing m => IdentifierStyle m
diff --git a/lib/Text/EDE/Internal/Types.hs b/lib/Text/EDE/Internal/Types.hs
--- a/lib/Text/EDE/Internal/Types.hs
+++ b/lib/Text/EDE/Internal/Types.hs
@@ -11,7 +11,7 @@
 
 -- |
 -- Module      : Text.EDE.Internal.Types
--- Copyright   : (c) 2013-2020 Brendan Hay <brendan.g.hay@gmail.com>
+-- Copyright   : (c) 2013-2022 Brendan Hay <brendan.g.hay@gmail.com>
 -- License     : This Source Code Form is subject to the terms of
 --               the Mozilla Public License, v. 2.0.
 --               A copy of the MPL can be found in the LICENSE file or
@@ -166,7 +166,9 @@
 data Template = Template
   { _tmplName :: !Text,
     _tmplExp :: !(Exp Delta),
-    _tmplIncl :: HashMap Id (Exp Delta)
+    _tmplIncl :: HashMap Id (Exp Delta),
+    _tmplExtends :: HashMap Id (Exp Delta),
+    _tmplBlocks :: HashMap Id (Exp Delta)
   }
   deriving (Eq)
 
@@ -203,9 +205,12 @@
   | EFun !Id
   | EApp !a !a
   | ELet !Id !a !a
+  | EBlock !Id !a
+  | EOverrideBlock !Id !a !a
   | ECase !a [Alt a]
   | ELoop !Id !a !a
   | EIncl !Text
+  | EExt !Text
   deriving (Eq, Show, Functor)
 
 instance Functor.Classes.Eq1 ExpF where
@@ -214,11 +219,14 @@
   liftEq _ (EFun a) (EFun b) = a == b
   liftEq c (EApp a1 a2) (EApp b1 b2) = a1 `c` b1 && a2 `c` b2
   liftEq c (ELet a0 a1 a2) (ELet b0 b1 b2) = a0 == b0 && a1 `c` b1 && a2 `c` b2
+  liftEq c (EBlock a0 a1) (EBlock b0 b1) = a0 == b0 && a1 `c` b1
+  liftEq c (EOverrideBlock a0 a1 a2) (EOverrideBlock b0 b1 b2) = a0 == b0 && a1 `c` b1 && a2 `c` b2
   liftEq c (ECase a as) (ECase b bs) = a `c` b && (List.all (uncurry altEq) $ zip as bs)
     where
       altEq (pA, a') (pB, b') = pA == pB && a' `c` b'
   liftEq c (ELoop a0 a1 a2) (ELoop b0 b1 b2) = a0 == b0 && a1 `c` b1 && a2 `c` b2
   liftEq _ (EIncl a) (EIncl b) = a == b
+  liftEq _ (EExt a) (EExt b) = a == b
   liftEq _ _ _ = False
 
 type Exp = Cofree ExpF
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -1,7 +1,7 @@
 {-# LANGUAGE OverloadedStrings #-}
 
 -- Module      : Main
--- Copyright   : (c) 2013-2020 Brendan Hay <brendan.g.hay@gmail.com>
+-- Copyright   : (c) 2013-2022 Brendan Hay <brendan.g.hay@gmail.com>
 -- License     : This Source Code Form is subject to the terms of
 --               the Mozilla Public License, v. 2.0.
 --               A copy of the MPL can be found in the LICENSE file or
diff --git a/test/resources/ext-base.ede b/test/resources/ext-base.ede
new file mode 100644
--- /dev/null
+++ b/test/resources/ext-base.ede
@@ -0,0 +1,7 @@
+{}
+---
+<!doctype html>
+{% block head %}
+<title>{% block title %}{% endblock %}</title>
+{% endblock %}
+{% block body %}{% endblock %}
diff --git a/test/resources/ext-base.golden b/test/resources/ext-base.golden
new file mode 100644
--- /dev/null
+++ b/test/resources/ext-base.golden
@@ -0,0 +1,2 @@
+<!doctype html>
+<title></title>
diff --git a/test/resources/ext-child.ede b/test/resources/ext-child.ede
new file mode 100644
--- /dev/null
+++ b/test/resources/ext-child.ede
@@ -0,0 +1,16 @@
+{}
+---
+{% extends "ext-base.ede" %}
+{% block title %}Index{% endblock %}
+{% block head %}
+  {{ super }}
+  <style type="text/css">
+    .important { color: #336699; }
+  </style>
+{% endblock %}
+{% block body %}
+  <h1>Index</h1>
+  <p class="important">
+    Welcome to my awesome homepage.
+  </p>
+{% endblock %}
diff --git a/test/resources/ext-child.golden b/test/resources/ext-child.golden
new file mode 100644
--- /dev/null
+++ b/test/resources/ext-child.golden
@@ -0,0 +1,13 @@
+{}
+---
+<!doctype html>
+  <title>Index</title>
+
+  <style type="text/css">
+    .important { color: #336699; }
+  </style>
+  <h1>Index</h1>
+  <p class="important">
+    Welcome to my awesome homepage.
+  </p>
+
diff --git a/test/resources/polymorphic-show.golden b/test/resources/polymorphic-show.golden
--- a/test/resources/polymorphic-show.golden
+++ b/test/resources/polymorphic-show.golden
@@ -4,4 +4,4 @@
 1.0
 ["a","b","c","d","e"]
 [1,2,3,4,5]
-{"foo":"x","bar":"y"}
+{"bar":"y","foo":"x"}
diff --git a/test/resources/set.ede b/test/resources/set.ede
new file mode 100644
--- /dev/null
+++ b/test/resources/set.ede
@@ -0,0 +1,17 @@
+{
+  "var": true
+}
+---
+{% set x %}
+
+  {% set y %}
+    This is a nested block {{var}}
+  {% endset %}
+
+  This is a big expression! {{var}}
+
+  {{ y }}
+
+{% endset %}
+
+{{x}}
diff --git a/test/resources/set.golden b/test/resources/set.golden
new file mode 100644
--- /dev/null
+++ b/test/resources/set.golden
@@ -0,0 +1,8 @@
+
+
+
+  This is a big expression! true
+
+      This is a nested block true
+
+
diff --git a/test/resources/whitespace.golden b/test/resources/whitespace.golden
--- a/test/resources/whitespace.golden
+++ b/test/resources/whitespace.golden
@@ -6,6 +6,8 @@
 
   
 
+       
        test  
+    
   
 
