diff --git a/ascii-predicates.cabal b/ascii-predicates.cabal
--- a/ascii-predicates.cabal
+++ b/ascii-predicates.cabal
@@ -1,11 +1,13 @@
-cabal-version: 2.0
+cabal-version: 3.0
 
 name: ascii-predicates
-version: 1.0.0.8
+version: 1.0.0.10
 synopsis: Various categorizations of ASCII characters
 category: Data, Text
 
-description: This package provides a variety of predicates on the ASCII character set.
+description:
+    This package provides a variety of predicates
+    on the ASCII character set.
 
 license: Apache-2.0
 license-file: license.txt
@@ -13,38 +15,46 @@
 author: Chris Martin
 maintainer: Chris Martin, Julie Moronuki
 
-homepage:    https://github.com/typeclasses/ascii
+homepage: https://github.com/typeclasses/ascii
 bug-Reports: https://github.com/typeclasses/ascii/issues
 
 build-type: Simple
 
-extra-source-files: changelog.txt
-
 source-repository head
-    type:     git
+    type: git
     location: git://github.com/typeclasses/ascii.git
 
-library
+common base
     default-language: Haskell2010
-    default-extensions: NoImplicitPrelude
     ghc-options: -Wall
-    hs-source-dirs: library
 
-    build-depends: ascii-char ^>= 1.0
+    default-extensions:
+        NoImplicitPrelude
 
-    build-depends: base       >= 4.11 && < 4.17
+    build-depends:
+        ascii-char ^>= 1.0
+      , base >= 4.11 && < 4.17
 
-    exposed-modules: ASCII.Predicates
-    exposed-modules: ASCII.Lists
-    exposed-modules: ASCII.ListsAndPredicates
+library
+    import: base
+    hs-source-dirs: library
 
-test-suite test
+    exposed-modules:
+        ASCII.Predicates
+      , ASCII.Lists
+      , ASCII.ListsAndPredicates
+
+test-suite test-ascii-predicates
+    import: base
     type: exitcode-stdio-1.0
-    default-language: Haskell2010
-    default-extensions: NoImplicitPrelude
-    default-extensions: OverloadedStrings
-    default-extensions: QuasiQuotes
-    ghc-options: -Wall
     hs-source-dirs: test
-    main-is: test.hs
-    build-depends: ascii-char, ascii-predicates, base
+    main-is: Main.hs
+
+    default-extensions:
+        OverloadedStrings
+        QuasiQuotes
+        TemplateHaskell
+
+    build-depends:
+        ascii-predicates
+      , hedgehog ^>= 1.0 || ^>= 1.1
diff --git a/changelog.txt b/changelog.txt
deleted file mode 100644
--- a/changelog.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-1.0.0.0 - 2020-05-05 - Initial release
-1.0.0.2 - 2020-05-18 - Support GHC 8.10
-1.0.0.4 - 2021-02-20 - Support GHC 9.0
-1.0.0.6 - 2021-09-26 - Add a test suite
-1.0.0.8 - 2022-01-09 - Support GHC 9.2
diff --git a/library/ASCII/Lists.hs b/library/ASCII/Lists.hs
--- a/library/ASCII/Lists.hs
+++ b/library/ASCII/Lists.hs
@@ -9,10 +9,9 @@
   )
   where
 
-import ASCII.Char ( Char (..) )
-import Data.List  ( (++) )
-import Prelude    ( enumFromTo,
-                    Bounded (..), minBound, maxBound )
+import ASCII.Char (Char (..))
+import Data.List ((++))
+import Prelude (Bounded (..), enumFromTo, maxBound, minBound)
 
 -- | All 128 ASCII characters, listed in order from 'Null' to 'Delete'.
 
diff --git a/library/ASCII/Predicates.hs b/library/ASCII/Predicates.hs
--- a/library/ASCII/Predicates.hs
+++ b/library/ASCII/Predicates.hs
@@ -9,11 +9,11 @@
   )
   where
 
-import ASCII.Char    ( Char (..) )
-import Data.Bool     ( Bool (..), otherwise )
-import Data.Eq       ( (==) )
-import Data.Function ( (.) )
-import Data.Ord      ( (<), (<=), (>=) )
+import ASCII.Char (Char (..))
+import Data.Bool (Bool (..), otherwise)
+import Data.Eq ((==))
+import Data.Function ((.))
+import Data.Ord ((<), (<=), (>=))
 
 import qualified ASCII.Char as Char
 import qualified Data.Bool as Bool
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,142 @@
+module Main (main) where
+
+import ASCII.ListsAndPredicates
+
+import qualified ASCII.Char
+
+import Control.Monad (Monad (..), when)
+import Data.Bool (not)
+import Data.Eq (Eq ((==)))
+import Data.Function (($), (.))
+import Data.List (filter)
+import System.Exit (exitFailure)
+import System.IO (IO)
+
+import qualified Data.Char as Char
+import qualified Data.List as List
+
+import Hedgehog (MonadTest, Property, assert, checkParallel, discover, property,
+                 withTests, (===))
+
+main :: IO ()
+main = checkParallel $$(discover) >>= \ok -> when (not ok) exitFailure
+
+---
+
+lists :: [[ASCII.Char.Char]]
+lists = [ all, printableCharacters, controlCodes, letters,
+          capitalLetters, smallLetters, digits, octDigits,
+          hexDigits, numbers ]
+
+prop_lists_are_sorted :: Property
+prop_lists_are_sorted = withTests 1 $ property $
+    assert $ List.all (\xs -> List.sort xs == xs) lists
+
+---
+
+eq :: (MonadTest m, Eq a) => (ASCII.Char.Char -> a) -> (Char.Char -> a) -> m ()
+eq f g = assert $ List.all (\x -> f x == g (convert x)) ASCII.Char.allCharacters
+  where
+    convert = Char.chr . ASCII.Char.toInt
+
+prop_eq_control :: Property
+prop_eq_control = withTests 1 $ property $
+    eq isControl Char.isControl
+
+prop_eq_space :: Property
+prop_eq_space = withTests 1 $ property $
+    eq isSpace Char.isSpace
+
+prop_eq_lower :: Property
+prop_eq_lower = withTests 1 $ property $
+    eq isLower Char.isLower
+
+prop_eq_upper :: Property
+prop_eq_upper = withTests 1 $ property $
+    eq isUpper Char.isUpper
+
+prop_eq_alpha :: Property
+prop_eq_alpha = withTests 1 $ property $
+    eq isAlpha Char.isAlpha
+
+prop_eq_alphaNum :: Property
+prop_eq_alphaNum = withTests 1 $ property $
+    eq isAlphaNum Char.isAlphaNum
+
+prop_eq_print :: Property
+prop_eq_print = withTests 1 $ property $
+    eq isPrint Char.isPrint
+
+prop_eq_digit :: Property
+prop_eq_digit = withTests 1 $ property $
+    eq isDigit Char.isDigit
+
+prop_eq_octDigit :: Property
+prop_eq_octDigit = withTests 1 $ property $
+    eq isOctDigit Char.isOctDigit
+
+prop_eq_hexDigit :: Property
+prop_eq_hexDigit = withTests 1 $ property $
+    eq isHexDigit Char.isHexDigit
+
+prop_eq_letter :: Property
+prop_eq_letter = withTests 1 $ property $
+    eq isLetter Char.isLetter
+
+prop_eq_mark :: Property
+prop_eq_mark = withTests 1 $ property $
+    eq isMark Char.isMark
+
+prop_eq_number :: Property
+prop_eq_number = withTests 1 $ property $
+    eq isNumber Char.isNumber
+
+prop_eq_punctuation :: Property
+prop_eq_punctuation = withTests 1 $ property $
+    eq isPunctuation Char.isPunctuation
+
+prop_eq_symbol :: Property
+prop_eq_symbol = withTests 1 $ property $
+    eq isSymbol Char.isSymbol
+
+prop_eq_separator :: Property
+prop_eq_separator = withTests 1 $ property $
+    eq isSeparator Char.isSeparator
+
+---
+
+prop_list_control :: Property
+prop_list_control = withTests 1 $ property $
+    controlCodes === filter isControl all
+
+prop_list_printable :: Property
+prop_list_printable = withTests 1 $ property $
+    printableCharacters === filter isPrint all
+
+prop_list_letter :: Property
+prop_list_letter = withTests 1 $ property $
+    letters === filter isLetter all
+
+prop_list_capital :: Property
+prop_list_capital = withTests 1 $ property $
+    capitalLetters === filter isUpper all
+
+prop_list_small :: Property
+prop_list_small = withTests 1 $ property $
+    smallLetters === filter isLower all
+
+prop_list_digit :: Property
+prop_list_digit = withTests 1 $ property $
+    digits === filter isDigit all
+
+prop_list_number :: Property
+prop_list_number = withTests 1 $ property $
+    numbers === filter isNumber all
+
+prop_list_oct :: Property
+prop_list_oct = withTests 1 $ property $
+    octDigits === filter isOctDigit all
+
+prop_list_hex :: Property
+prop_list_hex = withTests 1 $ property $
+    hexDigits === filter isHexDigit all
diff --git a/test/test.hs b/test/test.hs
deleted file mode 100644
--- a/test/test.hs
+++ /dev/null
@@ -1,89 +0,0 @@
-module Main where
-
-import ASCII.ListsAndPredicates
-
-import qualified ASCII.Char
-
-import Control.Applicative (Applicative (..))
-import Control.Monad (Monad (..))
-import Data.Bool (Bool (..))
-import Data.Eq (Eq ((==)))
-import Data.Function ((.), ($))
-import Data.Functor (Functor (..))
-import Data.List (filter, intercalate, map, null)
-import Data.Semigroup ((<>))
-import Numeric.Natural (Natural)
-import System.Exit (die)
-import System.IO (IO, putStrLn)
-import Text.Show (show)
-
-import qualified Data.Char as Char
-import qualified Data.List as List
-
-main :: IO ()
-main = dieIfFailures $ do
-
-    do
-        let lists = [ all, printableCharacters, controlCodes, letters,
-                      capitalLetters, smallLetters, digits, octDigits,
-                      hexDigits, numbers]
-
-        test 1 $ List.all (\xs -> List.sort xs == xs) lists
-
-    do
-        let convert = Char.chr . ASCII.Char.toInt
-            eq f g = List.all (\x -> f x == g (convert x))
-                              ASCII.Char.allCharacters
-
-        test 2 $ eq isControl Char.isControl
-        test 3 $ eq isSpace Char.isSpace
-        test 4 $ eq isLower Char.isLower
-        test 5 $ eq isUpper Char.isUpper
-        test 6 $ eq isAlpha Char.isAlpha
-        test 7 $ eq isAlphaNum Char.isAlphaNum
-        test 8 $ eq isPrint Char.isPrint
-        test 9 $ eq isDigit Char.isDigit
-        test 10 $ eq isOctDigit Char.isOctDigit
-        test 11 $ eq isHexDigit Char.isHexDigit
-        test 12 $ eq isLetter Char.isLetter
-        test 13 $ eq isMark Char.isMark
-        test 14 $ eq isNumber Char.isNumber
-        test 15 $ eq isPunctuation Char.isPunctuation
-        test 16 $ eq isSymbol Char.isSymbol
-        test 17 $ eq isSeparator Char.isSeparator
-
-    test 18 $ controlCodes == filter isControl all
-    test 19 $ printableCharacters == filter isPrint all
-    test 20 $ letters == filter isLetter all
-    test 21 $ capitalLetters == filter isUpper all
-    test 22 $ smallLetters == filter isLower all
-    test 23 $ digits == filter isDigit all
-    test 24 $ numbers == filter isNumber all
-    test 25 $ octDigits == filter isOctDigit all
-    test 26 $ hexDigits == filter isHexDigit all
-
-dieIfFailures :: Failures a -> IO a
-dieIfFailures (Failures fs x) =
-    if null fs
-        then do putStrLn "💯"; return x
-        else die $ intercalate " " (map (("🔥" <> ) . show) fs)
-
-type TestNumber = Natural
-
-test :: TestNumber -> Bool -> Failures ()
-test n t = Failures (if t then [] else [n]) ()
-
-data Failures a = Failures [TestNumber] a
-
-instance Functor Failures
-  where
-    fmap f (Failures a x) = Failures a (f x)
-
-instance Applicative Failures
-  where
-    pure x = Failures [] x
-    Failures a f <*> Failures b x = Failures (a <> b) (f x)
-
-instance Monad Failures
-  where
-    Failures a x >>= f = let Failures b y = f x in Failures (a <> b) y
