diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -136,8 +136,7 @@
 
 ## `Show`
 
-We define custom `Show` and `Read` instances to be able to write terse
-[doctests](https://hackage.haskell.org/package/doctest) like
+We define custom `Show` and `Read` instances to be able to write terse tests like:
 
 ```haskell
 >>> addSpan (read "1:1-6:1") (read "[1:1-3:1,6:1-6:2,7:4-7:5]")
diff --git a/changelog.md b/changelog.md
deleted file mode 100644
--- a/changelog.md
+++ /dev/null
@@ -1,58 +0,0 @@
-0.1.3.16 - 2022 Jan 24
-
-  - Fix test suite failure on case-insensitive file systems
-
-0.1.3.14 - 2022 Jan 13
-
-  - Drop support for GHC 8.0 and 8.2
-
-0.1.3.12 - 2022 Jan 13
-
-  - Support GHC 9.0 and 9.2
-  - Tighten dependency version bounds
-
-0.1.3.10 - 2020 Nov 4
-
-  - Added `Data` instances for:
-      - `Area`
-      - `Loc`
-      - `Pos`
-      - `Line`
-      - `Column`
-      - `Span`
-
-0.1.3.8 - 2020 May 20
-
-  - Support GHC 8.10
-
-The change log was not maintained before this point.
-
-0.1.3.6 - 2020 Mar 15
-
-  - Support GHC 8.8
-
-0.1.3.4 - 2018 Nov 22
-
-0.1.3.3 - 2018 Aug 23
-
-0.1.3.2 - 2017 Dec 3
-
-0.1.3.1 - 2017 Aug 20
-
-0.1.3.0 - 2017 July 22
-
-0.1.2.3 - 2017 May 28
-
-0.1.2.2 - 2017 May 28
-
-0.1.2.1 - 2017 May 16
-
-0.1.2.0 - 2017 May 7
-
-0.1.1.0 - 2017 May 7
-
-0.1.0.2 - 2017 May 7
-
-0.1.0.1 - 2017 May 7
-
-0.1.0.0 - 2017 May 7
diff --git a/loc-test-src/Test/Loc/Hedgehog/Gen.hs b/loc-test-src/Test/Loc/Hedgehog/Gen.hs
--- a/loc-test-src/Test/Loc/Hedgehog/Gen.hs
+++ b/loc-test-src/Test/Loc/Hedgehog/Gen.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE ScopedTypeVariables #-}
-
 {- |
 
 Hedgehog generators for types defined in the /loc/ package.
@@ -7,25 +5,25 @@
 -}
 module Test.Loc.Hedgehog.Gen
   (
-  -- * Line
-    line, line', defMaxLine
-
-  -- * Column
-  , column, column', defMaxColumn
+    -- * Line
+    line, line', defMaxLine,
 
-  -- * Loc
-  , loc, loc'
+    -- * Column
+    column, column', defMaxColumn,
 
-  -- * Span
-  , span, span'
+    -- * Loc
+    loc, loc',
 
-  -- * Area
-  , area, area'
+    -- * Span
+    span, span',
 
-  -- * Generator bounds
-  , Bounds, boundsSize
+    -- * Area
+    area, area',
 
-  ) where
+    -- * Generator bounds
+    Bounds, boundsSize,
+  )
+  where
 
 import Data.Loc (ToNat (..))
 import Data.Loc.Internal.Prelude
diff --git a/loc.cabal b/loc.cabal
--- a/loc.cabal
+++ b/loc.cabal
@@ -1,7 +1,7 @@
 cabal-version: 3.0
 
 name: loc
-version: 0.1.3.16
+version: 0.1.4.0
 
 synopsis: Types representing line and column positions and ranges in text files.
 
@@ -13,26 +13,35 @@
     the @Span@ type is a nonempty range between two @Loc@s,
     and the @Area@ type is a set of non-touching @Span@s.
 
-category: Data Structures
+category: Data Structures, Text
 
-homepage: https://github.com/chris-martin/loc
+homepage: https://github.com/typeclasses/loc
 
-author:     Chris Martin <ch.martin@gmail.com>
-maintainer: Chris Martin <ch.martin@gmail.com>
+author: Chris Martin
+maintainer: Chris Martin, Julie Moronuki
 
+copyright: 2017 Mission Valley Software LLC
 license: Apache-2.0
 license-file: license.txt
 
 build-type: Simple
 
-extra-source-files: example.png example.svg README.md changelog.md
+extra-source-files: example.png example.svg README.md
 
 common base
     default-language: Haskell2010
-    default-extensions: NoImplicitPrelude
+    default-extensions:
+        BlockArguments
+        DeriveDataTypeable
+        DeriveFoldable
+        DeriveFunctor
+        GeneralizedNewtypeDeriving
+        LambdaCase
+        NoImplicitPrelude
+        ScopedTypeVariables
     ghc-options: -Wall
-    build-depends: base >= 4.11 && < 4.17
-    build-depends: containers >= 0.5.7.1 && < 0.7
+    build-depends: base >= 4.14 && < 4.17
+    build-depends: containers >= 0.6.5.1 && < 0.7
 
 common test
     import: base
@@ -54,18 +63,17 @@
         Data.Loc.Loc
         Data.Loc.Pos
         Data.Loc.Span
+        Data.Loc.SpanOrLoc
         Data.Loc.Types
 
-test-suite doctest
-    import: test
-    type: exitcode-stdio-1.0
-    main-is: test-with-doctest.hs
-    build-depends: doctest >= 0.16 && < 0.21
-
-test-suite hedgehog
+test-suite test-loc-properties
     import: test
     type: exitcode-stdio-1.0
-    main-is: test-with-hedgehog.hs
+    default-extensions: TemplateHaskell
+    main-is: Main.hs
     hs-source-dirs: loc-test-src
-    build-depends: hedgehog ^>= 1.0
     other-modules: Test.Loc.Hedgehog.Gen
+    build-depends:
+        hspec
+      , hspec-hedgehog
+      , hedgehog ^>= 1.0.2 || ^>= 1.1
diff --git a/src/Data/Loc.hs b/src/Data/Loc.hs
--- a/src/Data/Loc.hs
+++ b/src/Data/Loc.hs
@@ -1,40 +1,44 @@
 module Data.Loc
   (
-  -- * Concepts
-  -- $concepts
-
-  -- * Imports
-  -- $imports
+    -- * Concepts
+    -- $concepts
 
-  -- * Core types
-    Line, Column, Loc, Span, Area
+    -- * Imports
+    -- $imports
 
-  -- * Constructing
-  -- ** Loc
-  , loc, origin
-  -- ** Span
-  , spanFromTo, spanFromToMay
-  -- ** Area
-  , areaFromTo, spanArea
+    -- * Core types
+    Line, Column, Loc, Span, SpanOrLoc, Area,
 
-  -- * Deconstructing
-  -- ** Loc
-  , locLine, locColumn
-  -- ** Span
-  , spanStart, spanEnd
-  -- ** Area
-  , areaStart, areaEnd, areaSpansAsc
+    -- * Constructing
+    -- ** Loc
+    loc, origin,
+    -- ** Span
+    spanFromTo, spanFromToMay,
+    -- ** SpanOrLoc
+    spanOrLocFromTo,
+    -- ** Area
+    areaFromTo, spanArea,
 
-  -- * Combining
-  -- ** Span
-  , spanUnion, spanDifference
-  -- ** Area
-  , areaUnion, areaDifference
+    -- * Deconstructing
+    -- ** Loc
+    locLine, locColumn,
+    -- ** Span
+    spanStart, spanEnd,
+    -- ** SpanOrLoc
+    spanOrLocStart, spanOrLocEnd,
+    -- ** Area
+    areaStart, areaEnd, areaSpansAsc,
 
-  -- * Miscellaneous
-  , Pos, OneToTwo, ZeroToTwo, ToNat (..), LocException (..)
+    -- * Combining
+    -- ** Span
+    spanUnion, spanDifference,
+    -- ** Area
+    areaUnion, areaDifference,
 
-  ) where
+    -- * Miscellaneous
+    Pos, OneToTwo, ZeroToTwo, ToNat (..), LocException (..),
+  )
+  where
 
 import Data.Loc.Internal.Prelude
 
@@ -45,10 +49,12 @@
 import Data.Loc.Loc (Loc)
 import Data.Loc.Pos (Column, Line, Pos, ToNat (..))
 import Data.Loc.Span (Span)
+import Data.Loc.SpanOrLoc (SpanOrLoc)
 
 import qualified Data.Loc.Area as Area
 import qualified Data.Loc.Loc as Loc
 import qualified Data.Loc.Span as Span
+import qualified Data.Loc.SpanOrLoc as SpanOrLoc
 
 {- |
 The smallest location: @'loc' 1 1@.
@@ -97,6 +103,25 @@
 -}
 spanFromToMay :: Loc -> Loc -> Maybe Span
 spanFromToMay = Span.fromToMay
+
+{- |
+
+Construct a 'SpanOrLoc' from two 'Loc's. If the two locs are not equal,
+the lesser loc will be the start, and the greater loc will be the end.
+
+/This is an alias for 'SpanOrLoc.fromTo'./
+
+-}
+spanOrLocFromTo :: Loc -> Loc -> SpanOrLoc
+spanOrLocFromTo = SpanOrLoc.fromTo
+
+-- | /This is an alias for 'SpanOrLoc.start'./
+spanOrLocStart :: SpanOrLoc -> Loc
+spanOrLocStart = SpanOrLoc.start
+
+-- | /This is an alias for 'SpanOrLoc.end'./
+spanOrLocEnd :: SpanOrLoc -> Loc
+spanOrLocEnd = SpanOrLoc.end
 
 {- |
 Construct a contiguous 'Area' consisting of a single 'Span' specified by two
diff --git a/src/Data/Loc/Area.hs b/src/Data/Loc/Area.hs
--- a/src/Data/Loc/Area.hs
+++ b/src/Data/Loc/Area.hs
@@ -1,32 +1,22 @@
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-
 module Data.Loc.Area
-  ( Area
-
-  -- * Constructing
-  , fromTo
-  , spanArea
+  (
+    Area,
 
-  -- * Combining
-  , (+)
-  , (-)
-  , addSpan
+    -- * Constructing
+    fromTo, spanArea,
 
-  -- * Querying
-  , firstSpan
-  , lastSpan
-  , start
-  , end
-  , areaSpan
-  , spansAsc
-  , spanCount
+    -- * Combining
+    (+), (-), addSpan,
 
-  -- * Show and Read
-  , areaShowsPrec
-  , areaReadPrec
+    -- * Querying
+    firstSpan, lastSpan,
+    start, end,
+    areaSpan, spansAsc, spanCount,
 
-  ) where
+    -- * Show and Read
+    areaShowsPrec, areaReadPrec,
+  )
+  where
 
 import Data.Loc.Internal.Prelude
 
diff --git a/src/Data/Loc/Exception.hs b/src/Data/Loc/Exception.hs
--- a/src/Data/Loc/Exception.hs
+++ b/src/Data/Loc/Exception.hs
@@ -1,6 +1,8 @@
 module Data.Loc.Exception
-  ( LocException (..)
-  ) where
+  (
+    LocException (..),
+  )
+  where
 
 import Data.Loc.Internal.Prelude
 
diff --git a/src/Data/Loc/Internal/Map.hs b/src/Data/Loc/Internal/Map.hs
--- a/src/Data/Loc/Internal/Map.hs
+++ b/src/Data/Loc/Internal/Map.hs
@@ -1,7 +1,9 @@
 module Data.Loc.Internal.Map
-  ( module Data.Map
-  , below, above, belowInclusive, aboveInclusive
-  ) where
+  (
+    module Data.Map,
+    below, above, belowInclusive, aboveInclusive,
+  )
+  where
 
 import Data.Loc.Internal.Prelude
 
diff --git a/src/Data/Loc/Internal/Prelude.hs b/src/Data/Loc/Internal/Prelude.hs
--- a/src/Data/Loc/Internal/Prelude.hs
+++ b/src/Data/Loc/Internal/Prelude.hs
@@ -1,8 +1,10 @@
 module Data.Loc.Internal.Prelude
-  ( module X
-  , (<&>)
-  , readPrecChar
-  ) where
+  (
+    module X,
+    (<&>),
+    readPrecChar,
+  )
+  where
 
 import Control.Applicative as X (empty, pure, (*>), (<*), (<*>))
 import Control.Arrow as X ((<<<), (>>>))
diff --git a/src/Data/Loc/List/OneToTwo.hs b/src/Data/Loc/List/OneToTwo.hs
--- a/src/Data/Loc/List/OneToTwo.hs
+++ b/src/Data/Loc/List/OneToTwo.hs
@@ -1,17 +1,15 @@
-{-# LANGUAGE DeriveFoldable, DeriveFunctor, LambdaCase #-}
-
 module Data.Loc.List.OneToTwo
   (
-  -- * Imports
-  -- $imports
+    -- * Imports
+    -- $imports
 
-  -- * Type
-    OneToTwo (..)
+    -- * Type
+    OneToTwo (..),
 
-  -- * Tuple conversion
-  , toTuple
-  , toTuple'
-  ) where
+    -- * Tuple conversion
+    toTuple, toTuple',
+  )
+  where
 
 import Data.Loc.Internal.Prelude
 
diff --git a/src/Data/Loc/List/ZeroToTwo.hs b/src/Data/Loc/List/ZeroToTwo.hs
--- a/src/Data/Loc/List/ZeroToTwo.hs
+++ b/src/Data/Loc/List/ZeroToTwo.hs
@@ -1,13 +1,12 @@
-{-# LANGUAGE DeriveFoldable, DeriveFunctor #-}
-
 module Data.Loc.List.ZeroToTwo
   (
-  -- Imports
-  -- $imports
+    -- Imports
+    -- $imports
 
-  -- * Type
-    ZeroToTwo (..)
-  ) where
+    -- * Type
+    ZeroToTwo (..),
+  )
+  where
 
 import Data.Loc.Internal.Prelude
 
diff --git a/src/Data/Loc/Loc.hs b/src/Data/Loc/Loc.hs
--- a/src/Data/Loc/Loc.hs
+++ b/src/Data/Loc/Loc.hs
@@ -1,19 +1,15 @@
-{-# LANGUAGE DeriveDataTypeable #-}
-
 module Data.Loc.Loc
-  ( Loc
+  (
+    Loc,
 
-  -- * Constructing
-  , loc
-  , origin
+    -- * Constructing
+    loc, origin,
 
-  -- * Querying
-  , line
-  , column
+    -- * Querying
+    line, column,
 
-  -- * Show and Read
-  , locShowsPrec
-  , locReadPrec
+    -- * Show and Read
+    locShowsPrec, locReadPrec,
 
   ) where
 
diff --git a/src/Data/Loc/Pos.hs b/src/Data/Loc/Pos.hs
--- a/src/Data/Loc/Pos.hs
+++ b/src/Data/Loc/Pos.hs
@@ -1,17 +1,11 @@
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-
 module Data.Loc.Pos
-  ( Pos
-  , Line
-  , Column
-  , ToNat (..)
-
-  -- * Show and Read
-  , posShowsPrec
-  , posReadPrec
+  (
+    Pos, Line, Column, ToNat (..),
 
-  ) where
+    -- * Show and Read
+    posShowsPrec, posReadPrec,
+  )
+  where
 
 import Data.Loc.Internal.Prelude
 
diff --git a/src/Data/Loc/Span.hs b/src/Data/Loc/Span.hs
--- a/src/Data/Loc/Span.hs
+++ b/src/Data/Loc/Span.hs
@@ -1,32 +1,23 @@
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE LambdaCase #-}
-
 module Data.Loc.Span
-  ( Span
-
-  -- * Constructing
-  , fromTo
-  , fromToMay
+  (
+    Span,
 
-  -- * Querying
-  , start
-  , end
+    -- * Constructing
+    fromTo, fromToMay,
 
-  -- * Calculations
-  , lines
-  , overlapping
-  , linesOverlapping
-  , touching
-  , join
-  , joinAsc
-  , (+)
-  , (-)
+    -- * Querying
+    start, end,
 
-  -- * Show and Read
-  , spanShowsPrec
-  , spanReadPrec
+    -- * Calculations
+    lines,
+    overlapping, linesOverlapping, touching,
+    join, joinAsc,
+    (+), (-),
 
-  ) where
+    -- * Show and Read
+    spanShowsPrec, spanReadPrec,
+  )
+  where
 
 import Data.Loc.Internal.Prelude
 
diff --git a/src/Data/Loc/SpanOrLoc.hs b/src/Data/Loc/SpanOrLoc.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Loc/SpanOrLoc.hs
@@ -0,0 +1,69 @@
+module Data.Loc.SpanOrLoc
+  (
+    SpanOrLoc,
+
+    -- * Constructing
+    span, loc, fromTo,
+
+    -- * Deconstructing
+    spanOrLoc,
+
+    -- * Querying
+    start, end,
+  )
+  where
+
+import Data.Loc.Internal.Prelude
+
+import Data.Loc.Loc (Loc)
+import Data.Loc.Span (Span)
+
+import qualified Data.Loc.Loc as Loc
+import qualified Data.Loc.Span as Span
+
+import Data.Data (Data)
+
+{- |
+
+A 'SpanOrLoc' consists of a start location and an end location.
+The end location must be greater than or equal to the start location;
+in other words, backwards spans are not permitted.
+
+If the start and end location are the same, then the value is a 'Loc'.
+If they differ, then the value is a 'Span'.
+
+-}
+data SpanOrLoc = Span Span | Loc Loc
+  deriving (Data, Eq, Ord)
+
+instance Show SpanOrLoc
+  where
+    showsPrec i = \case
+        Span x -> Span.spanShowsPrec i x
+        Loc x -> Loc.locShowsPrec i x
+
+span :: Span -> SpanOrLoc
+span = Span
+
+loc :: Loc -> SpanOrLoc
+loc = Loc
+
+spanOrLoc :: (Span -> a) -> (Loc -> a) -> SpanOrLoc -> a
+spanOrLoc f _ (Span x) = f x
+spanOrLoc _ f (Loc x) = f x
+
+{- |
+
+Construct a 'SpanOrLoc' from two 'Loc's. If the two locs are not equal,
+the lesser loc will be the start, and the greater loc will be the end.
+
+-}
+fromTo :: Loc -> Loc -> SpanOrLoc
+fromTo a b =
+  maybe (Loc a) Span (Span.fromToMay a b)
+
+start :: SpanOrLoc -> Loc
+start = spanOrLoc Span.start id
+
+end :: SpanOrLoc -> Loc
+end = spanOrLoc Span.end id
diff --git a/src/Data/Loc/Types.hs b/src/Data/Loc/Types.hs
--- a/src/Data/Loc/Types.hs
+++ b/src/Data/Loc/Types.hs
@@ -4,7 +4,15 @@
 
 -}
 module Data.Loc.Types
-  ( Pos, Line, Column, Loc, Span, Area
-  ) where
+  (
+    Pos,
+    Line,
+    Column,
+    Loc,
+    Span,
+    SpanOrLoc,
+    Area,
+  )
+  where
 
 import Data.Loc
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,237 @@
+module Main (main) where
+
+import Data.Loc
+import Data.Loc.Internal.Prelude
+
+import qualified Data.Loc.Pos as Pos
+import qualified Data.Loc.Loc as Loc
+import qualified Data.Loc.Area as Area
+import qualified Data.Loc.Span as Span
+import qualified Data.Loc.List.OneToTwo as OneToTwo
+import qualified Data.Loc.List.ZeroToTwo as ZeroToTwo
+
+import Hedgehog
+import Test.Hspec
+import Test.Hspec.Hedgehog
+
+import qualified Data.List as List
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
+import qualified Test.Loc.Hedgehog.Gen as Gen
+
+import Prelude (fromInteger, ($!), Num (..))
+
+import qualified Data.List.NonEmpty as NonEmpty
+
+main :: IO ()
+main = hspec do
+    posSpec
+    locSpec
+    spanSpec
+    areaSpec
+
+posSpec :: SpecWith ()
+posSpec = describe "Pos" do
+    specify "fromInteger" $ (fromInteger 3 :: Pos) == 3
+    specify "fromInteger underflow" $ (return $! (fromInteger 0 :: Pos)) `shouldThrow` (== Underflow)
+    specify "2 + 3" $ (2 + 3 :: Pos) == 5
+    specify "3 - 2" $ (3 - 2 :: Pos) == 1
+    specify "(-) underflow" $ (return $! (3 - 3 :: Pos)) `shouldThrow` (== Underflow)
+    specify "2 * 3" $ (2 * 3 :: Pos) == 6
+    specify "negate underflow" $ (return $! (negate 3 :: Pos)) `shouldThrow` (== Underflow)
+    specify "toEnum" $ (toEnum 3 :: Pos) == 3
+    specify "toEnum underflow" $ (return $! (toEnum 0 :: Pos)) `shouldThrow` (== Underflow)
+    specify "fromEnum" $ fromEnum (3 :: Pos) == 3
+    specify "show 1" $ Pos.posShowsPrec minPrec 1 "" == "1"
+    specify "show 42" $ Pos.posShowsPrec minPrec 42 "" == "42"
+    specify "read 1" $ readPrec_to_S Pos.posReadPrec minPrec "1" == [(1,"")]
+    specify "read 42" $ readPrec_to_S Pos.posReadPrec minPrec "42" == [(42,"")]
+    specify "read 0" $ readPrec_to_S Pos.posReadPrec minPrec "0" == []
+    specify "read -1" $ readPrec_to_S Pos.posReadPrec minPrec "-1" == []
+
+locSpec :: SpecWith ()
+locSpec = describe "Loc" do
+
+    specify "read and show" $ hedgehog do
+        x <- forAll Gen.loc'
+        read (show x) === x
+
+    specify "read and show examples" $ hedgehog do
+        show Loc.origin === "1:1"
+        Loc.locShowsPrec minPrec (loc 3 14) "" === "3:14"
+        readPrec_to_S Loc.locReadPrec minPrec "3:14" === [(read "3:14","")]
+
+spanSpec :: SpecWith ()
+spanSpec = describe "Span" do
+
+    specify "joinAsc" $ hedgehog do
+        spans <- forAll (Gen.list (Range.linear 1 10) Gen.span')
+        areaSpansAsc (foldMap spanArea spans) === Span.joinAsc (List.sort spans)
+
+    specify "read and show" $ hedgehog do
+        x <- forAll Gen.span'
+        read (show x) === x
+
+    describe "read and show examples" do
+        specify "show 3:14-6:5" $ Span.spanShowsPrec minPrec (Span.fromTo (read "3:14") (read "6:5")) "" == "3:14-6:5"
+        specify "read 3:14-6:5" $ show (readPrec_to_S Span.spanReadPrec minPrec "3:14-6:5") == "[(3:14-6:5,\"\")]"
+        specify "read 6:5-3:14" $ show (readPrec_to_S Span.spanReadPrec minPrec "6:5-3:14") == "[(3:14-6:5,\"\")]"
+        specify "read 6:5-6:5" $ readPrec_to_S Span.spanReadPrec minPrec "6:5-6:5" == []
+
+    describe "lines" do
+        specify "one" $ NonEmpty.toList (Span.lines (read "2:6-2:10")) == [2]
+        specify "many" $ NonEmpty.toList (Span.lines (read "2:6-8:4")) == [2,3,4,5,6,7,8]
+
+    describe "overlapping" do
+        specify "if only touching, no" $ not $ Span.overlapping (read "1:5-1:8") (read "1:8-1:12")
+        specify "on a single line" $ Span.overlapping (read "1:5-1:9") (read "1:8-1:12")
+        specify "one contained within another" $ Span.overlapping (read "1:5-1:15") (read "1:6-1:10")
+        specify "same span" $ hedgehog do
+            x <- forAll Gen.span'
+            assert (Span.overlapping x x)
+
+    describe "linesOverlapping" do
+        specify "same span" $ hedgehog do
+            x <- forAll Gen.span'
+            assert (Span.linesOverlapping x x)
+        specify "multi line" $ Span.linesOverlapping (read "1:1-1:2") (read "1:1-2:1")
+        specify "no" $ not $ Span.linesOverlapping (read "1:1-1:2") (read "2:1-2:2")
+
+    describe "touching" do
+        specify "same span" $ hedgehog do
+            x <- forAll Gen.span'
+            assert (Span.touching x x)
+        specify "barely" $ Span.touching (read "1:1-1:2") (read "1:2-1:3")
+        specify "overlapping" $ Span.touching (read "1:1-1:2") (read "1:1-1:3")
+        specify "no" $ not $ Span.touching (read "1:1-1:2") (read "1:3-1:4")
+
+    describe "join" do
+        specify "touching" $ Span.join (read "1:1-1:2") (read "1:2-1:3") == read "1:1-1:3"
+        specify "overlapping" $ Span.join (read "1:1-1:2") (read "1:1-1:3") == read "1:1-1:3"
+
+    describe "addition" do
+        specify "example 1" $ read "1:1-1:2" Span.+ read "1:2-1:3" == OneToTwo.One (read "1:1-1:3")
+        specify "example 2" $ read "1:1-1:2" Span.+ read "1:1-3:1" == OneToTwo.One (read "1:1-3:1")
+        specify "example 3" $ read "1:1-1:2" Span.+ read "1:1-11:1" == OneToTwo.One (read "1:1-11:1")
+        specify "example 4" $ read "1:1-1:2" Span.+ read "2:1-2:5" == OneToTwo.Two (read "1:1-1:2") (read "2:1-2:5")
+        specify "example 5" $ read "2:1-2:5" Span.+ read "1:1-1:2" == OneToTwo.Two (read "2:1-2:5") (read "1:1-1:2")
+
+    describe "subtraction" do
+        specify "x - x" $ hedgehog do
+            x <- forAll Gen.span'
+            x Span.- x === ZeroToTwo.Zero
+        specify "example 1" $ read "2:5-4:1" Span.- read "2:9-3:5" == ZeroToTwo.Two (read "2:5-2:9") (read "3:5-4:1")
+        specify "example 2" $ read "2:5-4:1" Span.- read "2:5-3:5" == ZeroToTwo.One (read "3:5-4:1")
+        specify "example 3" $ read "2:5-4:1" Span.- read "2:2-3:5" == ZeroToTwo.One (read "3:5-4:1")
+        specify "example 4" $ read "2:5-4:1" Span.- read "2:2-4:4" == ZeroToTwo.Zero
+        specify "example 5" $ read "1:1-8:1" Span.- read "1:2-8:1" == ZeroToTwo.One (read "1:1-1:2")
+
+areaSpec :: SpecWith ()
+areaSpec = describe "Area" do
+
+    specify "add mempty = id for a single span" $ hedgehog do
+        a <- forAll Gen.span'
+        spanArea a Area.+ mempty === spanArea a
+
+    specify "subtract mempty = id for a single span" $ hedgehog do
+        a <- forAll Gen.span'
+        spanArea a Area.- mempty === spanArea a
+
+    specify "addition is commutative" $ hedgehog do
+        a <- forAll Gen.area'
+        b <- forAll Gen.area'
+        a Area.+ b === b Area.+ a
+
+    specify "add mempty = id" $ hedgehog do
+        a <- forAll Gen.area'
+        a Area.+ mempty === a
+
+    specify "subtract mempty = id" $ hedgehog do
+        a <- forAll Gen.area'
+        a Area.- mempty === a
+
+    specify "addition and subtraction" $ hedgehog do
+        a <- forAll Gen.area'
+        b <- forAll Gen.area'
+        c <- forAll Gen.area'
+        a Area.- b Area.- c === a Area.- (b Area.+ c)
+
+    specify "addSpan" $ hedgehog do
+        a <- forAll Gen.area'
+        s <- forAll Gen.span'
+        Area.addSpan s a === areaUnion (spanArea s) a
+
+    specify "fromTo mempty 1" $ hedgehog do
+        x <- forAll Gen.loc'
+        y <- forAll Gen.loc'
+        (Area.fromTo x y == mempty) === (x == y)
+
+    specify "fromTo mempty 2" $ hedgehog do
+        x <- forAll Gen.loc'
+        Area.fromTo x x === mempty
+
+    specify "read and show" $ hedgehog do
+        x <- forAll Gen.area'
+        read (show x) === x
+
+    specify "read and show example 1" $ hedgehog do
+        let x = show (readPrec_to_S Area.areaReadPrec minPrec "[]")
+        x === "[([],\"\")]"
+
+    specify "read and show example 2" $ hedgehog do
+        x <- forAll (Gen.element ["[3:2-5:5,8:3-11:4]", "[3:2-5:5,11:4-8:3]"])
+        let y = show (readPrec_to_S Area.areaReadPrec minPrec x)
+        y === "[([3:2-5:5,8:3-11:4],\"\")]"
+
+    specify "read and show example 3" $ hedgehog do
+        let x = show (readPrec_to_S Area.areaReadPrec minPrec "[3:2-5:5,8:3-8:3]")
+        x === "[]"
+
+    specify "constructed from a single span" $ hedgehog do
+        let x = read "4:5-6:3"
+        spanArea x === read "[4:5-6:3]"
+
+    specify "converted to a span, maybe" $ hedgehog do
+        Area.areaSpan mempty === Nothing
+        Area.areaSpan (read "[3:4-7:2]") === Just (read "3:4-7:2")
+        Area.areaSpan (read "[3:4-7:2,15:6-17:9]") === Just (read "3:4-17:9")
+
+    specify "converted to a list of spans" $ hedgehog do
+        Area.spansAsc mempty === []
+        Area.spansAsc (read "[3:4-7:2,15:6-17:9]") === [read "3:4-7:2", read "15:6-17:9"]
+
+    specify "spanCount" $ hedgehog do
+        Area.spanCount mempty === 0
+        Area.spanCount (read "[3:4-7:2]") === 1
+        Area.spanCount (read "[3:4-7:2,15:6-17:9]") === 2
+
+    specify "firstSpan" $ hedgehog do
+        Area.firstSpan mempty === Nothing
+        Area.firstSpan (read "[3:4-7:2]") === Just (read "3:4-7:2")
+        Area.firstSpan (read "[3:4-7:2,15:6-17:9]") === Just (read "3:4-7:2")
+
+    specify "lastSpan" $ hedgehog do
+        Area.lastSpan mempty === Nothing
+        Area.lastSpan (read "[3:4-7:2]") === Just (read "3:4-7:2")
+        Area.lastSpan (read "[3:4-7:2,15:6-17:9]") === Just (read "15:6-17:9")
+
+    specify "start" $ hedgehog do
+        Area.start mempty === Nothing
+        Area.start (read "[3:4-7:2]") === Just (read "3:4")
+        Area.start (read "[3:4-7:2,15:6-17:9]") === Just (read "3:4")
+
+    specify "end" $ hedgehog do
+        Area.end mempty === Nothing
+        Area.end (read "[3:4-7:2]") === Just (read "7:2")
+        Area.end (read "[3:4-7:2,15:6-17:9]") === Just (read "17:9")
+
+    specify "addition examples" $ hedgehog do
+        read "[1:1-1:2]" Area.+ mempty === read "[1:1-1:2]"
+        read "[1:1-1:2]" Area.+ read "[1:2-1:3]" === read "[1:1-1:3]"
+        read "[1:1-1:2]" Area.+ read "[1:1-3:1]" === read "[1:1-3:1]"
+        read "[1:1-1:2]" Area.+ read "[1:1-11:1]" === read "[1:1-11:1]"
+        read "[1:1-3:1,6:1-6:2]" Area.+ read "[1:1-6:1]" === read "[1:1-6:2]"
+        read "[1:1-3:1]" Area.+ read "[5:1-6:2]" === read "[1:1-3:1,5:1-6:2]"
+
+    specify "addSpan examples" $ hedgehog do
+        Area.addSpan (read "1:1-6:1") (read "[1:1-3:1,6:1-6:2]") === read "[1:1-6:2]"
diff --git a/test/test-with-doctest.hs b/test/test-with-doctest.hs
deleted file mode 100644
--- a/test/test-with-doctest.hs
+++ /dev/null
@@ -1,6 +0,0 @@
-import Prelude
-import Test.DocTest
-
-main :: IO ()
-main =
-  doctest ["-XNoImplicitPrelude", "src"]
diff --git a/test/test-with-hedgehog.hs b/test/test-with-hedgehog.hs
deleted file mode 100644
--- a/test/test-with-hedgehog.hs
+++ /dev/null
@@ -1,118 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-import Data.Loc
-import Data.Loc.Internal.Prelude
-
-import qualified Data.Loc.Area as Area
-import qualified Data.Loc.Span as Span
-
-import Hedgehog
-import System.IO (hSetEncoding, stdout, stderr, utf8)
-
-import qualified Data.List as List
-import qualified Hedgehog.Gen as Gen
-import qualified Hedgehog.Range as Range
-import qualified Test.Loc.Hedgehog.Gen as Gen
-
-main :: IO ()
-main =
-  hSetEncoding stdout utf8 *>
-  hSetEncoding stderr utf8 *>
-  checkParallel $$discover >>= \ok ->
-  when (not ok) exitFailure
-
-prop_Span_add_mempty :: Property
-prop_Span_add_mempty =
-    property $
-    forAll Gen.span' >>= \a ->
-    spanArea a + mempty === spanArea a
-  where
-    (+) = (Area.+)
-
-prop_Span_subtract_mempty :: Property
-prop_Span_subtract_mempty =
-    property $
-    forAll Gen.span' >>= \a ->
-    spanArea a - mempty === spanArea a
-  where
-    (-) = (Area.-)
-
-prop_Area_addition_commutativity :: Property
-prop_Area_addition_commutativity =
-    property $
-    forAll Gen.area' >>= \a ->
-    forAll Gen.area' >>= \b ->
-    a + b === b + a
-  where
-    (+) = (Area.+)
-
-prop_Area_add_mempty :: Property
-prop_Area_add_mempty =
-    property $
-    forAll Gen.area' >>= \a ->
-    a + mempty === a
-  where
-    (+) = (Area.+)
-
-prop_Area_subtract_mempty :: Property
-prop_Area_subtract_mempty =
-    property $
-    forAll Gen.area' >>= \a ->
-    a - mempty === a
-  where
-    (-) = (Area.-)
-
-prop_Area_addition_and_subtraction :: Property
-prop_Area_addition_and_subtraction =
-    property $
-    forAll Gen.area' >>= \a ->
-    forAll Gen.area' >>= \b ->
-    forAll Gen.area' >>= \c ->
-    a - b - c === a - (b + c)
-  where
-    (+) = (Area.+)
-    (-) = (Area.-)
-
-prop_Span_joinAsc :: Property
-prop_Span_joinAsc =
-  property $
-  forAll (Gen.list (Range.linear 1 10) Gen.span') >>= \spans ->
-  areaSpansAsc (foldMap spanArea spans) === Span.joinAsc (List.sort spans)
-
-prop_Area_addSpan :: Property
-prop_Area_addSpan =
-  property $
-  forAll Gen.area' >>= \a ->
-  forAll Gen.span' >>= \s ->
-  Area.addSpan s a === areaUnion (spanArea s) a
-
-prop_Area_fromTo_mempty1 :: Property
-prop_Area_fromTo_mempty1 =
-  property $
-  forAll Gen.loc' >>= \x ->
-  forAll Gen.loc' >>= \y ->
-  (Area.fromTo x y == mempty) === (x == y)
-
-prop_Area_fromTo_mempty2 :: Property
-prop_Area_fromTo_mempty2 =
-  property $
-  forAll Gen.loc' >>= \x ->
-  Area.fromTo x x === mempty
-
-prop_Loc_read_show :: Property
-prop_Loc_read_show =
-  property $
-  forAll Gen.loc' >>= \x ->
-  read (show x) === x
-
-prop_Span_read_show :: Property
-prop_Span_read_show =
-  property $
-  forAll Gen.span' >>= \x ->
-  read (show x) === x
-
-prop_Area_read_show :: Property
-prop_Area_read_show =
-  property $
-  forAll Gen.area' >>= \x ->
-  read (show x) === x
