diff --git a/ascii.cabal b/ascii.cabal
--- a/ascii.cabal
+++ b/ascii.cabal
@@ -1,7 +1,7 @@
 cabal-version: 3.0
 
 name: ascii
-version: 1.4.1.0
+version: 1.4.1.1
 synopsis: The ASCII character set and encoding
 category: Data, Text
 
@@ -28,7 +28,7 @@
     ghc-options: -Wall
     default-extensions: NoImplicitPrelude
     build-depends:
-        ascii-caseless == 0.0.0.*
+      , ascii-caseless == 0.0.0.*
       , ascii-char == 1.0.1.*
       , ascii-group == 1.0.0.*
       , ascii-case == 1.0.1.*
@@ -46,7 +46,7 @@
     default-extensions: RankNTypes ScopedTypeVariables TypeApplications
     exposed-modules: ASCII
     reexported-modules:
-        ASCII.Case
+      , ASCII.Case
       , ASCII.Caseless
       , ASCII.CaseRefinement
       , ASCII.Char
@@ -69,7 +69,7 @@
     type: exitcode-stdio-1.0
     hs-source-dirs: test
     main-is: Main.hs
-    default-extensions: OverloadedStrings QuasiQuotes TemplateHaskell
+    default-extensions: OverloadedStrings QuasiQuotes
     build-depends:
-        ascii
-      , hedgehog ^>= 1.0.5 || ^>= 1.1 || ^>= 1.2
+      , ascii
+      , hspec ^>= 2.8.5 || ^>= 2.9 || ^>= 2.10
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,7 @@
+### 1.4.1.1 (2023-01-05)
+
+Change test suite from `hedgehog` to `hspec`
+
 ### 1.4.1.0 (2023-01-05)
 
 Raise `ascii-char` version to `1.0.1`. This adds `Word8` conversions to the
diff --git a/library/ASCII.hs b/library/ASCII.hs
--- a/library/ASCII.hs
+++ b/library/ASCII.hs
@@ -35,16 +35,16 @@
 
     {- * Character classifications -}
     {- ** Print/control groups -} {- $groups -}
-    Group (..), charGroup,  inGroup,
+    Group (..), charGroup, inGroup,
     {- ** Upper/lower case -} {- $case -}
     Case (..), letterCase, isCase, toCaseChar, toCaseString,
     disregardCase, refineCharToCase, refineStringToCase,
-    {- ** Letters  -} isLetter,
-    {- ** Letters and numbers  -} isAlphaNum,
+    {- ** Letters -} isLetter,
+    {- ** Letters and numbers -} isAlphaNum,
     {- ** Decimal digits -} {- $digit -} isDigit, Digit,
     {- ** Hexadecimal digits -} {- $hexchar -} isHexDigit, HexChar,
     {- ** Octal digits -} isOctDigit,
-    {- ** Spaces and symbols   -} isSpace, isPunctuation, isSymbol, isVisible,
+    {- ** Spaces and symbols -} isSpace, isPunctuation, isSymbol, isVisible,
 
     {- * Monomorphic character conversions -} {- $monomorphicConversions -}
     {- ** @ASCII.Char@ ↔ @Int@ -} {- $intConversions -}
@@ -158,145 +158,127 @@
 
 {- $char
 
-See also: "ASCII.Char"
-
--}
+See also: "ASCII.Char" -}
 
 {- $caselessChar
 
-See also: "ASCII.Caseless"
+See also: "ASCII.Caseless" -}
 
--}
+{-| A character in the full range of Unicode
 
--- | A character in the full range of Unicode
---
--- ASCII 'Char' is a subset of this type. Convert using 'charToUnicode' and 'unicodeToCharMaybe'.
+ASCII 'Char' is a subset of this type. Convert using 'charToUnicode' and
+'unicodeToCharMaybe'. -}
 type UnicodeChar = Unicode.Char
 
 {- $groups
 
-ASCII characters are broadly categorized into two groups: /control codes/ and /printable characters/.
-
-See also: "ASCII.Group"
-
--}
-
+ASCII characters are broadly categorized into two groups: /control codes/ and
+/printable characters/.
 
-{- | Determine which group a particular character belongs to.
+See also: "ASCII.Group" -}
 
->>> map charGroup [CapitalLetterA,EndOfTransmission]
-[Printable,Control]
+{-| Determine which group a particular character belongs to
 
--}
+@
+charGroup CapitalLetterA == Printable
 
+charGroup EndOfTransmission == Control
+@ -}
 charGroup :: CharIso char => char -> Group
 charGroup = ASCII.Group.charGroup . ASCII.Isomorphism.toChar
 
-{- | Test whether a character belongs to a particular ASCII group.
-
->>> inGroup Printable EndOfTransmission
-False
-
->>> inGroup Control EndOfTransmission
-True
+{-| Test whether a character belongs to a particular ASCII group
 
->>> map (inGroup Printable) ( [-1, 5, 65, 97, 127, 130] :: [Int] )
-[False,False,True,True,False,False]
+@
+not (inGroup Printable EndOfTransmission)
 
--}
+inGroup Control EndOfTransmission
 
+map (inGroup Printable) ([-1, 5, 65, 97, 127, 130] :: [Int])
+    == [False, False, True, True, False, False]
+@ -}
 inGroup :: CharSuperset char => Group -> char -> Bool
 inGroup g = maybe False (ASCII.Group.inGroup g) . ASCII.Superset.toCharMaybe
 
 {- $case
 
-/Case/ is a property of letters. /A-Z/ are /upper case/ letters, and /a-z/ are /lower case/ letters. No other ASCII characters have case.
-
-See also: "ASCII.Case"
-
--}
-
-{- | Determines whether a character is an ASCII letter, and if so, whether it is upper or lower case.
+/Case/ is a property of letters. /A-Z/ are /upper case/ letters, and /a-z/ are
+/lower case/ letters. No other ASCII characters have case.
 
->>> map letterCase [SmallLetterA, CapitalLetterA, ExclamationMark]
-[Just LowerCase,Just UpperCase,Nothing]
+See also: "ASCII.Case" -}
 
->>> map letterCase ( [string|Hey!|] :: [ASCII Word8] )
-[Just UpperCase,Just LowerCase,Just LowerCase,Nothing]
+{-| Determines whether a character is an ASCII letter, and if so, whether it is
+    upper or lower case
 
--}
+@
+map letterCase [SmallLetterA, CapitalLetterA, ExclamationMark]
+    == [Just LowerCase, Just UpperCase, Nothing]
 
+map letterCase ([string|Hey!|] :: [ASCII Word8])
+    == [Just UpperCase, Just LowerCase, Just LowerCase, Nothing]
+@ -}
 letterCase :: CharSuperset char => char -> Maybe Case
 letterCase = ASCII.Superset.toCharMaybe >=> ASCII.Case.letterCase
 
-{- | Determines whether a character is an ASCII letter of a particular case.
-
->>> map (isCase UpperCase) [SmallLetterA, CapitalLetterA, ExclamationMark]
-[False,True,False]
-
->>> map (isCase UpperCase) ( [string|Hey!|] :: [ASCII Word8] )
-[True,False,False,False]
+{-| Determines whether a character is an ASCII letter of a particular case
 
->>> map (isCase UpperCase) ( [-1, 65, 97, 150] :: [Int] )
-[False,True,False,False]
+@
+map (isCase UpperCase) [SmallLetterA, CapitalLetterA, ExclamationMark]
+    == [False, True, False]
 
--}
+map (isCase UpperCase) ([string|Hey!|] :: [ASCII Word8])
+    == [True, False, False, False]
 
+map (isCase UpperCase) ([-1, 65, 97, 150] :: [Int])
+    == [False, True, False, False]
+@ -}
 isCase :: CharSuperset char => Case -> char -> Bool
 isCase c = maybe False (ASCII.Case.isCase c) . ASCII.Superset.toCharMaybe
 
-{- | Maps a letter character to its upper/lower case equivalent.
-
->>> toCaseChar UpperCase SmallLetterA
-CapitalLetterA
-
->>> ([char|a|] :: ASCII Word8, toCaseChar UpperCase [char|a|] :: ASCII Word8)
-(asciiUnsafe 97,asciiUnsafe 65)
+{-| Maps a letter character to its upper/lower case equivalent
 
--}
+@
+toCaseChar UpperCase SmallLetterA == CapitalLetterA
 
+(                     [char|a|] :: ASCII Word8) == asciiUnsafe 97
+(toCaseChar UpperCase [char|a|] :: ASCII Word8) == asciiUnsafe 65
+@ -}
 toCaseChar :: CharSuperset char => Case -> char -> char
 toCaseChar = ASCII.Superset.toCaseChar
 
-{- | Maps each of the characters in a string to its upper/lower case equivalent.
-
->>> toCaseString UpperCase [CapitalLetterH,SmallLetterE,SmallLetterY,ExclamationMark]
-[CapitalLetterH,CapitalLetterE,CapitalLetterY,ExclamationMark]
-
->>> toCaseString UpperCase [string|Hey!|] :: ASCII Text
-asciiUnsafe "HEY!"
+{-| Maps each of the characters in a string to its upper/lower case equivalent
 
--}
+@
+toCaseString UpperCase [CapitalLetterH, SmallLetterE, SmallLetterY, ExclamationMark]
+    == [CapitalLetterH, CapitalLetterE, CapitalLetterY, ExclamationMark]
 
+(toCaseString UpperCase [string|Hey!|] :: ASCII Text) == asciiUnsafe "HEY!"
+@ -}
 toCaseString :: StringSuperset string => Case -> string -> string
 toCaseString = ASCII.Superset.toCaseString
 
 {-| Convert from ASCII character to caseless ASCII character, discarding the
-case if the character is a letter -}
+    case if the character is a letter -}
 disregardCase :: Char -> CaselessChar
 disregardCase = ASCII.Caseless.disregardCase
 
 {- $monomorphicConversions
 
-These are a few simple monomorphic functions to convert between ASCII and types representing some other character set.
-
-This is not intended to be an exhaustive list of all possible conversions. For more options, see "ASCII.Superset".
+These are a few simple monomorphic functions to convert between ASCII and types
+representing some other character set.
 
--}
+This is not intended to be an exhaustive list of all possible conversions. For
+more options, see "ASCII.Superset". -}
 
 {- $intConversions
 
-These functions convert between the ASCII 'Char' type and 'Int'.
-
--}
-
-{- |
-
->>> map charToInt [Null, CapitalLetterA, SmallLetterA, Delete]
-[0,65,97,127]
+These functions convert between the ASCII 'Char' type and 'Int'. -}
 
--}
+{-|
 
+@
+map charToInt [Null, CapitalLetterA, SmallLetterA, Delete] == [0, 65, 97, 127]
+@ -}
 charToInt :: Char -> Int
 charToInt = ASCII.Superset.fromChar
 
@@ -308,17 +290,13 @@
 
 {- $word8Conversions
 
-These functions convert between the ASCII 'Char' type and 'Word8'.
-
--}
-
-{- |
-
->>> map charToWord8 [Null, CapitalLetterA, SmallLetterA, Delete]
-[0,65,97,127]
+These functions convert between the ASCII 'Char' type and 'Word8'. -}
 
--}
+{-|
 
+@
+map charToWord8 [Null, CapitalLetterA, SmallLetterA, Delete] == [0, 65, 97, 127]
+@ -}
 charToWord8 :: Char -> Word8
 charToWord8 = ASCII.Superset.fromChar
 
@@ -330,9 +308,8 @@
 
 {- $unicodeCharConversions
 
-These functions convert between the ASCII 'Char' type and the 'UnicodeChar' type.
-
--}
+These functions convert between the ASCII 'Char' type and the 'UnicodeChar'
+type. -}
 
 charToUnicode :: Char -> UnicodeChar
 charToUnicode = ASCII.Superset.fromChar
@@ -345,11 +322,11 @@
 
 {- $digitWord8Conversions
 
-These functions convert between the ASCII 'Digit' type and ASCII digits in their byte encoding.
-
-These conversions do /not/ correspond to the numeric interpretations of 'Digit' and 'Word8'. For example, 'digitToWord8' 'ASCII.Decimal.Digit0' is 48, not 0.
+These functions convert between the ASCII 'Digit' type and ASCII digits in their
+byte encoding.
 
--}
+These conversions do /not/ correspond to the numeric interpretations of 'Digit'
+and 'Word8'. For example, 'digitToWord8' 'ASCII.Decimal.Digit0' is 48, not 0. -}
 
 digitToWord8 :: Digit -> Word8
 digitToWord8 = ASCII.Decimal.fromDigit
@@ -362,9 +339,8 @@
 
 {- $digitCharConversions
 
-These functions convert between the ASCII 'Digit' type and the ASCII 'Char' type.
-
--}
+These functions convert between the ASCII 'Digit' type and the ASCII 'Char'
+type. -}
 
 digitToChar :: Digit -> Char
 digitToChar = ASCII.Decimal.fromDigit
@@ -377,9 +353,8 @@
 
 {- $digitUnicodeConversions
 
-These functions convert between the ASCII 'Digit' type and the 'UnicodeChar' type.
-
--}
+These functions convert between the ASCII 'Digit' type and the 'UnicodeChar'
+type. -}
 
 digitToUnicode :: Digit -> UnicodeChar
 digitToUnicode = ASCII.Decimal.fromDigit
@@ -393,11 +368,12 @@
 
 {- $hexCharWord8Conversions
 
-These functions convert between the ASCII 'HexChar' type and ASCII characters in their byte encoding.
-
-These conversions do /not/ correspond to the numeric interpretations of 'HexChar' and 'Word8'. For example, 'hexCharToWord8' 'ASCII.Hexadecimal.CapitalLetterA' is 65, not 10.
+These functions convert between the ASCII 'HexChar' type and ASCII characters in
+their byte encoding.
 
--}
+These conversions do /not/ correspond to the numeric interpretations of
+'HexChar' and 'Word8'. For example, 'hexCharToWord8'
+'ASCII.Hexadecimal.CapitalLetterA' is 65, not 10. -}
 
 hexCharToWord8 :: HexChar -> Word8
 hexCharToWord8 = ASCII.Hexadecimal.fromHexChar
@@ -410,9 +386,8 @@
 
 {- $hexCharCharConversions
 
-These functions convert between the ASCII 'HexChar' type and the ASCII 'Char' type.
-
--}
+These functions convert between the ASCII 'HexChar' type and the ASCII 'Char'
+type. -}
 
 hexCharToChar :: HexChar -> Char
 hexCharToChar = ASCII.Hexadecimal.fromHexChar
@@ -425,9 +400,8 @@
 
 {- $hexCharUnicodeConversions
 
-These functions convert between the ASCII 'HexChar' type and the 'UnicodeChar' type.
-
--}
+These functions convert between the ASCII 'HexChar' type and the 'UnicodeChar'
+type. -}
 
 hexCharToUnicode :: HexChar -> UnicodeChar
 hexCharToUnicode = ASCII.Hexadecimal.fromHexChar
@@ -440,9 +414,8 @@
 
 {- $unicodeStringConversions
 
-These functions convert between @['Char']@ (a list of ASCII characters) and 'Unicode.String' (a list of Unicode characters).
-
--}
+These functions convert between @['Char']@ (a list of ASCII characters) and
+'Unicode.String' (a list of Unicode characters). -}
 
 charListToUnicodeString :: [Char] -> Unicode.String
 charListToUnicodeString = ASCII.Superset.fromCharList
@@ -455,17 +428,13 @@
 
 {- $textConversions
 
-These functions convert between @['Char']@ and 'Text.Text'.
-
--}
-
-{- |
-
->>> charListToText [CapitalLetterH,SmallLetterI,ExclamationMark]
-"Hi!"
+These functions convert between @['Char']@ and 'Text.Text'. -}
 
--}
+{-|
 
+@
+charListToText [CapitalLetterH, SmallLetterI, ExclamationMark] == "Hi!"
+@ -}
 charListToText :: [Char] -> Text.Text
 charListToText = ASCII.Superset.fromCharList
 
@@ -477,9 +446,7 @@
 
 {- $byteStringConversions
 
-These functions convert between @['Char']@ and 'BS.ByteString'.
-
--}
+These functions convert between @['Char']@ and 'BS.ByteString'. -}
 
 charListToByteString :: [Char] -> BS.ByteString
 charListToByteString = ASCII.Superset.fromCharList
@@ -492,40 +459,36 @@
 
 {- $refinement
 
-See also: "ASCII.Refinement", "ASCII.CaseRefinement"
-
--}
+See also: "ASCII.Refinement", "ASCII.CaseRefinement" -}
 
 {- $lift
 
-See also: "ASCII.Lift"
-
--}
+See also: "ASCII.Lift" -}
 
-{- | Converts from ASCII to any larger type.
+{-| Converts from ASCII to any larger type
 
 For example, @(lift \@ASCII.Char \@Word8)@ is the same function as 'charToWord8'.
 
->>> lift CapitalLetterA :: Word8
-65
+@
+(lift CapitalLetterA :: Word8) == 65
+@
 
 And @(lift \@[ASCII.Char] \@Text)@ is equivalent to 'charListToText'.
 
->>> lift [CapitalLetterH,SmallLetterI,ExclamationMark] :: Text
-"Hi!"
-
-Due to the highly polymorphic nature of the 'lift' function, often it must used with an explicit type signature or type application to avoid any type ambiguity.
+@
+(lift [CapitalLetterH,SmallLetterI,ExclamationMark] :: Text) == "Hi!"
+@
 
+Due to the highly polymorphic nature of the 'lift' function, often it must used
+with an explicit type signature or type application to avoid any type ambiguity.
 -}
-
 lift :: Lift ascii superset => ascii -> superset
 lift = ASCII.Lift.lift
 
 {- $supersetConversions
 
-These functions all convert from one ASCII-superset type to another, failing if any of the characters in the input is outside the ASCII character set.
-
--}
+These functions all convert from one ASCII-superset type to another, failing if
+any of the characters in the input is outside the ASCII character set. -}
 
 convertCharMaybe :: (CharSuperset char1, CharSuperset char2) => char1 -> Maybe char2
 convertCharMaybe = ASCII.Superset.convertCharMaybe
@@ -541,18 +504,19 @@
 
 {- $monoSupersetConversions
 
-These functions are all specializations of 'convertStringMaybe'. They convert a string from one ASCII-superset type to another.
-
->>> ASCII.byteListToUnicodeStringMaybe [0x48, 0x54, 0x54, 0x50]
-Just "HTTP"
-
-If any of the characters in the input is outside the ASCII character set, the result is 'Nothing'.
+These functions are all specializations of 'convertStringMaybe'.
+They convert a string from one ASCII-superset type to another.
 
->>> ASCII.byteListToUnicodeStringMaybe [0x48, 0x54, 0x54, 0x80]
-Nothing
+@
+ASCII.byteListToUnicodeStringMaybe [0x48, 0x54, 0x54, 0x50] == Just "HTTP"
+@
 
--}
+If any of the characters in the input is outside the ASCII character set, the
+result is 'Nothing'.
 
+@
+ASCII.byteListToUnicodeStringMaybe [0x48, 0x54, 0x54, 0x80] == Nothing
+@ -}
 byteStringToUnicodeStringMaybe :: BS.ByteString -> Maybe Unicode.String
 byteStringToUnicodeStringMaybe = convertStringMaybe
 
@@ -565,59 +529,48 @@
 unicodeStringToByteListMaybe :: Unicode.String -> Maybe [Word8]
 unicodeStringToByteListMaybe = convertStringMaybe
 
-{- | Returns True for ASCII letters:
+{-| Returns True for ASCII letters:
 
 - 'ASCII.Char.SmallLetterA' to 'ASCII.Char.SmallLetterZ'
-- 'ASCII.Char.CapitalLetterA' to 'ASCII.Char.CapitalLetterZ'
-
--}
-
+- 'ASCII.Char.CapitalLetterA' to 'ASCII.Char.CapitalLetterZ' -}
 isLetter :: CharSuperset char => char -> Bool
 isLetter x = any ASCII.Predicates.isLetter (convertCharMaybe x)
 
-{- | Returns True for the characters from 'ASCII.Char.Digit0' to 'ASCII.Char.Digit9'. -}
-
+{-| Returns True for the characters from 'ASCII.Char.Digit0' to
+'ASCII.Char.Digit9'. -}
 isDigit :: CharSuperset char => char -> Bool
 isDigit x = any ASCII.Predicates.isDigit (convertCharMaybe x)
 
-
-{- | Returns True for the characters from 'ASCII.Char.Digit0' to 'ASCII.Char.Digit7'. -}
-
+{-| Returns True for the characters from 'ASCII.Char.Digit0' to
+'ASCII.Char.Digit7'. -}
 isOctDigit :: CharSuperset char => char -> Bool
 isOctDigit x = any ASCII.Predicates.isOctDigit (convertCharMaybe x)
 
-
-{- | Returns True for characters in any of the following ranges:
+{-| Returns True for characters in any of the following ranges:
 
 - 'ASCII.Char.Digit0' to 'ASCII.Char.Digit9'
 - 'ASCII.Char.CapitalLetterA' to 'ASCII.Char.CapitalLetterF'
-- 'ASCII.Char.SmallLetterA' to 'ASCII.Char.SmallLetterF'
-
--}
-
+- 'ASCII.Char.SmallLetterA' to 'ASCII.Char.SmallLetterF' -}
 isHexDigit :: CharSuperset char => char -> Bool
 isHexDigit x = any ASCII.Predicates.isHexDigit (convertCharMaybe x)
 
-{- | Returns True for the following characters:
+{-| Returns True for the following characters:
 
 - 'ASCII.Char.Space'
 - 'ASCII.Char.HorizontalTab'
 - 'ASCII.Char.LineFeed'
 - 'ASCII.Char.VerticalTab'
 - 'ASCII.Char.FormFeed'
-- 'ASCII.Char.CarriageReturn'
-
--}
-
+- 'ASCII.Char.CarriageReturn' -}
 isSpace :: CharSuperset char => char -> Bool
 isSpace x = any ASCII.Predicates.isSpace (convertCharMaybe x)
 
-{- | Returns True if the character is either an ASCII letter ('isLetter') or an ASCII digit ('isDigit'). -}
-
+{-| Returns True if the character is either an ASCII letter ('isLetter') or an
+ASCII digit ('isDigit'). -}
 isAlphaNum :: CharSuperset char => char -> Bool
 isAlphaNum x = any ASCII.Predicates.isAlphaNum (convertCharMaybe x)
 
-{- | Returns True for the following characters:
+{-| Returns True for the following characters:
 
 - 'ASCII.Char.ExclamationMark'
 - 'ASCII.Char.QuotationMark'
@@ -641,14 +594,11 @@
 - 'ASCII.Char.RightSquareBracket'
 - 'ASCII.Char.Underscore'
 - 'ASCII.Char.LeftCurlyBracket'
-- 'ASCII.Char.RightCurlyBracket'
-
--}
-
+- 'ASCII.Char.RightCurlyBracket' -}
 isPunctuation :: CharSuperset char => char -> Bool
 isPunctuation x = any ASCII.Predicates.isPunctuation (convertCharMaybe x)
 
-{- | Returns True for the following characters:
+{-| Returns True for the following characters:
 
 - 'ASCII.Char.DollarSign'
 - 'ASCII.Char.PlusSign'
@@ -658,61 +608,46 @@
 - 'ASCII.Char.Caret'
 - 'ASCII.Char.GraveAccent'
 - 'ASCII.Char.VerticalLine'
-- 'ASCII.Char.Tilde'
-
--}
-
+- 'ASCII.Char.Tilde' -}
 isSymbol :: CharSuperset char => char -> Bool
 isSymbol x = any ASCII.Predicates.isSymbol (convertCharMaybe x)
 
-{- | Returns True for visible characters.
-
-This includes all print characters except 'ASCII.Char.Space'.
-
--}
+{- |Returns True for visible characters
 
+This includes all print characters except 'ASCII.Char.Space'. -}
 isVisible :: CharSuperset char => char -> Bool
 isVisible x = any ASCII.Predicates.isVisible (convertCharMaybe x)
 
 {- $numbers
 
-See also: "ASCII.Decimal" and "ASCII.Hexadecimal"
-
--}
-
-{- |
-
-Gives the ASCII string representation of an integer in decimal (base 10)
-notation, using digits 'ASCII.Char.Digit0' through 'ASCII.Char.Digit9',
-leading with 'ASCII.Char.HyphenMinus' for negative numbers.
-
-For example, @'showIntegralDecimal' (-512 :: 'Prelude.Integer')@ = @"-512"@.
+See also: "ASCII.Decimal" and "ASCII.Hexadecimal" -}
 
--}
+{-| Gives the ASCII string representation of an integer in decimal (base 10)
+    notation, using digits 'ASCII.Char.Digit0' through 'ASCII.Char.Digit9',
+    leading with 'ASCII.Char.HyphenMinus' for negative numbers
 
+For example, @'showIntegralDecimal' (-512 :: 'Prelude.Integer')@ = @"-512"@. -}
 showIntegralDecimal :: (Integral n, StringSuperset string) => n -> string
 showIntegralDecimal = ASCII.Decimal.showIntegral
 
-{- |
+{-| Gives the ASCII string representation of an integer in hexadecimal (base 16)
+    notation
 
-Gives the ASCII string representation of an integer in hexadecimal (base 16)
-notation, using digits 'ASCII.Char.Digit0' through 'ASCII.Char.Digit9', for
-digits 0 though 9. The representation of digits 10 to 15 is determined by the
-value of 'Case' parameter: 'UpperCase' means 'ASCII.Char.CapitalLetterA' to
+The characters 'ASCII.Char.Digit0' through 'ASCII.Char.Digit9' represent digits
+0 though 9. The representation of digits 10 to 15 is determined by the value of
+'Case' parameter: 'UpperCase' means 'ASCII.Char.CapitalLetterA' to
 'ASCII.Char.CapitalLetterF', and 'LowerCase' means 'ASCII.Char.SmallLetterA' to
 'ASCII.Char.SmallLetterF'. For negative numbers, the resulting string begins
 with 'ASCII.Char.HyphenMinus'.
 
-For example, @'showIntegralHexadecimal' 'UpperCase' ('Prelude.negate' (256 + 12) :: 'Prelude.Integer')@ = @"-10C"@.
-
--}
-
-showIntegralHexadecimal :: (Integral n, StringSuperset string) => Case -> n -> string
+@
+'showIntegralHexadecimal' 'UpperCase' ('Prelude.negate' (256 + 12) :: 'Prelude.Integer') == "-10C"
+@ -}
+showIntegralHexadecimal :: (Integral n, StringSuperset string) =>
+    Case -> n -> string
 showIntegralHexadecimal = ASCII.Hexadecimal.showIntegral
 
-{- |
-
-Roughly the inverse of 'showIntegralDecimal'
+{-| Roughly the inverse of 'showIntegralDecimal'
 
 * Leading zeroes are accepted, as in @"0074"@ and @"-0074"@
 
@@ -720,16 +655,13 @@
 
 * If the input is empty
 * If the input contains any other extraneous characters
-* If the resulting number would be outside the range supported by the 'Integral' (determined by its 'Bits' instance)
-
--}
-
-readIntegralDecimal :: (StringSuperset string, Integral number, Bits number) => string -> Maybe number
+* If the resulting number would be outside the range supported by the 'Integral'
+  (determined by its 'Bits' instance) -}
+readIntegralDecimal :: (StringSuperset string, Integral number, Bits number) =>
+    string -> Maybe number
 readIntegralDecimal = ASCII.Decimal.readIntegral
 
-{- |
-
-Roughly the inverse of 'showIntegralHexadecimal'
+{-| Roughly the inverse of 'showIntegralHexadecimal'
 
 * Upper and lower case letters are treated equally
 * Leading zeroes are accepted, as in @"006a"@ and @"-006a"@
@@ -738,60 +670,48 @@
 
 * If the input is empty
 * If the input contains any other extraneous characters
-* If the resulting number would be outside the range supported by the 'Integral' (determined by its 'Bits' instance)
-
--}
-
-readIntegralHexadecimal :: (StringSuperset string, Integral number, Bits number) => string -> Maybe number
+* If the resulting number would be outside the range supported by the 'Integral'
+  (determined by its 'Bits' instance) -}
+readIntegralHexadecimal :: (StringSuperset string, Integral number, Bits number) =>
+    string -> Maybe number
 readIntegralHexadecimal = ASCII.Hexadecimal.readIntegral
 
-{- |
-
-Gives the ASCII string representation of an natural number in decimal (base 10)
-notation, using digits 'ASCII.Char.Digit0' through 'ASCII.Char.Digit9'.
-
-For example, @'showNaturalDecimal' 512@ = @"512"@.
-
--}
+{-| Gives the ASCII string representation of an natural number in decimal
+    (base 10) notation, using digits 'ASCII.Char.Digit0' through 'ASCII.Char.Digit9'
 
+@
+'showNaturalDecimal' 512 == @"512"
+@ -}
 showNaturalDecimal :: DigitStringSuperset string => Natural -> string
 showNaturalDecimal = ASCII.Decimal.showNatural
 
-{- |
+{-| Gives the ASCII string representation of an integer in hexadecimal (base 16)
+    notation
 
-Gives the ASCII string representation of an integer in hexadecimal (base 16)
-notation, using digits 'ASCII.Char.Digit0' through 'ASCII.Char.Digit9', for
-digits 0 though 9. The representation of digits 10 to 15 is determined by the
-value of 'Case' parameter: 'UpperCase' means 'ASCII.Char.CapitalLetterA' to
+Characters 'ASCII.Char.Digit0' through 'ASCII.Char.Digit9' represent digits 0
+though 9. The representation of digits 10 to 15 is determined by the value of
+'Case' parameter: 'UpperCase' means 'ASCII.Char.CapitalLetterA' to
 'ASCII.Char.CapitalLetterF', and 'LowerCase' means 'ASCII.Char.SmallLetterA' to
 'ASCII.Char.SmallLetterF'.
 
-For example, @'showNaturalHexadecimal' 'UpperCase' (256 + 12)@ = @"10C"@.
-
--}
-
+@
+'showNaturalHexadecimal' 'UpperCase' (256 + 12) == "10C"
+@ -}
 showNaturalHexadecimal :: HexStringSuperset string => Case -> Natural -> string
 showNaturalHexadecimal = ASCII.Hexadecimal.showNatural
 
-{- |
-
-Roughly the inverse of 'showNaturalDecimal'
+{-| Roughly the inverse of 'showNaturalDecimal'
 
 * Leading zeroes are accepted, as in @"0074"@
 
 Conditions where the result is 'Data.Maybe.Nothing':
 
 * If the input is empty
-* If the input contains any other extraneous characters
-
--}
-
+* If the input contains any other extraneous characters -}
 readNaturalDecimal :: DigitStringSuperset string => string -> Maybe Natural
 readNaturalDecimal = ASCII.Decimal.readNatural
 
-{- |
-
-Roughly the inverse of 'showNaturalHexadecimal'
+{-| Roughly the inverse of 'showNaturalHexadecimal'
 
 * Upper and lower case letters are treated equally
 * Leading zeroes are accepted, as in @"006a"@
@@ -799,82 +719,49 @@
 Conditions where the result is 'Data.Maybe.Nothing':
 
 * If the input is empty
-* If the input contains any other extraneous characters
-
--}
-
+* If the input contains any other extraneous characters -}
 readNaturalHexadecimal :: HexStringSuperset string => string -> Maybe Natural
 readNaturalHexadecimal = ASCII.Hexadecimal.readNatural
 
 {- $digit
 
-See also: "ASCII.Decimal"
-
--}
+See also: "ASCII.Decimal" -}
 
 {- $hexchar
 
-See also: "ASCII.Hexadecimal"
-
--}
-
-{- |
-
-Specialization of 'showNaturalDecimal'
-
-See also: 'showIntegralDecimal'
+See also: "ASCII.Hexadecimal" -}
 
--}
+{-| Specialization of 'showNaturalDecimal'
 
+See also: 'showIntegralDecimal' -}
 showNaturalDigits :: Natural -> [Digit]
 showNaturalDigits = showNaturalDecimal
 
-{- |
-
-Specialization of 'readNaturalDecimal'
-
-See also: 'readIntegralDecimal'
-
--}
+{-| Specialization of 'readNaturalDecimal'
 
+See also: 'readIntegralDecimal' -}
 readNaturalDigits :: [Digit] -> Maybe Natural
 readNaturalDigits = readNaturalDecimal
 
-{- |
-
-Specialization of 'showNaturalHexadecimal'
-
-See also: 'showIntegralHexadecimal'
+{-| Specialization of 'showNaturalHexadecimal'
 
--}
+See also: 'showIntegralHexadecimal' -}
 
 showNaturalHexChars :: Case -> Natural -> [HexChar]
 showNaturalHexChars = showNaturalHexadecimal
 
-{- |
-
-Specialization of 'readNaturalHexadecimal'
-
-See also: 'readIntegralHexadecimal'
-
--}
+{-| Specialization of 'readNaturalHexadecimal'
 
+See also: 'readIntegralHexadecimal' -}
 readNaturalHexChars :: [HexChar] -> Maybe Natural
 readNaturalHexChars = readNaturalHexadecimal
 
-{- |
-
-A string containing a single digit character 0-9
-
--}
+{-| A string containing a single digit character 0-9 -}
 digitString :: DigitStringSuperset string => Digit -> string
 digitString x = ASCII.Decimal.fromDigitList [x]
 
-{- |
-
-A string containing a single hexadecimal digit character 0-9, A-F, or a-f.
-
--}
+{-| A string containing a single hexadecimal digit character
+    0-9, A-F, or a-f -}
 hexCharString :: HexStringSuperset string => HexChar -> string
 hexCharString x = ASCII.Hexadecimal.fromHexCharList [x]
 
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -5,8 +5,7 @@
 import ASCII.Char (Char (..))
 import ASCII.Refinement (asciiUnsafe)
 
-import Control.Monad (Monad (..), when)
-import Data.Bool (Bool (..), not)
+import Data.Bool (Bool (..))
 import Data.Function (($))
 import Data.Int (Int)
 import Data.List (map)
@@ -14,108 +13,134 @@
 import Data.Text (Text)
 import Data.Word (Word8)
 import Prelude (enumFromTo, maxBound, minBound)
-import System.Exit (exitFailure)
 import System.IO (IO)
 
-import Hedgehog (Property, assert, checkParallel, discover, property, withTests,
-                 (===))
+import Test.Hspec (describe, hspec, it, shouldBe)
 
 main :: IO ()
-main = checkParallel $$(discover) >>= \ok -> when (not ok) exitFailure
+main = hspec $ do
 
-prop_letter_printable :: Property
-prop_letter_printable = withTests 1 $ property $
-    charGroup CapitalLetterA === Printable
+    describe "charGroup" $ do
+        let c ~> g = charGroup c `shouldBe` g
 
-prop_eot_control :: Property
-prop_eot_control = withTests 1 $ property $
-    charGroup EndOfTransmission === Control
+        it "A is printable"                 $ CapitalLetterA    ~> Printable
+        it "end-of-transmission is control" $ EndOfTransmission ~> Control
 
-prop_eot_not_printable :: Property
-prop_eot_not_printable = withTests 1 $ property $
-    assert $ not $ inGroup Printable EndOfTransmission
+    describe "inGroup" $ do
 
-prop_is_eot_control :: Property
-prop_is_eot_control = withTests 1 $ property $
-    assert $ inGroup Control EndOfTransmission
+        describe "tests Char" $ do
+            it "end-of-transmission is not printable" $
+                inGroup Printable EndOfTransmission `shouldBe` False
+            it "end-of-transmission is control" $
+                inGroup Control   EndOfTransmission `shouldBe` True
 
-prop_inGroup_printable :: Property
-prop_inGroup_printable = withTests 1 $ property $
-    map (inGroup Printable) ([-1, 5, 65, 97, 127, 130] :: [Int]) === [False, False, True, True, False, False]
+        describe "tests Int" $ do
+            let i ~> b = inGroup Printable (i :: Int) `shouldBe` b
 
-prop_cases :: Property
-prop_cases = withTests 1 $ property $
-    map letterCase [SmallLetterA, CapitalLetterA, ExclamationMark] === [Just LowerCase, Just UpperCase, Nothing]
+            it "-1 is not printable (isn't anything)"  $ (-1) ~> False
+            it "5 is not printable"                    $ 5    ~> False
+            it "65 is printable"                       $ 65   ~> True
+            it "97 is printable"                       $ 97   ~> True
+            it "127 is not printable"                  $ 127  ~> False
+            it "128 is not printable (isn't anything)" $ 128  ~> False
 
-prop_cases_string_qq :: Property
-prop_cases_string_qq = withTests 1 $ property $
-    map letterCase ([string|Hey!|] :: [ASCII Word8]) === [Just UpperCase, Just LowerCase, Just LowerCase, Nothing]
+    describe "letterCase" $ do
+        let ch ~> ca = letterCase ch `shouldBe` ca
 
-prop_is_upper_char :: Property
-prop_is_upper_char = withTests 1 $ property $
-    map (isCase UpperCase) [SmallLetterA, CapitalLetterA, ExclamationMark] === [False, True, False]
+        describe "works with Char" $ do
+            it "a is lower"    $ SmallLetterA    ~> Just LowerCase
+            it "A is upper"    $ CapitalLetterA  ~> Just UpperCase
+            it "! has no case" $ ExclamationMark ~> Nothing
 
-prop_is_upper_string_qq :: Property
-prop_is_upper_string_qq = withTests 1 $ property $
-    map (isCase UpperCase) ([string|Hey!|] :: [ASCII Word8]) === [True, False, False, False]
+        it "works with ASCII Word8" $ do
+            map letterCase ([string|Hey!|] :: [ASCII Word8]) `shouldBe`
+                [Just UpperCase, Just LowerCase, Just LowerCase, Nothing]
 
-prop_is_upper_int :: Property
-prop_is_upper_int = withTests 1 $ property $
-    map (isCase UpperCase) ([-1, 65, 97, 150] :: [Int]) === [False, True, False, False]
+    describe "isCase" $ do
 
-prop_to_upper_letter :: Property
-prop_to_upper_letter = withTests 1 $ property $
-    toCaseChar UpperCase SmallLetterA === CapitalLetterA
+        describe "works with Char" $ do
+            let c ~> b = isCase UpperCase c `shouldBe` b
 
-prop_to_upper_char_qq :: Property
-prop_to_upper_char_qq = withTests 1 $ property $
-    ([char|a|] :: ASCII Word8, toCaseChar UpperCase [char|a|] :: ASCII Word8) === (asciiUnsafe 97, asciiUnsafe 65)
+            it "a is not upper" $ SmallLetterA    ~> False
+            it "A is upper"     $ CapitalLetterA  ~> True
+            it "! is not upper" $ ExclamationMark ~> False
 
-prop_to_upper_various :: Property
-prop_to_upper_various = withTests 1 $ property $
-    toCaseString UpperCase [CapitalLetterH,SmallLetterE,SmallLetterY,ExclamationMark] === [CapitalLetterH, CapitalLetterE, CapitalLetterY, ExclamationMark]
+        it "works with ASCII Word8" $ do
+            map (isCase UpperCase) ([string|Hey!|] :: [ASCII Word8])
+                `shouldBe` [True, False, False, False]
 
-prop_to_upper_string_qq :: Property
-prop_to_upper_string_qq = withTests 1 $ property $
-    (toCaseString UpperCase [string|Hey!|] :: ASCII Text) === asciiUnsafe "HEY!"
+        describe "works with Int" $ do
+            let i ~> b = isCase UpperCase (i :: Int) `shouldBe` b
 
-prop_to_int :: Property
-prop_to_int = withTests 1 $ property $
-    map charToInt [Null, CapitalLetterA, SmallLetterA, Delete] === [0, 65, 97, 127]
+            it "-1 is not upper (isn't anything)"  $ (-1) ~> False
+            it "65 is upper"                       $ 65   ~> True
+            it "97 is not upper"                   $ 97   ~> False
+            it "128 is not upper (isn't anything)" $ 128  ~> False
 
-prop_to_word8 :: Property
-prop_to_word8 = withTests 1 $ property $
-    map charToWord8 [Null, CapitalLetterA, SmallLetterA, Delete] === [0, 65, 97, 127]
+    describe "toCaseChar" $ do
 
-prop_to_text :: Property
-prop_to_text = withTests 1 $ property $
-    charListToText [CapitalLetterH, SmallLetterI, ExclamationMark] === "Hi!"
+        describe "works with Char" $ do
+            it "a to upper is A" $ toCaseChar UpperCase SmallLetterA
+                `shouldBe` CapitalLetterA
 
-prop_lift_to_word8 :: Property
-prop_lift_to_word8 = withTests 1 $ property $
-    (lift CapitalLetterA :: Word8) === 65
+        describe "works with ASCII Word8" $ do
+            it "a to upper is 65" $ toCaseChar UpperCase ([char|a|] :: ASCII Word8)
+                `shouldBe` (asciiUnsafe 65 :: ASCII Word8)
 
-prop_lift_to_text :: Property
-prop_lift_to_text = withTests 1 $ property $
-    (lift [CapitalLetterH,SmallLetterI,ExclamationMark] :: Text) === "Hi!"
+    describe "toCaseString" $ do
+        it "converts each Char to a case" $ do
+            let check a b = toCaseString UpperCase a `shouldBe` b
+            check [ CapitalLetterH, SmallLetterE,   SmallLetterY,   ExclamationMark ]
+                  [ CapitalLetterH, CapitalLetterE, CapitalLetterY, ExclamationMark ]
 
-prop_bytes_to_string :: Property
-prop_bytes_to_string = withTests 1 $ property $
-    ASCII.byteListToUnicodeStringMaybe [0x48, 0x54, 0x54, 0x50] === Just "HTTP"
+        it "works with ASCII Text" $
+            (toCaseString UpperCase [string|Hey!|] :: ASCII Text)
+                `shouldBe` asciiUnsafe "HEY!"
 
-prop_bytes_to_string_fail :: Property
-prop_bytes_to_string_fail = withTests 1 $ property $
-    ASCII.byteListToUnicodeStringMaybe [0x48, 0x54, 0x54, 0x80] === Nothing
+    describe "charToInt" $ do
+        let c ~> i = charToInt c `shouldBe` i
 
-prop_digitStrings :: Property
-prop_digitStrings = withTests 1 $ property $
-    map digitString (enumFromTo minBound maxBound) ===
-    (["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] :: [Text])
+        it "Null is 0"     $ Null           ~> 0
+        it "A is 65"       $ CapitalLetterA ~> 65
+        it "a is 97"       $ SmallLetterA   ~> 97
+        it "Delete is 127" $ Delete         ~> 127
 
-prop_hexCharStrings :: Property
-prop_hexCharStrings = withTests 1 $ property $
-    map hexCharString (enumFromTo minBound maxBound) ===
-    ( [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
-      , "A", "B", "C", "D", "E", "F"
-      , "a", "b", "c", "d", "e", "f"
-      ] :: [Text] )
+    describe "charToWord8" $ do
+        let c ~> i = charToWord8 c `shouldBe` i
+
+        it "Null is 0"     $ Null           ~> 0
+        it "A is 65"       $ CapitalLetterA ~> 65
+        it "a is 97"       $ SmallLetterA   ~> 97
+        it "Delete is 127" $ Delete         ~> 127
+
+    describe "charListToText" $ do
+        it "packs a list of Char into Text" $
+            charListToText [CapitalLetterH, SmallLetterI, ExclamationMark]
+                `shouldBe` "Hi!"
+
+    describe "lift" $ do
+        it "converts Char to Word8" $ (lift CapitalLetterA :: Word8) `shouldBe` 65
+        it "converts [Char] to Text" $
+            (lift [CapitalLetterH, SmallLetterI, ExclamationMark] :: Text)
+                `shouldBe` "Hi!"
+
+    describe "byteListToUnicodeStringMaybe" $ do
+        it "converts [Word8] to String" $
+            ASCII.byteListToUnicodeStringMaybe [0x48, 0x54, 0x54, 0x50]
+                `shouldBe` Just "HTTP"
+        it "can fail" $ ASCII.byteListToUnicodeStringMaybe [0x48, 0x54, 0x54, 0x80]
+            `shouldBe` Nothing
+
+    describe "digitString" $ do
+        it "converts Digit to a single character of Text" $ do
+            let f = digitString :: Digit -> Text
+            map f (enumFromTo minBound maxBound) `shouldBe`
+                ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
+
+    describe "hexCharString" $ do
+        it "converts HexChar to a single character of Text" $ do
+            let f = hexCharString :: HexChar -> Text
+            map f (enumFromTo minBound maxBound) `shouldBe`
+                [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
+                , "A", "B", "C", "D", "E", "F"
+                , "a", "b", "c", "d", "e", "f" ]
