diff --git a/enumerator.cabal b/enumerator.cabal
--- a/enumerator.cabal
+++ b/enumerator.cabal
@@ -1,5 +1,5 @@
 name: enumerator
-version: 0.4.14
+version: 0.4.15
 synopsis: Reliable, high-performance processing with left-fold enumerators
 license: MIT
 license-file: license.txt
@@ -63,39 +63,94 @@
   scripts/common.bash
   scripts/dist
   scripts/run-benchmarks
+  scripts/run-coverage
   scripts/run-tests
   --
   tests/enumerator-tests.cabal
-  tests/Tests.hs
+  tests/EnumeratorTests.hs
+  tests/EnumeratorTests/Binary.hs
+  tests/EnumeratorTests/Binary/Consume.hs
+  tests/EnumeratorTests/Binary/Drop.hs
+  tests/EnumeratorTests/Binary/Fold.hs
+  tests/EnumeratorTests/Binary/Handle.hs
+  tests/EnumeratorTests/Binary/Isolate.hs
+  tests/EnumeratorTests/Binary/Iterate.hs
+  tests/EnumeratorTests/Binary/Map.hs
+  tests/EnumeratorTests/Binary/Repeat.hs
+  tests/EnumeratorTests/Binary/Replicate.hs
+  tests/EnumeratorTests/Binary/Require.hs
+  tests/EnumeratorTests/Binary/Split.hs
+  tests/EnumeratorTests/Binary/Unfold.hs
+  tests/EnumeratorTests/Binary/Util.hs
+  tests/EnumeratorTests/Binary/Zip.hs
+  tests/EnumeratorTests/CatchError.hs
+  tests/EnumeratorTests/Compatibility.hs
+  tests/EnumeratorTests/Instances.hs
+  tests/EnumeratorTests/Join.hs
+  tests/EnumeratorTests/List.hs
+  tests/EnumeratorTests/List/Consume.hs
+  tests/EnumeratorTests/List/Drop.hs
+  tests/EnumeratorTests/List/Fold.hs
+  tests/EnumeratorTests/List/Isolate.hs
+  tests/EnumeratorTests/List/Iterate.hs
+  tests/EnumeratorTests/List/Map.hs
+  tests/EnumeratorTests/List/Repeat.hs
+  tests/EnumeratorTests/List/Replicate.hs
+  tests/EnumeratorTests/List/Require.hs
+  tests/EnumeratorTests/List/Split.hs
+  tests/EnumeratorTests/List/Unfold.hs
+  tests/EnumeratorTests/List/Unique.hs
+  tests/EnumeratorTests/List/Util.hs
+  tests/EnumeratorTests/List/Zip.hs
+  tests/EnumeratorTests/Misc.hs
+  tests/EnumeratorTests/Sequence.hs
+  tests/EnumeratorTests/Stream.hs
+  tests/EnumeratorTests/Text.hs
+  tests/EnumeratorTests/Text/Codecs.hs
+  tests/EnumeratorTests/Text/Consume.hs
+  tests/EnumeratorTests/Text/Drop.hs
+  tests/EnumeratorTests/Text/Fold.hs
+  tests/EnumeratorTests/Text/Handle.hs
+  tests/EnumeratorTests/Text/Isolate.hs
+  tests/EnumeratorTests/Text/Iterate.hs
+  tests/EnumeratorTests/Text/Map.hs
+  tests/EnumeratorTests/Text/Repeat.hs
+  tests/EnumeratorTests/Text/Replicate.hs
+  tests/EnumeratorTests/Text/Require.hs
+  tests/EnumeratorTests/Text/Split.hs
+  tests/EnumeratorTests/Text/Unfold.hs
+  tests/EnumeratorTests/Text/Util.hs
+  tests/EnumeratorTests/Text/Zip.hs
+  tests/EnumeratorTests/Util.hs
 
 source-repository head
   type: bazaar
-  location: http://john-millikin.com/software/enumerator/
+  location: https://john-millikin.com/software/enumerator/
 
+source-repository this
+  type: bazaar
+  location: https://john-millikin.com/branches/enumerator/0.4/
+  tag: enumerator_0.4.15
+
 library
   ghc-options: -Wall -O2
   hs-source-dirs: lib
 
   build-depends:
-      transformers >= 0.2 && < 0.3
+      base >= 4.0 && < 5.0
+    , transformers >= 0.2 && < 0.3
     , bytestring >= 0.9 && < 0.10
-    , containers
+    , containers >= 0.1 && < 0.5
     , text >= 0.7 && < 0.12
 
-  if impl(ghc >= 6.10)
-    build-depends:
-        base >= 4 && < 5
-  else
-    build-depends:
-        base >= 3 && < 4
-      , extensible-exceptions >= 0.1 && < 0.2
-
   exposed-modules:
     Data.Enumerator
     Data.Enumerator.Binary
-    Data.Enumerator.Text
-    Data.Enumerator.List
+    Data.Enumerator.Internal
     Data.Enumerator.IO
+    Data.Enumerator.List
+    Data.Enumerator.Text
 
   other-modules:
+    Data.Enumerator.Compatibility
     Data.Enumerator.Util
diff --git a/examples/wc.hs b/examples/wc.hs
--- a/examples/wc.hs
+++ b/examples/wc.hs
@@ -8,30 +8,30 @@
 --
 -----------------------------------------------------------------------------
 module Main (main) where
-import Data.Enumerator as E
-import qualified Data.Enumerator.Binary as EB
-import qualified Data.Enumerator.Text as ET
-import qualified Data.ByteString as B
 
--- support imports
-import Control.Monad (unless, forM_)
-import System.IO
-import System.Console.GetOpt
-import System.Environment
-import System.Exit
+import           Control.Monad (unless, forM_)
+import           Data.ByteString (ByteString)
+import           System.Console.GetOpt
+import           System.IO
+import           System.Environment
+import           System.Exit
 
+import           Data.Enumerator as E
+import qualified Data.Enumerator.Binary as Binary
+import qualified Data.Enumerator.Text as Text
+
 -- support wc modes -c (bytes), -m (characters), and -l (lines)
 
 -- iterBytes simply counts how many bytes are in each chunk, accumulates this
 -- count, and returns it when EOF is received.
 
-iterBytes :: Monad m => Iteratee B.ByteString m Integer
-iterBytes = EB.fold (\acc _ -> acc + 1) 0
+iterBytes :: Monad m => Iteratee ByteString m Integer
+iterBytes = Binary.fold (\acc _ -> acc + 1) 0
 
 -- iterLines is similar, except it only counts newlines ('\n')
 
-iterLines :: Monad m => Iteratee B.ByteString m Integer
-iterLines = EB.fold step 0 where
+iterLines :: Monad m => Iteratee ByteString m Integer
+iterLines = Binary.fold step 0 where
 	step acc 0xA = acc + 1
 	step acc _ = acc
 
@@ -39,13 +39,11 @@
 -- assuming UTF-8) before performing any counting. Leftover bytes, not part
 -- of a valid UTF-8 character, are yielded as surplus
 --
--- Note the use of joinI. 'ET.decode' is an enumeratee, which means it returns
--- an iteratee yielding an inner step. 'joinI' "collapses" an enumeratee's
--- return value, much as 'join' does to monadic values.
+-- Note the use of (=$). This lets an enumeratee send data directly to an
+-- iteratee, without worrying about leftover input.
 
-iterChars :: Monad m => Iteratee B.ByteString m Integer
-iterChars = joinI (ET.decode ET.utf8 $$ count) where
-	count = ET.fold (\acc _ -> acc + 1) 0
+iterChars :: Monad m => Iteratee ByteString m Integer
+iterChars = Text.decode Text.utf8 =$ Text.fold (\acc _ -> acc + 1) 0
 
 main :: IO ()
 main = do
@@ -60,9 +58,9 @@
 		OptionChars -> iterChars
 	
 	forM_ files $ \filename -> do
-		putStr $ filename ++ ": "
+		putStr (filename ++ ": ")
 		
-		eitherStat <- run (EB.enumFile filename $$ iter)
+		eitherStat <- run (Binary.enumFile filename $$ iter)
 		putStrLn $ case eitherStat of
 			Left err -> "ERROR: " ++ show err
 			Right stat -> show stat
diff --git a/lib/Data/Enumerator.hs b/lib/Data/Enumerator.hs
--- a/lib/Data/Enumerator.hs
+++ b/lib/Data/Enumerator.hs
@@ -6,9 +6,7 @@
 -- Maintainer: jmillikin@gmail.com
 -- Portability: portable
 --
--- Core enumerator types, and some useful primitives.
---
--- This module is intended to be imported qualified:
+-- For compatibility reasons, this module should imported qualified:
 --
 -- @
 -- import qualified Data.Enumerator as E
@@ -17,18 +15,18 @@
 	(
 	
 	-- * Types
-	  Stream (..)
-	, Iteratee (..)
-	, Step (..)
+	  Iteratee
 	, Enumerator
 	, Enumeratee
 	
-	-- * Primitives
-	, returnI
-	, continue
-	, yield
+	-- * Running iteratees
+	, run
+	, run_
 	
-	-- ** Operators
+	-- * Operators
+	-- | Compatibility note: Most of these will be obsoleted by
+	-- version 0.5. Please make sure your @.cabal@ files have a
+	-- @<0.5@ limit on the @enumerator@ dependency.
 	, (>>==)
 	, (==<<)
 	, ($$)
@@ -37,11 +35,7 @@
 	, (=$)
 	, ($=)
 	
-	-- ** Running iteratees
-	, run
-	, run_
-	
-	-- ** Error handling
+	-- * Error handling
 	, throwError
 	, catchError
 	
@@ -50,222 +44,55 @@
 	, joinI
 	, joinE
 	, Data.Enumerator.sequence
-	, enumEOF
-	, checkContinue0
-	, checkContinue1
-	, checkDoneEx
-	, checkDone
 	, isEOF
 	, tryIO
+	, liftTrans
 	
-	-- ** Testing and debugging
+	-- * Testing and debugging
 	, printChunks
 	, enumList
+	, enumLists
+	, runLists
+	, runLists_
 	
-	-- * Legacy compatibility
+	-- * Internal interfaces
+	-- | This module export will be removed in version 0.5. If you
+	-- depend on internal implementation details, please import
+	-- @"Data.Enumerator.Internal"@ directly.
+	, module Data.Enumerator.Internal
 	
-	-- ** Obsolete
-	, liftTrans
-	, liftI
+	-- Obsolete and pointless
 	, peek
 	, Data.Enumerator.last
 	, Data.Enumerator.length
 	
-	-- ** Aliases
-	, Data.Enumerator.head
-	, Data.Enumerator.drop
-	, Data.Enumerator.dropWhile
-	, Data.Enumerator.span
-	, Data.Enumerator.break
-	, consume
-	, Data.Enumerator.foldl
-	, Data.Enumerator.foldl'
-	, foldM
-	, Data.Enumerator.iterate
-	, iterateM
-	, Data.Enumerator.repeat
-	, repeatM
-	, Data.Enumerator.replicate
-	, replicateM
-	, generateM
-	, Data.Enumerator.map
-	, Data.Enumerator.mapM
-	, Data.Enumerator.concatMap
-	, concatMapM
-	, Data.Enumerator.filter
-	, filterM
-	, liftFoldL
-	, liftFoldL'
-	, liftFoldM
-	
+	-- Legacy compatibility
+	-- These legacy compatibility functions will be removed in
+	-- version 0.5.
+	, module Data.Enumerator.Compatibility
 	) where
 
-import           Control.Applicative as A
 import qualified Control.Exception as Exc
 import qualified Control.Monad as CM
 import           Control.Monad.IO.Class (MonadIO, liftIO)
 import           Control.Monad.Trans.Class (MonadTrans, lift)
-import           Data.Function (fix)
+import           Data.Functor.Identity (Identity, runIdentity)
 import           Data.List (genericLength, genericSplitAt)
-import           Data.Monoid (Monoid, mempty, mappend, mconcat)
-import           Data.Typeable ( Typeable, typeOf
-                               , Typeable1, typeOf1
-                               , mkTyConApp, mkTyCon)
 
-import {-# SOURCE #-} qualified Data.Enumerator.List as EL
-
--- | A 'Stream' is a sequence of chunks generated by an 'Enumerator'.
---
--- @('Chunks' [])@ is used to indicate that a stream is still active, but
--- currently has no available data. Iteratees should ignore empty chunks.
-data Stream a
-	= Chunks [a]
-	| EOF
-	deriving (Show, Eq)
-
-instance Monad Stream where
-	return = Chunks . return
-	Chunks xs >>= f = mconcat (fmap f xs)
-	EOF >>= _ = EOF
-
-instance Monoid (Stream a) where
-	mempty = Chunks mempty
-	mappend (Chunks xs) (Chunks ys) = Chunks (xs ++ ys)
-	mappend _ _ = EOF
-
-data Step a m b
-	-- | The 'Iteratee' is capable of accepting more input. Note that more input
-	-- is not necessarily required; the 'Iteratee' might be able to generate a
-	-- value immediately if it receives 'EOF'.
-	= Continue (Stream a -> Iteratee a m b)
-	
-	-- | The 'Iteratee' cannot receive any more input, and has generated a
-	-- result. Included in this value is left-over input, which can be passed to
-	-- composed 'Iteratee's.
-	| Yield b (Stream a)
-	
-	-- | The 'Iteratee' encountered an error which prevents it from proceeding
-	-- further.
-	| Error Exc.SomeException
-
--- | The primary data type for this library, which consumes
--- input from a 'Stream' until it either generates a value or encounters
--- an error. Rather than requiring all input at once, an iteratee will
--- return 'Continue' when it is capable of processing more data.
---
--- In general, iteratees begin in the 'Continue' state. As each chunk is
--- passed to the continuation, the iteratee returns the next step:
--- 'Continue' for more data, 'Yield' when it's finished, or 'Error' to
--- abort processing.
-newtype Iteratee a m b = Iteratee
-	{ runIteratee :: m (Step a m b)
-	}
-
-instance Monad m => Monad (Iteratee a m) where
-	return x = yield x (Chunks [])
-
-	m0 >>= f = ($ m0) $ fix $
-		\bind m -> Iteratee $ runIteratee m >>= \r1 ->
-			case r1 of
-				Continue k -> return (Continue (bind . k))
-				Error err -> return (Error err)
-				Yield x (Chunks []) -> runIteratee (f x)
-				Yield x extra -> runIteratee (f x) >>= \r2 ->
-					case r2 of
-						Continue k -> runIteratee (k extra)
-						Error err -> return (Error err)
-						Yield x' _ -> return (Yield x' extra)
-
-instance MonadTrans (Iteratee a) where
-	lift m = Iteratee (m >>= runIteratee . return)
-
-instance MonadIO m => MonadIO (Iteratee a m) where
-	liftIO = lift . liftIO
-
--- | While 'Iteratee's consume data, enumerators generate it. Since
--- @'Iteratee'@ is an alias for @m ('Step' a m b)@, 'Enumerator's can
--- be considered step transformers of type
--- @'Step' a m b -> m ('Step' a m b)@.
---
--- 'Enumerator's typically read from an external source (parser, handle,
--- random generator, etc). They feed chunks into an 'Iteratee' until the
--- source runs out of data (triggering 'EOF') or the iteratee finishes
--- processing ('Yield's a value).
-type Enumerator a m b = Step a m b -> Iteratee a m b
-
--- | In cases where an enumerator acts as both a source and sink, the resulting
--- type is named an 'Enumeratee'. Enumeratees have two input types,
--- &#x201c;outer a&#x201d; (@aOut@) and &#x201c;inner a&#x201d; (@aIn@).
-type Enumeratee ao ai m b = Step ai m b -> Iteratee ao m (Step ai m b)
-
--- | Since: 0.4.8
-instance Typeable1 Stream where
-	typeOf1 _ = mkTyConApp tyCon [] where
-		tyCon = mkTyCon "Data.Enumerator.Stream"
-
--- | Since: 0.4.6
-instance (Typeable a, Typeable1 m) =>
-	Typeable1 (Iteratee a m) where
-		typeOf1 i = let
-			tyCon = mkTyCon "Data.Enumerator.Iteratee"
-			(a, m) = peel i
-			
-			peel :: Iteratee a m b -> (a, m ())
-			peel = undefined
-			
-			in mkTyConApp tyCon [typeOf a, typeOf1 m]
-
--- | Since: 0.4.8
-instance (Typeable a, Typeable1 m) =>
-	Typeable1 (Step a m) where
-		typeOf1 s = let
-			tyCon = mkTyCon "Data.Enumerator.Step"
-			(a, m) = peel s
-			
-			peel :: Step a m b -> (a, m ())
-			peel = undefined
-			
-			in mkTyConApp tyCon [typeOf a, typeOf1 m]
-
-instance Monad m => Functor (Iteratee a m) where
-	fmap = CM.liftM
-
-instance Monad m => A.Applicative (Iteratee a m) where
-	pure = return
-	(<*>) = CM.ap
-
-instance Functor Stream where
-	fmap f (Chunks xs) = Chunks (fmap f xs)
-	fmap _ EOF = EOF
-
--- | Since: 0.4.5
-instance A.Applicative Stream where
-	pure = return
-	(<*>) = CM.ap
-
--- | @'returnI' step = 'Iteratee' (return step)@
-returnI :: Monad m => Step a m b -> Iteratee a m b
-returnI step = Iteratee (return step)
-
--- | @'yield' x extra = 'returnI' ('Yield' x extra)@
---
--- WARNING: due to the current encoding of iteratees in this library,
--- careless use of the 'yield' primitive may violate the monad laws.
--- To prevent this, always make sure that an iteratee never yields
--- extra data unless it has received at least one input element.
---
--- More strictly, iteratees may not yield data that they did not
--- receive as input. Don't use 'yield' to &#x201c;inject&#x201d; elements
--- into the stream.
-yield :: Monad m => b -> Stream a -> Iteratee a m b
-yield x extra = returnI (Yield x extra)
-
--- | @'continue' k = 'returnI' ('Continue' k)@
-continue :: Monad m => (Stream a -> Iteratee a m b) -> Iteratee a m b
-continue k = returnI (Continue k)
+import           Data.Enumerator.Compatibility
+import           Data.Enumerator.Internal
 
 -- | Run an iteratee until it finishes, and return either the final value
 -- (if it succeeded) or the error (if it failed).
+--
+-- > import Data.Enumerator
+-- > import Data.Enumerator.List as EL
+-- >
+-- > main = do
+-- >     result <- run (EL.iterate succ 'A' $$ EL.take 5)
+-- >     case result of
+-- >         Left exc -> putStrLn ("Got an exception: " ++ show exc)
+-- >         Right chars -> putStrLn ("Got characters: " ++ show chars)
 run :: Monad m => Iteratee a m b
     -> m (Either Exc.SomeException b)
 run i = do
@@ -278,11 +105,18 @@
 -- | Like 'run', except errors are converted to exceptions and thrown.
 -- Primarily useful for small scripts or other simple cases.
 --
+-- > import Data.Enumerator
+-- > import Data.Enumerator.List as EL
+-- >
+-- > main = do
+-- >     chars <- run_ (EL.iterate succ 'A' $$ EL.take 5)
+-- >     putStrLn ("Got characters: " ++ show chars)
+--
 -- Since: 0.4.1
 run_ :: Monad m => Iteratee a m b -> m b
 run_ i = run i >>= either Exc.throw return
 
--- | @'throwError' exc = 'returnI' ('Error' ('Exc.toException' exc))@
+-- | The moral equivalent of 'Exc.throwIO' for iteratees.
 throwError :: (Monad m, Exc.Exception e) => e -> Iteratee a m b
 throwError exc = returnI (Error (Exc.toException exc))
 
@@ -291,15 +125,12 @@
 -- all errors to be represented by 'Exc.SomeException', libraries with
 -- varying error types can be easily composed.
 --
--- WARNING: after a few rounds of "catchError doesn't work because X", this
--- function has grown into a horrible monster. I have no concept of what
--- unexpected behaviors lurk in its dark crevices. Users are strongly advised
--- to wrap all uses of @catchError@ with an appropriate @isolate@, such as
--- @Data.Enumerator.List.isolate@ or @Data.Enumerator.Binary.isolate@, which
--- will handle input framing even in the face of unexpected errors.
---
--- Within the error handler, it is difficult or impossible to know how much
--- input the original iteratee has consumed.
+-- WARNING: Within the error handler, it is difficult or impossible to know
+-- how much input the original iteratee has consumed. Users are strongly
+-- advised to wrap all uses of @catchError@ with an appropriate isolation
+-- enumeratee, such as @Data.Enumerator.List.isolate@ or
+-- @Data.Enumerator.Binary.isolate@, which will handle input framing even
+-- in the face of unexpected errors.
 --
 -- Since: 0.1.1
 catchError :: Monad m
@@ -331,61 +162,6 @@
 					_ -> return step'
 			Continue k' -> return (Continue (wrap k'))
 
-infixl 1 >>==
-infixr 1 ==<<
-infixr 0 $$
-infixr 1 >==>
-infixr 1 <==<
-
--- | Equivalent to '(>>=)' for @m ('Step' a m b)@; allows 'Iteratee's with
--- different input types to be composed.
-
-(>>==) :: Monad m
-       => Iteratee a m b
-       -> (Step a m b -> Iteratee a' m b')
-       -> Iteratee a' m b'
-i >>== f = Iteratee (runIteratee i >>= runIteratee . f)
-
--- | @'(==\<\<)' = flip '(\>\>==)'@
-(==<<) :: Monad m
-       => (Step a m b -> Iteratee a' m b')
-       -> Iteratee a m b
-       -> Iteratee a' m b'
-(==<<) = flip (>>==)
-
--- | @'($$)' = '(==\<\<)'@
---
--- This might be easier to read when passing a chain of iteratees to an
--- enumerator.
---
--- Since: 0.1.1
-($$) :: Monad m
-     => (Step a m b -> Iteratee a' m b')
-     -> Iteratee a m b
-     -> Iteratee a' m b'
-($$) = (==<<)
-
--- | @'(>==>)' e1 e2 s = e1 s '>>==' e2@
---
--- Since: 0.1.1
-(>==>) :: Monad m
-       => Enumerator a m b
-       -> (Step a m b -> Iteratee a' m b')
-       -> Step a m b
-       -> Iteratee a' m b'
-(>==>) e1 e2 s = e1 s >>== e2
-
--- | @'(\<==\<)' = flip '(>==>)'@
---
--- Since: 0.1.1
-
-(<==<) :: Monad m
-       => (Step a m b -> Iteratee a' m b')
-       -> Enumerator a m b
-       -> Step a m b
-       -> Iteratee a' m b'
-(<==<) = flip (>==>)
-
 -- | Print chunks as they're received from the enumerator, optionally
 -- printing empty chunks.
 printChunks :: (MonadIO m, Show a)
@@ -402,9 +178,13 @@
 		yield () EOF
 
 -- | @'enumList' n xs@ enumerates /xs/ as a stream, passing /n/ inputs per
--- chunk.
+-- chunk. This is primarily useful for testing, debugging, and REPL
+-- exploration.
 --
--- Primarily useful for testing and debugging.
+-- Compatibility note: In version 0.5, 'enumList' will be changed to the
+-- type:
+--
+-- > enumList :: Monad m => [a] -> Enumerator a m b
 enumList :: Monad m => Integer -> [a] -> Enumerator a m b
 enumList n = loop where
 	loop xs (Continue k) | not (null xs) = let
@@ -412,14 +192,39 @@
 		in k (Chunks s1) >>== loop s2
 	loop _ step = returnI step
 
+-- | @'enumLists' xs@ enumerates /xs/ as a stream, where each element is a
+-- separate chunk. This is primarily useful for testing and debugging.
+--
+-- Since: 0.4.15
+enumLists :: Monad m => [[a]] -> Enumerator a m b
+enumLists (xs:xss) (Continue k) = k (Chunks xs) >>== enumLists xss
+enumLists _ step = returnI step
 
--- | Compose a list of 'Enumerator's using @'(>>==)'@
+-- | Run an iteratee with the given input, and return either the final value
+-- (if it succeeded) or the error (if it failed).
+--
+-- Since: 0.4.15
+runLists :: [[a]] -> Iteratee a Identity b -> Either Exc.SomeException b
+runLists lists iter = runIdentity (run (enumLists lists $$ iter))
+
+-- | Like 'runLists', except errors are converted to exceptions and thrown.
+--
+-- Since: 0.4.15
+runLists_ :: [[a]] -> Iteratee a Identity b -> b
+runLists_ lists iter = runIdentity (run_ (enumLists lists $$ iter))
+
+-- | Compose a list of 'Enumerator's using @('>==>').@
 concatEnums :: Monad m => [Enumerator a m b]
             -> Enumerator a m b
 concatEnums = Prelude.foldl (>==>) returnI
 
--- | 'joinI' is used to &#x201C;flatten&#x201D; 'Enumeratee's into an
--- 'Iteratee'.
+-- | &#x201c;Wraps&#x201d; an iteratee /inner/ in an enumeratee /wrapper/.
+-- The resulting iteratee will consume /wrapper/&#x2019;s input type and
+-- yield /inner/&#x2019;s output type.
+--
+-- See the documentation for ('=$').
+--
+-- @joinI (enum $$ iter) = enum =$ iter@
 joinI :: Monad m => Iteratee a m (Step a' m b)
       -> Iteratee a m b
 joinI outer = outer >>= check where
@@ -431,9 +236,7 @@
 
 infixr 0 =$
 
--- | @enum =$ iter = 'joinI' (enum $$ iter)@
---
--- &#x201c;Wraps&#x201d; an iteratee /inner/ in an enumeratee /wrapper/.
+-- | &#x201c;Wraps&#x201d; an iteratee /inner/ in an enumeratee /wrapper/.
 -- The resulting iteratee will consume /wrapper/&#x2019;s input type and
 -- yield /inner/&#x2019;s output type.
 --
@@ -441,11 +244,11 @@
 -- that extra will be discarded.
 --
 -- As an example, consider an iteratee that converts a stream of UTF8-encoded
--- bytes into a single 'TL.Text':
+-- bytes into a single @Text@:
 --
 -- > consumeUTF8 :: Monad m => Iteratee ByteString m Text
 --
--- It could be written with either 'joinI' or '(=$)':
+-- It could be written with either 'joinI' or @(=$)@:
 --
 -- > import Data.Enumerator.Text as ET
 -- >
@@ -456,7 +259,14 @@
 (=$) :: Monad m => Enumeratee ao ai m b -> Iteratee ai m b -> Iteratee ao m b
 enum =$ iter = joinI (enum $$ iter)
 
--- | Flatten an enumerator/enumeratee pair into a single enumerator.
+-- | &#x201c;Wraps&#x201d; an enumerator /inner/ in an enumeratee /wrapper/.
+-- The resulting enumerator will generate /wrapper/&#x2019;s output type.
+--
+-- See the documentation for ('$=').
+--
+-- @joinE enum enee = enum $= enee@
+--
+-- Since: 0.4.5
 joinE :: Monad m
       => Enumerator ao m (Step ai m b)
       -> Enumeratee ao ai m b
@@ -468,11 +278,9 @@
 		Yield x _ -> return x
 		Continue _ -> error "joinE: divergent iteratee"
 
-infixr 0 $=
+infixl 1 $=
 
--- | @enum $= enee = 'joinE' enum enee@
---
--- &#x201c;Wraps&#x201d; an enumerator /inner/ in an enumeratee /wrapper/.
+-- | &#x201c;Wraps&#x201d; an enumerator /inner/ in an enumeratee /wrapper/.
 -- The resulting enumerator will generate /wrapper/&#x2019;s output type.
 --
 -- As an example, consider an enumerator that yields line character counts
@@ -480,7 +288,7 @@
 --
 -- > enumFileCounts :: FilePath -> Enumerator Int IO b
 --
--- It could be written with either 'joinE' or '($=)':
+-- It could be written with either 'joinE' or @($=)@:
 --
 -- > import Data.Text as T
 -- > import Data.Enumerator.List as EL
@@ -489,6 +297,9 @@
 -- > enumFileCounts path = joinE (enumFile path) (EL.map T.length)
 -- > enumFileCounts path = enumFile path $= EL.map T.length
 --
+-- Compatibility note: in version 0.4.15, the associativity of @($=)@ was
+-- changed from @infixr 0@ to @infixl 1@.
+--
 -- Since: 0.4.9
 ($=) :: Monad m
      => Enumerator ao m (Step ai m b)
@@ -507,45 +318,15 @@
 		else step k
 	step k = i >>= \v -> k (Chunks [v]) >>== loop
 
--- | Sends 'EOF' to its iteratee. Most clients should use 'run' or 'run_'
--- instead.
-enumEOF :: Monad m => Enumerator a m b
-enumEOF (Yield x _) = yield x EOF
-enumEOF (Error err) = throwError err
-enumEOF (Continue k) = k EOF >>== check where
-	check (Continue _) = error "enumEOF: divergent iteratee"
-	check s = enumEOF s
-
--- | A common pattern in 'Enumeratee' implementations is to check whether
--- the inner 'Iteratee' has finished, and if so, to return its output.
--- 'checkDone' passes its parameter a continuation if the 'Iteratee'
--- can still consume input, or yields otherwise.
---
--- Since: 0.4.3
-checkDoneEx :: Monad m =>
-	Stream a' ->
-	((Stream a -> Iteratee a m b) -> Iteratee a' m (Step a m b)) ->
-	Enumeratee a' a m b
-checkDoneEx _     f (Continue k) = f k
-checkDoneEx extra _ step         = yield step extra
-
--- | @'checkDone' = 'checkDoneEx' ('Chunks' [])@
---
--- Use this for enumeratees which do not have an input buffer.
-checkDone :: Monad m =>
-	((Stream a -> Iteratee a m b) -> Iteratee a' m (Step a m b)) ->
-	Enumeratee a' a m b
-checkDone = checkDoneEx (Chunks [])
-
--- | Check whether a stream has reached EOF. Most clients should use
--- 'Data.Enumerator.List.head' instead.
+-- | Check whether a stream has reached EOF. Note that if the stream is not
+-- at EOF, @isEOF@ may cause data to be read from the enumerator.
 isEOF :: Monad m => Iteratee a m Bool
 isEOF = continue $ \s -> case s of
 	EOF -> yield True s
 	_ -> yield False s
 
 -- | Try to run an IO computation. If it throws an exception, the exception
--- is caught and converted into an {\tt Error}.
+-- is caught and passed to 'throwError'.
 --
 -- Since: 0.4.9
 tryIO :: MonadIO m => IO b -> Iteratee a m b
@@ -555,54 +336,8 @@
 		Right b -> Yield b (Chunks [])
 		Left err -> Error err
 
--- | A common pattern in 'Enumerator' implementations is to check whether
--- the inner 'Iteratee' has finished, and if so, to return its output.
--- 'checkContinue0' passes its parameter a continuation if the 'Iteratee'
--- can still consume input; if not, it returns the iteratee's step.
---
--- The type signature here is a bit crazy, but it's actually very easy to
--- use. Take this code:
---
--- > repeat :: Monad m => a -> Enumerator a m b
--- > repeat x = loop where
--- > 	loop (Continue k) = k (Chunks [x]) >>== loop
--- > 	loop step = returnI step
---
--- And rewrite it without the boilerplate:
---
--- > repeat :: Monad m => a -> Enumerator a m b
--- > repeat x = checkContinue0 $ \loop k -> k (Chunks [x] >>== loop
---
--- Since: 0.4.9
-checkContinue0 :: Monad m
-               => (Enumerator a m b
-                -> (Stream a -> Iteratee a m b)
-                -> Iteratee a m b)
-               -> Enumerator a m b
-checkContinue0 inner = loop where
-	loop (Continue k) = inner loop k
-	loop step = returnI step
-
-
--- | Like 'checkContinue0', but allows each loop step to use a state value:
---
--- > iterate :: Monad m => (a -> a) -> a -> Enumerator a m b
--- > iterate f = checkContinue1 $ \loop a k -> k (Chunks [a]) >>== loop (f a)
---
--- Since: 0.4.9
-checkContinue1 :: Monad m
-               => ((s1 -> Enumerator a m b)
-                -> s1
-                -> (Stream a -> Iteratee a m b)
-                -> Iteratee a m b)
-               -> s1
-               -> Enumerator a m b
-checkContinue1 inner = loop where
-	loop s (Continue k) = inner loop s k
-	loop _ step = returnI step
-
--- | Lift an 'Iteratee' onto a monad transformer, re-wrapping the
--- 'Iteratee'&#x2019;s inner monadic values.
+-- | Lift an 'Iteratee' onto a monad transformer, re-wrapping its
+-- inner monadic values.
 --
 -- Since: 0.1.1
 liftTrans :: (Monad m, MonadTrans t, Monad (t m)) =>
@@ -614,13 +349,6 @@
 		Error err -> Error err
 		Continue k -> Continue (liftTrans . k)
 
-{-# DEPRECATED liftI "Use 'Data.Enumerator.continue' instead" #-}
-
--- | Deprecated in 0.4.5: use 'Data.Enumerator.continue' instead
-liftI :: Monad m => (Stream a -> Step a m b)
-      -> Iteratee a m b
-liftI k = continue (returnI . k)
-
 -- | Peek at the next element in the stream, or 'Nothing' if the stream
 -- has ended.
 peek :: Monad m => Iteratee a m (Maybe a)
@@ -648,172 +376,3 @@
 	len = genericLength
 	loop n (Chunks xs) = continue (loop (n + len xs))
 	loop n EOF = yield n EOF
-
-{-# DEPRECATED head "Use 'Data.Enumerator.List.head' instead" #-}
--- | Deprecated in 0.4.5: use 'Data.Enumerator.List.head' instead
-head :: Monad m => Iteratee a m (Maybe a)
-head = EL.head
-
-{-# DEPRECATED drop "Use 'Data.Enumerator.List.drop' instead" #-}
--- | Deprecated in 0.4.5: use 'Data.Enumerator.List.drop' instead
-drop :: Monad m => Integer -> Iteratee a m ()
-drop = EL.drop
-
-{-# DEPRECATED dropWhile "Use 'Data.Enumerator.List.dropWhile' instead" #-}
--- | Deprecated in 0.4.5: use 'Data.Enumerator.List.dropWhile' instead
-dropWhile :: Monad m => (a -> Bool) -> Iteratee a m ()
-dropWhile = EL.dropWhile
-
-{-# DEPRECATED span "Use 'Data.Enumerator.List.takeWhile' instead" #-}
--- | Deprecated in 0.4.5: use 'Data.Enumerator.List.takeWhile' instead
-span :: Monad m => (a -> Bool) -> Iteratee a m [a]
-span = EL.takeWhile
-
-{-# DEPRECATED break "Use 'Data.Enumerator.List.takeWhile' instead" #-}
--- | Deprecated in 0.4.5: use 'Data.Enumerator.List.takeWhile' instead
-break :: Monad m => (a -> Bool) -> Iteratee a m [a]
-break p = EL.takeWhile (not . p)
-
-{-# DEPRECATED consume "Use 'Data.Enumerator.List.consume' instead" #-}
--- | Deprecated in 0.4.5: use 'Data.Enumerator.List.consume' instead
-consume :: Monad m => Iteratee a m [a]
-consume = EL.consume
-
-{-# DEPRECATED foldl "Use Data.Enumerator.List.fold instead" #-}
--- | Deprecated in 0.4.8: use 'Data.Enumerator.List.fold' instead
---
--- Since: 0.4.5
-foldl :: Monad m => (b -> a -> b) -> b -> Iteratee a m b
-foldl step = continue . loop where
-	fold = Prelude.foldl step
-	loop acc stream = case stream of
-		Chunks [] -> continue (loop acc)
-		Chunks xs -> continue (loop (fold acc xs))
-		EOF -> yield acc EOF
-
-{-# DEPRECATED foldl' "Use Data.Enumerator.List.fold instead" #-}
--- | Deprecated in 0.4.8: use 'Data.Enumerator.List.fold' instead
---
--- Since: 0.4.5
-foldl' :: Monad m => (b -> a -> b) -> b -> Iteratee a m b
-foldl' = EL.fold
-
-{-# DEPRECATED foldM "Use Data.Enumerator.List.foldM instead" #-}
--- | Deprecated in 0.4.8: use 'Data.Enumerator.List.foldM' instead
---
--- Since: 0.4.5
-foldM :: Monad m => (b -> a -> m b) -> b -> Iteratee a m b
-foldM = EL.foldM
-
-{-# DEPRECATED iterate "Use Data.Enumerator.List.iterate instead" #-}
--- | Deprecated in 0.4.8: use 'Data.Enumerator.List.iterate' instead
---
--- Since: 0.4.5
-iterate :: Monad m => (a -> a) -> a -> Enumerator a m b
-iterate = EL.iterate
-
-{-# DEPRECATED iterateM "Use Data.Enumerator.List.iterateM instead" #-}
--- | Deprecated in 0.4.8: use 'Data.Enumerator.List.iterateM' instead
---
--- Since: 0.4.5
-iterateM :: Monad m => (a -> m a) -> a -> Enumerator a m b
-iterateM = EL.iterateM
-
-{-# DEPRECATED repeat "Use Data.Enumerator.List.repeat instead" #-}
--- | Deprecated in 0.4.8: use 'Data.Enumerator.List.repeat' instead
---
--- Since: 0.4.5
-repeat :: Monad m => a -> Enumerator a m b
-repeat = EL.repeat
-
-{-# DEPRECATED repeatM "Use Data.Enumerator.List.repeatM instead" #-}
--- | Deprecated in 0.4.8: use 'Data.Enumerator.List.repeatM' instead
---
--- Since: 0.4.5
-repeatM :: Monad m => m a -> Enumerator a m b
-repeatM = EL.repeatM
-
-{-# DEPRECATED replicate "Use Data.Enumerator.List.replicate instead" #-}
--- | Deprecated in 0.4.8: use 'Data.Enumerator.List.replicate' instead
---
--- Since: 0.4.5
-replicate :: Monad m => Integer -> a -> Enumerator a m b
-replicate = EL.replicate
-
-{-# DEPRECATED replicateM "Use Data.Enumerator.List.replicateM instead" #-}
--- | Deprecated in 0.4.8: use 'Data.Enumerator.List.replicateM' instead
---
--- Since: 0.4.5
-replicateM :: Monad m => Integer -> m a -> Enumerator a m b
-replicateM = EL.replicateM
-
-{-# DEPRECATED generateM "Use Data.Enumerator.List.generateM instead" #-}
--- | Deprecated in 0.4.8: use 'Data.Enumerator.List.generateM' instead
---
--- Since: 0.4.5
-generateM :: Monad m => m (Maybe a) -> Enumerator a m b
-generateM = EL.generateM
-
-{-# DEPRECATED map "Use Data.Enumerator.List.map instead" #-}
--- | Deprecated in 0.4.8: use 'Data.Enumerator.List.map' instead
-map :: Monad m => (ao -> ai) -> Enumeratee ao ai m b
-map = EL.map
-
-{-# DEPRECATED mapM "Use Data.Enumerator.List.mapM instead" #-}
--- | Deprecated in 0.4.8: use 'Data.Enumerator.List.mapM' instead
---
--- Since: 0.4.3
-mapM :: Monad m => (ao -> m ai) -> Enumeratee ao ai m b
-mapM = EL.mapM
-
-{-# DEPRECATED concatMap "Use Data.Enumerator.List.concatMap instead" #-}
--- | Deprecated in 0.4.8: use 'Data.Enumerator.List.concatMap' instead
---
--- Since: 0.4.3
-concatMap :: Monad m => (ao -> [ai]) -> Enumeratee ao ai m b
-concatMap = EL.concatMap
-
-{-# DEPRECATED concatMapM "Use Data.Enumerator.List.concatMapM instead" #-}
--- | Deprecated in 0.4.8: use 'Data.Enumerator.List.concatMapM' instead
---
--- Since: 0.4.5
-concatMapM :: Monad m => (ao -> m [ai]) -> Enumeratee ao ai m b
-concatMapM = EL.concatMapM
-
-{-# DEPRECATED filter "Use Data.Enumerator.List.filter instead" #-}
--- | Deprecated in 0.4.8: use 'Data.Enumerator.List.filter' instead
---
--- Since: 0.4.5
-filter :: Monad m => (a -> Bool) -> Enumeratee a a m b
-filter = EL.filter
-
-{-# DEPRECATED filterM "Use Data.Enumerator.List.filterM instead" #-}
--- | Deprecated in 0.4.8: use 'Data.Enumerator.List.filterM' instead
---
--- Since: 0.4.5
-filterM :: Monad m => (a -> m Bool) -> Enumeratee a a m b
-filterM = EL.filterM
-
-{-# DEPRECATED liftFoldL "Use Data.Enumerator.List.fold instead" #-}
--- | Deprecated in 0.4.5: use 'Data.Enumerator.List.fold' instead
---
--- Since: 0.1.1
-liftFoldL :: Monad m => (b -> a -> b) -> b
-          -> Iteratee a m b
-liftFoldL = Data.Enumerator.foldl
-
-{-# DEPRECATED liftFoldL' "Use Data.Enumerator.List.fold instead" #-}
--- | Deprecated in 0.4.5: use 'Data.Enumerator.List.fold' instead
---
--- Since: 0.1.1
-liftFoldL' :: Monad m => (b -> a -> b) -> b
-           -> Iteratee a m b
-liftFoldL' = EL.fold
-
-{-# DEPRECATED liftFoldM "Use Data.Enumerator.List.foldM instead" #-}
--- | Deprecated in 0.4.5: use 'Data.Enumerator.List.foldM' instead
---
--- Since: 0.1.1
-liftFoldM :: Monad m => (b -> a -> m b) -> b
-          -> Iteratee a m b
-liftFoldM = EL.foldM
diff --git a/lib/Data/Enumerator/Binary.hs b/lib/Data/Enumerator/Binary.hs
--- a/lib/Data/Enumerator/Binary.hs
+++ b/lib/Data/Enumerator/Binary.hs
@@ -108,9 +108,8 @@
 import qualified System.IO as IO
 import           System.IO.Error (isEOFError)
 
-import           Data.Enumerator hiding ( head, drop, iterateM, repeatM, replicateM
-                                        , generateM, filterM, consume, foldM
-                                        , concatMapM)
+import           Data.Enumerator.Internal
+import           Data.Enumerator (isEOF, throwError, tryIO)
 import qualified Data.Enumerator.List as EL
 
 -- | Consume the entire input stream with a strict left fold, one byte
@@ -194,7 +193,7 @@
 	loop k (x:xs) = do
 		fx <- lift (f x)
 		k (Chunks [fx]) >>==
-			checkDoneEx (Chunks [B.pack xs]) (\k' -> loop k' xs)
+			checkDoneEx (Chunks [B.pack xs]) (`loop` xs)
 
 -- | Similar to 'Data.Enumerator.Binary.concatMap', but with a stateful step
 -- function.
@@ -326,7 +325,7 @@
 		len = toInteger (BL.length lazy)
 		
 		iter = if len < n'
-			then continue (loop (acc . (BL.append lazy)) (n' - len))
+			then continue (loop (acc . BL.append lazy) (n' - len))
 			else let
 				(xs', extra) = BL.splitAt (fromInteger n') lazy
 				in yield (acc xs') (toChunks extra)
@@ -343,7 +342,7 @@
 		lazy = BL.fromChunks xs
 		(xs', extra) = BL.span p lazy
 		iter = if BL.null extra
-			then continue (loop (acc . (BL.append lazy)))
+			then continue (loop (acc . BL.append lazy))
 			else yield (acc xs') (toChunks extra)
 	loop acc EOF = yield (acc BL.empty) EOF
 
@@ -355,7 +354,7 @@
 	loop acc (Chunks []) = continue (loop acc)
 	loop acc (Chunks xs) = iter where
 		lazy = BL.fromChunks xs
-		iter = continue (loop (acc . (BL.append lazy)))
+		iter = continue (loop (acc . BL.append lazy))
 	loop acc EOF = yield (acc BL.empty) EOF
 
 -- | Pass input from a stream through two iteratees at once. Excess input is
@@ -665,7 +664,7 @@
 		lazy = BL.fromChunks xs
 		len = toInteger (BL.length lazy)
 		iter = if len < n'
-			then continue (loop (acc . (BL.append lazy)) (n' - len))
+			then continue (loop (acc . BL.append lazy) (n' - len))
 			else yield () (toChunks (acc lazy))
 	loop _ _ EOF = throwError (Exc.ErrorCall "require: Unexpected EOF")
 
@@ -686,8 +685,8 @@
 			then k (Chunks xs) >>== isolate (n - len)
 			else let
 				(s1, s2) = BL.splitAt (fromInteger n) lazy
-				in k (toChunks s1) >>== (\step -> yield step (toChunks s2))
-	loop EOF = k EOF >>== (\step -> yield step EOF)
+				in k (toChunks s1) >>== (`yield` toChunks s2)
+	loop EOF = k EOF >>== (`yield` EOF)
 isolate n step = drop n >> return step
 
 -- | Split on bytes satisfying a given predicate.
@@ -774,7 +773,7 @@
 			if B.null bytes
 				then continue k
 				else feed bytes
-		feed bs = k (Chunks [bs]) >>== loop (n - (toInteger (B.length bs)))
+		feed bs = k (Chunks [bs]) >>== loop (n - toInteger (B.length bs))
 		in if rem <= 0
 			then continue k
 			else keepGoing
diff --git a/lib/Data/Enumerator/Compatibility.hs b/lib/Data/Enumerator/Compatibility.hs
new file mode 100644
--- /dev/null
+++ b/lib/Data/Enumerator/Compatibility.hs
@@ -0,0 +1,219 @@
+-- |
+-- Module: Data.Enumerator.Compatibility
+-- Copyright: 2010-2011 John Millikin
+-- License: MIT
+--
+-- Maintainer: jmillikin@gmail.com
+-- Portability: portable
+--
+-- Shims for compatibility with earlier versions of the Enumerator library.
+module Data.Enumerator.Compatibility
+	( liftI
+	, head
+	, drop
+	, dropWhile
+	, span
+	, break
+	, consume
+	, foldl
+	, foldl'
+	, foldM
+	, iterate
+	, iterateM
+	, repeat
+	, repeatM
+	, replicate
+	, replicateM
+	, generateM
+	, map
+	, mapM
+	, concatMap
+	, concatMapM
+	, filter
+	, filterM
+	, liftFoldL
+	, liftFoldL'
+	, liftFoldM
+	
+	) where
+
+import qualified Prelude
+import           Prelude (Bool, Integer, Maybe, Monad, not, (.))
+
+import           Data.Enumerator.Internal
+import {-# SOURCE #-} qualified Data.Enumerator.List as EL
+
+{-# DEPRECATED liftI "Use 'Data.Enumerator.continue' instead" #-}
+-- | Deprecated in 0.4.5: use 'Data.Enumerator.continue' instead
+liftI :: Monad m => (Stream a -> Step a m b)
+      -> Iteratee a m b
+liftI k = continue (returnI . k)
+
+{-# DEPRECATED head "Use 'Data.Enumerator.List.head' instead" #-}
+-- | Deprecated in 0.4.5: use 'Data.Enumerator.List.head' instead
+head :: Monad m => Iteratee a m (Maybe a)
+head = EL.head
+
+{-# DEPRECATED drop "Use 'Data.Enumerator.List.drop' instead" #-}
+-- | Deprecated in 0.4.5: use 'Data.Enumerator.List.drop' instead
+drop :: Monad m => Integer -> Iteratee a m ()
+drop = EL.drop
+
+{-# DEPRECATED dropWhile "Use 'Data.Enumerator.List.dropWhile' instead" #-}
+-- | Deprecated in 0.4.5: use 'Data.Enumerator.List.dropWhile' instead
+dropWhile :: Monad m => (a -> Bool) -> Iteratee a m ()
+dropWhile = EL.dropWhile
+
+{-# DEPRECATED span "Use 'Data.Enumerator.List.takeWhile' instead" #-}
+-- | Deprecated in 0.4.5: use 'Data.Enumerator.List.takeWhile' instead
+span :: Monad m => (a -> Bool) -> Iteratee a m [a]
+span = EL.takeWhile
+
+{-# DEPRECATED break "Use 'Data.Enumerator.List.takeWhile' instead" #-}
+-- | Deprecated in 0.4.5: use 'Data.Enumerator.List.takeWhile' instead
+break :: Monad m => (a -> Bool) -> Iteratee a m [a]
+break p = EL.takeWhile (not . p)
+
+{-# DEPRECATED consume "Use 'Data.Enumerator.List.consume' instead" #-}
+-- | Deprecated in 0.4.5: use 'Data.Enumerator.List.consume' instead
+consume :: Monad m => Iteratee a m [a]
+consume = EL.consume
+
+{-# DEPRECATED foldl "Use Data.Enumerator.List.fold instead" #-}
+-- | Deprecated in 0.4.8: use 'Data.Enumerator.List.fold' instead
+--
+-- Since: 0.4.5
+foldl :: Monad m => (b -> a -> b) -> b -> Iteratee a m b
+foldl step = continue . loop where
+	fold = Prelude.foldl step
+	loop acc stream = case stream of
+		Chunks [] -> continue (loop acc)
+		Chunks xs -> continue (loop (fold acc xs))
+		EOF -> yield acc EOF
+
+{-# DEPRECATED foldl' "Use Data.Enumerator.List.fold instead" #-}
+-- | Deprecated in 0.4.8: use 'Data.Enumerator.List.fold' instead
+--
+-- Since: 0.4.5
+foldl' :: Monad m => (b -> a -> b) -> b -> Iteratee a m b
+foldl' = EL.fold
+
+{-# DEPRECATED foldM "Use Data.Enumerator.List.foldM instead" #-}
+-- | Deprecated in 0.4.8: use 'Data.Enumerator.List.foldM' instead
+--
+-- Since: 0.4.5
+foldM :: Monad m => (b -> a -> m b) -> b -> Iteratee a m b
+foldM = EL.foldM
+
+{-# DEPRECATED iterate "Use Data.Enumerator.List.iterate instead" #-}
+-- | Deprecated in 0.4.8: use 'Data.Enumerator.List.iterate' instead
+--
+-- Since: 0.4.5
+iterate :: Monad m => (a -> a) -> a -> Enumerator a m b
+iterate = EL.iterate
+
+{-# DEPRECATED iterateM "Use Data.Enumerator.List.iterateM instead" #-}
+-- | Deprecated in 0.4.8: use 'Data.Enumerator.List.iterateM' instead
+--
+-- Since: 0.4.5
+iterateM :: Monad m => (a -> m a) -> a -> Enumerator a m b
+iterateM = EL.iterateM
+
+{-# DEPRECATED repeat "Use Data.Enumerator.List.repeat instead" #-}
+-- | Deprecated in 0.4.8: use 'Data.Enumerator.List.repeat' instead
+--
+-- Since: 0.4.5
+repeat :: Monad m => a -> Enumerator a m b
+repeat = EL.repeat
+
+{-# DEPRECATED repeatM "Use Data.Enumerator.List.repeatM instead" #-}
+-- | Deprecated in 0.4.8: use 'Data.Enumerator.List.repeatM' instead
+--
+-- Since: 0.4.5
+repeatM :: Monad m => m a -> Enumerator a m b
+repeatM = EL.repeatM
+
+{-# DEPRECATED replicate "Use Data.Enumerator.List.replicate instead" #-}
+-- | Deprecated in 0.4.8: use 'Data.Enumerator.List.replicate' instead
+--
+-- Since: 0.4.5
+replicate :: Monad m => Integer -> a -> Enumerator a m b
+replicate = EL.replicate
+
+{-# DEPRECATED replicateM "Use Data.Enumerator.List.replicateM instead" #-}
+-- | Deprecated in 0.4.8: use 'Data.Enumerator.List.replicateM' instead
+--
+-- Since: 0.4.5
+replicateM :: Monad m => Integer -> m a -> Enumerator a m b
+replicateM = EL.replicateM
+
+{-# DEPRECATED generateM "Use Data.Enumerator.List.generateM instead" #-}
+-- | Deprecated in 0.4.8: use 'Data.Enumerator.List.generateM' instead
+--
+-- Since: 0.4.5
+generateM :: Monad m => m (Maybe a) -> Enumerator a m b
+generateM = EL.generateM
+
+{-# DEPRECATED map "Use Data.Enumerator.List.map instead" #-}
+-- | Deprecated in 0.4.8: use 'Data.Enumerator.List.map' instead
+map :: Monad m => (ao -> ai) -> Enumeratee ao ai m b
+map = EL.map
+
+{-# DEPRECATED mapM "Use Data.Enumerator.List.mapM instead" #-}
+-- | Deprecated in 0.4.8: use 'Data.Enumerator.List.mapM' instead
+--
+-- Since: 0.4.3
+mapM :: Monad m => (ao -> m ai) -> Enumeratee ao ai m b
+mapM = EL.mapM
+
+{-# DEPRECATED concatMap "Use Data.Enumerator.List.concatMap instead" #-}
+-- | Deprecated in 0.4.8: use 'Data.Enumerator.List.concatMap' instead
+--
+-- Since: 0.4.3
+concatMap :: Monad m => (ao -> [ai]) -> Enumeratee ao ai m b
+concatMap = EL.concatMap
+
+{-# DEPRECATED concatMapM "Use Data.Enumerator.List.concatMapM instead" #-}
+-- | Deprecated in 0.4.8: use 'Data.Enumerator.List.concatMapM' instead
+--
+-- Since: 0.4.5
+concatMapM :: Monad m => (ao -> m [ai]) -> Enumeratee ao ai m b
+concatMapM = EL.concatMapM
+
+{-# DEPRECATED filter "Use Data.Enumerator.List.filter instead" #-}
+-- | Deprecated in 0.4.8: use 'Data.Enumerator.List.filter' instead
+--
+-- Since: 0.4.5
+filter :: Monad m => (a -> Bool) -> Enumeratee a a m b
+filter = EL.filter
+
+{-# DEPRECATED filterM "Use Data.Enumerator.List.filterM instead" #-}
+-- | Deprecated in 0.4.8: use 'Data.Enumerator.List.filterM' instead
+--
+-- Since: 0.4.5
+filterM :: Monad m => (a -> m Bool) -> Enumeratee a a m b
+filterM = EL.filterM
+
+{-# DEPRECATED liftFoldL "Use Data.Enumerator.List.fold instead" #-}
+-- | Deprecated in 0.4.5: use 'Data.Enumerator.List.fold' instead
+--
+-- Since: 0.1.1
+liftFoldL :: Monad m => (b -> a -> b) -> b
+          -> Iteratee a m b
+liftFoldL = foldl
+
+{-# DEPRECATED liftFoldL' "Use Data.Enumerator.List.fold instead" #-}
+-- | Deprecated in 0.4.5: use 'Data.Enumerator.List.fold' instead
+--
+-- Since: 0.1.1
+liftFoldL' :: Monad m => (b -> a -> b) -> b
+           -> Iteratee a m b
+liftFoldL' = EL.fold
+
+{-# DEPRECATED liftFoldM "Use Data.Enumerator.List.foldM instead" #-}
+-- | Deprecated in 0.4.5: use 'Data.Enumerator.List.foldM' instead
+--
+-- Since: 0.1.1
+liftFoldM :: Monad m => (b -> a -> m b) -> b
+          -> Iteratee a m b
+liftFoldM = EL.foldM
diff --git a/lib/Data/Enumerator/IO.hs b/lib/Data/Enumerator/IO.hs
--- a/lib/Data/Enumerator/IO.hs
+++ b/lib/Data/Enumerator/IO.hs
@@ -1,3 +1,5 @@
+{-# OPTIONS_HADDOCK hide #-}
+
 -- |
 -- Module: Data.Enumerator.IO
 -- Copyright: 2010-2011 John Millikin
diff --git a/lib/Data/Enumerator/Internal.hs b/lib/Data/Enumerator/Internal.hs
new file mode 100644
--- /dev/null
+++ b/lib/Data/Enumerator/Internal.hs
@@ -0,0 +1,342 @@
+-- |
+-- Module: Data.Enumerator.Internal
+-- Copyright: 2010-2011 John Millikin
+-- License: MIT
+--
+-- Maintainer: jmillikin@gmail.com
+-- Portability: portable
+--
+-- Core enumerator types, and some useful primitives.
+--
+-- Be careful when using the functions defined in this module, as they will
+-- allow you to create iteratees which violate the monad laws.
+module Data.Enumerator.Internal
+	( Stream (..)
+	, Iteratee (..)
+	, Step (..)
+	, Enumerator
+	, Enumeratee
+	
+	-- * Primitives
+	, returnI
+	, continue
+	, yield
+	
+	-- * Operators
+	, (>>==)
+	, (==<<)
+	, ($$)
+	, (>==>)
+	, (<==<)
+	
+	-- * Miscellaneous
+	, enumEOF
+	, checkContinue0
+	, checkContinue1
+	, checkDoneEx
+	, checkDone
+	
+	) where
+
+import           Control.Applicative as A
+import qualified Control.Exception as Exc
+import qualified Control.Monad as CM
+import           Control.Monad.IO.Class (MonadIO, liftIO)
+import           Control.Monad.Trans.Class (MonadTrans, lift)
+import           Data.Function (fix)
+import           Data.Monoid (Monoid, mempty, mappend, mconcat)
+import           Data.Typeable ( Typeable, typeOf
+                               , Typeable1, typeOf1
+                               , mkTyConApp, mkTyCon)
+
+-- | A 'Stream' is a sequence of chunks generated by an 'Enumerator'.
+--
+-- @('Chunks' [])@ is used to indicate that a stream is still active, but
+-- currently has no available data. Iteratees should ignore empty chunks.
+data Stream a
+	= Chunks [a]
+	| EOF
+	deriving (Show, Eq)
+
+instance Monad Stream where
+	return = Chunks . return
+	Chunks xs >>= f = mconcat (fmap f xs)
+	EOF >>= _ = EOF
+
+instance Monoid (Stream a) where
+	mempty = Chunks mempty
+	mappend (Chunks xs) (Chunks ys) = Chunks (xs ++ ys)
+	mappend _ _ = EOF
+
+data Step a m b
+	-- | The 'Iteratee' is capable of accepting more input. Note that more input
+	-- is not necessarily required; the 'Iteratee' might be able to generate a
+	-- value immediately if it receives 'EOF'.
+	= Continue (Stream a -> Iteratee a m b)
+	
+	-- | The 'Iteratee' cannot receive any more input, and has generated a
+	-- result. Included in this value is left-over input, which can be passed to
+	-- composed 'Iteratee's.
+	| Yield b (Stream a)
+	
+	-- | The 'Iteratee' encountered an error which prevents it from proceeding
+	-- further.
+	| Error Exc.SomeException
+
+-- | The primary data type for this library; an iteratee consumes
+-- chunks of input from a stream until it either yields a value or
+-- encounters an error.
+--
+-- Compatibility note: @Iteratee@ will become abstract in @enumerator_0.5@. If
+-- you depend on internal implementation details, please import
+-- @"Data.Enumerator.Internal"@.
+
+-- In general, iteratees begin in the 'Continue' state. As each chunk is
+-- passed to the continuation, the iteratee returns the next step:
+-- 'Continue' for more data, 'Yield' when it's finished, or 'Error' to
+-- abort processing.
+newtype Iteratee a m b = Iteratee
+	{ runIteratee :: m (Step a m b)
+	}
+
+instance Monad m => Monad (Iteratee a m) where
+	return x = yield x (Chunks [])
+
+	m0 >>= f = ($ m0) $ fix $
+		\bind m -> Iteratee $ runIteratee m >>= \r1 ->
+			case r1 of
+				Continue k -> return (Continue (bind . k))
+				Error err -> return (Error err)
+				Yield x (Chunks []) -> runIteratee (f x)
+				Yield x extra -> runIteratee (f x) >>= \r2 ->
+					case r2 of
+						Continue k -> runIteratee (k extra)
+						Error err -> return (Error err)
+						Yield x' _ -> return (Yield x' extra)
+
+instance MonadTrans (Iteratee a) where
+	lift m = Iteratee (m >>= runIteratee . return)
+
+instance MonadIO m => MonadIO (Iteratee a m) where
+	liftIO = lift . liftIO
+
+-- | Enumerators are sources of data, to be consumed by iteratees.
+-- Enumerators typically read from an external source (parser, handle,
+-- random generator, etc), then feed chunks into an tteratee until:
+--
+-- * The input source runs out of data.
+--
+-- * The iteratee yields a result value.
+--
+-- * The iteratee throws an exception.
+
+-- Since @'Iteratee'@ is an alias for @m ('Step' a m b)@, 'Enumerator's can
+-- be considered step transformers of type @'Step' a m b -> m ('Step' a m b)@.
+type Enumerator a m b = Step a m b -> Iteratee a m b
+
+-- | An enumeratee acts as a stream adapter; place one between an enumerator
+-- and an iteratee, and it changes the type or contents of the input stream.
+--
+-- Most users will want to combine enumerators, enumeratees, and iteratees
+-- using the stream combinators @joinI@ and @joinE@, or their operator aliases
+-- @(=$)@ and @($=)@. These combinators are used to manage how left-over input
+-- is passed between elements of the data processing pipeline.
+type Enumeratee ao ai m b = Step ai m b -> Iteratee ao m (Step ai m b)
+
+-- | Since: 0.4.8
+instance Typeable1 Stream where
+	typeOf1 _ = mkTyConApp tyCon [] where
+		tyCon = mkTyCon "Data.Enumerator.Stream"
+
+-- | Since: 0.4.6
+instance (Typeable a, Typeable1 m) =>
+	Typeable1 (Iteratee a m) where
+		typeOf1 i = let
+			tyCon = mkTyCon "Data.Enumerator.Iteratee"
+			(a, m) = peel i
+			
+			peel :: Iteratee a m b -> (a, m ())
+			peel = undefined
+			
+			in mkTyConApp tyCon [typeOf a, typeOf1 m]
+
+-- | Since: 0.4.8
+instance (Typeable a, Typeable1 m) =>
+	Typeable1 (Step a m) where
+		typeOf1 s = let
+			tyCon = mkTyCon "Data.Enumerator.Step"
+			(a, m) = peel s
+			
+			peel :: Step a m b -> (a, m ())
+			peel = undefined
+			
+			in mkTyConApp tyCon [typeOf a, typeOf1 m]
+
+instance Monad m => Functor (Iteratee a m) where
+	fmap = CM.liftM
+
+instance Monad m => A.Applicative (Iteratee a m) where
+	pure = return
+	(<*>) = CM.ap
+
+instance Functor Stream where
+	fmap f (Chunks xs) = Chunks (fmap f xs)
+	fmap _ EOF = EOF
+
+-- | Since: 0.4.5
+instance A.Applicative Stream where
+	pure = return
+	(<*>) = CM.ap
+
+-- | @'returnI' step = 'Iteratee' (return step)@
+returnI :: Monad m => Step a m b -> Iteratee a m b
+returnI step = Iteratee (return step)
+
+-- | @'yield' x extra = 'returnI' ('Yield' x extra)@
+--
+-- WARNING: due to the current encoding of iteratees in this library,
+-- careless use of the 'yield' primitive may violate the monad laws.
+-- To prevent this, always make sure that an iteratee never yields
+-- extra data unless it has received at least one input element.
+--
+-- More strictly, iteratees may not yield data that they did not
+-- receive as input. Don't use 'yield' to &#x201c;inject&#x201d; elements
+-- into the stream.
+yield :: Monad m => b -> Stream a -> Iteratee a m b
+yield x extra = returnI (Yield x extra)
+
+-- | @'continue' k = 'returnI' ('Continue' k)@
+continue :: Monad m => (Stream a -> Iteratee a m b) -> Iteratee a m b
+continue k = returnI (Continue k)
+
+infixl 1 >>==
+infixr 1 ==<<
+infixr 0 $$
+infixr 1 >==>
+infixr 1 <==<
+
+-- | The most primitive stream operator. @iter >>== enum@ returns a new
+-- iteratee which will read from @enum@ before continuing.
+(>>==) :: Monad m
+       => Iteratee a m b
+       -> (Step a m b -> Iteratee a' m b')
+       -> Iteratee a' m b'
+i >>== f = Iteratee (runIteratee i >>= runIteratee . f)
+
+-- | @('==<<') = flip ('>>==')@
+(==<<) :: Monad m
+       => (Step a m b -> Iteratee a' m b')
+       -> Iteratee a m b
+       -> Iteratee a' m b'
+(==<<) = flip (>>==)
+
+-- | @('$$') = ('==<<')@
+--
+-- This is somewhat easier to read when constructing an iteratee from many
+-- processing stages. You can treat it like @('$')@, and read the data flow
+-- from left to right.
+--
+-- Since: 0.1.1
+($$) :: Monad m
+     => (Step a m b -> Iteratee a' m b')
+     -> Iteratee a m b
+     -> Iteratee a' m b'
+($$) = (==<<)
+
+-- | @('>==>') enum1 enum2 step = enum1 step '>>==' enum2@
+--
+-- The moral equivalent of @('CM.>=>')@ for iteratees.
+--
+-- Since: 0.1.1
+(>==>) :: Monad m
+       => Enumerator a m b
+       -> (Step a m b -> Iteratee a' m b')
+       -> Step a m b
+       -> Iteratee a' m b'
+(>==>) e1 e2 s = e1 s >>== e2
+
+-- | @('<==<') = flip ('>==>')@
+--
+-- Since: 0.1.1
+(<==<) :: Monad m
+       => (Step a m b -> Iteratee a' m b')
+       -> Enumerator a m b
+       -> Step a m b
+       -> Iteratee a' m b'
+(<==<) = flip (>==>)
+
+-- | Sends 'EOF' to its iteratee. Most clients should use 'run' or 'run_'
+-- instead.
+enumEOF :: Monad m => Enumerator a m b
+enumEOF (Yield x _) = yield x EOF
+enumEOF (Error err) = returnI (Error err)
+enumEOF (Continue k) = k EOF >>== check where
+	check (Continue _) = error "enumEOF: divergent iteratee"
+	check s = enumEOF s
+
+-- | A common pattern in 'Enumeratee' implementations is to check whether
+-- the inner 'Iteratee' has finished, and if so, to return its output.
+-- 'checkDone' passes its parameter a continuation if the 'Iteratee'
+-- can still consume input, or yields otherwise.
+--
+-- Since: 0.4.3
+checkDoneEx :: Monad m =>
+	Stream a' ->
+	((Stream a -> Iteratee a m b) -> Iteratee a' m (Step a m b)) ->
+	Enumeratee a' a m b
+checkDoneEx _     f (Continue k) = f k
+checkDoneEx extra _ step         = yield step extra
+
+-- | @'checkDone' = 'checkDoneEx' ('Chunks' [])@
+--
+-- Use this for enumeratees which do not have an input buffer.
+checkDone :: Monad m =>
+	((Stream a -> Iteratee a m b) -> Iteratee a' m (Step a m b)) ->
+	Enumeratee a' a m b
+checkDone = checkDoneEx (Chunks [])
+
+-- | A common pattern in 'Enumerator' implementations is to check whether
+-- the inner 'Iteratee' has finished, and if so, to return its output.
+-- 'checkContinue0' passes its parameter a continuation if the 'Iteratee'
+-- can still consume input; if not, it returns the iteratee's step.
+--
+-- The type signature here is a bit crazy, but it's actually very easy to
+-- use. Take this code:
+--
+-- > repeat :: Monad m => a -> Enumerator a m b
+-- > repeat x = loop where
+-- > 	loop (Continue k) = k (Chunks [x]) >>== loop
+-- > 	loop step = returnI step
+--
+-- And rewrite it without the boilerplate:
+--
+-- > repeat :: Monad m => a -> Enumerator a m b
+-- > repeat x = checkContinue0 $ \loop k -> k (Chunks [x] >>== loop
+--
+-- Since: 0.4.9
+checkContinue0 :: Monad m
+               => (Enumerator a m b
+                -> (Stream a -> Iteratee a m b)
+                -> Iteratee a m b)
+               -> Enumerator a m b
+checkContinue0 inner = loop where
+	loop (Continue k) = inner loop k
+	loop step = returnI step
+
+
+-- | Like 'checkContinue0', but allows each loop step to use a state value:
+--
+-- > iterate :: Monad m => (a -> a) -> a -> Enumerator a m b
+-- > iterate f = checkContinue1 $ \loop a k -> k (Chunks [a]) >>== loop (f a)
+--
+-- Since: 0.4.9
+checkContinue1 :: Monad m
+               => ((s1 -> Enumerator a m b)
+                -> s1
+                -> (Stream a -> Iteratee a m b)
+                -> Iteratee a m b)
+               -> s1
+               -> Enumerator a m b
+checkContinue1 inner = loop where
+	loop s (Continue k) = inner loop s k
+	loop _ step = returnI step
diff --git a/lib/Data/Enumerator/List.hs b/lib/Data/Enumerator/List.hs
--- a/lib/Data/Enumerator/List.hs
+++ b/lib/Data/Enumerator/List.hs
@@ -91,9 +91,8 @@
 import           Data.Monoid (mappend)
 import qualified Data.Set
 
-import           Data.Enumerator hiding ( concatMapM, iterateM, replicateM, head, drop
-                                        , foldM, repeatM, generateM, filterM, consume
-                                        , length)
+import           Data.Enumerator (sequence, throwError)
+import           Data.Enumerator.Internal
 
 -- | Consume the entire input stream with a strict left fold, one element
 -- at a time.
@@ -160,7 +159,7 @@
 	loop k (x:xs) = do
 		fx <- lift (f x)
 		k (Chunks fx) >>==
-			checkDoneEx (Chunks xs) (\k' -> loop k' xs)
+			checkDoneEx (Chunks xs) (`loop` xs)
 
 -- | @'Data.Enumerator.List.concatMap' f@ applies /f/ to each input element
 -- and feeds the resulting outputs to the inner iteratee.
@@ -676,8 +675,8 @@
 		| len xs <= n = k (Chunks xs) >>== isolate (n - len xs)
 		| otherwise = let
 			(s1, s2) = L.genericSplitAt n xs
-			in k (Chunks s1) >>== (\step -> yield step (Chunks s2))
-	loop EOF = k EOF >>== (\step -> yield step EOF)
+			in k (Chunks s1) >>== (`yield` Chunks s2)
+	loop EOF = k EOF >>== (`yield` EOF)
 isolate n step = drop n >> return step
 
 -- | Split on elements satisfying a given predicate.
diff --git a/lib/Data/Enumerator/List.hs-boot b/lib/Data/Enumerator/List.hs-boot
--- a/lib/Data/Enumerator/List.hs-boot
+++ b/lib/Data/Enumerator/List.hs-boot
@@ -1,5 +1,5 @@
 module Data.Enumerator.List where
-import {-# SOURCE #-} Data.Enumerator
+import Data.Enumerator.Internal
 head :: Monad m => Iteratee a m (Maybe a)
 drop :: Monad m => Integer -> Iteratee a m ()
 dropWhile :: Monad m => (a -> Bool) -> Iteratee a m ()
diff --git a/lib/Data/Enumerator/Text.hs b/lib/Data/Enumerator/Text.hs
--- a/lib/Data/Enumerator/Text.hs
+++ b/lib/Data/Enumerator/Text.hs
@@ -129,9 +129,8 @@
 import           System.IO.Error (isEOFError)
 import           System.IO.Unsafe (unsafePerformIO)
 
-import           Data.Enumerator hiding ( head, drop, generateM, filterM, consume
-                                        , concatMapM, iterateM, repeatM, replicateM
-                                        , foldM)
+import           Data.Enumerator.Internal
+import           Data.Enumerator (isEOF, tryIO, throwError)
 import qualified Data.Enumerator.List as EL
 import           Data.Enumerator.Util (tSpanBy, tlSpanBy, reprWord, reprChar, textToStrict)
 
@@ -216,7 +215,7 @@
 	loop k (x:xs) = do
 		fx <- lift (f x)
 		k (Chunks [fx]) >>==
-			checkDoneEx (Chunks [T.pack xs]) (\k' -> loop k' xs)
+			checkDoneEx (Chunks [T.pack xs]) (`loop` xs)
 
 -- | Similar to 'Data.Enumerator.Text.concatMap', but with a stateful step
 -- function.
@@ -347,7 +346,7 @@
 		len = toInteger (TL.length lazy)
 		
 		iter = if len < n'
-			then continue (loop (acc . (TL.append lazy)) (n' - len))
+			then continue (loop (acc . TL.append lazy) (n' - len))
 			else let
 				(xs', extra) = TL.splitAt (fromInteger n') lazy
 				in yield (acc xs') (toChunks extra)
@@ -364,7 +363,7 @@
 		lazy = TL.fromChunks xs
 		(xs', extra) = tlSpanBy p lazy
 		iter = if TL.null extra
-			then continue (loop (acc . (TL.append lazy)))
+			then continue (loop (acc . TL.append lazy))
 			else yield (acc xs') (toChunks extra)
 	loop acc EOF = yield (acc TL.empty) EOF
 
@@ -376,7 +375,7 @@
 	loop acc (Chunks []) = continue (loop acc)
 	loop acc (Chunks xs) = iter where
 		lazy = TL.fromChunks xs
-		iter = continue (loop (acc . (TL.append lazy)))
+		iter = continue (loop (acc . TL.append lazy))
 	loop acc EOF = yield (acc TL.empty) EOF
 
 -- | Pass input from a stream through two iteratees at once. Excess input is
@@ -686,7 +685,7 @@
 		lazy = TL.fromChunks xs
 		len = toInteger (TL.length lazy)
 		iter = if len < n'
-			then continue (loop (acc . (TL.append lazy)) (n' - len))
+			then continue (loop (acc . TL.append lazy) (n' - len))
 			else yield () (toChunks (acc lazy))
 	loop _ _ EOF = throwError (Exc.ErrorCall "require: Unexpected EOF")
 
@@ -707,8 +706,8 @@
 			then k (Chunks xs) >>== isolate (n - len)
 			else let
 				(s1, s2) = TL.splitAt (fromInteger n) lazy
-				in k (toChunks s1) >>== (\step -> yield step (toChunks s2))
-	loop EOF = k EOF >>== (\step -> yield step EOF)
+				in k (toChunks s1) >>== (`yield` toChunks s2)
+	loop EOF = k EOF >>== (`yield` EOF)
 isolate n step = drop n >> return step
 
 -- | Split on characters satisfying a given predicate.
@@ -838,13 +837,18 @@
 decode :: Monad m => Codec
        -> Enumeratee B.ByteString T.Text m b
 decode codec = checkDone (continue . step B.empty) where
-	step _   k EOF = yield (Continue k) EOF
+	step acc k EOF = if B.null acc
+		then yield (Continue k) EOF
+		else throwError (Exc.ErrorCall "Unexpected EOF while decoding")
 	step acc k (Chunks xs) = loop acc k xs
 	
 	loop acc k [] = continue (step acc k)
 	loop acc k (x:xs) = let
 		(text, extra) = codecDecode codec (B.append acc x)
-		extraChunks = Chunks (either snd id extra : xs)
+		extraChunks = Chunks $ case extra of
+			Right bytes | B.null bytes -> xs
+			Right bytes -> bytes:xs
+			Left (_, bytes) -> bytes:xs
 		
 		checkError k' = case extra of
 			Left (exc, _) -> throwError exc
@@ -927,8 +931,8 @@
 		       | (n + 1) == maxN = decodeTo n
 		loop n = let
 			req = utf16Required
-				(B.index bytes 0)
-				(B.index bytes 1)
+				(B.index bytes n)
+				(B.index bytes (n + 1))
 			decodeMore = loop $! n + req
 			in if n + req > maxN
 				then decodeTo n
@@ -952,8 +956,8 @@
 		       | (n + 1) == maxN = decodeTo n
 		loop n = let
 			req = utf16Required
-				(B.index bytes 1)
-				(B.index bytes 0)
+				(B.index bytes (n + 1))
+				(B.index bytes n)
 			decodeMore = loop $! n + req
 			in if n + req > maxN
 				then decodeTo n
diff --git a/lib/Data/Enumerator/Util.hs b/lib/Data/Enumerator/Util.hs
--- a/lib/Data/Enumerator/Util.hs
+++ b/lib/Data/Enumerator/Util.hs
@@ -15,10 +15,10 @@
 		else Prelude.replicate (size - len) '0' ++ str
 
 reprChar :: Char -> String
-reprChar c = "U+" ++ (pad0 4 (showIntAtBase 16 (toUpper . intToDigit) (ord c) ""))
+reprChar c = "U+" ++ pad0 4 (showIntAtBase 16 (toUpper . intToDigit) (ord c) "")
 
 reprWord :: Word8 -> String
-reprWord w = "0x" ++ (pad0 2 (showIntAtBase 16 (toUpper . intToDigit) w ""))
+reprWord w = "0x" ++ pad0 2 (showIntAtBase 16 (toUpper . intToDigit) w "")
 
 tSpanBy  :: (Char -> Bool) -> T.Text -> (T.Text, T.Text)
 tlSpanBy :: (Char -> Bool) -> TL.Text -> (TL.Text, TL.Text)
diff --git a/scripts/run-coverage b/scripts/run-coverage
new file mode 100644
--- /dev/null
+++ b/scripts/run-coverage
@@ -0,0 +1,78 @@
+#!/bin/bash
+if [ ! -f 'enumerator.cabal' ]; then
+	echo -n "Can't find enumerator.cabal; please run this script as"
+	echo -n " ./scripts/run-coverage from within the enumerator source"
+	echo " directory"
+	exit 1
+fi
+
+. scripts/common.bash
+
+require_cabal_dev
+
+pushd tests
+$CABAL_DEV -s ../cabal-dev install --flags="coverage" || exit 1
+popd
+
+rm -f enumerator_tests.tix
+cabal-dev/bin/enumerator_tests $@
+
+EXCLUDES="\
+--exclude=Main \
+--exclude=EnumeratorTests.Binary \
+--exclude=EnumeratorTests.Binary.Consume \
+--exclude=EnumeratorTests.Binary.Drop \
+--exclude=EnumeratorTests.Binary.Fold \
+--exclude=EnumeratorTests.Binary.Handle \
+--exclude=EnumeratorTests.Binary.Isolate \
+--exclude=EnumeratorTests.Binary.Iterate \
+--exclude=EnumeratorTests.Binary.Map \
+--exclude=EnumeratorTests.Binary.Repeat \
+--exclude=EnumeratorTests.Binary.Replicate \
+--exclude=EnumeratorTests.Binary.Require \
+--exclude=EnumeratorTests.Binary.Split \
+--exclude=EnumeratorTests.Binary.Unfold \
+--exclude=EnumeratorTests.Binary.Util \
+--exclude=EnumeratorTests.Binary.Zip \
+--exclude=EnumeratorTests.CatchError \
+--exclude=EnumeratorTests.Compatibility \
+--exclude=EnumeratorTests.Instances \
+--exclude=EnumeratorTests.Join \
+--exclude=EnumeratorTests.List \
+--exclude=EnumeratorTests.List.Consume \
+--exclude=EnumeratorTests.List.Drop \
+--exclude=EnumeratorTests.List.Fold \
+--exclude=EnumeratorTests.List.Isolate  \
+--exclude=EnumeratorTests.List.Iterate \
+--exclude=EnumeratorTests.List.Map \
+--exclude=EnumeratorTests.List.Repeat \
+--exclude=EnumeratorTests.List.Replicate \
+--exclude=EnumeratorTests.List.Require \
+--exclude=EnumeratorTests.List.Split \
+--exclude=EnumeratorTests.List.Unfold \
+--exclude=EnumeratorTests.List.Unique \
+--exclude=EnumeratorTests.List.Util \
+--exclude=EnumeratorTests.List.Zip \
+--exclude=EnumeratorTests.Misc \
+--exclude=EnumeratorTests.Sequence \
+--exclude=EnumeratorTests.Stream \
+--exclude=EnumeratorTests.Text \
+--exclude=EnumeratorTests.Text.Codecs \
+--exclude=EnumeratorTests.Text.Consume \
+--exclude=EnumeratorTests.Text.Drop \
+--exclude=EnumeratorTests.Text.Fold \
+--exclude=EnumeratorTests.Text.Handle \
+--exclude=EnumeratorTests.Text.Isolate \
+--exclude=EnumeratorTests.Text.Iterate \
+--exclude=EnumeratorTests.Text.Map \
+--exclude=EnumeratorTests.Text.Repeat \
+--exclude=EnumeratorTests.Text.Replicate \
+--exclude=EnumeratorTests.Text.Require \
+--exclude=EnumeratorTests.Text.Split \
+--exclude=EnumeratorTests.Text.Unfold \
+--exclude=EnumeratorTests.Text.Util \
+--exclude=EnumeratorTests.Text.Zip \
+--exclude=EnumeratorTests.Util"
+
+hpc markup --srcdir=src/ --srcdir=tests/ enumerator_tests.tix --destdir=hpc-markup $EXCLUDES > /dev/null
+hpc report --srcdir=src/ --srcdir=tests/ enumerator_tests.tix $EXCLUDES
diff --git a/tests/EnumeratorTests.hs b/tests/EnumeratorTests.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests.hs
@@ -0,0 +1,48 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module Main
+	( tests
+	, main
+	) where
+
+import           Test.Chell (Suite, defaultMain)
+
+import           EnumeratorTests.Binary (test_Binary)
+import           EnumeratorTests.CatchError (test_CatchError)
+import           EnumeratorTests.Compatibility (test_Compatibility)
+import           EnumeratorTests.Instances (test_Instances)
+import           EnumeratorTests.Join
+import           EnumeratorTests.List (test_List)
+import           EnumeratorTests.Misc
+import           EnumeratorTests.Sequence (test_Sequence)
+import           EnumeratorTests.Stream (test_Stream)
+import           EnumeratorTests.Text (test_Text)
+
+tests :: [Suite]
+tests =
+	[ test_Binary
+	, test_CatchError
+	, test_Compatibility
+	, test_ConcatEnums
+	, test_EnumEOF
+	, test_Instances
+	, test_JoinE
+	, test_JoinI
+	, test_JoinOperatorAssociativity
+	, test_Last
+	, test_Length
+	, test_LiftTrans
+	, test_List
+	, test_Peek
+	, test_PrintChunks
+	, test_Sequence
+	, test_Stream
+	, test_Text
+	, test_TryIO
+	]
+
+main :: IO ()
+main = Test.Chell.defaultMain tests
diff --git a/tests/EnumeratorTests/Binary.hs b/tests/EnumeratorTests/Binary.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Binary.hs
@@ -0,0 +1,64 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Binary
+	( test_Binary
+	) where
+
+import           Test.Chell
+
+import           EnumeratorTests.Binary.Consume
+import           EnumeratorTests.Binary.Drop
+import           EnumeratorTests.Binary.Fold
+import           EnumeratorTests.Binary.Handle
+import           EnumeratorTests.Binary.Isolate
+import           EnumeratorTests.Binary.Iterate
+import           EnumeratorTests.Binary.Map
+import           EnumeratorTests.Binary.Repeat
+import           EnumeratorTests.Binary.Replicate
+import           EnumeratorTests.Binary.Require
+import           EnumeratorTests.Binary.Split
+import           EnumeratorTests.Binary.Unfold
+import           EnumeratorTests.Binary.Zip
+
+test_Binary :: Suite
+test_Binary = suite "binary"
+	[ test_Consume
+	, test_ConcatMap
+	, test_ConcatMapM
+	, test_ConcatMapAccum
+	, test_ConcatMapAccumM
+	, test_Drop
+	, test_DropWhile
+	, test_EnumHandle
+	, test_EnumHandleRange
+	, test_Filter
+	, test_FilterM
+	, test_Fold
+	, test_FoldM
+	, test_GenerateM
+	, test_Head
+	, test_Head_
+	, test_Isolate
+	, test_Iterate
+	, test_IterateM
+	, test_IterHandle
+	, test_Map
+	, test_MapM
+	, test_MapM_
+	, test_MapAccum
+	, test_MapAccumM
+	, test_Repeat
+	, test_RepeatM
+	, test_Replicate
+	, test_ReplicateM
+	, test_Require
+	, test_SplitWhen
+	, test_Take
+	, test_TakeWhile
+	, test_Unfold
+	, test_UnfoldM
+	, test_Zip
+	]
diff --git a/tests/EnumeratorTests/Binary/Consume.hs b/tests/EnumeratorTests/Binary/Consume.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Binary/Consume.hs
@@ -0,0 +1,104 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Binary.Consume
+	( test_Consume
+	, test_Head
+	, test_Head_
+	, test_Take
+	, test_TakeWhile
+	) where
+
+import           Control.Exception
+import           Data.Word (Word8)
+import           Test.Chell
+
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+import qualified Data.Enumerator.Binary as EB
+
+import           EnumeratorTests.Util (equalExc)
+
+test_Consume :: Suite
+test_Consume = assertions "consume" $ do
+	$expect $ equal
+		("ABC", Nothing)
+		(E.runLists_ [[], ["A", "B"], ["C"]] $ do
+			xs <- EB.consume
+			h <- EL.head
+			return (xs, h))
+
+test_Head :: Suite
+test_Head = assertions "head" $ do
+	$expect $ equal
+		(Just 0x41, ["BC", "DE"])
+		(E.runLists_ [[], ["ABC", "DE"]] $ do
+			x <- EB.head
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		(Nothing :: Maybe Word8, [])
+		(E.runLists_ [] $ do
+			x <- EB.head
+			extra <- EL.consume
+			return (x, extra))
+
+test_Head_ :: Suite
+test_Head_ = assertions "head_" $ do
+	$expect $ equal
+		(0x41, ["BC", "DE"])
+		(E.runLists_ [["ABC"], ["DE"]] $ do
+			x <- EB.head_
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equalExc
+		(ErrorCall "head_: stream has ended")
+		(E.runLists [] $ do
+			x <- EB.head_
+			extra <- EL.consume
+			return (x, extra))
+
+test_Take :: Suite
+test_Take = assertions "take" $ do
+	$expect $ equal
+		("ABC", ["D", "E"])
+		(E.runLists_ [["A", "B"], ["C", "D"], ["E"]] $ do
+			x <- EB.take 3
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		("AB", [])
+		(E.runLists_ [["A"], ["B"]] $ do
+			x <- EB.take 3
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		("", ["A", "B"])
+		(E.runLists_ [["A"], ["B"]] $ do
+			x <- EB.take 0
+			extra <- EL.consume
+			return (x, extra))
+
+test_TakeWhile :: Suite
+test_TakeWhile = assertions "takeWhile" $ do
+	$expect $ equal
+		("ABC", ["D", "E"])
+		(E.runLists_ [[], ["A", "B"], ["C", "D"], ["E"]] $ do
+			x <- EB.takeWhile (< 0x44)
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		("AB", [])
+		(E.runLists_ [["A"], ["B"]] $ do
+			x <- EB.takeWhile (< 0x44)
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		("", ["A", "B"])
+		(E.runLists_ [["A"], ["B"]] $ do
+			x <- EB.takeWhile (< 0x41)
+			extra <- EL.consume
+			return (x, extra))
diff --git a/tests/EnumeratorTests/Binary/Drop.hs b/tests/EnumeratorTests/Binary/Drop.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Binary/Drop.hs
@@ -0,0 +1,64 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Binary.Drop
+	( test_Drop
+	, test_DropWhile
+	, test_Filter
+	, test_FilterM
+	) where
+
+import           Test.Chell
+
+import           Data.Enumerator ((=$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.Binary as EB
+import qualified Data.Enumerator.List as EL
+
+test_Drop :: Suite
+test_Drop = assertions "drop" $ do
+	$expect $ equal
+		["ABCDE"]
+		(E.runLists_ [["ABCDE"]] $ do
+			EB.drop 0
+			EL.consume)
+	$expect $ equal
+		["CDE"]
+		(E.runLists_ [["ABCDE"]] $ do
+			EB.drop 2
+			EL.consume)
+	$expect $ equal
+		["CDE"]
+		(E.runLists_ [["A"], ["BCDE"]] $ do
+			EB.drop 2
+			EL.consume)
+
+test_DropWhile :: Suite
+test_DropWhile = assertions "dropWhile" $ do
+	$expect $ equal
+		["CDE"]
+		(E.runLists_ [["ABCDE"]] $ do
+			EB.dropWhile (< 0x43)
+			EL.consume)
+	$expect $ equal
+		[]
+		(E.runLists_ [["ABCDE"]] $ do
+			EB.dropWhile (\_ -> True)
+			EL.consume)
+
+test_Filter :: Suite
+test_Filter = assertions "filter" $ do
+	$expect $ equal
+		["A", "B", "", "D", "E"]
+		(E.runLists_ [["ABCDE"]] $ do
+			EB.filter (/= 0x43) =$ EL.consume)
+
+test_FilterM :: Suite
+test_FilterM = assertions "filterM" $ do
+	$expect $ equal
+		["A", "B", "", "D", "E"]
+		(E.runLists_ [["ABCDE"]] $ do
+			EB.filterM (\x -> return (x /= 0x43)) =$ EL.consume)
diff --git a/tests/EnumeratorTests/Binary/Fold.hs b/tests/EnumeratorTests/Binary/Fold.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Binary/Fold.hs
@@ -0,0 +1,42 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Binary.Fold
+	( test_Fold
+	, test_FoldM
+	) where
+
+import           Control.Monad (foldM)
+import qualified Data.ByteString
+import           Data.ByteString (ByteString)
+import           Data.Functor.Identity (runIdentity)
+import           Data.Word (Word8)
+import           Test.Chell
+import           Test.Chell.QuickCheck
+import           Test.QuickCheck.Poly
+import           Test.QuickCheck.Modifiers
+
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.Binary as EB
+
+import           EnumeratorTests.Util ()
+
+test_Fold :: Suite
+test_Fold = property "fold" prop_Fold
+
+prop_Fold :: Blind (B -> Word8 -> B) -> B -> ByteString -> Bool
+prop_Fold (Blind f) z text = result == expected where
+	result = E.runLists_ [[text]] (EB.fold f z)
+	expected = Data.ByteString.foldl' f z text
+
+test_FoldM :: Suite
+test_FoldM = property "foldM" prop_FoldM
+
+prop_FoldM :: Blind (B -> Word8 -> B) -> B -> ByteString -> Bool
+prop_FoldM (Blind f) z text = result == expected where
+	result = E.runLists_ [[text]] (EB.foldM f' z)
+	expected = runIdentity (foldM f' z (Data.ByteString.unpack text))
+	f' b a = return (f b a)
diff --git a/tests/EnumeratorTests/Binary/Handle.hs b/tests/EnumeratorTests/Binary/Handle.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Binary/Handle.hs
@@ -0,0 +1,88 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Binary.Handle
+	( test_EnumHandle
+	, test_EnumHandleRange
+	, test_IterHandle
+	) where
+
+import           Test.Chell
+
+#ifdef MIN_VERSION_knob
+
+import           Data.Knob
+import qualified System.IO as IO
+
+import qualified Data.Enumerator as E
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator.Binary as EB
+import qualified Data.Enumerator.List as EL
+
+test_EnumHandle :: Suite
+test_EnumHandle = assertions "enumHandle" $ do
+	knob <- newKnob "01234567"
+	chunks <- withFileHandle knob "" IO.ReadMode $ \h -> do
+		E.run_ (EB.enumHandle 3 h $$ EL.consume)
+	$expect (equal chunks ["012", "345", "67"])
+
+test_EnumHandleRange :: Suite
+test_EnumHandleRange = assertions "enumHandleRange" $ do
+	knob <- newKnob "01234567"
+	
+	-- no offset or count
+	do
+		chunks <- withFileHandle knob "" IO.ReadMode $ \h -> do
+			E.run_ (EB.enumHandleRange 3 Nothing Nothing h $$ EL.consume)
+		$expect (equal chunks ["012", "345", "67"])
+	
+	-- offset
+	do
+		chunks <- withFileHandle knob "" IO.ReadMode $ \h -> do
+			E.run_ (EB.enumHandleRange 3 (Just 1) Nothing h $$ EL.consume)
+		$expect (equal chunks ["123", "456", "7"])
+	
+	-- count
+	do
+		chunks <- withFileHandle knob "" IO.ReadMode $ \h -> do
+			E.run_ (EB.enumHandleRange 3 Nothing (Just 7) h $$ EL.consume)
+		$expect (equal chunks ["012", "345", "6"])
+	
+	-- count beyond EOF
+	do
+		chunks <- withFileHandle knob "" IO.ReadMode $ \h -> do
+			E.run_ (EB.enumHandleRange 3 Nothing (Just 10) h $$ EL.consume)
+		$expect (equal chunks ["012", "345", "67"])
+	
+	-- offset + count
+	do
+		chunks <- withFileHandle knob "" IO.ReadMode $ \h -> do
+			E.run_ (EB.enumHandleRange 3 (Just 1) (Just 6) h $$ EL.consume)
+		$expect (equal chunks ["123", "456"])
+
+test_IterHandle :: Suite
+test_IterHandle = assertions "iterHandle" $ do
+	knob <- newKnob ""
+	withFileHandle knob "" IO.WriteMode $ \h -> do
+		E.run_ (E.enumLists [[], ["A", "B"], ["C"]] $$ EB.iterHandle h)
+	bytes <- Data.Knob.getContents knob
+	$expect (equal bytes "ABC")
+
+#else
+
+import           EnumeratorTests.Util (todo)
+
+test_EnumHandle :: Suite
+test_EnumHandle = todo "enumHandle"
+
+test_EnumHandleRange :: Suite
+test_EnumHandleRange = todo "enumHandleRange"
+
+test_IterHandle :: Suite
+test_IterHandle = todo "iterHandle"
+
+#endif
diff --git a/tests/EnumeratorTests/Binary/Isolate.hs b/tests/EnumeratorTests/Binary/Isolate.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Binary/Isolate.hs
@@ -0,0 +1,74 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Binary.Isolate
+	( test_Isolate
+	) where
+
+import qualified Data.ByteString.Lazy as BL
+import           Data.Word (Word8)
+
+import           Test.Chell
+import           Test.Chell.QuickCheck
+
+import           Data.Enumerator (($$), (=$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.Binary as EB
+import qualified Data.Enumerator.List as EL
+
+import           EnumeratorTests.Binary.Util (prop_Bytes)
+
+test_Isolate :: Suite
+test_Isolate = suite "isolate"
+	[ prop_Isolate
+	, test_DropExtra
+	, test_HandleEOF
+	, test_BadParameter
+	]
+
+prop_Isolate :: Suite
+prop_Isolate = property "model" $ prop_Bytes
+	(do
+		x <- E.joinI (EB.isolate 2 $$ EB.head)
+		extra <- EB.consume
+		return (x, extra))
+	(\bytes -> Right $ case BL.unpack bytes of
+		[] -> (Nothing, BL.empty)
+		(x:[]) -> (Just x, BL.empty)
+		(x:_:xs) -> (Just x, BL.pack xs))
+
+test_DropExtra :: Suite
+test_DropExtra = assertions "drop-extra" $ do
+	$expect $ equal
+		(Just 0x41, ["C"])
+		(E.runLists_ [[], ["A"], ["B"], ["C"]] $ do
+			x <- EB.isolate 2 =$ EB.head
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		(Just 0x41, ["C"])
+		(E.runLists_ [["A", "B", "C"]] $ do
+			x <- EB.isolate 2 =$ EB.head
+			extra <- EL.consume
+			return (x, extra))
+
+test_HandleEOF :: Suite
+test_HandleEOF = assertions "handle-eof" $ do
+	$expect $ equal
+		(Nothing :: Maybe Word8, [])
+		(E.runLists_ [] $ do
+			x <- EB.isolate 2 =$ EB.head
+			extra <- EL.consume
+			return (x, extra))
+
+test_BadParameter :: Suite
+test_BadParameter = assertions "bad-parameter" $ do
+	$expect $ equal
+		(Nothing, ["A", "B", "C"])
+		(E.runLists_ [["A"], ["B"], ["C"]] $ do
+			x <- EB.isolate 0 =$ EB.head
+			extra <- EL.consume
+			return (x, extra))
diff --git a/tests/EnumeratorTests/Binary/Iterate.hs b/tests/EnumeratorTests/Binary/Iterate.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Binary/Iterate.hs
@@ -0,0 +1,31 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Binary.Iterate
+	( test_Iterate
+	, test_IterateM
+	) where
+
+import           Data.Functor.Identity (runIdentity)
+import           Test.Chell
+
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.Binary as EB
+import qualified Data.Enumerator.List as EL
+
+test_Iterate :: Suite
+test_Iterate = assertions "iterate" $ do
+	$expect $ equal
+		["A", "B", "C"]
+		(runIdentity (E.run_ (EB.iterate succ 0x41 $$ EL.take 3)))
+
+test_IterateM :: Suite
+test_IterateM = assertions "iterateM" $ do
+	let succM = return . succ
+	$expect $ equal
+		["A", "B", "C"]
+		(runIdentity (E.run_ (EB.iterateM succM 0x41 $$ EL.take 3)))
diff --git a/tests/EnumeratorTests/Binary/Map.hs b/tests/EnumeratorTests/Binary/Map.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Binary/Map.hs
@@ -0,0 +1,140 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Binary.Map
+	( test_Map
+	, test_MapM
+	, test_MapM_
+	, test_ConcatMap
+	, test_ConcatMapM
+	, test_ConcatMapAccum
+	, test_ConcatMapAccumM
+	, test_MapAccum
+	, test_MapAccumM
+	) where
+
+import           Control.Monad.Trans.Writer (execWriter, tell)
+import qualified Data.ByteString as B
+import           Test.Chell
+
+import           Data.Enumerator (($$), (=$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.Binary as EB
+import qualified Data.Enumerator.List as EL
+
+test_Map :: Suite
+test_Map = assertions "map" $ do
+	$expect $ equal
+		["a", "b"]
+		(E.runLists_ [["AB"]] $ do
+			EB.map (+ 0x20) =$ EL.consume)
+	$expect $ equal
+		(["a", "b"], ["CDEF", "GH"])
+		(E.runLists_ [["ABCD", "EF"], ["GH"]] $ do
+			xs <- EB.map (+ 0x20) =$ EL.take 2
+			extra <- EL.consume
+			return (xs, extra))
+
+test_MapM :: Suite
+test_MapM = assertions "mapM" $ do
+	$expect $ equal
+		["a", "b"]
+		(E.runLists_ [["AB"]] $ do
+			EB.mapM (\x -> return (x + 0x20)) =$ EL.consume)
+	$expect $ equal
+		(["a", "b"], ["CDEF", "GH"])
+		(E.runLists_ [["ABCD", "EF"], ["GH"]] $ do
+			xs <- EB.mapM (\x -> return (x + 0x20)) =$ EL.take 2
+			extra <- EL.consume
+			return (xs, extra))
+
+test_MapM_ :: Suite
+test_MapM_ = assertions "mapM_" $ do
+	$expect $ equal
+		[0x41, 0x42]
+		(execWriter (E.run_ (E.enumLists [["AB"]] $$ EB.mapM_ (\x -> tell [x]))))
+
+test_ConcatMap :: Suite
+test_ConcatMap = assertions "concatMap" $ do
+	$expect $ equal
+		["Aa", "Bb"]
+		(E.runLists_ [["AB"]] $ do
+			EB.concatMap (\x -> B.pack [x, x + 0x20]) =$ EL.consume)
+	$expect $ equal
+		(["Aa", "Bb"], ["CDEF", "GH"])
+		(E.runLists_ [["ABCD", "EF"], ["GH"]] $ do
+			xs <- EB.concatMap (\x -> B.pack [x, x + 0x20]) =$ EL.take 2
+			extra <- EL.consume
+			return (xs, extra))
+
+test_ConcatMapM :: Suite
+test_ConcatMapM = assertions "concatMapM" $ do
+	$expect $ equal
+		["Aa", "Bb"]
+		(E.runLists_ [["AB"]] $ do
+			EB.concatMapM (\x -> return (B.pack [x, x + 0x20])) =$ EL.consume)
+	$expect $ equal
+		(["Aa", "Bb"], ["CDEF", "GH"])
+		(E.runLists_ [["ABCD", "EF"], ["GH"]] $ do
+			xs <- EB.concatMapM (\x -> return (B.pack [x, x + 0x20])) =$ EL.take 2
+			extra <- EL.consume
+			return (xs, extra))
+
+test_MapAccum :: Suite
+test_MapAccum = assertions "mapAccum" $ do
+	let step s ao = (s + 1, ao + s)
+	$expect $ equal
+		["B", "D", "F"]
+		(E.runLists_ [["A", "B"], ["C"]] $ do
+			EB.mapAccum step 1 =$ EL.consume)
+	$expect $ equal
+		("B", ["", "B", "C"])
+		(E.runLists_ [["A", "B"], ["C"]] $ do
+			xs <- EB.mapAccum step 1 =$ EB.take 1
+			extra <- EL.consume
+			return (xs, extra))
+
+test_MapAccumM :: Suite
+test_MapAccumM = assertions "mapAccumM" $ do
+	let step s ao = return (s + 1, ao + s)
+	$expect $ equal
+		["B", "D", "F"]
+		(E.runLists_ [["A", "B"], ["C"]] $ do
+			EB.mapAccumM step 1 =$ EL.consume)
+	$expect $ equal
+		("B", ["", "B", "C"])
+		(E.runLists_ [["A", "B"], ["C"]] $ do
+			xs <- EB.mapAccumM step 1 =$ EB.take 1
+			extra <- EL.consume
+			return (xs, extra))
+
+test_ConcatMapAccum :: Suite
+test_ConcatMapAccum = assertions "concatMapAccum" $ do
+	let step s ao = (s + 1, B.replicate s ao)
+	$expect $ equal
+		["A", "BB", "CCC"]
+		(E.runLists_ [["A", "B"], ["C"]] $ do
+			EB.concatMapAccum step 1 =$ EL.consume)
+	$expect $ equal
+		("AB", ["", "C"])
+		(E.runLists_ [["A", "B"], ["C"]] $ do
+			xs <- EB.concatMapAccum step 1 =$ EB.take 2
+			extra <- EL.consume
+			return (xs, extra))
+
+test_ConcatMapAccumM :: Suite
+test_ConcatMapAccumM = assertions "concatMapAccumM" $ do
+	let step s ao = return (s + 1, B.replicate s ao)
+	$expect $ equal
+		["A", "BB", "CCC"]
+		(E.runLists_ [["A", "B"], ["C"]] $ do
+			EB.concatMapAccumM step 1 =$ EL.consume)
+	$expect $ equal
+		("AB", ["", "C"])
+		(E.runLists_ [["A", "B"], ["C"]] $ do
+			xs <- EB.concatMapAccumM step 1 =$ EB.take 2
+			extra <- EL.consume
+			return (xs, extra))
diff --git a/tests/EnumeratorTests/Binary/Repeat.hs b/tests/EnumeratorTests/Binary/Repeat.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Binary/Repeat.hs
@@ -0,0 +1,49 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Binary.Repeat
+	( test_Repeat
+	, test_RepeatM
+	, test_GenerateM
+	) where
+
+import           Control.Monad.Trans.State
+import           Data.Functor.Identity (runIdentity)
+import           Test.Chell
+
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.Binary as EB
+import qualified Data.Enumerator.List as EL
+
+test_Repeat :: Suite
+test_Repeat = assertions "repeat" $ do
+	$expect $ equal
+		["A", "A", "A"]
+		(runIdentity (E.run_ (EB.repeat 0x41 $$ EL.take 3)))
+
+test_RepeatM :: Suite
+test_RepeatM = assertions "repeatM" $ do
+	let step = do
+		c <- get
+		put (succ c)
+		return c
+	$expect $ equal
+		["A", "B", "C"]
+		(evalState (E.run_ (EB.repeatM step $$ EL.take 3)) 0x41)
+
+test_GenerateM :: Suite
+test_GenerateM = assertions "generateM" $ do
+	let step = do
+		c <- get
+		if c > 0x43
+			then return Nothing
+			else do
+				put (succ c)
+				return (Just c)
+	$expect $ equal
+		["A", "B", "C"]
+		(evalState (E.run_ (EB.generateM step $$ EL.consume)) 0x41)
diff --git a/tests/EnumeratorTests/Binary/Replicate.hs b/tests/EnumeratorTests/Binary/Replicate.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Binary/Replicate.hs
@@ -0,0 +1,38 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Binary.Replicate
+	( test_Replicate
+	, test_ReplicateM
+	) where
+
+import           Control.Monad.Trans.State
+import           Data.Functor.Identity (runIdentity)
+import           Test.Chell
+
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.Binary as EB
+import qualified Data.Enumerator.List as EL
+
+test_Replicate :: Suite
+test_Replicate = assertions "replicate" $ do
+	$expect $ equal
+		["A", "A", "A"]
+		(runIdentity (E.run_ (EB.replicate 3 0x41 $$ EL.consume)))
+
+test_ReplicateM :: Suite
+test_ReplicateM = assertions "replicateM" $ do
+	let step = do
+		c <- get
+		put (succ c)
+		return c
+	$expect $ equal
+		["A", "B", "C"]
+		(evalState (E.run_ (EB.replicateM 3 step $$ EL.consume)) 0x41)
+	$expect $ equal
+		["A", "B"]
+		(evalState (E.run_ (EB.replicateM 3 step $$ EL.take 2)) 0x41)
diff --git a/tests/EnumeratorTests/Binary/Require.hs b/tests/EnumeratorTests/Binary/Require.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Binary/Require.hs
@@ -0,0 +1,67 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Binary.Require
+	( test_Require
+	) where
+
+import qualified Control.Exception as Exc
+import qualified Data.ByteString.Lazy as BL
+import           Test.Chell
+import           Test.Chell.QuickCheck
+
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.Binary as EB
+import qualified Data.Enumerator.List as EL
+
+import           EnumeratorTests.Binary.Util
+
+test_Require :: Suite
+test_Require = suite "require"
+	[ prop_Require
+	, test_YieldsInput
+	, test_HandleEOF
+	, test_BadParameter
+	]
+
+prop_Require :: Suite
+prop_Require = property "model" $ prop_BytesN
+	(\n -> do
+		EB.require n
+		EB.consume)
+	(\n xs -> if n > toInteger (BL.length xs)
+		then Left (Exc.ErrorCall "require: Unexpected EOF")
+		else Right xs)
+
+test_YieldsInput :: Suite
+test_YieldsInput = assertions "yields-input" $ do
+	$expect $ equal
+		["A", "B", "C"]
+		(E.runLists_ [["A"], ["B"], ["C"]] $ do
+			EB.require 2
+			EL.consume)
+	$expect $ equal
+		["A", "B", "C"]
+		(E.runLists_ [["A", "B", "C"]] $ do
+			EB.require 2
+			EL.consume)
+
+test_HandleEOF :: Suite
+test_HandleEOF = assertions "handle-eof" $ do
+	$expect $ throwsEq
+		(Exc.ErrorCall "require: Unexpected EOF")
+		(E.run_ (E.enumLists [] $$ do
+			EB.require 2
+			EL.consume))
+
+test_BadParameter :: Suite
+test_BadParameter = assertions "bad-parameter" $ do
+	$expect $ equal
+		[]
+		(E.runLists_ [] $ do
+			EB.require 0
+			EL.consume)
diff --git a/tests/EnumeratorTests/Binary/Split.hs b/tests/EnumeratorTests/Binary/Split.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Binary/Split.hs
@@ -0,0 +1,49 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Binary.Split
+	( test_SplitWhen
+	) where
+
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as BL
+
+import qualified Data.List.Split as LS
+import           Test.Chell
+import           Test.Chell.QuickCheck
+
+import           Data.Enumerator (($$), (=$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+import qualified Data.Enumerator.Binary as EB
+
+import           EnumeratorTests.Binary.Util
+
+test_SplitWhen :: Suite
+test_SplitWhen = suite "splitWhen"
+	[ prop_SplitWhen
+	, test_HandleEmpty
+	]
+
+prop_SplitWhen :: Suite
+prop_SplitWhen = property "model" $ prop_BytesX
+	(\c -> do
+		xs <- E.joinI (EB.splitWhen (== c) $$ EL.consume)
+		extra <- EL.consume
+		return (xs, extra))
+	(\c text -> let
+		split = LS.split . LS.dropFinalBlank . LS.dropDelims . LS.whenElt
+		chars = BL.unpack text
+		in Right (map B.pack (split (== c) chars), []))
+
+test_HandleEmpty :: Suite
+test_HandleEmpty = assertions "empty" $ do
+	$expect $ equal
+		([], Nothing)
+		(E.runLists_ [[""]] $ do
+			xs <- EB.splitWhen (== 0x2C) =$ EL.consume
+			extra <- EL.head
+			return (xs, extra))
diff --git a/tests/EnumeratorTests/Binary/Unfold.hs b/tests/EnumeratorTests/Binary/Unfold.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Binary/Unfold.hs
@@ -0,0 +1,37 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Binary.Unfold
+	( test_Unfold
+	, test_UnfoldM
+	) where
+
+import           Data.Functor.Identity (runIdentity)
+import           Test.Chell
+
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.Binary as EB
+import qualified Data.Enumerator.List as EL
+
+test_Unfold :: Suite
+test_Unfold = assertions "unfold" $ do
+	let step x = if x > 0x43
+		then Nothing
+		else Just (x, succ x)
+	$expect $ equal
+		["A", "B", "C"]
+		(runIdentity (E.run_ (EB.unfold step 0x41 $$ EL.consume)))
+
+test_UnfoldM :: Suite
+test_UnfoldM = assertions "unfoldM" $ do
+	let step x = return $ if x > 0x43
+		then Nothing
+		else Just (x, succ x)
+	$expect $ equal
+		["A", "B", "C"]
+		(runIdentity (E.run_ (EB.unfoldM step 0x41 $$ EL.consume)))
diff --git a/tests/EnumeratorTests/Binary/Util.hs b/tests/EnumeratorTests/Binary/Util.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Binary/Util.hs
@@ -0,0 +1,44 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Binary.Util
+	( prop_Bytes
+	, prop_BytesN
+	, prop_BytesX
+	) where
+
+import           Control.Exception (ErrorCall)
+import           Data.ByteString (ByteString)
+import qualified Data.ByteString.Lazy as BL
+import           Data.Functor.Identity (Identity)
+
+import           Test.QuickCheck hiding (property)
+
+import           Data.Enumerator (Iteratee)
+
+import           EnumeratorTests.Util (check)
+
+prop_Bytes :: Eq b
+           => Iteratee ByteString Identity b
+           -> (BL.ByteString -> Either ErrorCall b)
+           -> [ByteString]
+           -> Bool
+prop_Bytes iter plain = check iter (plain . BL.fromChunks)
+
+prop_BytesN :: Eq b
+            => (t -> Iteratee ByteString Identity b)
+            -> (t -> BL.ByteString -> Either ErrorCall b)
+            -> Positive t
+            -> [ByteString]
+            -> Bool
+prop_BytesN iter plain (Positive n) = check (iter n) (plain n . BL.fromChunks)
+
+prop_BytesX :: Eq b
+            => (t -> Iteratee ByteString Identity b)
+            -> (t -> BL.ByteString -> Either ErrorCall b)
+            -> t
+            -> [ByteString]
+            -> Bool
+prop_BytesX iter plain x = check (iter x) (plain x . BL.fromChunks)
diff --git a/tests/EnumeratorTests/Binary/Zip.hs b/tests/EnumeratorTests/Binary/Zip.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Binary/Zip.hs
@@ -0,0 +1,165 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Binary.Zip
+	( test_Zip
+	) where
+
+import qualified Control.Exception as Exc
+import           Data.ByteString (ByteString)
+import           Data.Functor.Identity (Identity)
+import           Data.Text (Text)
+import           Test.Chell
+
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.Binary as EB
+import qualified Data.Enumerator.List as EL
+
+import           EnumeratorTests.Util (equalExc)
+
+test_ZipN :: (Eq b, Show b) => Text -> E.Iteratee ByteString Identity b -> b -> Suite
+test_ZipN name iter expected = assertions name $ do
+	$expect $ equal
+		expected
+		(E.runLists_ [[], ["A"], ["B"]] iter)
+
+$([d||])
+
+test_Zip :: Suite
+test_Zip = suite "zip"
+	[ test_ContinueContinue
+	, test_YieldContinue
+	, test_ContinueYield
+	, test_YieldYield
+	, test_ErrorFirst
+	, test_ErrorSecond
+	, test_HandleEOF
+	, test_Zip3
+	, test_Zip4
+	, test_Zip5
+	, test_Zip6
+	, test_Zip7
+	, test_ZipWith
+	, test_ZipWith3
+	, test_ZipWith4
+	, test_ZipWith5
+	, test_ZipWith6
+	, test_ZipWith7
+	]
+
+test_ContinueContinue :: Suite
+test_ContinueContinue = assertions "continue-continue" $ do
+	$expect $ equal
+		("AB", "AB", ["C"])
+		(E.runLists_ [["A"], ["B"], ["C"]] $ do
+			(x, y) <- EB.zip (EB.take 2) (EB.take 2)
+			extra <- EL.consume
+			return (x, y, extra))
+
+test_YieldContinue :: Suite
+test_YieldContinue = assertions "yield-continue" $ do
+	$expect $ equal
+		("A", "AB", ["C"])
+		(E.runLists_ [["A"], ["B"], ["C"]] $ do
+			(x, y) <- EB.zip (EB.take 1) (EB.take 2)
+			extra <- EL.consume
+			return (x, y, extra))
+
+test_ContinueYield :: Suite
+test_ContinueYield = assertions "continue-yield" $ do
+	$expect $ equal
+		("AB", "A", ["C"])
+		(E.runLists_ [["A"], ["B"], ["C"]] $ do
+			(x, y) <- EB.zip (EB.take 2) (EB.take 1)
+			extra <- EL.consume
+			return (x, y, extra))
+
+test_YieldYield :: Suite
+test_YieldYield = assertions "yield-yield" $ do
+	$expect $ equal
+		("A", "A", ["B", "C"])
+		(E.runLists_ [["A"], ["B"], ["C"]] $ do
+			(x, y) <- EB.zip (EB.take 1) (EB.take 1)
+			extra <- EL.consume
+			return (x, y, extra))
+
+test_ErrorFirst :: Suite
+test_ErrorFirst = assertions "error-first" $ do
+	$expect $ equalExc
+		(Exc.ErrorCall "error")
+		(E.runLists [["A"], ["B"], ["C"]] $ do
+			EB.zip (E.throwError (Exc.ErrorCall "error")) (EB.take 1))
+
+test_ErrorSecond :: Suite
+test_ErrorSecond = assertions "error-second" $ do
+	$expect $ equalExc
+		(Exc.ErrorCall "error")
+		(E.runLists [["A"], ["B"], ["C"]] $ do
+			EB.zip (EB.take 1) (E.throwError (Exc.ErrorCall "error")))
+
+test_HandleEOF :: Suite
+test_HandleEOF = assertions "handle-eof" $ do
+	$expect $ equal
+		("A", "AB", [])
+		(E.runLists_ [["A"], ["B"]] $ do
+			(x, y) <- EB.zip (EB.take 1) (EB.take 3)
+			extra <- EL.consume
+			return (x, y, extra))
+
+test_Zip3 :: Suite
+test_Zip3 = test_ZipN "zip3"
+	(EB.zip3 EB.head EB.head EB.head)
+	(Just 0x41, Just 0x41, Just 0x41)
+
+test_Zip4 :: Suite
+test_Zip4 = test_ZipN "zip4"
+	(EB.zip4 EB.head EB.head EB.head EB.head)
+	(Just 0x41, Just 0x41, Just 0x41, Just 0x41)
+
+test_Zip5 :: Suite
+test_Zip5 = test_ZipN "zip5"
+	(EB.zip5 EB.head EB.head EB.head EB.head EB.head)
+	(Just 0x41, Just 0x41, Just 0x41, Just 0x41, Just 0x41)
+
+test_Zip6 :: Suite
+test_Zip6 = test_ZipN "zip6"
+	(EB.zip6 EB.head EB.head EB.head EB.head EB.head EB.head)
+	(Just 0x41, Just 0x41, Just 0x41, Just 0x41, Just 0x41, Just 0x41)
+
+test_Zip7 :: Suite
+test_Zip7 = test_ZipN "zip7"
+	(EB.zip7 EB.head EB.head EB.head EB.head EB.head EB.head EB.head)
+	(Just 0x41, Just 0x41, Just 0x41, Just 0x41, Just 0x41, Just 0x41, Just 0x41)
+
+test_ZipWith :: Suite
+test_ZipWith = test_ZipN "zipWith"
+	(EB.zipWith (,) EB.head EB.head)
+	(Just 0x41, Just 0x41)
+
+test_ZipWith3 :: Suite
+test_ZipWith3 = test_ZipN "zipWith3"
+	(EB.zipWith3 (,,) EB.head EB.head EB.head)
+	(Just 0x41, Just 0x41, Just 0x41)
+
+test_ZipWith4 :: Suite
+test_ZipWith4 = test_ZipN "zipWith4"
+	(EB.zipWith4 (,,,) EB.head EB.head EB.head EB.head)
+	(Just 0x41, Just 0x41, Just 0x41, Just 0x41)
+
+test_ZipWith5 :: Suite
+test_ZipWith5 = test_ZipN "zipWith5"
+	(EB.zipWith5 (,,,,) EB.head EB.head EB.head EB.head EB.head)
+	(Just 0x41, Just 0x41, Just 0x41, Just 0x41, Just 0x41)
+
+test_ZipWith6 :: Suite
+test_ZipWith6 = test_ZipN "zipWith6"
+	(EB.zipWith6 (,,,,,) EB.head EB.head EB.head EB.head EB.head EB.head)
+	(Just 0x41, Just 0x41, Just 0x41, Just 0x41, Just 0x41, Just 0x41)
+
+test_ZipWith7 :: Suite
+test_ZipWith7 = test_ZipN "zipWith7"
+	(EB.zipWith7 (,,,,,,) EB.head EB.head EB.head EB.head EB.head EB.head EB.head)
+	(Just 0x41, Just 0x41, Just 0x41, Just 0x41, Just 0x41, Just 0x41, Just 0x41)
diff --git a/tests/EnumeratorTests/CatchError.hs b/tests/EnumeratorTests/CatchError.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/CatchError.hs
@@ -0,0 +1,107 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.CatchError
+	( test_CatchError
+	) where
+
+import           Control.Concurrent
+import qualified Control.Exception as Exc
+import           Control.Monad.IO.Class (liftIO)
+
+import           Test.Chell
+
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+
+import           EnumeratorTests.Util (equalExc, within)
+
+test_CatchError :: Suite
+test_CatchError = suite "catchError"
+	[ test_WithoutContinue
+	, test_NotDivergent
+	, test_Interleaved
+	, test_YieldImmediately
+	, test_HandleError
+	, test_HandleEOF
+	, test_GotStream
+	]
+
+test_WithoutContinue :: Suite
+test_WithoutContinue = assertions "without-continue" $ do
+	$expect $ equalExc
+		(Exc.ErrorCall "require: Unexpected EOF")
+		(E.runLists [] $ E.catchError
+			(E.throwError (Exc.ErrorCall "error"))
+			(\_ -> EL.require 1))
+
+test_NotDivergent :: Suite
+test_NotDivergent = assertions "not-divergent" $ do
+	$expect $ equalExc
+		(Exc.ErrorCall "require: Unexpected EOF")
+		(E.runLists [] $ E.catchError
+			(do
+				_ <- EL.head
+				E.throwError (Exc.ErrorCall "error"))
+			(\_ -> EL.require 1))
+
+test_Interleaved :: Suite
+test_Interleaved = within 1000 $ assertions "interleaved" $ do
+	let enumMVar mvar = EL.repeatM (liftIO (takeMVar mvar))
+	let iter mvar = do
+	    	liftIO (putMVar mvar ())
+	    	_ <- EL.head
+	    	return True
+	let onError _ = return False
+	
+	mvar <- liftIO newEmptyMVar
+	E.run_ (enumMVar mvar $$ E.catchError (iter mvar) onError)
+
+test_YieldImmediately :: Suite
+test_YieldImmediately = assertions "yield-immediately" $ do
+	$expect $ equal
+		'A'
+		(E.runLists_ [['A']] $ do
+			E.catchError (return 'A') (\_ -> return 'B'))
+
+test_HandleError :: Suite
+test_HandleError = assertions "handle-error" $ do
+	$expect $ equal
+		"error"
+		(E.runLists_ [] $ E.catchError
+			(EL.head >> E.throwError (Exc.ErrorCall "error"))
+			(\err -> return (show err)))
+	$expect $ equal
+		"error"
+		(E.runLists_ [['A']] $ E.catchError
+			(E.throwError (Exc.ErrorCall "error"))
+			(\err -> return (show err)))
+	$expect $ equal
+		"error"
+		(E.runLists_ [['A'], ['B'], ['C']] $ E.catchError
+			(EL.drop 1 >> E.throwError (Exc.ErrorCall "error"))
+			(\err -> return (show err)))
+
+test_HandleEOF :: Suite
+test_HandleEOF = assertions "handle-eof" $ do
+	$expect $ equal
+		(Nothing, Nothing)
+		(E.runLists_ [] $ do
+			x <- E.catchError EL.head (\_ -> return (Just 'B'))
+			y <- EL.head
+			return (x, y))
+
+test_GotStream :: Suite
+test_GotStream = assertions "got-stream" $ do
+	$expect $ equal
+		(Just 'B')
+		(E.runLists_ [['A'], ['B'], ['C']] $ E.catchError
+			(do
+				_ <- EL.head
+				_ <- EL.head
+				E.throwError (Exc.ErrorCall "error"))
+			(\_ -> EL.head))
diff --git a/tests/EnumeratorTests/Compatibility.hs b/tests/EnumeratorTests/Compatibility.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Compatibility.hs
@@ -0,0 +1,204 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Compatibility
+	( test_Compatibility
+	) where
+
+import           Data.Functor.Identity (Identity, runIdentity)
+import           Data.Text (Text)
+import           Test.Chell
+
+import qualified Data.Enumerator as E
+import           Data.Enumerator (($$), (=$))
+import qualified Data.Enumerator.List as EL
+
+compatIter :: (Eq a, Show a)
+           => Text
+           -> E.Iteratee Char Identity a
+           -> E.Iteratee Char Identity a
+           -> Suite
+compatIter name i1 i2 = assertions name $ do
+	let run i = E.runLists_ [[], ['A', 'B'], ['C', 'D'], ['E']] $ do
+		x <- i
+		y <- EL.consume
+		return (x, y)
+	$expect (equal (run i1) (run i2))
+
+compatEnum :: (Eq a, Show a)
+           => Text
+           -> E.Enumerator a Identity [a]
+           -> E.Enumerator a Identity [a]
+           -> Suite
+compatEnum name e1 e2 = assertions name $ do
+	let run e = runIdentity (E.run_ (e $$ EL.take 10))
+	$expect (equal (run e1) (run e2))
+
+compatEnee :: (Eq ai, Show ai)
+           => Text
+           -> E.Enumeratee Char ai Identity [ai]
+           -> E.Enumeratee Char ai Identity [ai]
+           -> Suite
+compatEnee name e1 e2 = assertions name $ do
+	let run e = E.runLists_ [[], ['A', 'B'], ['C', 'D'], ['E']] (e =$ EL.consume)
+	$expect (equal (run e1) (run e2))
+
+$([d||])
+
+test_Compatibility :: Suite
+test_Compatibility = suite "compatibility"
+	[ test_Head
+	, test_Drop
+	, test_DropWhile
+	, test_Span
+	, test_Break
+	, test_Consume
+	, test_Foldl
+	, test_Foldl'
+	, test_FoldM
+	, test_Iterate
+	, test_IterateM
+	, test_Repeat
+	, test_RepeatM
+	, test_Replicate
+	, test_ReplicateM
+	, test_GenerateM
+	, test_Map
+	, test_MapM
+	, test_ConcatMap
+	, test_ConcatMapM
+	, test_Filter
+	, test_FilterM
+	, test_LiftFoldL
+	, test_LiftFoldL'
+	, test_LiftFoldM
+	, test_LiftI
+	]
+
+test_Head :: Suite
+test_Head = compatIter "head" E.head EL.head
+
+test_Drop :: Suite
+test_Drop = compatIter "drop" (E.drop 1) (EL.drop 1)
+
+test_DropWhile :: Suite
+test_DropWhile = compatIter "dropWhile"
+	(E.dropWhile (< 'C'))
+	(EL.dropWhile (< 'C'))
+
+test_Span :: Suite
+test_Span = compatIter "span"
+	(E.span (< 'C'))
+	(EL.takeWhile (< 'C'))
+
+test_Break :: Suite
+test_Break = compatIter "break"
+	(E.break (> 'C'))
+	(EL.takeWhile (<= 'C'))
+
+test_Consume :: Suite
+test_Consume = compatIter "consume" E.consume EL.consume
+
+test_Foldl :: Suite
+test_Foldl = compatIter "foldl"
+	(E.foldl (flip (:)) [])
+	(EL.fold (flip (:)) [])
+
+test_LiftFoldL :: Suite
+test_LiftFoldL = compatIter "liftFoldL"
+	(E.liftFoldL (flip (:)) [])
+	(EL.fold (flip (:)) [])
+
+test_Foldl' :: Suite
+test_Foldl' = compatIter "foldl'"
+	(E.foldl' (flip (:)) [])
+	(EL.fold (flip (:)) [])
+
+test_LiftFoldL' :: Suite
+test_LiftFoldL' = compatIter "liftFoldl'"
+	(E.liftFoldL' (flip (:)) [])
+	(EL.fold (flip (:)) [])
+
+test_FoldM :: Suite
+test_FoldM = compatIter "foldM"
+	(E.foldM (\xs x -> return (x:xs)) [])
+	(EL.foldM (\xs x -> return (x:xs)) [])
+
+test_LiftFoldM :: Suite
+test_LiftFoldM = compatIter "liftFoldM"
+	(E.liftFoldM (\xs x -> return (x:xs)) [])
+	(EL.foldM (\xs x -> return (x:xs)) [])
+
+test_Iterate :: Suite
+test_Iterate = compatEnum "iterate"
+	(E.iterate succ 'A')
+	(EL.iterate succ 'A')
+
+test_IterateM :: Suite
+test_IterateM = compatEnum "iterateM"
+	(E.iterateM (return . succ) 'A')
+	(EL.iterateM (return . succ) 'A')
+
+test_Repeat :: Suite
+test_Repeat = compatEnum "repeat"
+	(E.repeat 'A')
+	(EL.repeat 'A')
+
+test_RepeatM :: Suite
+test_RepeatM = compatEnum "repeatM"
+	(E.repeatM (return 'A'))
+	(EL.repeatM (return 'A'))
+
+test_Replicate :: Suite
+test_Replicate = compatEnum "replicate"
+	(E.replicate 5 'A')
+	(EL.replicate 5 'A')
+
+test_ReplicateM :: Suite
+test_ReplicateM = compatEnum "replicateM"
+	(E.replicateM 5 (return 'A'))
+	(EL.replicateM 5 (return 'A'))
+
+test_GenerateM :: Suite
+test_GenerateM = compatEnum "generateM"
+	(E.generateM (return (Just 'A')))
+	(EL.generateM (return (Just 'A')))
+
+test_Map :: Suite
+test_Map = compatEnee "map"
+	(E.map succ)
+	(EL.map succ)
+
+test_MapM :: Suite
+test_MapM = compatEnee "mapM"
+	(E.mapM (return . succ))
+	(EL.mapM (return . succ))
+
+test_ConcatMap :: Suite
+test_ConcatMap = compatEnee "concatMap"
+	(E.concatMap (\x -> [succ x]))
+	(EL.concatMap (\x -> [succ x]))
+
+test_ConcatMapM :: Suite
+test_ConcatMapM = compatEnee "concatMapM"
+	(E.concatMapM (\x -> return [succ x]))
+	(EL.concatMapM (\x -> return [succ x]))
+
+test_Filter :: Suite
+test_Filter = compatEnee "filter"
+	(E.filter (< 'C'))
+	(EL.filter (< 'C'))
+
+test_FilterM :: Suite
+test_FilterM = compatEnee "filterM"
+	(E.filterM (return . (< 'C')))
+	(EL.filterM (return . (< 'C')))
+
+test_LiftI :: Suite
+test_LiftI = compatIter "liftI"
+	(E.liftI (\s -> E.Yield s (E.Chunks [])))
+	(E.continue (\s -> E.yield s (E.Chunks [])))
diff --git a/tests/EnumeratorTests/Instances.hs b/tests/EnumeratorTests/Instances.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Instances.hs
@@ -0,0 +1,66 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Instances
+	( test_Instances
+	) where
+
+import           Control.Applicative (pure, (<*>))
+import           Data.Functor.Identity (runIdentity)
+import           Data.Typeable (typeOf)
+import           Test.Chell
+
+import           Data.Enumerator
+
+test_Instances :: Suite
+test_Instances = suite "instances"
+	[ test_Typeable
+	, test_Functor
+	, test_Applicative
+	]
+
+test_Typeable :: Suite
+test_Typeable = suite "typeable"
+	[ test_TypeableStream
+	, test_TypeableIteratee
+	, test_TypeableStep
+	]
+
+test_TypeableStream :: Suite
+test_TypeableStream = assertions "stream" $ do
+	let x = undefined :: Stream Char
+	$expect $ equal
+		"Data.Enumerator.Stream Char"
+		(show (typeOf x))
+
+test_TypeableIteratee :: Suite
+test_TypeableIteratee = assertions "iteratee" $ do
+	let x = undefined :: Iteratee Char Maybe Int
+	$expect $ equal
+		"Data.Enumerator.Iteratee Char Maybe Int"
+		(show (typeOf x))
+
+test_TypeableStep :: Suite
+test_TypeableStep = assertions "step" $ do
+	let x = undefined :: Step Char Maybe Int
+	$expect $ equal
+		"Data.Enumerator.Step Char Maybe Int"
+		(show (typeOf x))
+
+test_Functor :: Suite
+test_Functor = assertions "functor" $ do
+	$expect $ equal
+		'B'
+		(runIdentity (run_ (fmap succ (return 'A'))))
+
+test_Applicative :: Suite
+test_Applicative = assertions "applicative" $ do
+	$expect $ equal
+		'A'
+		(runIdentity (run_ (pure 'A')))
+	$expect $ equal
+		'B'
+		(runIdentity (run_ (pure succ <*> pure 'A')))
diff --git a/tests/EnumeratorTests/Join.hs b/tests/EnumeratorTests/Join.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Join.hs
@@ -0,0 +1,90 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Join
+	( test_JoinE
+	, test_JoinI
+	, test_JoinOperatorAssociativity
+	) where
+
+import           Control.Exception
+import           Data.Char (toLower)
+import           Data.Functor.Identity (Identity, runIdentity)
+
+import           Test.Chell
+
+import           Data.Enumerator (($$), ($=), (=$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+
+import           EnumeratorTests.Util (equalExc)
+
+test_JoinE :: Suite
+test_JoinE = suite "joinE"
+	[ test_JoinE_Success
+	, test_JoinE_Error
+	, test_JoinE_Divergent
+	]
+
+test_JoinE_Success :: Suite
+test_JoinE_Success = assertions "success" $ do
+	let enum :: Monad m => E.Enumerator Char m b
+	    enum = E.joinE (E.enumLists [['A', 'B', 'C']]) (EL.map toLower)
+	$expect $ equal
+		['a', 'b', 'c']
+		(runIdentity (E.run_ (enum $$ EL.consume)))
+
+test_JoinE_Error :: Suite
+test_JoinE_Error = assertions "error" $ do
+	let enum :: Monad m => E.Enumerator Char m b
+	    enum = E.joinE (E.enumLists [['A', 'B', 'C']]) (E.sequence (E.throwError (ErrorCall "foo")))
+	
+	$expect $ equalExc
+		(ErrorCall "foo")
+		(runIdentity (E.run (enum $$ EL.consume)))
+
+test_JoinE_Divergent :: Suite
+test_JoinE_Divergent = assertions "divergent" $ do
+	let enum :: Monad m => E.Enumerator Char m b
+	    enum = E.joinE (E.enumLists [['A', 'B', 'C']]) (EL.map toLower)
+	let diverg :: Monad m => E.Iteratee a m b
+	    diverg = E.continue (\_ -> diverg)
+	
+	$expect $ throwsEq
+		(ErrorCall "enumEOF: divergent iteratee")
+		(E.run_ (enum $$ diverg))
+
+test_JoinI :: Suite
+test_JoinI = assertions "joinI" $ do
+	let enum :: Monad m => E.Enumerator Char m b
+	    enum = E.enumLists [['A', 'B', 'C']]
+	
+	let diverg :: Monad m => E.Iteratee a m b
+	    diverg = E.continue (\_ -> diverg)
+	
+	$expect $ equal
+		['a', 'b', 'c']
+		(runIdentity (E.run_ (enum $$ E.joinI (EL.map toLower $$ EL.consume))))
+	$expect $ equalExc
+		(ErrorCall "foo")
+		(runIdentity (E.run (enum $$ E.joinI (EL.map toLower $$ E.throwError (ErrorCall "foo")))))
+	$expect $ throwsEq
+		(ErrorCall "joinI: divergent iteratee")
+		(E.run_ (enum $$ E.joinI (EL.map toLower $$ diverg)))
+
+test_JoinOperatorAssociativity :: Suite
+test_JoinOperatorAssociativity = assertions "join-operator-associativity" $ do
+	let xs = ['A', 'B', 'C']
+	let enum = E.enumList 1 xs
+	let enee = EL.map id
+	let iter = EL.consume
+	xs1 <- E.run_ $ enum $$ enee =$ enee =$ iter
+	xs2 <- E.run_ $ enum $= enee $$ enee =$ iter
+	xs3 <- E.run_ $ enum $= enee $= enee $$ iter
+	$expect (equal xs xs1)
+	$expect (equal xs xs2)
+	$expect (equal xs xs3)
+	return ()
diff --git a/tests/EnumeratorTests/List.hs b/tests/EnumeratorTests/List.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/List.hs
@@ -0,0 +1,62 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.List
+	( test_List
+	) where
+
+import           Test.Chell
+
+import           EnumeratorTests.List.Consume
+import           EnumeratorTests.List.Drop
+import           EnumeratorTests.List.Fold
+import           EnumeratorTests.List.Isolate
+import           EnumeratorTests.List.Iterate
+import           EnumeratorTests.List.Map
+import           EnumeratorTests.List.Repeat
+import           EnumeratorTests.List.Replicate
+import           EnumeratorTests.List.Require
+import           EnumeratorTests.List.Split
+import           EnumeratorTests.List.Unfold
+import           EnumeratorTests.List.Unique
+import           EnumeratorTests.List.Zip
+
+test_List :: Suite
+test_List = suite "list"
+	[ test_Consume
+	, test_ConcatMap
+	, test_ConcatMapM
+	, test_ConcatMapAccum
+	, test_ConcatMapAccumM
+	, test_Drop
+	, test_DropWhile
+	, test_Fold
+	, test_FoldM
+	, test_Filter
+	, test_FilterM
+	, test_GenerateM
+	, test_Head
+	, test_Head_
+	, test_Isolate
+	, test_Iterate
+	, test_IterateM
+	, test_Map
+	, test_MapM
+	, test_MapM_
+	, test_MapAccum
+	, test_MapAccumM
+	, test_Repeat
+	, test_RepeatM
+	, test_Replicate
+	, test_ReplicateM
+	, test_Require
+	, test_SplitWhen
+	, test_Take
+	, test_TakeWhile
+	, test_Unfold
+	, test_UnfoldM
+	, test_Unique
+	, test_Zip
+	]
diff --git a/tests/EnumeratorTests/List/Consume.hs b/tests/EnumeratorTests/List/Consume.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/List/Consume.hs
@@ -0,0 +1,102 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.List.Consume
+	( test_Consume
+	, test_Head
+	, test_Head_
+	, test_Take
+	, test_TakeWhile
+	) where
+
+import           Control.Exception
+import           Test.Chell
+
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+
+import           EnumeratorTests.Util (equalExc)
+
+test_Consume :: Suite
+test_Consume = assertions "consume" $ do
+	$expect $ equal
+		(['A', 'B', 'C'], Nothing)
+		(E.runLists_ [[], ['A', 'B'], ['C']] $ do
+			xs <- EL.consume
+			h <- EL.head
+			return (xs, h))
+
+test_Head :: Suite
+test_Head = assertions "head" $ do
+	$expect $ equal
+		(Just 'A', ['B', 'C'])
+		(E.runLists_ [[], ['A', 'B'], ['C']] $ do
+			x <- EL.head
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		(Nothing :: Maybe Char, [])
+		(E.runLists_ [] $ do
+			x <- EL.head
+			extra <- EL.consume
+			return (x, extra))
+
+test_Head_ :: Suite
+test_Head_ = assertions "head_" $ do
+	$expect $ equal
+		('A', ['B', 'C'])
+		(E.runLists_ [[], ['A', 'B'], ['C']] $ do
+			x <- EL.head_
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equalExc
+		(ErrorCall "head_: stream has ended")
+		(E.runLists [] $ do
+			x <- EL.head_
+			extra <- EL.consume
+			return (x, extra))
+
+test_Take :: Suite
+test_Take = assertions "take" $ do
+	$expect $ equal
+		(['A', 'B', 'C'], ['D', 'E'])
+		(E.runLists_ [['A', 'B'], ['C', 'D'], ['E']] $ do
+			x <- EL.take 3
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		(['A', 'B'], [])
+		(E.runLists_ [['A'], ['B']] $ do
+			x <- EL.take 3
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		([], ['A', 'B'])
+		(E.runLists_ [['A'], ['B']] $ do
+			x <- EL.take 0
+			extra <- EL.consume
+			return (x, extra))
+
+test_TakeWhile :: Suite
+test_TakeWhile = assertions "takeWhile" $ do
+	$expect $ equal
+		(['A', 'B', 'C'], ['D', 'E'])
+		(E.runLists_ [[], ['A', 'B'], ['C', 'D'], ['E']] $ do
+			x <- EL.takeWhile (< 'D')
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		(['A', 'B'], [])
+		(E.runLists_ [['A'], ['B']] $ do
+			x <- EL.takeWhile (< 'D')
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		([], ['A', 'B'])
+		(E.runLists_ [['A'], ['B']] $ do
+			x <- EL.takeWhile (< 'A')
+			extra <- EL.consume
+			return (x, extra))
diff --git a/tests/EnumeratorTests/List/Drop.hs b/tests/EnumeratorTests/List/Drop.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/List/Drop.hs
@@ -0,0 +1,63 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.List.Drop
+	( test_Drop
+	, test_DropWhile
+	, test_Filter
+	, test_FilterM
+	) where
+
+import           Test.Chell
+
+import           Data.Enumerator ((=$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+
+test_Drop :: Suite
+test_Drop = assertions "drop" $ do
+	$expect $ equal
+		['A', 'B', 'C', 'D', 'E']
+		(E.runLists_ [['A'], ['B'], ['C'], ['D'], ['E']] $ do
+			EL.drop 0
+			EL.consume)
+	$expect $ equal
+		['C', 'D', 'E']
+		(E.runLists_ [['A'], ['B'], ['C'], ['D'], ['E']] $ do
+			EL.drop 2
+			EL.consume)
+	$expect $ equal
+		[]
+		(E.runLists_ [['A']] $ do
+			EL.drop 2
+			EL.consume)
+
+test_DropWhile :: Suite
+test_DropWhile = assertions "dropWhile" $ do
+	$expect $ equal
+		['C', 'D', 'E']
+		(E.runLists_ [['A'], ['B'], ['C'], ['D'], ['E']] $ do
+			EL.dropWhile (< 'C')
+			EL.consume)
+	$expect $ equal
+		[]
+		(E.runLists_ [['A'], ['B'], ['C'], ['D'], ['E']] $ do
+			EL.dropWhile (\_ -> True)
+			EL.consume)
+
+test_Filter :: Suite
+test_Filter = assertions "filter" $ do
+	$expect $ equal
+		['A', 'B', 'D', 'E']
+		(E.runLists_ [['A'], ['B'], ['C'], ['D'], ['E']] $ do
+			EL.filter (/= 'C') =$ EL.consume)
+
+test_FilterM :: Suite
+test_FilterM = assertions "filterM" $ do
+	$expect $ equal
+		['A', 'B', 'D', 'E']
+		(E.runLists_ [['A'], ['B'], ['C'], ['D'], ['E']] $ do
+			EL.filterM (\x -> return (x /= 'C')) =$ EL.consume)
diff --git a/tests/EnumeratorTests/List/Fold.hs b/tests/EnumeratorTests/List/Fold.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/List/Fold.hs
@@ -0,0 +1,74 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.List.Fold
+	( test_Fold
+	, test_FoldM
+	) where
+
+import qualified Control.Exception as Exception
+import           Control.Monad (foldM)
+import           Data.Functor.Identity (runIdentity)
+import           Data.List (foldl')
+import           Test.Chell
+import           Test.Chell.QuickCheck
+import           Test.QuickCheck.Poly
+import           Test.QuickCheck.Modifiers
+
+import qualified Data.Enumerator as E
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator.List as EL
+
+import           EnumeratorTests.List.Util ()
+
+test_Fold :: Suite
+test_Fold = suite "fold"
+	[ property "model" prop_Fold
+	, test_FoldStrict
+	, test_Fold_EOF
+	]
+
+prop_Fold :: Blind (B -> A -> B) -> B -> [A] -> Bool
+prop_Fold (Blind f) z xs = result == expected where
+	result = E.runLists_ [xs] (EL.fold f z)
+	expected = foldl' f z xs
+
+test_FoldStrict :: Suite
+test_FoldStrict = assertions "strict" $ do
+	let exc = Exception.ErrorCall "fail-step"
+	let step _ x = case x of
+		'C' -> Exception.throw exc
+		_ -> 'a'
+	$expect $ throwsEq exc (E.run_ (E.enumList 1 ['A', 'B', 'C'] $$ EL.fold step 'a'))
+	$expect $ throwsEq exc (E.run_ (E.enumList 3 ['A', 'B', 'C'] $$ EL.fold step 'a'))
+
+test_Fold_EOF :: Suite
+test_Fold_EOF = assertions "eof" $ do
+	$expect $ equal
+		Nothing
+		(E.runLists_ [] $ do
+			_ <- EL.fold (++) ['A']
+			EL.head)
+
+test_FoldM :: Suite
+test_FoldM = suite "foldM"
+	[ property "model" prop_FoldM
+	, test_FoldM_EOF
+	]
+
+prop_FoldM :: Blind (B -> A -> B) -> B -> [A] -> Bool
+prop_FoldM (Blind f) z xs = result == expected where
+	result = E.runLists_ [xs] (EL.foldM f' z)
+	expected = runIdentity (foldM f' z xs)
+	f' b a = return (f b a)
+
+test_FoldM_EOF :: Suite
+test_FoldM_EOF = assertions "eof" $ do
+	$expect $ equal
+		Nothing
+		(E.runLists_ [] $ do
+			_ <- EL.foldM (\x y -> return (x ++ y)) ['A']
+			EL.head)
diff --git a/tests/EnumeratorTests/List/Isolate.hs b/tests/EnumeratorTests/List/Isolate.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/List/Isolate.hs
@@ -0,0 +1,70 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.List.Isolate
+	( test_Isolate
+	) where
+
+import           Test.Chell
+import           Test.Chell.QuickCheck
+
+import           Data.Enumerator ((=$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+
+import           EnumeratorTests.List.Util
+
+test_Isolate :: Suite
+test_Isolate = suite "isolate"
+	[ prop_Isolate
+	, test_DropExtra
+	, test_HandleEOF
+	, test_BadParameter
+	]
+
+prop_Isolate :: Suite
+prop_Isolate = property "model" $ prop_List
+	(do
+		x <- EL.isolate 2 =$ EL.head
+		extra <- EL.consume
+		return (x, extra))
+	(\xs -> Right $ case xs of
+		[] -> (Nothing, [])
+		(x:[]) -> (Just x, [])
+		(x:_:xs') -> (Just x, xs'))
+
+test_DropExtra :: Suite
+test_DropExtra = assertions "drop-extra" $ do
+	$expect $ equal
+		(Just 'A', ['C'])
+		(E.runLists_ [[], ['A'], ['B'], ['C']] $ do
+			x <- EL.isolate 2 =$ EL.head
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		(Just 'A', ['C'])
+		(E.runLists_ [['A', 'B', 'C']] $ do
+			x <- EL.isolate 2 =$ EL.head
+			extra <- EL.consume
+			return (x, extra))
+
+test_HandleEOF :: Suite
+test_HandleEOF = assertions "handle-eof" $ do
+	$expect $ equal
+		(Nothing :: Maybe Char, [])
+		(E.runLists_ [] $ do
+			x <- EL.isolate 2 =$ EL.head
+			extra <- EL.consume
+			return (x, extra))
+
+test_BadParameter :: Suite
+test_BadParameter = assertions "bad-parameter" $ do
+	$expect $ equal
+		(Nothing, ['A', 'B', 'C'])
+		(E.runLists_ [['A'], ['B'], ['C']] $ do
+			x <- EL.isolate 0 =$ EL.head
+			extra <- EL.consume
+			return (x, extra))
diff --git a/tests/EnumeratorTests/List/Iterate.hs b/tests/EnumeratorTests/List/Iterate.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/List/Iterate.hs
@@ -0,0 +1,30 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.List.Iterate
+	( test_Iterate
+	, test_IterateM
+	) where
+
+import           Data.Functor.Identity (runIdentity)
+import           Test.Chell
+
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+
+test_Iterate :: Suite
+test_Iterate = assertions "iterate" $ do
+	$expect $ equal
+		['A', 'B', 'C']
+		(runIdentity (E.run_ (EL.iterate succ 'A' $$ EL.take 3)))
+
+test_IterateM :: Suite
+test_IterateM = assertions "iterateM" $ do
+	let succM = return . succ
+	$expect $ equal
+		['A', 'B', 'C']
+		(runIdentity (E.run_ (EL.iterateM succM 'A' $$ EL.take 3)))
diff --git a/tests/EnumeratorTests/List/Map.hs b/tests/EnumeratorTests/List/Map.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/List/Map.hs
@@ -0,0 +1,142 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.List.Map
+	( test_Map
+	, test_MapM
+	, test_MapM_
+	, test_ConcatMap
+	, test_ConcatMapM
+	, test_ConcatMapAccum
+	, test_ConcatMapAccumM
+	, test_MapAccum
+	, test_MapAccumM
+	) where
+
+import           Control.Monad.Trans.Writer (execWriter, tell)
+import           Data.Char (chr, ord, toLower)
+import           Test.Chell
+
+import           Data.Enumerator (($$), (=$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+
+test_Map :: Suite
+test_Map = assertions "map" $ do
+	$expect $ equal
+		['a', 'b', 'c']
+		(E.runLists_ [['A', 'B'], ['C']] $ do
+			EL.map toLower =$ EL.consume)
+	$expect $ equal
+		(['a'], ['B', 'C'])
+		(E.runLists_ [['A', 'B'], ['C']] $ do
+			xs <- EL.map toLower =$ EL.take 1
+			extra <- EL.consume
+			return (xs, extra))
+
+test_MapM :: Suite
+test_MapM = assertions "mapM" $ do
+	let step = return . toLower
+	$expect $ equal
+		['a', 'b', 'c']
+		(E.runLists_ [['A', 'B'], ['C']] $ do
+			EL.mapM step =$ EL.consume)
+	$expect $ equal
+		(['a'], ['B', 'C'])
+		(E.runLists_ [['A', 'B'], ['C']] $ do
+			xs <- EL.mapM step =$ EL.take 1
+			extra <- EL.consume
+			return (xs, extra))
+
+test_MapM_ :: Suite
+test_MapM_ = assertions "mapM_" $ do
+	$expect $ equal
+		['A', 'B', 'C']
+		(execWriter (E.run_ (E.enumList 1 ['A', 'B', 'C'] $$ EL.mapM_ (\x -> tell [x]))))
+
+test_ConcatMap :: Suite
+test_ConcatMap = assertions "concatMap" $ do
+	let step ao = [ao, toLower ao]
+	$expect $ equal
+		['A', 'a', 'B', 'b', 'C', 'c']
+		(E.runLists_ [['A', 'B'], ['C']] $ do
+			EL.concatMap step =$ EL.consume)
+	$expect $ equal
+		(['A', 'a'], ['B', 'C'])
+		(E.runLists_ [['A', 'B'], ['C']] $ do
+			xs <- EL.concatMap step =$ EL.take 2
+			extra <- EL.consume
+			return (xs, extra))
+
+test_ConcatMapM :: Suite
+test_ConcatMapM = assertions "concatMapM" $ do
+	let step ao = return [ao, toLower ao]
+	$expect $ equal
+		['A', 'a', 'B', 'b', 'C', 'c']
+		(E.runLists_ [['A', 'B'], ['C']] $ do
+			EL.concatMapM step =$ EL.consume)
+	$expect $ equal
+		(['A', 'a'], ['B', 'C'])
+		(E.runLists_ [['A', 'B'], ['C']] $ do
+			xs <- EL.concatMapM step =$ EL.take 2
+			extra <- EL.consume
+			return (xs, extra))
+
+test_MapAccum :: Suite
+test_MapAccum = assertions "mapAccum" $ do
+	let step s ao = (s + 1, chr (ord ao + s))
+	$expect $ equal
+		['B', 'D', 'F']
+		(E.runLists_ [['A', 'B'], ['C']] $ do
+			EL.mapAccum step 1 =$ EL.consume)
+	$expect $ equal
+		(['B'], ['B', 'C'])
+		(E.runLists_ [['A', 'B'], ['C']] $ do
+			xs <- EL.mapAccum step 1 =$ EL.take 1
+			extra <- EL.consume
+			return (xs, extra))
+
+test_MapAccumM :: Suite
+test_MapAccumM = assertions "mapAccumM" $ do
+	let step s ao = return (s + 1, chr (ord ao + s))
+	$expect $ equal
+		['B', 'D', 'F']
+		(E.runLists_ [['A', 'B'], ['C']] $ do
+			EL.mapAccumM step 1 =$ EL.consume)
+	$expect $ equal
+		(['B'], ['B', 'C'])
+		(E.runLists_ [['A', 'B'], ['C']] $ do
+			xs <- EL.mapAccumM step 1 =$ EL.take 1
+			extra <- EL.consume
+			return (xs, extra))
+
+test_ConcatMapAccum :: Suite
+test_ConcatMapAccum = assertions "concatMapAccum" $ do
+	let step s ao = (s + 1, replicate s ao)
+	$expect $ equal
+		['A', 'B', 'B', 'C', 'C', 'C']
+		(E.runLists_ [['A', 'B'], ['C']] $ do
+			EL.concatMapAccum step 1 =$ EL.consume)
+	$expect $ equal
+		(['A', 'B'], ['C'])
+		(E.runLists_ [['A', 'B'], ['C']] $ do
+			xs <- EL.concatMapAccum step 1 =$ EL.take 2
+			extra <- EL.consume
+			return (xs, extra))
+
+test_ConcatMapAccumM :: Suite
+test_ConcatMapAccumM = assertions "concatMapAccumM" $ do
+	let step s ao = return (s + 1, replicate s ao)
+	$expect $ equal
+		['A', 'B', 'B', 'C', 'C', 'C']
+		(E.runLists_ [['A', 'B'], ['C']] $ do
+			EL.concatMapAccumM step 1 =$ EL.consume)
+	$expect $ equal
+		(['A', 'B'], ['C'])
+		(E.runLists_ [['A', 'B'], ['C']] $ do
+			xs <- EL.concatMapAccumM step 1 =$ EL.take 2
+			extra <- EL.consume
+			return (xs, extra))
diff --git a/tests/EnumeratorTests/List/Repeat.hs b/tests/EnumeratorTests/List/Repeat.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/List/Repeat.hs
@@ -0,0 +1,48 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.List.Repeat
+	( test_Repeat
+	, test_RepeatM
+	, test_GenerateM
+	) where
+
+import           Control.Monad.Trans.State
+import           Data.Functor.Identity (runIdentity)
+import           Test.Chell
+
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+
+test_Repeat :: Suite
+test_Repeat = assertions "repeat" $ do
+	$expect $ equal
+		['A', 'A', 'A']
+		(runIdentity (E.run_ (EL.repeat 'A' $$ EL.take 3)))
+
+test_RepeatM :: Suite
+test_RepeatM = assertions "repeatM" $ do
+	let step = do
+		c <- get
+		put (succ c)
+		return c
+	$expect $ equal
+		['A', 'B', 'C']
+		(evalState (E.run_ (EL.repeatM step $$ EL.take 3)) 'A')
+
+test_GenerateM :: Suite
+test_GenerateM = assertions "generateM" $ do
+	let step = do
+		c <- get
+		if c > 'C'
+			then return Nothing
+			else do
+				put (succ c)
+				return (Just c)
+	$expect $ equal
+		['A', 'B', 'C']
+		(evalState (E.run_ (EL.generateM step $$ EL.consume)) 'A')
diff --git a/tests/EnumeratorTests/List/Replicate.hs b/tests/EnumeratorTests/List/Replicate.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/List/Replicate.hs
@@ -0,0 +1,40 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.List.Replicate
+	( test_Replicate
+	, test_ReplicateM
+	) where
+
+import           Control.Monad.Trans.State
+import           Data.Functor.Identity (runIdentity)
+import           Test.Chell
+
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+
+test_Replicate :: Suite
+test_Replicate = assertions "replicate" $ do
+	$expect $ equal
+		['A', 'A', 'A']
+		(runIdentity (E.run_ (EL.replicate 3 'A' $$ EL.consume)))
+	$expect $ equal
+		['A', 'A']
+		(runIdentity (E.run_ (EL.replicate 3 'A' $$ EL.take 2)))
+
+test_ReplicateM :: Suite
+test_ReplicateM = assertions "replicateM" $ do
+	let step = do
+		c <- get
+		put (succ c)
+		return c
+	$expect $ equal
+		['A', 'B', 'C']
+		(evalState (E.run_ (EL.replicateM 3 step $$ EL.consume)) 'A')
+	$expect $ equal
+		['A', 'B']
+		(evalState (E.run_ (EL.replicateM 3 step $$ EL.take 2)) 'A')
diff --git a/tests/EnumeratorTests/List/Require.hs b/tests/EnumeratorTests/List/Require.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/List/Require.hs
@@ -0,0 +1,65 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.List.Require
+	( test_Require
+	) where
+
+import qualified Control.Exception as Exc
+import           Test.Chell
+import           Test.Chell.QuickCheck
+
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+
+import           EnumeratorTests.List.Util (prop_ListN)
+import           EnumeratorTests.Util (equalExc)
+
+test_Require :: Suite
+test_Require = suite "require"
+	[ prop_Require
+	, test_YieldsInput
+	, test_HandleEOF
+	, test_BadParameter
+	]
+
+prop_Require :: Suite
+prop_Require = property "model" $ prop_ListN
+	(\n -> do
+		EL.require n
+		EL.consume)
+	(\n xs -> if n > toInteger (length xs)
+		then Left (Exc.ErrorCall "require: Unexpected EOF")
+		else Right xs)
+
+test_YieldsInput :: Suite
+test_YieldsInput = assertions "yields-input" $ do
+	$expect $ equal
+		['A', 'B', 'C']
+		(E.runLists_ [['A'], ['B'], ['C']] $ do
+			EL.require 2
+			EL.consume)
+	$expect $ equal
+		['A', 'B', 'C']
+		(E.runLists_ [['A', 'B', 'C']] $ do
+			EL.require 2
+			EL.consume)
+
+test_HandleEOF :: Suite
+test_HandleEOF = assertions "handle-eof" $ do
+	$expect $ equalExc
+		(Exc.ErrorCall "require: Unexpected EOF")
+		(E.runLists [] $ do
+			EL.require 2
+			EL.consume)
+
+test_BadParameter :: Suite
+test_BadParameter = assertions "bad-parameter" $ do
+	$expect $ equal
+		([] :: [Char])
+		(E.runLists_ [] $ do
+			EL.require 0
+			EL.consume)
diff --git a/tests/EnumeratorTests/List/Split.hs b/tests/EnumeratorTests/List/Split.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/List/Split.hs
@@ -0,0 +1,28 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.List.Split
+	( test_SplitWhen
+	) where
+
+
+import qualified Data.List.Split as LS
+import           Test.Chell
+import           Test.Chell.QuickCheck
+
+import           Data.Enumerator ((=$))
+import qualified Data.Enumerator.List as EL
+
+import           EnumeratorTests.List.Util
+
+test_SplitWhen :: Suite
+test_SplitWhen = property "splitWhen" $ prop_ListX
+	(\x -> do
+		xs <- EL.splitWhen (== x) =$ EL.consume
+		extra <- EL.consume
+		return (xs, extra))
+	(\x xs -> let
+		split = LS.split . LS.dropFinalBlank . LS.dropDelims . LS.whenElt
+		in Right (split (== x) xs, []))
diff --git a/tests/EnumeratorTests/List/Unfold.hs b/tests/EnumeratorTests/List/Unfold.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/List/Unfold.hs
@@ -0,0 +1,35 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.List.Unfold
+	( test_Unfold
+	, test_UnfoldM
+	) where
+
+import           Data.Functor.Identity (runIdentity)
+import           Test.Chell
+
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+
+test_Unfold :: Suite
+test_Unfold = assertions "unfold" $ do
+	let step x = if x > 'C'
+		then Nothing
+		else Just (x, succ x)
+	$expect $ equal
+		['A', 'B', 'C']
+		(runIdentity (E.run_ (EL.unfold step 'A' $$ EL.consume)))
+
+test_UnfoldM :: Suite
+test_UnfoldM = assertions "unfoldM" $ do
+	let step x = return $ if x > 'C'
+		then Nothing
+		else Just (x, succ x)
+	$expect $ equal
+		['A', 'B', 'C']
+		(runIdentity (E.run_ (EL.unfoldM step 'A' $$ EL.consume)))
diff --git a/tests/EnumeratorTests/List/Unique.hs b/tests/EnumeratorTests/List/Unique.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/List/Unique.hs
@@ -0,0 +1,22 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.List.Unique
+	( test_Unique
+	) where
+
+import           Test.Chell
+
+import           Data.Enumerator ((=$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+
+test_Unique :: Suite
+test_Unique = assertions "unique" $ do
+	$expect $ equal
+		['B', 'A', 'C']
+		(E.runLists_ [['B'], ['A'], ['B'], ['C'], ['A']] $ do
+			EL.unique =$ EL.consume)
diff --git a/tests/EnumeratorTests/List/Util.hs b/tests/EnumeratorTests/List/Util.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/List/Util.hs
@@ -0,0 +1,81 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.List.Util
+	( test_Enumeratee
+	, prop_List
+	, prop_ListN
+	, prop_ListX
+	) where
+
+import qualified Control.Exception as Exc
+import           Data.Functor.Identity (Identity, runIdentity)
+import qualified Data.Text as T
+
+import           Test.Chell
+import           Test.Chell.QuickCheck
+import           Test.QuickCheck hiding (property)
+import           Test.QuickCheck.Poly (A)
+
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+
+import           EnumeratorTests.Util (check)
+
+test_Enumeratee :: T.Text -> E.Enumeratee A A Identity (Maybe A) -> Suite
+test_Enumeratee name enee = suite name props where
+	props = [ property "incremental" prop_incremental
+	        , property "nest-errors" prop_nest_errors
+	        ]
+	
+	prop_incremental (Positive n) (NonEmpty xs) = let
+		result = runIdentity (E.run_ iter)
+		expected = (Just (head xs), tail xs)
+		
+		iter = E.enumList n xs $$ do
+			a <- E.joinI (enee $$ EL.head)
+			b <- EL.consume
+			return (a, b)
+		
+		in result == expected
+	
+	prop_nest_errors (Positive n) (NonEmpty xs) = let
+		result = runIdentity (E.run_ iter)
+		
+		iter = E.enumList n xs $$ do
+			_ <- enee $$ E.throwError (Exc.ErrorCall "")
+			EL.consume
+		
+		in result == xs
+
+prop_List :: Eq b
+          => E.Iteratee A Identity b
+          -> ([A] -> Either Exc.ErrorCall b)
+          -> [A]
+          -> Bool
+prop_List iter plain = prop where
+	prop :: [A] -> Bool
+	prop = check iter plain
+
+prop_ListN :: Eq b
+           => (Integer -> E.Iteratee A Identity b)
+           -> (Integer -> [A] -> Either Exc.ErrorCall b)
+           -> Positive Integer
+           -> [A]
+           -> Bool
+prop_ListN iter plain = prop where
+	prop :: Positive Integer -> [A] -> Bool
+	prop (Positive n) = check (iter n) (plain n)
+
+prop_ListX :: Eq b
+           => (A -> E.Iteratee A Identity b)
+           -> (A -> [A] -> Either Exc.ErrorCall b)
+           -> A
+           -> [A]
+           -> Bool
+prop_ListX iter plain = prop where
+	prop :: A -> [A] -> Bool
+	prop x = check (iter x) (plain x)
diff --git a/tests/EnumeratorTests/List/Zip.hs b/tests/EnumeratorTests/List/Zip.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/List/Zip.hs
@@ -0,0 +1,172 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.List.Zip
+	( test_Zip
+	) where
+
+import qualified Control.Exception as Exc
+import           Data.Functor.Identity (Identity)
+import           Data.Text (Text)
+import           Test.Chell
+
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+
+import           EnumeratorTests.List.Util ()
+import           EnumeratorTests.Util (equalExc)
+
+test_ZipN :: (Eq b, Show b) => Text -> E.Iteratee Char Identity b -> b -> Suite
+test_ZipN name iter expected = assertions name $ do
+	$expect $ equal
+		expected
+		(E.runLists_ [[], ['A'], ['B']] iter)
+
+$([d||])
+
+test_Zip :: Suite
+test_Zip = suite "zip"
+	[ test_ContinueContinue
+	, test_YieldContinue
+	, test_ContinueYield
+	, test_YieldYield
+	, test_ErrorFirst
+	, test_ErrorSecond
+	, test_HandleEOF
+	, test_Zip3
+	, test_Zip4
+	, test_Zip5
+	, test_Zip6
+	, test_Zip7
+	, test_ZipWith
+	, test_ZipWith3
+	, test_ZipWith4
+	, test_ZipWith5
+	, test_ZipWith6
+	, test_ZipWith7
+	]
+
+test_ContinueContinue :: Suite
+test_ContinueContinue = assertions "continue-continue" $ do
+	$expect $ equal
+		(['A', 'B'], ['A', 'B'], ['C'])
+		(E.runLists_ [['A'], ['B'], ['C']] $ do
+			(x, y) <- EL.zip (EL.take 2) (EL.take 2)
+			extra <- EL.consume
+			return (x, y, extra))
+
+test_YieldContinue :: Suite
+test_YieldContinue = assertions "yield-continue" $ do
+	$expect $ equal
+		(['A'], ['A', 'B'], ['C'])
+		(E.runLists_ [['A'], ['B'], ['C']] $ do
+			(x, y) <- EL.zip (EL.take 1) (EL.take 2)
+			extra <- EL.consume
+			return (x, y, extra))
+
+test_ContinueYield :: Suite
+test_ContinueYield = assertions "continue-yield" $ do
+	$expect $ equal
+		(['A', 'B'], ['A'], ['C'])
+		(E.runLists_ [['A'], ['B'], ['C']] $ do
+			(x, y) <- EL.zip (EL.take 2) (EL.take 1)
+			extra <- EL.consume
+			return (x, y, extra))
+
+test_YieldYield :: Suite
+test_YieldYield = assertions "yield-yield" $ do
+	$expect $ equal
+		(['A'], ['A'], ['B', 'C'])
+		(E.runLists_ [['A'], ['B'], ['C']] $ do
+			(x, y) <- EL.zip (EL.take 1) (EL.take 1)
+			extra <- EL.consume
+			return (x, y, extra))
+
+test_ErrorFirst :: Suite
+test_ErrorFirst = assertions "error-first" $ do
+	$expect $ equalExc
+		(Exc.ErrorCall "error")
+		(E.runLists [['A'], ['B'], ['C']] $ do
+			EL.zip (E.throwError (Exc.ErrorCall "error")) (EL.take 1))
+
+test_ErrorSecond :: Suite
+test_ErrorSecond = assertions "error-second" $ do
+	$expect $ equalExc
+		(Exc.ErrorCall "error")
+		(E.runLists [['A'], ['B'], ['C']] $ do
+			EL.zip (EL.take 1) (E.throwError (Exc.ErrorCall "error")))
+
+test_HandleEOF :: Suite
+test_HandleEOF = assertions "handle-eof" $ do
+	$expect $ equal
+		(['A'], ['A', 'B'], [])
+		(E.runLists_ [['A'], ['B']] $ do
+			(x, y) <- EL.zip (EL.take 1) (EL.take 3)
+			extra <- EL.consume
+			return (x, y, extra))
+	$expect $ equal
+		(['a'], ['b'], [])
+		(E.runLists_ [['A'], ['B']] $ do
+			(x, y) <- EL.zip
+				(E.yield ['a'] (E.Chunks []))
+				(E.yield ['b'] E.EOF)
+			extra <- EL.consume
+			return (x, y, extra))
+
+test_Zip3 :: Suite
+test_Zip3 = test_ZipN "zip3"
+	(EL.zip3 EL.head EL.head EL.head)
+	(Just 'A', Just 'A', Just 'A')
+
+test_Zip4 :: Suite
+test_Zip4 = test_ZipN "zip4"
+	(EL.zip4 EL.head EL.head EL.head EL.head)
+	(Just 'A', Just 'A', Just 'A', Just 'A')
+
+test_Zip5 :: Suite
+test_Zip5 = test_ZipN "zip5"
+	(EL.zip5 EL.head EL.head EL.head EL.head EL.head)
+	(Just 'A', Just 'A', Just 'A', Just 'A', Just 'A')
+
+test_Zip6 :: Suite
+test_Zip6 = test_ZipN "zip6"
+	(EL.zip6 EL.head EL.head EL.head EL.head EL.head EL.head)
+	(Just 'A', Just 'A', Just 'A', Just 'A', Just 'A', Just 'A')
+
+test_Zip7 :: Suite
+test_Zip7 = test_ZipN "zip7"
+	(EL.zip7 EL.head EL.head EL.head EL.head EL.head EL.head EL.head)
+	(Just 'A', Just 'A', Just 'A', Just 'A', Just 'A', Just 'A', Just 'A')
+
+test_ZipWith :: Suite
+test_ZipWith = test_ZipN "zipWith"
+	(EL.zipWith (,) EL.head EL.head)
+	(Just 'A', Just 'A')
+
+test_ZipWith3 :: Suite
+test_ZipWith3 = test_ZipN "zipWith3"
+	(EL.zipWith3 (,,) EL.head EL.head EL.head)
+	(Just 'A', Just 'A', Just 'A')
+
+test_ZipWith4 :: Suite
+test_ZipWith4 = test_ZipN "zipWith4"
+	(EL.zipWith4 (,,,) EL.head EL.head EL.head EL.head)
+	(Just 'A', Just 'A', Just 'A', Just 'A')
+
+test_ZipWith5 :: Suite
+test_ZipWith5 = test_ZipN "zipWith5"
+	(EL.zipWith5 (,,,,) EL.head EL.head EL.head EL.head EL.head)
+	(Just 'A', Just 'A', Just 'A', Just 'A', Just 'A')
+
+test_ZipWith6 :: Suite
+test_ZipWith6 = test_ZipN "zipWith6"
+	(EL.zipWith6 (,,,,,) EL.head EL.head EL.head EL.head EL.head EL.head)
+	(Just 'A', Just 'A', Just 'A', Just 'A', Just 'A', Just 'A')
+
+test_ZipWith7 :: Suite
+test_ZipWith7 = test_ZipN "zipWith7"
+	(EL.zipWith7 (,,,,,,) EL.head EL.head EL.head EL.head EL.head EL.head EL.head)
+	(Just 'A', Just 'A', Just 'A', Just 'A', Just 'A', Just 'A', Just 'A')
diff --git a/tests/EnumeratorTests/Misc.hs b/tests/EnumeratorTests/Misc.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Misc.hs
@@ -0,0 +1,143 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Misc
+	( test_ConcatEnums
+	, test_EnumEOF
+	, test_Last
+	, test_Length
+	, test_LiftTrans
+	, test_Peek
+	, test_TryIO
+	, test_PrintChunks
+	) where
+
+import           Control.Exception
+import           Control.Monad.Trans.Reader
+import           Control.Monad.IO.Class
+import           Data.Functor.Identity
+import           Test.Chell
+
+#ifdef MIN_VERSION_silently
+
+import           System.IO.Silently (capture)
+
+#endif
+
+import           Data.Enumerator (($$), (<==<))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+
+import           EnumeratorTests.Util (equalExc)
+
+test_ConcatEnums :: Suite
+test_ConcatEnums = assertions "concatEnums" $ do
+	let enum = E.concatEnums
+		[ E.enumLists [['A']]
+		, E.enumLists [['B']]
+		, E.enumLists [['C']]
+		]
+	$expect $ equal
+		['A', 'B', 'C']
+		(runIdentity (E.run_ (enum $$ EL.consume)))
+	$expect $ equal
+		['A', 'B']
+		(runIdentity (E.run_ (E.enumLists [['B']] <==< E.enumLists [['A']] $$ EL.consume)))
+
+test_EnumEOF :: Suite
+test_EnumEOF = assertions "enumEOF" $ do
+	let iter = E.continue (\_ -> iter)
+	
+	$expect $ throwsEq
+		(ErrorCall "enumEOF: divergent iteratee")
+		(E.runIteratee (E.enumEOF $$ iter))
+
+test_Last :: Suite
+test_Last = assertions "last" $ do
+	$expect $ equal
+		(Nothing :: Maybe Char, [])
+		(E.runLists_ [[]] $ do
+			x <- E.last
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		(Just 'E', [])
+		(E.runLists_ [['A', 'B'], ['C', 'D'], ['E']] $ do
+			x <- E.last
+			extra <- EL.consume
+			return (x, extra))
+
+test_Length :: Suite
+test_Length = assertions "length" $ do
+	$expect $ equal
+		5
+		(E.runLists_ [['A', 'B'], ['C', 'D'], ['E']] E.length)
+
+test_LiftTrans :: Suite
+test_LiftTrans = assertions "liftTrans" $ do
+	let iter1 :: E.Iteratee Char Identity (Maybe Char)
+	    iter1 = EL.head
+	
+	let iter2 :: Bool -> E.Iteratee Char (ReaderT Int Identity) (Maybe Char, [Char])
+	    iter2 bad = do
+	    	x <- E.liftTrans (if bad then E.throwError (ErrorCall "failed") else iter1)
+	    	xs <- EL.consume
+	    	return (x, xs)
+	
+	$expect $ equal
+		(Just 'A', ['B', 'C', 'D', 'E'])
+		(runIdentity (runReaderT (E.run_ (E.enumList 1 ['A'..'E'] $$ iter2 False)) 0))
+	
+	$expect $ equalExc
+		(ErrorCall "failed")
+		(runIdentity (runReaderT (E.run (E.enumList 1 ['A'..'E'] $$ iter2 True)) 0))
+
+test_Peek :: Suite
+test_Peek = assertions "peek" $ do
+	$expect $ equal
+		(Nothing :: Maybe Char, [])
+		(E.runLists_ [] $ do
+			x <- E.peek
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		(Just 'A', ['A', 'B', 'C', 'D', 'E'])
+		(E.runLists_ [[], ['A', 'B'], ['C', 'D'], ['E']] $ do
+			x <- E.peek
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		(Just 'A', ['A'])
+		(E.runLists_ [['A']] $ do
+			x <- E.peek
+			extra <- EL.consume
+			return (x, extra))
+
+test_TryIO :: Suite
+test_TryIO = assertions "tryIO" $ do
+	do
+		res <- E.run (E.tryIO (return 'A'))
+		$expect (right res)
+		let Right res' = res
+		$expect (equal 'A' res')
+	
+	do
+		res <- E.run (E.tryIO (throwIO (ErrorCall "failed")))
+		$expect (equalExc (ErrorCall "failed") res)
+
+test_PrintChunks :: Suite
+#ifdef MIN_VERSION_silently
+test_PrintChunks = assertions "printChunks" $ do
+	do
+		(stdout, _) <- liftIO (capture (E.run_ (E.enumLists [[], ['A', 'B'], ['C']] $$ E.printChunks False)))
+		$expect (equal stdout "\"AB\"\n\"C\"\nEOF\n")
+	do
+		(stdout, _) <- liftIO (capture (E.run_ (E.enumLists [[], ['A', 'B'], ['C']] $$ E.printChunks True)))
+		$expect (equal stdout "\"\"\n\"AB\"\n\"C\"\nEOF\n")
+#else
+test_PrintChunks = skipIf True (assertions "printChunks" (return ()))
+#endif
diff --git a/tests/EnumeratorTests/Sequence.hs b/tests/EnumeratorTests/Sequence.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Sequence.hs
@@ -0,0 +1,28 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Sequence
+	( test_Sequence
+	) where
+
+import           Data.Functor.Identity (Identity, runIdentity)
+
+import           Test.Chell
+import           Test.Chell.QuickCheck
+import           Test.QuickCheck hiding (property)
+import           Test.QuickCheck.Poly (A)
+
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+
+test_Sequence :: Suite
+test_Sequence = property "sequence" prop where
+	prop :: Positive Integer -> [A] -> Bool
+	prop (Positive n) xs = result == expected where
+		result = runIdentity (E.run_ iter)
+		expected = map Just xs
+		
+		iter = E.enumList n xs $$ E.joinI (E.sequence EL.head $$ EL.consume)
diff --git a/tests/EnumeratorTests/Stream.hs b/tests/EnumeratorTests/Stream.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Stream.hs
@@ -0,0 +1,82 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010-2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Stream
+	( test_Stream
+	) where
+
+import           Control.Applicative (pure, (<*>))
+import           Data.Monoid (mappend, mempty, mconcat)
+
+import           Test.Chell
+import           Test.Chell.QuickCheck
+import           Test.QuickCheck hiding (property)
+import           Test.QuickCheck.Poly (A, B, C)
+
+import qualified Data.Enumerator as E
+
+import           EnumeratorTests.Util ()
+
+test_Stream :: Suite
+test_Stream = suite "stream"
+	[ test_Monoid
+	, test_Functor
+	, test_Monad
+	, test_Applicative
+	]
+
+test_Monoid :: Suite
+test_Monoid = suite "monoid"
+	[ property "law-1" prop_MonoidLaw1
+	, property "law-2" prop_MonoidLaw2
+	, property "law-3" prop_MonoidLaw3
+	, property "law-4" prop_MonoidLaw4
+	]
+
+prop_MonoidLaw1 :: E.Stream A -> Bool
+prop_MonoidLaw1 x = mappend mempty x == x
+
+prop_MonoidLaw2 :: E.Stream A -> Bool
+prop_MonoidLaw2 x = mappend x mempty == x
+
+prop_MonoidLaw3 :: E.Stream A -> E.Stream A -> E.Stream A -> Bool
+prop_MonoidLaw3 x y z = mappend x (mappend y z) == mappend (mappend x y) z
+
+prop_MonoidLaw4 :: [E.Stream A] -> Bool
+prop_MonoidLaw4 xs = mconcat xs == foldr mappend mempty xs
+
+test_Functor :: Suite
+test_Functor = suite "functor"
+	[ property "law-1" prop_FunctorLaw1
+	, property "law-2" prop_FunctorLaw2
+	]
+
+prop_FunctorLaw1 :: E.Stream A -> Bool
+prop_FunctorLaw1 x = fmap id x == id x
+
+prop_FunctorLaw2 :: E.Stream A -> Blind (B -> C) -> Blind (A -> B) -> Bool
+prop_FunctorLaw2 x (Blind f) (Blind g) = fmap (f . g) x == (fmap f . fmap g) x
+
+test_Monad :: Suite
+test_Monad = suite "monad"
+	[ property "law-1" prop_MonadLaw1
+	, property "law-2" prop_MonadLaw2
+	, property "law-3" prop_MonadLaw3
+	]
+
+prop_MonadLaw1 :: A -> Blind (A -> E.Stream B) -> Bool
+prop_MonadLaw1 a (Blind f) = (return a >>= f) == f a
+
+prop_MonadLaw2 :: E.Stream A -> Bool
+prop_MonadLaw2 m = (m >>= return) == m
+
+prop_MonadLaw3 :: E.Stream A -> Blind (A -> E.Stream B) -> Blind (B -> E.Stream C) -> Bool
+prop_MonadLaw3 m (Blind f) (Blind g) = ((m >>= f) >>= g) == (m >>= (\x -> f x >>= g))
+
+test_Applicative :: Suite
+test_Applicative = assertions "applicative" $ do
+	$expect (equal (E.Chunks ['A']) (pure 'A'))
+	$expect (equal (E.Chunks ['B']) (pure succ <*> E.Chunks ['A']))
diff --git a/tests/EnumeratorTests/Text.hs b/tests/EnumeratorTests/Text.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Text.hs
@@ -0,0 +1,66 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Text
+	( test_Text
+	) where
+
+import           Test.Chell
+
+import           EnumeratorTests.Text.Codecs
+import           EnumeratorTests.Text.Consume
+import           EnumeratorTests.Text.Drop
+import           EnumeratorTests.Text.Fold
+import           EnumeratorTests.Text.Handle
+import           EnumeratorTests.Text.Isolate
+import           EnumeratorTests.Text.Iterate
+import           EnumeratorTests.Text.Map
+import           EnumeratorTests.Text.Repeat
+import           EnumeratorTests.Text.Replicate
+import           EnumeratorTests.Text.Require
+import           EnumeratorTests.Text.Split
+import           EnumeratorTests.Text.Unfold
+import           EnumeratorTests.Text.Zip
+
+test_Text :: Suite
+test_Text = suite "text"
+	[ test_TextCodecs
+	, test_Consume
+	, test_ConcatMap
+	, test_ConcatMapM
+	, test_ConcatMapAccum
+	, test_ConcatMapAccumM
+	, test_Drop
+	, test_DropWhile
+	, test_EnumHandle
+	, test_Filter
+	, test_FilterM
+	, test_Fold
+	, test_FoldM
+	, test_GenerateM
+	, test_Head
+	, test_Head_
+	, test_Isolate
+	, test_Iterate
+	, test_IterateM
+	, test_IterHandle
+	, test_Lines
+	, test_Map
+	, test_MapM
+	, test_MapM_
+	, test_MapAccum
+	, test_MapAccumM
+	, test_Repeat
+	, test_RepeatM
+	, test_Replicate
+	, test_ReplicateM
+	, test_Require
+	, test_SplitWhen
+	, test_Take
+	, test_TakeWhile
+	, test_Unfold
+	, test_UnfoldM
+	, test_Zip
+	]
diff --git a/tests/EnumeratorTests/Text/Codecs.hs b/tests/EnumeratorTests/Text/Codecs.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Text/Codecs.hs
@@ -0,0 +1,322 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Text.Codecs
+	( test_TextCodecs
+	) where
+
+import           Prelude hiding (words)
+
+import           Control.Exception (ErrorCall(..))
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Char8 as B8
+import qualified Data.ByteString.Lazy as BL
+import           Data.Char (ord)
+import qualified Data.Text as T
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Encoding as TE
+import qualified Data.Text.Encoding.Error as TE
+
+import           Test.Chell
+import           Test.Chell.QuickCheck
+import           Test.QuickCheck hiding (property)
+
+import           Data.Enumerator ((=$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.Binary as EB
+import qualified Data.Enumerator.Text as ET
+import qualified Data.Enumerator.List as EL
+
+import           EnumeratorTests.Util
+
+test_TextCodecs :: Suite
+test_TextCodecs = suite "codecs"
+	[ test_ASCII
+	, test_ISO8859_1
+	, test_UTF8
+	, test_UTF16_BE
+	, test_UTF16_LE
+	, test_UTF32_BE
+	, test_UTF32_LE
+	]
+
+test_ASCII :: Suite
+test_ASCII = suite "ascii"
+	[ property "encode" (forAll genASCII (prop_Encode ET.ascii encodeASCII))
+	, property "decode" (forAll genASCII (prop_Decode ET.ascii decodeASCII))
+	
+	, assertions "show" $ do
+	  	$expect $ equal
+	  		"Codec \"ASCII\""
+	  		(show ET.ascii)
+	
+	, assertions "encode-invalid" $ do
+	  	$expect $ equalExc
+	  		(ErrorCall "Codec \"ASCII\" can't encode character U+00FF")
+	  		(E.runLists [["\xFF"]] (ET.encode ET.ascii =$ EB.consume))
+	
+	, assertions "decode-invalid" $ do
+	  	$expect $ equalExc
+	  		(ErrorCall "Codec \"ASCII\" can't decode byte 0xFF")
+	  		(E.runLists [["\xFF"]] (ET.decode ET.ascii =$ ET.consume))
+	
+	, skipIf True $ assertions "lazy.broken" $ do
+	  	$expect $ equal
+	  		(Just 0x61, ["b"])
+	  		(E.runLists_ [["", "ab"]] (do
+	  			x <- ET.encode ET.ascii =$ EB.head
+	  			y <- EL.consume
+	  			return (x, y)))
+	
+	, assertions "lazy" $ do
+	  	$expect $ equal
+	  		(Just 0x61, ["b"])
+	  		(E.runLists_ [[""], ["a"], ["b"]] $ do
+	  			x <- ET.encode ET.ascii =$ EB.head
+	  			y <- EL.consume
+	  			return (x, y))
+	  	$expect $ equal
+	  		(Just 0x61, ["\xFF"])
+	  		(E.runLists_ [[""], ["a\xFF"]] $ do
+	  			x <- ET.encode ET.ascii =$ EB.head
+	  			y <- EL.consume
+	  			return (x, y))
+	  	$expect $ equal
+	  		(Just 'a', ["b"])
+	  		(E.runLists_ [[""], ["a"], ["b"]] $ do
+	  			x <- ET.decode ET.ascii =$ ET.head
+	  			y <- EL.consume
+	  			return (x, y))
+	  	$expect $ equal
+	  		(Just 'a', ["\xFF"])
+	  		(E.runLists_ [[""], ["a\xFF"]] $ do
+	  			x <- ET.decode ET.ascii =$ ET.head
+	  			y <- EL.consume
+	  			return (x, y))
+	]
+
+encodeASCII :: T.Text -> B.ByteString
+encodeASCII text = if T.any (\c -> ord c > 127) text
+	then error "encodeASCII: input contains non-ASCII characters."
+	else B8.pack (T.unpack text)
+
+decodeASCII :: B.ByteString -> T.Text
+decodeASCII bytes = if B.any (> 127) bytes
+	then error "decodeASCII: input contains non-ASCII characters."
+	else T.pack (B8.unpack bytes)
+
+test_ISO8859_1 :: Suite
+test_ISO8859_1 = suite "iso8859-1"
+	[ property "encode" (forAll genISO8859_1 (prop_Encode ET.iso8859_1 encodeISO8859_1))
+	, property "decode" (forAll genISO8859_1 (prop_Decode ET.iso8859_1 decodeISO8859_1))
+	
+	, assertions "show" $ do
+	  	$expect $ equal
+	  		"Codec \"ISO-8859-1\""
+	  		(show ET.iso8859_1)
+	
+	, assertions "encode-invalid" $ do
+	  	$expect $ equalExc
+	  		(ErrorCall "Codec \"ISO-8859-1\" can't encode character U+01FF")
+	  		(E.runLists [["\x1FF"]] (ET.encode ET.iso8859_1 =$ EB.consume))
+	
+	, assertions "lazy" $ do
+	  	$expect $ equal
+	  		(Just 0x61, ["b"])
+	  		(E.runLists_ [[""], ["a"], ["b"]] $ do
+	  			x <- ET.encode ET.iso8859_1 =$ EB.head
+	  			y <- EL.consume
+	  			return (x, y))
+	  	$expect $ equal
+	  		(Just 0x61, ["\x1FF"])
+	  		(E.runLists_ [[""], ["a\x1FF"]] $ do
+	  			x <- ET.encode ET.iso8859_1 =$ EB.head
+	  			y <- EL.consume
+	  			return (x, y))
+	  	$expect $ equal
+	  		(Just 'a', ["b"])
+	  		(E.runLists_ [[""], ["a"], ["b"]] $ do
+	  			x <- ET.decode ET.iso8859_1 =$ ET.head
+	  			y <- EL.consume
+	  			return (x, y))
+	]
+
+encodeISO8859_1 :: T.Text -> B.ByteString
+encodeISO8859_1 text = if T.any (\c -> ord c > 255) text
+	then error "encodeASCII: input contains non-ISO8859-1 characters."
+	else B8.pack (T.unpack text)
+
+decodeISO8859_1 :: B.ByteString -> T.Text
+decodeISO8859_1 bytes = T.pack (B8.unpack bytes)
+
+test_UTF8 :: Suite
+test_UTF8 = suite "utf8"
+	[ property "encode" (prop_Encode ET.utf8 TE.encodeUtf8)
+	, property "decode" (prop_Decode ET.utf8 TE.decodeUtf8 . TE.encodeUtf8)
+	
+	, assertions "show" $ do
+	  	$expect $ equal
+	  		"Codec \"UTF-8\""
+	  		(show ET.utf8)
+	
+	, assertions "decode-invalid" $ do
+	  	$expect $ equalExc
+	  		(TE.DecodeError "Data.Text.Encoding.decodeUtf8: Invalid UTF-8 stream" (Just 0xFF))
+	  		(E.runLists [["\xFF"]] (ET.decode ET.utf8 =$ ET.consume))
+	  	$expect $ equalExc
+	  		(ErrorCall "Unexpected EOF while decoding")
+	  		(E.runLists [["\xF0"]] (ET.decode ET.utf8 =$ ET.consume))
+	
+	, assertions "lazy" $ do
+	  	$expect $ equal
+	  		(Just 'a', ["\xEF\xBD"])
+	  		(E.runLists_ [["a\xEF\xBD"]] $ do
+	  			x <- ET.decode ET.utf8 =$ ET.head
+	  			y <- EL.consume
+	  			return (x, y))
+	]
+
+test_UTF16_BE :: Suite
+test_UTF16_BE = suite "utf16-be"
+	[ property "encode" (prop_Encode ET.utf16_be TE.encodeUtf16BE)
+	, property "decode" (prop_Decode ET.utf16_be TE.decodeUtf16BE . TE.encodeUtf16BE)
+	
+	, assertions "show" $ do
+	  	$expect $ equal
+	  		"Codec \"UTF-16-BE\""
+	  		(show ET.utf16_be)
+	
+	, assertions "decode-invalid" $ do
+	  	$expect $ equalExc
+	  		(TE.DecodeError "Data.Text.Encoding.Fusion.streamUtf16BE: Invalid UTF-16BE stream" Nothing)
+	  		(E.runLists [["\xDD\x1E"]] (ET.decode ET.utf16_be =$ ET.consume))
+	  	$expect $ equalExc
+	  		(ErrorCall "Unexpected EOF while decoding")
+	  		(E.runLists [["\xD8\x00"]] (ET.decode ET.utf16_be =$ ET.consume))
+	
+	, assertions "lazy" $ do
+	  	$expect $ equal
+	  		(Just 'a', ["\x00"])
+	  		(E.runLists_ [["\x00\x61\x00"]] $ do
+	  			x <- ET.decode ET.utf16_be =$ ET.head
+	  			y <- EL.consume
+	  			return (x, y))
+	  	$expect $ equal
+	  		(Just 'a', ["\xDD\x1E"])
+	  		(E.runLists_ [["\x00\x61\xDD\x1E"]] $ do
+	  			x <- ET.decode ET.utf16_be =$ ET.head
+	  			y <- EL.consume
+	  			return (x, y))
+	]
+
+test_UTF16_LE :: Suite
+test_UTF16_LE = suite "utf16-le"
+	[ property "encode" (prop_Encode ET.utf16_le TE.encodeUtf16LE)
+	, property "decode" (prop_Decode ET.utf16_le TE.decodeUtf16LE . TE.encodeUtf16LE)
+	
+	, assertions "show" $ do
+	  	$expect $ equal
+	  		"Codec \"UTF-16-LE\""
+	  		(show ET.utf16_le)
+	
+	, assertions "decode-invalid" $ do
+	  	$expect $ equalExc
+	  		(TE.DecodeError "Data.Text.Encoding.Fusion.streamUtf16LE: Invalid UTF-16LE stream" Nothing)
+	  		(E.runLists [["\x1E\xDD"]] (ET.decode ET.utf16_le =$ ET.consume))
+	  	$expect $ equalExc
+	  		(ErrorCall "Unexpected EOF while decoding")
+	  		(E.runLists [["\x00\xD8"]] (ET.decode ET.utf16_le =$ ET.consume))
+	
+	, assertions "lazy" $ do
+	  	$expect $ equal
+	  		(Just 'a', ["\x00"])
+	  		(E.runLists_ [["\x61\x00\x00"]] $ do
+	  			x <- ET.decode ET.utf16_le =$ ET.head
+	  			y <- EL.consume
+	  			return (x, y))
+	  	$expect $ equal
+	  		(Just 'a', ["\x1E\xDD"])
+	  		(E.runLists_ [["\x61\x00\x1E\xDD"]] $ do
+	  			x <- ET.decode ET.utf16_le =$ ET.head
+	  			y <- EL.consume
+	  			return (x, y))
+	]
+
+test_UTF32_BE :: Suite
+test_UTF32_BE = suite "utf32-be"
+	[ property "encode" (prop_Encode ET.utf32_be TE.encodeUtf32BE)
+	, property "decode" (prop_Decode ET.utf32_be TE.decodeUtf32BE . TE.encodeUtf32BE)
+	
+	, assertions "show" $ do
+	  	$expect $ equal
+	  		"Codec \"UTF-32-BE\""
+	  		(show ET.utf32_be)
+	
+	, assertions "decode-invalid" $ do
+	  	$expect $ equalExc
+	  		(TE.DecodeError "Data.Text.Encoding.Fusion.streamUtf32BE: Invalid UTF-32BE stream" Nothing)
+	  		(E.runLists [["\xFF\xFF\xFF\xFF"]] (ET.decode ET.utf32_be =$ ET.consume))
+	  	$expect $ equalExc
+	  		(ErrorCall "Unexpected EOF while decoding")
+	  		(E.runLists [["\x00\x00"]] (ET.decode ET.utf32_be =$ ET.consume))
+	
+	, assertions "lazy" $ do
+	  	$expect $ equal
+	  		(Just 'a', ["\x00"])
+	  		(E.runLists_ [["\x00\x00\x00\x61\x00"]] $ do
+	  			x <- ET.decode ET.utf32_be =$ ET.head
+	  			y <- EL.consume
+	  			return (x, y))
+	  	$expect $ equal
+	  		(Just 'a', ["\xFF\xFF\xFF\xFF"])
+	  		(E.runLists_ [["\x00\x00\x00\x61\xFF\xFF\xFF\xFF"]] $ do
+	  			x <- ET.decode ET.utf32_be =$ ET.head
+	  			y <- EL.consume
+	  			return (x, y))
+	]
+
+test_UTF32_LE :: Suite
+test_UTF32_LE = suite "utf32-le"
+	[ property "encode" (prop_Encode ET.utf32_le TE.encodeUtf32LE)
+	, property "decode" (prop_Decode ET.utf32_le TE.decodeUtf32LE . TE.encodeUtf32LE)
+	
+	, assertions "show" $ do
+	  	$expect $ equal
+	  		"Codec \"UTF-32-LE\""
+	  		(show ET.utf32_le)
+	
+	, assertions "decode-invalid" $ do
+	  	$expect $ equalExc
+	  		(TE.DecodeError "Data.Text.Encoding.Fusion.streamUtf32LE: Invalid UTF-32LE stream" Nothing)
+	  		(E.runLists [["\xFF\xFF\xFF\xFF"]] (ET.decode ET.utf32_le =$ ET.consume))
+	  	$expect $ equalExc
+	  		(ErrorCall "Unexpected EOF while decoding")
+	  		(E.runLists [["\x00\x00"]] (ET.decode ET.utf32_le =$ ET.consume))
+	
+	, assertions "lazy" $ do
+	  	$expect $ equal
+	  		(Just 'a', ["\x00"])
+	  		(E.runLists_ [["\x61\x00\x00\x00\x00"]] $ do
+	  			x <- ET.decode ET.utf32_le =$ ET.head
+	  			y <- EL.consume
+	  			return (x, y))
+	  	$expect $ equal
+	  		(Just 'a', ["\xFF\xFF\xFF\xFF"])
+	  		(E.runLists_ [["\x61\x00\x00\x00\xFF\xFF\xFF\xFF"]] $ do
+	  			x <- ET.decode ET.utf32_le =$ ET.head
+	  			y <- EL.consume
+	  			return (x, y))
+	]
+
+prop_Encode :: ET.Codec -> (T.Text -> B.ByteString) -> T.Text -> Bool
+prop_Encode codec model text = encoded == model text where
+	lazy = E.runLists_ [[text]] (ET.encode codec =$ EB.consume)
+	encoded = B.concat (BL.toChunks lazy)
+
+prop_Decode :: ET.Codec -> (B.ByteString -> T.Text) -> B.ByteString -> Bool
+prop_Decode codec model bytes = decoded == model bytes where
+	lazy = E.runLists_ [[bytes]] (ET.decode codec =$ ET.consume)
+	decoded = TL.toStrict lazy
diff --git a/tests/EnumeratorTests/Text/Consume.hs b/tests/EnumeratorTests/Text/Consume.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Text/Consume.hs
@@ -0,0 +1,103 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Text.Consume
+	( test_Consume
+	, test_Head
+	, test_Head_
+	, test_Take
+	, test_TakeWhile
+	) where
+
+import           Control.Exception
+import           Test.Chell
+
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+import qualified Data.Enumerator.Text as ET
+
+import           EnumeratorTests.Util (equalExc)
+
+test_Consume :: Suite
+test_Consume = assertions "consume" $ do
+	$expect $ equal
+		("ABC", Nothing)
+		(E.runLists_ [[], ["A", "B"], ["C"]] $ do
+			xs <- ET.consume
+			h <- EL.head
+			return (xs, h))
+
+test_Head :: Suite
+test_Head = assertions "head" $ do
+	$expect $ equal
+		(Just 'A', ["BC", "DE"])
+		(E.runLists_ [[], ["ABC", "DE"]] $ do
+			x <- ET.head
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		(Nothing :: Maybe Char, [])
+		(E.runLists_ [] $ do
+			x <- ET.head
+			extra <- EL.consume
+			return (x, extra))
+
+test_Head_ :: Suite
+test_Head_ = assertions "head_" $ do
+	$expect $ equal
+		('A', ["BC", "DE"])
+		(E.runLists_ [["ABC"], ["DE"]] $ do
+			x <- ET.head_
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equalExc
+		(ErrorCall "head_: stream has ended")
+		(E.runLists [] $ do
+			x <- ET.head_
+			extra <- EL.consume
+			return (x, extra))
+
+test_Take :: Suite
+test_Take = assertions "take" $ do
+	$expect $ equal
+		("ABC", ["D", "E"])
+		(E.runLists_ [["A", "B"], ["C", "D"], ["E"]] $ do
+			x <- ET.take 3
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		("AB", [])
+		(E.runLists_ [["A"], ["B"]] $ do
+			x <- ET.take 3
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		("", ["A", "B"])
+		(E.runLists_ [["A"], ["B"]] $ do
+			x <- ET.take 0
+			extra <- EL.consume
+			return (x, extra))
+
+test_TakeWhile :: Suite
+test_TakeWhile = assertions "takeWhile" $ do
+	$expect $ equal
+		("ABC", ["D", "E"])
+		(E.runLists_ [[], ["A", "B"], ["C", "D"], ["E"]] $ do
+			x <- ET.takeWhile (< 'D')
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		("AB", [])
+		(E.runLists_ [["A"], ["B"]] $ do
+			x <- ET.takeWhile (< 'D')
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		("", ["A", "B"])
+		(E.runLists_ [["A"], ["B"]] $ do
+			x <- ET.takeWhile (< 'A')
+			extra <- EL.consume
+			return (x, extra))
diff --git a/tests/EnumeratorTests/Text/Drop.hs b/tests/EnumeratorTests/Text/Drop.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Text/Drop.hs
@@ -0,0 +1,64 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Text.Drop
+	( test_Drop
+	, test_DropWhile
+	, test_Filter
+	, test_FilterM
+	) where
+
+import           Test.Chell
+
+import           Data.Enumerator ((=$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+import qualified Data.Enumerator.Text as ET
+
+test_Drop :: Suite
+test_Drop = assertions "drop" $ do
+	$expect $ equal
+		["ABCDE"]
+		(E.runLists_ [["ABCDE"]] $ do
+			ET.drop 0
+			EL.consume)
+	$expect $ equal
+		["CDE"]
+		(E.runLists_ [["ABCDE"]] $ do
+			ET.drop 2
+			EL.consume)
+	$expect $ equal
+		["CDE"]
+		(E.runLists_ [["A"], ["BCDE"]] $ do
+			ET.drop 2
+			EL.consume)
+
+test_DropWhile :: Suite
+test_DropWhile = assertions "dropWhile" $ do
+	$expect $ equal
+		["CDE"]
+		(E.runLists_ [["ABCDE"]] $ do
+			ET.dropWhile (< 'C')
+			EL.consume)
+	$expect $ equal
+		[]
+		(E.runLists_ [["ABCDE"]] $ do
+			ET.dropWhile (\_ -> True)
+			EL.consume)
+
+test_Filter :: Suite
+test_Filter = assertions "filter" $ do
+	$expect $ equal
+		["A", "B", "", "D", "E"]
+		(E.runLists_ [["ABCDE"]] $ do
+			ET.filter (/= 'C') =$ EL.consume)
+
+test_FilterM :: Suite
+test_FilterM = assertions "filterM" $ do
+	$expect $ equal
+		["A", "B", "", "D", "E"]
+		(E.runLists_ [["ABCDE"]] $ do
+			ET.filterM (\x -> return (x /= 'C')) =$ EL.consume)
diff --git a/tests/EnumeratorTests/Text/Fold.hs b/tests/EnumeratorTests/Text/Fold.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Text/Fold.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Text.Fold
+	( test_Fold
+	, test_FoldM
+	) where
+
+import           Control.Monad (foldM)
+import           Data.Functor.Identity (runIdentity)
+import qualified Data.Text
+import           Data.Text (Text)
+import           Test.Chell
+import           Test.Chell.QuickCheck
+import           Test.QuickCheck.Poly
+import           Test.QuickCheck.Modifiers
+
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.Text as ET
+
+import           EnumeratorTests.Util ()
+
+test_Fold :: Suite
+test_Fold = property "fold" prop_Fold
+
+prop_Fold :: Blind (B -> Char -> B) -> B -> Text -> Bool
+prop_Fold (Blind f) z text = result == expected where
+	result = E.runLists_ [[text]] (ET.fold f z)
+	expected = Data.Text.foldl' f z text
+
+test_FoldM :: Suite
+test_FoldM = property "foldM" prop_FoldM
+
+prop_FoldM :: Blind (B -> Char -> B) -> B -> Text -> Bool
+prop_FoldM (Blind f) z text = result == expected where
+	result = E.runLists_ [[text]] (ET.foldM f' z)
+	expected = runIdentity (foldM f' z (Data.Text.unpack text))
+	f' b a = return (f b a)
diff --git a/tests/EnumeratorTests/Text/Handle.hs b/tests/EnumeratorTests/Text/Handle.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Text/Handle.hs
@@ -0,0 +1,50 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Text.Handle
+	( test_EnumHandle
+	, test_IterHandle
+	) where
+
+import           Test.Chell
+
+#ifdef MIN_VERSION_knob
+
+import           Data.Knob
+import qualified System.IO as IO
+
+import qualified Data.Enumerator as E
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator.List as EL
+import qualified Data.Enumerator.Text as ET
+
+test_EnumHandle :: Suite
+test_EnumHandle = assertions "enumHandle" $ do
+	knob <- newKnob "0123\n\n4567"
+	chunks <- withFileHandle knob "" IO.ReadMode $ \h -> do
+		E.run_ (ET.enumHandle h $$ EL.consume)
+	$expect (equal chunks ["0123", "", "4567"])
+
+test_IterHandle :: Suite
+test_IterHandle = assertions "iterHandle" $ do
+	knob <- newKnob ""
+	withFileHandle knob "" IO.WriteMode $ \h -> do
+		E.run_ (E.enumLists [[], ["A", "B"], ["C"]] $$ ET.iterHandle h)
+	bytes <- Data.Knob.getContents knob
+	$expect (equal bytes "ABC")
+
+#else
+
+import           EnumeratorTests.Util (todo)
+
+test_EnumHandle :: Suite
+test_EnumHandle = todo "enumHandle"
+
+test_IterHandle :: Suite
+test_IterHandle = todo "iterHandle"
+
+#endif
diff --git a/tests/EnumeratorTests/Text/Isolate.hs b/tests/EnumeratorTests/Text/Isolate.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Text/Isolate.hs
@@ -0,0 +1,73 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Text.Isolate
+	( test_Isolate
+	) where
+
+import qualified Data.Text.Lazy as TL
+
+import           Test.Chell
+import           Test.Chell.QuickCheck
+
+import           Data.Enumerator (($$), (=$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+import qualified Data.Enumerator.Text as ET
+
+import           EnumeratorTests.Text.Util (prop_Text)
+
+test_Isolate :: Suite
+test_Isolate = suite "isolate"
+	[ prop_Isolate
+	, test_DropExtra
+	, test_HandleEOF
+	, test_BadParameter
+	]
+
+prop_Isolate :: Suite
+prop_Isolate = property "model" $ prop_Text
+	(do
+		x <- E.joinI (ET.isolate 2 $$ ET.head)
+		extra <- ET.consume
+		return (x, extra))
+	(\text -> Right $ case TL.unpack text of
+		[] -> (Nothing, TL.empty)
+		(x:[]) -> (Just x, TL.empty)
+		(x:_:xs') -> (Just x, TL.pack xs'))
+
+test_DropExtra :: Suite
+test_DropExtra = assertions "drop-extra" $ do
+	$expect $ equal
+		(Just 'A', ["C"])
+		(E.runLists_ [[], ["A"], ["B"], ["C"]] $ do
+			x <- ET.isolate 2 =$ ET.head
+			extra <- EL.consume
+			return (x, extra))
+	$expect $ equal
+		(Just 'A', ["C"])
+		(E.runLists_ [["A", "B", "C"]] $ do
+			x <- ET.isolate 2 =$ ET.head
+			extra <- EL.consume
+			return (x, extra))
+
+test_HandleEOF :: Suite
+test_HandleEOF = assertions "handle-eof" $ do
+	$expect $ equal
+		(Nothing :: Maybe Char, [])
+		(E.runLists_ [] $ do
+			x <- ET.isolate 2 =$ ET.head
+			extra <- EL.consume
+			return (x, extra))
+
+test_BadParameter :: Suite
+test_BadParameter = assertions "bad-parameter" $ do
+	$expect $ equal
+		(Nothing, ["A", "B", "C"])
+		(E.runLists_ [["A"], ["B"], ["C"]] $ do
+			x <- ET.isolate 0 =$ ET.head
+			extra <- EL.consume
+			return (x, extra))
diff --git a/tests/EnumeratorTests/Text/Iterate.hs b/tests/EnumeratorTests/Text/Iterate.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Text/Iterate.hs
@@ -0,0 +1,31 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Text.Iterate
+	( test_Iterate
+	, test_IterateM
+	) where
+
+import           Data.Functor.Identity (runIdentity)
+import           Test.Chell
+
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+import qualified Data.Enumerator.Text as ET
+
+test_Iterate :: Suite
+test_Iterate = assertions "iterate" $ do
+	$expect $ equal
+		["A", "B", "C"]
+		(runIdentity (E.run_ (ET.iterate succ 'A' $$ EL.take 3)))
+
+test_IterateM :: Suite
+test_IterateM = assertions "iterateM" $ do
+	let succM = return . succ
+	$expect $ equal
+		["A", "B", "C"]
+		(runIdentity (E.run_ (ET.iterateM succM 'A' $$ EL.take 3)))
diff --git a/tests/EnumeratorTests/Text/Map.hs b/tests/EnumeratorTests/Text/Map.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Text/Map.hs
@@ -0,0 +1,138 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Text.Map
+	( test_Map
+	, test_MapM
+	, test_MapM_
+	, test_ConcatMap
+	, test_ConcatMapM
+	, test_ConcatMapAccum
+	, test_ConcatMapAccumM
+	, test_MapAccum
+	, test_MapAccumM
+	) where
+
+import           Control.Monad.Trans.Writer (execWriter, tell)
+import           Data.Char (chr, ord, toLower)
+import qualified Data.Text as T
+
+import           Test.Chell
+
+import           Data.Enumerator (($$), (=$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+import qualified Data.Enumerator.Text as ET
+
+test_Map :: Suite
+test_Map = assertions "map" $ do
+	$expect $ equal
+		["a", "b"]
+		(E.runLists_ [["AB"]] (ET.map toLower =$ EL.consume))
+	$expect $ equal
+		(["a", "b"], ["CDEF", "GH"])
+		(E.runLists_ [["ABCD", "EF"], ["GH"]] $ do
+			xs <- ET.map toLower =$ EL.take 2
+			extra <- EL.consume
+			return (xs, extra))
+
+test_MapM :: Suite
+test_MapM = assertions "mapM" $ do
+	$expect $ equal
+		["a", "b"]
+		(E.runLists_ [["AB"]] (ET.mapM (return . toLower) =$ EL.consume))
+	$expect $ equal
+		(["a", "b"], ["CDEF", "GH"])
+		(E.runLists_ [["ABCD", "EF"], ["GH"]] $ do
+			xs <- ET.mapM (return . toLower) =$ EL.take 2
+			extra <- EL.consume
+			return (xs, extra))
+
+test_MapM_ :: Suite
+test_MapM_ = assertions "mapM_" $ do
+	$expect $ equal
+		['A', 'B']
+		(execWriter (E.run_ (E.enumLists [["AB"]] $$ ET.mapM_ (\x -> tell [x]))))
+
+test_ConcatMap :: Suite
+test_ConcatMap = assertions "concatMap" $ do
+	$expect $ equal
+		["Aa", "Bb"]
+		(E.runLists_ [["AB"]] (ET.concatMap (\x -> T.pack [x, toLower x]) =$ EL.consume))
+	$expect $ equal
+		(["Aa", "Bb"], ["CDEF", "GH"])
+		(E.runLists_ [["ABCD", "EF"], ["GH"]] $ do
+			xs <- ET.concatMap (\x -> T.pack [x, toLower x]) =$ EL.take 2
+			extra <- EL.consume
+			return (xs, extra))
+
+test_ConcatMapM :: Suite
+test_ConcatMapM = assertions "concatMapM" $ do
+	$expect $ equal
+		["Aa", "Bb"]
+		(E.runLists_ [["AB"]] (ET.concatMapM (\x -> return (T.pack [x, toLower x])) =$ EL.consume))
+	$expect $ equal
+		(["Aa", "Bb"], ["CDEF", "GH"])
+		(E.runLists_ [["ABCD", "EF"], ["GH"]] $ do
+			xs <- ET.concatMapM (\x -> return (T.pack [x, toLower x])) =$ EL.take 2
+			extra <- EL.consume
+			return (xs, extra))
+
+test_MapAccum :: Suite
+test_MapAccum = assertions "mapAccum" $ do
+	let step s ao = (s + 1, chr (ord ao + s))
+	$expect $ equal
+		["B", "D", "F"]
+		(E.runLists_ [["A", "B"], ["C"]] $ do
+			ET.mapAccum step 1 =$ EL.consume)
+	$expect $ equal
+		("B", ["", "B", "C"])
+		(E.runLists_ [["A", "B"], ["C"]] $ do
+			xs <- ET.mapAccum step 1 =$ ET.take 1
+			extra <- EL.consume
+			return (xs, extra))
+
+test_MapAccumM :: Suite
+test_MapAccumM = assertions "mapAccumM" $ do
+	let step s ao = return (s + 1, chr (ord ao + s))
+	$expect $ equal
+		["B", "D", "F"]
+		(E.runLists_ [["A", "B"], ["C"]] $ do
+			ET.mapAccumM step 1 =$ EL.consume)
+	$expect $ equal
+		("B", ["", "B", "C"])
+		(E.runLists_ [["A", "B"], ["C"]] $ do
+			xs <- ET.mapAccumM step 1 =$ ET.take 1
+			extra <- EL.consume
+			return (xs, extra))
+
+test_ConcatMapAccum :: Suite
+test_ConcatMapAccum = assertions "concatMapAccum" $ do
+	let step s ao = (s + 1, T.replicate s (T.pack [ao]))
+	$expect $ equal
+		["A", "BB", "CCC"]
+		(E.runLists_ [["A", "B"], ["C"]] $ do
+			ET.concatMapAccum step 1 =$ EL.consume)
+	$expect $ equal
+		("AB", ["", "C"])
+		(E.runLists_ [["A", "B"], ["C"]] $ do
+			xs <- ET.concatMapAccum step 1 =$ ET.take 2
+			extra <- EL.consume
+			return (xs, extra))
+
+test_ConcatMapAccumM :: Suite
+test_ConcatMapAccumM = assertions "concatMapAccumM" $ do
+	let step s ao = return (s + 1, T.replicate s (T.pack [ao]))
+	$expect $ equal
+		["A", "BB", "CCC"]
+		(E.runLists_ [["A", "B"], ["C"]] $ do
+			ET.concatMapAccumM step 1 =$ EL.consume)
+	$expect $ equal
+		("AB", ["", "C"])
+		(E.runLists_ [["A", "B"], ["C"]] $ do
+			xs <- ET.concatMapAccumM step 1 =$ ET.take 2
+			extra <- EL.consume
+			return (xs, extra))
diff --git a/tests/EnumeratorTests/Text/Repeat.hs b/tests/EnumeratorTests/Text/Repeat.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Text/Repeat.hs
@@ -0,0 +1,49 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Text.Repeat
+	( test_Repeat
+	, test_RepeatM
+	, test_GenerateM
+	) where
+
+import           Control.Monad.Trans.State
+import           Data.Functor.Identity (runIdentity)
+import           Test.Chell
+
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+import qualified Data.Enumerator.Text as ET
+
+test_Repeat :: Suite
+test_Repeat = assertions "repeat" $ do
+	$expect $ equal
+		["A", "A", "A"]
+		(runIdentity (E.run_ (ET.repeat 'A' $$ EL.take 3)))
+
+test_RepeatM :: Suite
+test_RepeatM = assertions "repeatM" $ do
+	let step = do
+		c <- get
+		put (succ c)
+		return c
+	$expect $ equal
+		["A", "B", "C"]
+		(evalState (E.run_ (ET.repeatM step $$ EL.take 3)) 'A')
+
+test_GenerateM :: Suite
+test_GenerateM = assertions "generateM" $ do
+	let step = do
+		c <- get
+		if c > 'C'
+			then return Nothing
+			else do
+				put (succ c)
+				return (Just c)
+	$expect $ equal
+		["A", "B", "C"]
+		(evalState (E.run_ (ET.generateM step $$ EL.consume)) 'A')
diff --git a/tests/EnumeratorTests/Text/Replicate.hs b/tests/EnumeratorTests/Text/Replicate.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Text/Replicate.hs
@@ -0,0 +1,38 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Text.Replicate
+	( test_Replicate
+	, test_ReplicateM
+	) where
+
+import           Control.Monad.Trans.State
+import           Data.Functor.Identity (runIdentity)
+import           Test.Chell
+
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+import qualified Data.Enumerator.Text as ET
+
+test_Replicate :: Suite
+test_Replicate = assertions "replicate" $ do
+	$expect $ equal
+		["A", "A", "A"]
+		(runIdentity (E.run_ (ET.replicate 3 'A' $$ EL.consume)))
+
+test_ReplicateM :: Suite
+test_ReplicateM = assertions "replicateM" $ do
+	let step = do
+		c <- get
+		put (succ c)
+		return c
+	$expect $ equal
+		["A", "B", "C"]
+		(evalState (E.run_ (ET.replicateM 3 step $$ EL.consume)) 'A')
+	$expect $ equal
+		["A", "B"]
+		(evalState (E.run_ (ET.replicateM 3 step $$ EL.take 2)) 'A')
diff --git a/tests/EnumeratorTests/Text/Require.hs b/tests/EnumeratorTests/Text/Require.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Text/Require.hs
@@ -0,0 +1,67 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Text.Require
+	( test_Require
+	) where
+
+import qualified Control.Exception as Exc
+import qualified Data.Text.Lazy as TL
+import           Test.Chell
+import           Test.Chell.QuickCheck
+
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+import qualified Data.Enumerator.Text as ET
+
+import           EnumeratorTests.Text.Util
+import           EnumeratorTests.Util (equalExc)
+
+test_Require :: Suite
+test_Require = suite "require"
+	[ prop_Require
+	, test_YieldsInput
+	, test_HandleEOF
+	, test_BadParameter
+	]
+
+prop_Require :: Suite
+prop_Require = property "model" $ prop_TextN
+	(\n -> do
+		ET.require n
+		ET.consume)
+	(\n xs -> if n > toInteger (TL.length xs)
+		then Left (Exc.ErrorCall "require: Unexpected EOF")
+		else Right xs)
+
+test_YieldsInput :: Suite
+test_YieldsInput = assertions "yields-input" $ do
+	$expect $ equal
+		["A", "B", "C"]
+		(E.runLists_ [["A"], ["B"], ["C"]] $ do
+			ET.require 2
+			EL.consume)
+	$expect $ equal
+		["A", "B", "C"]
+		(E.runLists_ [["A", "B", "C"]] $ do
+			ET.require 2
+			EL.consume)
+
+test_HandleEOF :: Suite
+test_HandleEOF = assertions "handle-eof" $ do
+	$expect $ equalExc
+		(Exc.ErrorCall "require: Unexpected EOF")
+		(E.runLists [] $ do
+			ET.require 2
+			EL.consume)
+
+test_BadParameter :: Suite
+test_BadParameter = assertions "bad-parameter" $ do
+	$expect $ equal
+		[]
+		(E.runLists_ [] $ do
+			ET.require 0
+			EL.consume)
diff --git a/tests/EnumeratorTests/Text/Split.hs b/tests/EnumeratorTests/Text/Split.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Text/Split.hs
@@ -0,0 +1,61 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Text.Split
+	( test_SplitWhen
+	, test_Lines
+	) where
+
+import qualified Data.List.Split as LS
+import qualified Data.Text as T
+import qualified Data.Text.Lazy as TL
+import           Test.Chell
+import           Test.Chell.QuickCheck
+
+import           Data.Enumerator ((=$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+import qualified Data.Enumerator.Text as ET
+
+import           EnumeratorTests.Text.Util
+
+test_SplitWhen :: Suite
+test_SplitWhen = suite "splitWhen"
+	[ prop_SplitWhen
+	, test_HandleEmpty
+	]
+
+prop_SplitWhen :: Suite
+prop_SplitWhen = property "model" $ prop_TextX
+	(\c -> do
+		xs <- ET.splitWhen (== c) =$ EL.consume
+		extra <- EL.consume
+		return (xs, extra))
+	(\c text -> let
+		split = LS.split . LS.dropFinalBlank . LS.dropDelims . LS.whenElt
+		chars = TL.unpack text
+		in Right (map T.pack (split (== c) chars), []))
+
+test_HandleEmpty :: Suite
+test_HandleEmpty = assertions "empty" $ do
+	$expect $ equal
+		([], Nothing)
+		(E.runLists_ [[""]] $ do
+			xs <- ET.splitWhen (== ',') =$ EL.consume
+			extra <- EL.head
+			return (xs, extra))
+
+test_Lines :: Suite
+test_Lines = assertions "lines" $ do
+	$expect $ equal
+		["abc", "def"]
+		(E.runLists_ [["abc\ndef"]] (ET.lines =$ EL.consume))
+	$expect $ equal
+		["abc", "def"]
+		(E.runLists_ [["abc\ndef\n"]] (ET.lines =$ EL.consume))
+	$expect $ equal
+		["abc", "def", ""]
+		(E.runLists_ [["abc\ndef\n\n"]] (ET.lines =$ EL.consume))
diff --git a/tests/EnumeratorTests/Text/Unfold.hs b/tests/EnumeratorTests/Text/Unfold.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Text/Unfold.hs
@@ -0,0 +1,36 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Text.Unfold
+	( test_Unfold
+	, test_UnfoldM
+	) where
+
+import           Data.Functor.Identity (runIdentity)
+import           Test.Chell
+
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+import qualified Data.Enumerator.Text as ET
+
+test_Unfold :: Suite
+test_Unfold = assertions "unfold" $ do
+	let step x = if x > 'C'
+		then Nothing
+		else Just (x, succ x)
+	$expect $ equal
+		["A", "B", "C"]
+		(runIdentity (E.run_ (ET.unfold step 'A' $$ EL.consume)))
+
+test_UnfoldM :: Suite
+test_UnfoldM = assertions "unfoldM" $ do
+	let step x = return $ if x > 'C'
+		then Nothing
+		else Just (x, succ x)
+	$expect $ equal
+		["A", "B", "C"]
+		(runIdentity (E.run_ (ET.unfoldM step 'A' $$ EL.consume)))
diff --git a/tests/EnumeratorTests/Text/Util.hs b/tests/EnumeratorTests/Text/Util.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Text/Util.hs
@@ -0,0 +1,44 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Text.Util
+	( prop_Text
+	, prop_TextN
+	, prop_TextX
+	) where
+
+import           Control.Exception (ErrorCall)
+import           Data.Text (Text)
+import qualified Data.Text.Lazy as TL
+import           Data.Functor.Identity (Identity)
+
+import           Test.QuickCheck hiding (property)
+
+import           Data.Enumerator (Iteratee)
+
+import           EnumeratorTests.Util (check)
+
+prop_Text :: Eq b
+          => Iteratee Text Identity b
+          -> (TL.Text -> Either ErrorCall b)
+          -> [Text]
+          -> Bool
+prop_Text iter plain = check iter (plain . TL.fromChunks)
+
+prop_TextN :: Eq b
+           => (Integer -> Iteratee Text Identity b)
+           -> (Integer -> TL.Text -> Either ErrorCall b)
+           -> Positive Integer
+           -> [Text]
+           -> Bool
+prop_TextN iter plain (Positive n) = check (iter n) (plain n . TL.fromChunks)
+
+prop_TextX :: Eq b
+           => (Char -> Iteratee Text Identity b)
+           -> (Char -> TL.Text -> Either ErrorCall b)
+           -> Char
+           -> [Text]
+           -> Bool
+prop_TextX iter plain x = check (iter x) (plain x . TL.fromChunks)
diff --git a/tests/EnumeratorTests/Text/Zip.hs b/tests/EnumeratorTests/Text/Zip.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Text/Zip.hs
@@ -0,0 +1,164 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Text.Zip
+	( test_Zip
+	) where
+
+import qualified Control.Exception as Exc
+import           Data.Functor.Identity (Identity)
+import           Data.Text (Text)
+import           Test.Chell
+
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+import qualified Data.Enumerator.Text as ET
+
+import           EnumeratorTests.Util (equalExc)
+
+test_ZipN :: (Eq b, Show b) => Text -> E.Iteratee Text Identity b -> b -> Suite
+test_ZipN name iter expected = assertions name $ do
+	$expect $ equal
+		expected
+		(E.runLists_ [[], ["A"], ["B"]] iter)
+
+$([d||])
+
+test_Zip :: Suite
+test_Zip = suite "zip"
+	[ test_ContinueContinue
+	, test_YieldContinue
+	, test_ContinueYield
+	, test_YieldYield
+	, test_ErrorFirst
+	, test_ErrorSecond
+	, test_HandleEOF
+	, test_Zip3
+	, test_Zip4
+	, test_Zip5
+	, test_Zip6
+	, test_Zip7
+	, test_ZipWith
+	, test_ZipWith3
+	, test_ZipWith4
+	, test_ZipWith5
+	, test_ZipWith6
+	, test_ZipWith7
+	]
+
+test_ContinueContinue :: Suite
+test_ContinueContinue = assertions "continue-continue" $ do
+	$expect $ equal
+		("AB", "AB", ["C"])
+		(E.runLists_ [["A"], ["B"], ["C"]] $ do
+			(x, y) <- ET.zip (ET.take 2) (ET.take 2)
+			extra <- EL.consume
+			return (x, y, extra))
+
+test_YieldContinue :: Suite
+test_YieldContinue = assertions "yield-continue" $ do
+	$expect $ equal
+		("A", "AB", ["C"])
+		(E.runLists_ [["A"], ["B"], ["C"]] $ do
+			(x, y) <- ET.zip (ET.take 1) (ET.take 2)
+			extra <- EL.consume
+			return (x, y, extra))
+
+test_ContinueYield :: Suite
+test_ContinueYield = assertions "continue-yield" $ do
+	$expect $ equal
+		("AB", "A", ["C"])
+		(E.runLists_ [["A"], ["B"], ["C"]] $ do
+			(x, y) <- ET.zip (ET.take 2) (ET.take 1)
+			extra <- EL.consume
+			return (x, y, extra))
+
+test_YieldYield :: Suite
+test_YieldYield = assertions "yield-yield" $ do
+	$expect $ equal
+		("A", "A", ["B", "C"])
+		(E.runLists_ [["A"], ["B"], ["C"]] $ do
+			(x, y) <- ET.zip (ET.take 1) (ET.take 1)
+			extra <- EL.consume
+			return (x, y, extra))
+
+test_ErrorFirst :: Suite
+test_ErrorFirst = assertions "error-first" $ do
+	$expect $ equalExc
+		(Exc.ErrorCall "error")
+		(E.runLists [["A"], ["B"], ["C"]] $ do
+			ET.zip (E.throwError (Exc.ErrorCall "error")) (ET.take 1))
+
+test_ErrorSecond :: Suite
+test_ErrorSecond = assertions "error-second" $ do
+	$expect $ equalExc
+		(Exc.ErrorCall "error")
+		(E.runLists [["A"], ["B"], ["C"]] $ do
+			ET.zip (ET.take 1) (E.throwError (Exc.ErrorCall "error")))
+
+test_HandleEOF :: Suite
+test_HandleEOF = assertions "handle-eof" $ do
+	$expect $ equal
+		("A", "AB", [])
+		(E.runLists_ [["A"], ["B"]] $ do
+			(x, y) <- ET.zip (ET.take 1) (ET.take 3)
+			extra <- EL.consume
+			return (x, y, extra))
+
+test_Zip3 :: Suite
+test_Zip3 = test_ZipN "zip3"
+	(ET.zip3 ET.head ET.head ET.head)
+	(Just 'A', Just 'A', Just 'A')
+
+test_Zip4 :: Suite
+test_Zip4 = test_ZipN "zip4"
+	(ET.zip4 ET.head ET.head ET.head ET.head)
+	(Just 'A', Just 'A', Just 'A', Just 'A')
+
+test_Zip5 :: Suite
+test_Zip5 = test_ZipN "zip5"
+	(ET.zip5 ET.head ET.head ET.head ET.head ET.head)
+	(Just 'A', Just 'A', Just 'A', Just 'A', Just 'A')
+
+test_Zip6 :: Suite
+test_Zip6 = test_ZipN "zip6"
+	(ET.zip6 ET.head ET.head ET.head ET.head ET.head ET.head)
+	(Just 'A', Just 'A', Just 'A', Just 'A', Just 'A', Just 'A')
+
+test_Zip7 :: Suite
+test_Zip7 = test_ZipN "zip7"
+	(ET.zip7 ET.head ET.head ET.head ET.head ET.head ET.head ET.head)
+	(Just 'A', Just 'A', Just 'A', Just 'A', Just 'A', Just 'A', Just 'A')
+
+test_ZipWith :: Suite
+test_ZipWith = test_ZipN "zipWith"
+	(ET.zipWith (,) ET.head ET.head)
+	(Just 'A', Just 'A')
+
+test_ZipWith3 :: Suite
+test_ZipWith3 = test_ZipN "zipWith3"
+	(ET.zipWith3 (,,) ET.head ET.head ET.head)
+	(Just 'A', Just 'A', Just 'A')
+
+test_ZipWith4 :: Suite
+test_ZipWith4 = test_ZipN "zipWith4"
+	(ET.zipWith4 (,,,) ET.head ET.head ET.head ET.head)
+	(Just 'A', Just 'A', Just 'A', Just 'A')
+
+test_ZipWith5 :: Suite
+test_ZipWith5 = test_ZipN "zipWith5"
+	(ET.zipWith5 (,,,,) ET.head ET.head ET.head ET.head ET.head)
+	(Just 'A', Just 'A', Just 'A', Just 'A', Just 'A')
+
+test_ZipWith6 :: Suite
+test_ZipWith6 = test_ZipN "zipWith6"
+	(ET.zipWith6 (,,,,,) ET.head ET.head ET.head ET.head ET.head ET.head)
+	(Just 'A', Just 'A', Just 'A', Just 'A', Just 'A', Just 'A')
+
+test_ZipWith7 :: Suite
+test_ZipWith7 = test_ZipN "zipWith7"
+	(ET.zipWith7 (,,,,,,) ET.head ET.head ET.head ET.head ET.head ET.head ET.head)
+	(Just 'A', Just 'A', Just 'A', Just 'A', Just 'A', Just 'A', Just 'A')
diff --git a/tests/EnumeratorTests/Util.hs b/tests/EnumeratorTests/Util.hs
new file mode 100644
--- /dev/null
+++ b/tests/EnumeratorTests/Util.hs
@@ -0,0 +1,142 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
+--
+-- See license.txt for details
+module EnumeratorTests.Util
+	( check
+	, equalExc
+	, todo
+	, within
+	, genASCII
+	, genISO8859_1
+	, genUnicode
+	) where
+
+import qualified Control.Exception as Exc
+import           Data.Bits ((.&.))
+import qualified Data.ByteString as B
+import           Data.Char (chr)
+import           Data.Functor.Identity (Identity, runIdentity)
+import           Data.String (IsString, fromString)
+import qualified Data.Text as T
+import           System.Timeout (timeout)
+
+import           Test.Chell
+import           Test.QuickCheck hiding ((.&.), within)
+
+import           Data.Enumerator (($$))
+import qualified Data.Enumerator as E
+
+check :: Eq b => E.Iteratee a Identity b -> ([a] -> Either Exc.ErrorCall b) -> [a] -> Bool
+check iter plain xs = expected == run where
+	expected = case plain xs of
+		Left exc -> Left (Just exc)
+		Right x -> Right x
+	
+	run = case runIdentity (E.run (E.enumList 1 xs $$ iter)) of
+		Left exc -> Left (Exc.fromException exc)
+		Right x -> Right x
+
+todo :: T.Text -> Suite
+todo name = skipIf True (assertions name (return ()))
+
+genASCII :: IsString a => Gen a
+genASCII = fmap fromString string where
+	string = sized $ \n -> do
+		k <- choose (0,n)
+		sequence [ char | _ <- [1..k] ]
+	
+	char = chr `fmap` choose (0,0x7F)
+
+genISO8859_1 :: IsString a => Gen a
+genISO8859_1 = fmap fromString string where
+	string = sized $ \n -> do
+		k <- choose (0,n)
+		sequence [ char | _ <- [1..k] ]
+	
+	char = chr `fmap` choose (0,0xFF)
+
+genUnicode :: IsString a => Gen a
+genUnicode = fmap fromString string where
+	string = sized $ \n -> do
+		k <- choose (0,n)
+		sequence [ char | _ <- [1..k] ]
+	
+	excluding :: [a -> Bool] -> Gen a -> Gen a
+	excluding bad gen = loop where
+		loop = do
+			x <- gen
+			if or (map ($ x) bad)
+				then loop
+				else return x
+	
+	reserved = [lowSurrogate, highSurrogate, noncharacter]
+	lowSurrogate c = c >= 0xDC00 && c <= 0xDFFF
+	highSurrogate c = c >= 0xD800 && c <= 0xDBFF
+	noncharacter c = masked == 0xFFFE || masked == 0xFFFF where
+		masked = c .&. 0xFFFF
+	
+	ascii = choose (0,0x7F)
+	plane0 = choose (0xF0, 0xFFFF)
+	plane1 = oneof [ choose (0x10000, 0x10FFF)
+	               , choose (0x11000, 0x11FFF)
+	               , choose (0x12000, 0x12FFF)
+	               , choose (0x13000, 0x13FFF)
+	               , choose (0x1D000, 0x1DFFF)
+	               , choose (0x1F000, 0x1FFFF)
+	               ]
+	plane2 = oneof [ choose (0x20000, 0x20FFF)
+	               , choose (0x21000, 0x21FFF)
+	               , choose (0x22000, 0x22FFF)
+	               , choose (0x23000, 0x23FFF)
+	               , choose (0x24000, 0x24FFF)
+	               , choose (0x25000, 0x25FFF)
+	               , choose (0x26000, 0x26FFF)
+	               , choose (0x27000, 0x27FFF)
+	               , choose (0x28000, 0x28FFF)
+	               , choose (0x29000, 0x29FFF)
+	               , choose (0x2A000, 0x2AFFF)
+	               , choose (0x2B000, 0x2BFFF)
+	               , choose (0x2F000, 0x2FFFF)
+	               ]
+	plane14 = choose (0xE0000, 0xE0FFF)
+	planes = [ascii, plane0, plane1, plane2, plane14]
+	
+	char = chr `fmap` excluding reserved (oneof planes)
+
+instance Arbitrary a => Arbitrary (E.Stream a) where
+	arbitrary = frequency
+		[ (10, return E.EOF)
+		, (90, fmap E.Chunks arbitrary)
+		]
+
+instance Arbitrary T.Text where
+	arbitrary = genUnicode
+
+instance Arbitrary B.ByteString where
+	arbitrary = genUnicode
+
+instance Eq Exc.ErrorCall where
+	(Exc.ErrorCall s1) == (Exc.ErrorCall s2) = s1 == s2
+
+-- | Require a test to complete within /n/ milliseconds.
+within :: Int -> Suite -> Suite
+within time s = suite (suiteName s) (map wrapTest (suiteTests s)) where
+	wrapTest (Test name io) = test $ Test name $ \opts -> do
+		res <- timeout (time * 1000) (io opts)
+		case res of
+			Just res' -> return res'
+			Nothing -> return (TestAborted [] (T.pack ("Test timed out after " ++ show time ++ " milliseconds")))
+
+equalExc :: (Eq exc, Exc.Exception exc) => exc -> Either Exc.SomeException a -> Assertion
+equalExc expected funResult = Assertion (return result) where
+	failed :: String -> AssertionResult
+	failed str = AssertionFailed (T.pack ("equalExc: " ++ show str))
+	result = case funResult of
+		Right _ -> failed "received Right"
+		Left exc -> case Exc.fromException exc of
+			Nothing -> failed ("received unexpected exception: " ++ show exc)
+			Just exc' -> if expected == exc'
+				then AssertionPassed
+				else failed (show expected ++ " /= " ++ show exc')
diff --git a/tests/Tests.hs b/tests/Tests.hs
deleted file mode 100644
--- a/tests/Tests.hs
+++ /dev/null
@@ -1,936 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TemplateHaskell #-}
-
--- Copyright (C) 2010 John Millikin <jmillikin@gmail.com>
---
--- See license.txt for details
-module Main (tests, main) where
-
-import           Control.Concurrent
-import qualified Control.Exception as Exc
-import           Control.Monad.IO.Class (liftIO)
-import           Data.Bits ((.&.))
-import qualified Data.ByteString as B
-import qualified Data.ByteString.Char8 as B8
-import qualified Data.ByteString.Lazy as BL
-import           Data.Char (chr)
-import           Data.Functor.Identity (Identity, runIdentity)
-import qualified Data.List as L
-import qualified Data.List.Split as LS
-import           Data.Monoid (mappend, mempty, mconcat)
-import           Data.String (IsString, fromString)
-import qualified Data.Text as T
-import qualified Data.Text.Encoding as TE
-import qualified Data.Text.Lazy as TL
-import           Data.Word (Word8)
-import           System.Timeout (timeout)
-
-import           Test.Chell
-import           Test.Chell.QuickCheck
-import           Test.QuickCheck hiding ((.&.), property, within)
-import           Test.QuickCheck.Poly (A, B, C)
-
-import           Data.Enumerator (($$), (>>==))
-import qualified Data.Enumerator as E
-import qualified Data.Enumerator.Binary as EB
-import qualified Data.Enumerator.Text as ET
-import qualified Data.Enumerator.List as EL
-
-tests :: [Suite]
-tests =
-	[ suite_StreamInstances
-	, suite_Text
-	, suite_ListAnalogues
-	, suite_Other
-	]
-
-main :: IO ()
-main = Test.Chell.defaultMain tests
-
--- Stream instances {{{
-
-suite_StreamInstances :: Suite
-suite_StreamInstances = suite "stream-instances"
-	[ suite_StreamMonoid
-	, suite_StreamFunctor
-	, suite_StreamMonad
-	]
-
-suite_StreamMonoid :: Suite
-suite_StreamMonoid = suite "monoid" props where
-	props = [ property "law-1" prop_law1
-	        , property "law-2" prop_law2
-	        , property "law-3" prop_law3
-	        , property "law-4" prop_law4
-	        ]
-	
-	prop_law1 :: E.Stream A -> Bool
-	prop_law1 x = mappend mempty x == x
-	
-	prop_law2 :: E.Stream A -> Bool
-	prop_law2 x = mappend x mempty == x
-	
-	prop_law3 :: E.Stream A -> E.Stream A -> E.Stream A -> Bool
-	prop_law3 x y z = mappend x (mappend y z) == mappend (mappend x y) z
-	
-	prop_law4 :: [E.Stream A] -> Bool
-	prop_law4 xs = mconcat xs == foldr mappend mempty xs
-
-suite_StreamFunctor :: Suite
-suite_StreamFunctor = suite "functor" props where
-	props = [ property "law-1" prop_law1
-	        , property "law-2" prop_law2
-	        ]
-	
-	prop_law1 :: E.Stream A -> Bool
-	prop_law1 x = fmap id x == id x
-	
-	prop_law2 :: E.Stream A -> Blind (B -> C) -> Blind (A -> B) -> Bool
-	prop_law2 x (Blind f) (Blind g) = fmap (f . g) x == (fmap f . fmap g) x
-
-suite_StreamMonad :: Suite
-suite_StreamMonad = suite "Monad Stream" props where
-	props = [ property "law-1" prop_law1
-	        , property "law-2" prop_law2
-	        , property "law-3" prop_law3
-	        ]
-	
-	prop_law1 :: A -> Blind (A -> E.Stream B) -> Bool
-	prop_law1 a (Blind f) = (return a >>= f) == f a
-	
-	prop_law2 :: E.Stream A -> Bool
-	prop_law2 m = (m >>= return) == m
-	
-	prop_law3 :: E.Stream A -> Blind (A -> E.Stream B) -> Blind (B -> E.Stream C) -> Bool
-	prop_law3 m (Blind f) (Blind g) = ((m >>= f) >>= g) == (m >>= (\x -> f x >>= g))
-
--- }}}
-
--- Generic properties {{{
-
-test_Enumeratee :: T.Text -> E.Enumeratee A A Identity (Maybe A) -> Suite
-test_Enumeratee name enee = suite name props where
-	props = [ property "incremental" prop_incremental
-	        , property "nest-errors" prop_nest_errors
-	        ]
-	
-	prop_incremental (Positive n) (NonEmpty xs) = let
-		result = runIdentity (E.run_ iter)
-		expected = (Just (head xs), tail xs)
-		
-		iter = E.enumList n xs $$ do
-			a <- E.joinI (enee $$ EL.head)
-			b <- EL.consume
-			return (a, b)
-		
-		in result == expected
-	
-	prop_nest_errors (Positive n) (NonEmpty xs) = let
-		result = runIdentity (E.run_ iter)
-		
-		iter = E.enumList n xs $$ do
-			_ <- enee $$ E.throwError (Exc.ErrorCall "")
-			EL.consume
-		
-		in result == xs
-
--- }}}
-
--- Text encoding / decoding {{{
-
-suite_Text :: Suite
-suite_Text = suite "text"
-	[ suite_Encoding
-	, suite_Decoding
-	]
-
-suite_Encoding :: Suite
-suite_Encoding = suite "encoding"
-	[ suite_Encode_ASCII
-	, suite_Encode_ISO8859
-	]
-
-suite_Encode_ASCII :: Suite
-suite_Encode_ASCII = suite "ascii" props where
-	props = [ property "works" (forAll genASCII prop_works)
-	        , property "error" prop_error
-	        , property "lazy" prop_lazy
-	        ]
-	
-	encode iter input =
-		runIdentity . E.run $
-		E.enumList 1 input $$
-		E.joinI (ET.encode ET.ascii $$ iter)
-	
-	prop_works bytes = result == map B.singleton words where
-		Right result = encode EL.consume (map T.singleton chars)
-		
-		chars = B8.unpack bytes
-		words = B.unpack bytes
-	
-	prop_error = isLeft (encode EL.consume input)  where
-		isLeft = either (const True) (const False)
-		input = [T.pack "\x61\xFF"]
-	
-	prop_lazy = either (const False) (== expected) result where
-		result = encode EL.head input
-		input = [T.pack "\x61\xFF"]
-		expected = Just (B.singleton 0x61)
-
-suite_Encode_ISO8859 :: Suite
-suite_Encode_ISO8859 = suite "iso-8859-1" props where
-	props = [ property "works" (forAll genISO8859 prop_works)
-	        , property "error" prop_error
-	        , property "lazy" prop_lazy
-	        ]
-	
-	encode iter input =
-		runIdentity . E.run $
-		E.enumList 1 input $$
-		E.joinI (ET.encode ET.iso8859_1 $$ iter)
-	
-	prop_works bytes = result == map B.singleton words where
-		Right result = encode EL.consume (map T.singleton chars)
-		
-		chars = B8.unpack bytes
-		words = B.unpack bytes
-	
-	prop_error = isLeft (encode EL.consume input)  where
-		isLeft = either (const True) (const False)
-		input = [T.pack "\x61\xFF5E"]
-	
-	prop_lazy = either (const False) (== expected) result where
-		result = encode EL.head input
-		input = [T.pack "\x61\xFF5E"]
-		expected = Just (B.singleton 0x61)
-
-suite_Decoding :: Suite
-suite_Decoding = suite "decoding"
-	[ suite_Decode_ASCII
-	, suite_Decode_UTF8
-	, suite_Decode_UTF16_BE
-	, suite_Decode_UTF16_LE
-	, suite_Decode_UTF32_BE
-	, suite_Decode_UTF32_LE
-	]
-
-suite_Decode_ASCII :: Suite
-suite_Decode_ASCII = suite "ascii" props where
-	props = [ property "works" (forAll genASCII prop_works)
-	        , property "error" prop_error
-	        , property "lazy" prop_lazy
-	        ]
-	
-	decode iter input =
-		runIdentity . E.run $
-		E.enumList 1 input $$
-		E.joinI (ET.decode ET.ascii $$ iter)
-	
-	prop_works text = result == map T.singleton chars where
-		Right result = decode EL.consume (map B.singleton bytes)
-		
-		bytes = B.unpack (TE.encodeUtf8 text)
-		chars = T.unpack text
-	
-	prop_error = isLeft (decode EL.consume input)  where
-		isLeft = either (const True) (const False)
-		input = [B.pack [0xFF]]
-	
-	prop_lazy = either (const False) (== expected) result where
-		result = decode EL.head input
-		input = [B.pack [0x61, 0xFF]]
-		expected = Just (T.pack "a")
-
-suite_Decode_UTF8 :: Suite
-suite_Decode_UTF8 = suite "utf-8" props where
-	props = [ property "works" prop_works
-	        , property "error" prop_error
-	        , property "lazy" prop_lazy
-	        , property "incremental" prop_incremental
-	        ]
-	
-	decode iter input =
-		runIdentity . E.run $
-		E.enumList 1 input $$
-		E.joinI (ET.decode ET.utf8 $$ iter)
-	
-	prop_works text = result == map T.singleton chars where
-		Right result = decode EL.consume (map B.singleton bytes)
-		
-		bytes = B.unpack (TE.encodeUtf8 text)
-		chars = T.unpack text
-	
-	prop_error = isLeft (decode EL.consume input)  where
-		isLeft = either (const True) (const False)
-		input = [B.pack [0x61, 0x80]]
-	
-	prop_lazy = either (const False) (== expected) result where
-		result = decode EL.head input
-		input = [B.pack [0x61, 0x80]]
-		expected = Just (T.pack "a")
-	
-	prop_incremental = either (const False) (== expected) result where
-		result = decode EL.head input
-		input = [B.pack [0x61, 0xC2, 0xC2]]
-		expected = Just (T.pack "a")
-
-suite_Decode_UTF16_BE :: Suite
-suite_Decode_UTF16_BE = suite "utf-16-be" props where
-	props = [ property "works" prop_works
-	        , property "lazy" prop_lazy
-	        , property "error" prop_error
-	        , property "incremental" prop_incremental
-	        ]
-	
-	decode iter input =
-		runIdentity . E.run $
-		E.enumList 1 input $$
-		E.joinI (ET.decode ET.utf16_be $$ iter)
-	
-	prop_works text = result == map T.singleton chars where
-		Right result = decode EL.consume (map B.singleton bytes)
-		
-		bytes = B.unpack (TE.encodeUtf16BE text)
-		chars = T.unpack text
-	
-	prop_lazy = either (const False) (== expected) result where
-		result = decode EL.head input
-		input = [B.pack [0x00, 0x61, 0xDD, 0x1E]]
-		expected = Just (T.pack "a")
-	
-	prop_error = isLeft (decode EL.consume input)  where
-		isLeft = either (const True) (const False)
-		input = [B.pack [0x00, 0x61, 0xDD, 0x1E]]
-	
-	prop_incremental = either (const False) (== expected) result where
-		result = decode EL.head input
-		input = [B.pack [0x00, 0x61, 0xD8, 0x34, 0xD8, 0xD8]]
-		expected = Just (T.pack "a")
-
-suite_Decode_UTF16_LE :: Suite
-suite_Decode_UTF16_LE = suite "utf-16-le" props where
-	props = [ property "works" prop_works
-	        , property "lazy" prop_lazy
-	        , property "error" prop_error
-	        , property "incremental" prop_incremental
-	        ]
-	
-	decode iter input =
-		runIdentity . E.run $
-		E.enumList 1 input $$
-		E.joinI (ET.decode ET.utf16_le $$ iter)
-	
-	prop_works text = result == map T.singleton chars where
-		Right result = decode EL.consume (map B.singleton bytes)
-		
-		bytes = B.unpack (TE.encodeUtf16LE text)
-		chars = T.unpack text
-	
-	prop_lazy = either (const False) (== expected) result where
-		result = decode EL.head input
-		input = [B.pack [0x61, 0x00, 0x1E, 0xDD]]
-		expected = Just (T.pack "a")
-	
-	prop_error = isLeft (decode EL.consume input)  where
-		isLeft = either (const True) (const False)
-		input = [B.pack [0x61, 0x00, 0x1E, 0xDD]]
-	
-	prop_incremental = either (const False) (== expected) result where
-		result = decode EL.head input
-		input = [B.pack [0x61, 0x00, 0x34, 0xD8, 0xD8, 0xD8]]
-		expected = Just (T.pack "a")
-
-suite_Decode_UTF32_BE :: Suite
-suite_Decode_UTF32_BE = suite "utf-32-be" props where
-	props = [ property "works" prop_works
-	        , property "lazy" prop_lazy
-	        , property "error" prop_error
-	        ]
-	
-	decode iter input =
-		runIdentity . E.run $
-		E.enumList 1 input $$
-		E.joinI (ET.decode ET.utf32_be $$ iter)
-	
-	prop_works text = result == map T.singleton chars where
-		Right result = decode EL.consume (map B.singleton bytes)
-		
-		bytes = B.unpack (TE.encodeUtf32BE text)
-		chars = T.unpack text
-	
-	prop_lazy = either (const False) (== expected) result where
-		result = decode EL.head input
-		input = [B.pack [0x00, 0x00, 0x00, 0x61, 0xFF, 0xFF]]
-		expected = Just (T.pack "a")
-	
-	prop_error = isLeft (decode EL.consume input)  where
-		isLeft = either (const True) (const False)
-		input = [B.pack [0xFF, 0xFF, 0xFF, 0xFF]]
-
-suite_Decode_UTF32_LE :: Suite
-suite_Decode_UTF32_LE = suite "utf-32-le" props where
-	props = [ property "works" prop_works
-	        , property "lazy" prop_lazy
-	        , property "error" prop_error
-	        ]
-	
-	decode iter input =
-		runIdentity . E.run $
-		E.enumList 1 input $$
-		E.joinI (ET.decode ET.utf32_le $$ iter)
-	
-	prop_works text = result == map T.singleton chars where
-		Right result = decode EL.consume (map B.singleton bytes)
-		
-		bytes = B.unpack (TE.encodeUtf32LE text)
-		chars = T.unpack text
-	
-	prop_lazy = either (const False) (== expected) result where
-		result = decode EL.head input
-		input = [B.pack [0x61, 0x00, 0x00, 0x00, 0xFF, 0xFF]]
-		expected = Just (T.pack "a")
-	
-	prop_error = isLeft (decode EL.consume input)  where
-		isLeft = either (const True) (const False)
-		input = [B.pack [0xFF, 0xFF, 0xFF, 0xFF]]
-
--- }}}
-
--- List analogues {{{
-
-suite_ListAnalogues :: Suite
-suite_ListAnalogues = suite "list-analogues"
-	[ suite_Consume
-	, suite_Head
-	, suite_Drop
-	, suite_Take
-	, suite_Require
-	, suite_Isolate
-	, suite_SplitWhen
-	, suite_Map
-	, suite_ConcatMap
-	, suite_MapM
-	, suite_ConcatMapM
-	, suite_MapAccum
-	, suite_MapAccumM
-	, suite_Filter
-	, suite_FilterM
-	]
-
-check :: Eq b => E.Iteratee a Identity b -> ([a] -> Either Exc.ErrorCall b) -> [a] -> Bool
-check iter plain xs = expected == run iter xs where
-	expected = case plain xs of
-		Left exc -> Left (Just exc)
-		Right x -> Right x
-	
-	run iter xs = case runIdentity (E.run (E.enumList 1 xs $$ iter)) of
-		Left exc -> Left (Exc.fromException exc)
-		Right x -> Right x
-
-testListAnalogue name iterList plainList iterText plainText iterBytes plainBytes = suite name tests where
-	tests = [ property "list" prop_List
-	        , property "text" prop_Text
-	        , property "bytes" prop_Bytes
-	        ]
-	
-	prop_List :: [A] -> Bool
-	prop_List xs = check iterList plainList xs
-	
-	prop_Text xs = check iterText (plainText . TL.fromChunks) xs
-	prop_Bytes xs = check iterBytes (plainBytes . BL.fromChunks) xs
-
-testListAnalogueN name iterList plainList iterText plainText iterBytes plainBytes = suite name tests where
-	tests = [ property "list" prop_List
-	        , property "text" prop_Text
-	        , property "bytes" prop_Bytes
-	        ]
-	
-	prop_List :: Positive Integer -> [A] -> Bool
-	prop_List (Positive n) xs = check (iterList n) (plainList n) xs
-	
-	prop_Text (Positive n) xs = check (iterText n) (plainText n . TL.fromChunks) xs
-	prop_Bytes (Positive n) xs = check (iterBytes n) (plainBytes n . BL.fromChunks) xs
-
-testListAnalogueX name iterList plainList iterText plainText iterBytes plainBytes = suite name tests where
-	tests = [ property "list" prop_List
-	        , property "text" prop_Text
-	        , property "bytes" prop_Bytes
-	        ]
-	
-	prop_List :: A -> [A] -> Bool
-	prop_List x xs = check (iterList x) (plainList x) xs
-	
-	prop_Text x xs = check (iterText x) (plainText x . TL.fromChunks) xs
-	prop_Bytes x xs = check (iterBytes x) (plainBytes x . BL.fromChunks) xs
-
-suite_Consume :: Suite
-suite_Consume = testListAnalogue "consume"
-	EL.consume Right
-	ET.consume Right
-	EB.consume Right
-
-suite_Head :: Suite
-suite_Head = testListAnalogue "head"
-	(do
-		x <- EL.head
-		extra <- EL.consume
-		return (x, extra)
-	)
-	(\xs -> Right $ case xs of
-		[] -> (Nothing, [])
-		(x:xs') -> (Just x, xs'))
-	(do
-		x <- ET.head
-		extra <- ET.consume
-		return (x, extra)
-	)
-	(\text -> Right $ case TL.uncons text of
-		Nothing -> (Nothing, TL.empty)
-		Just (x, extra) -> (Just x, extra))
-	(do
-		x <- EB.head
-		extra <- EB.consume
-		return (x, extra)
-	)
-	(\bytes -> Right $ case BL.uncons bytes of
-		Nothing -> (Nothing, BL.empty)
-		Just (x, extra) -> (Just x, extra))
-
-suite_Drop :: Suite
-suite_Drop = testListAnalogueN "drop"
-	(\n -> EL.drop n >> EL.consume)
-	(\n -> Right . L.genericDrop n)
-	(\n -> ET.drop n >> ET.consume)
-	(\n -> Right . TL.drop (fromInteger n))
-	(\n -> EB.drop n >> EB.consume)
-	(\n -> Right . BL.drop (fromInteger n))
-
-suite_Take :: Suite
-suite_Take = testListAnalogueN "take"
-	(\n -> do
-		xs <- EL.take n
-		extra <- EL.consume
-		return (xs, extra))
-	(\n -> Right . L.genericSplitAt n)
-	(\n -> do
-		xs <- ET.take n
-		extra <- ET.consume
-		return (xs, extra))
-	(\n -> Right . TL.splitAt (fromInteger n))
-	(\n -> do
-		xs <- EB.take n
-		extra <- EB.consume
-		return (xs, extra))
-	(\n -> Right . BL.splitAt (fromInteger n))
-
-suite_Require :: Suite
-suite_Require = testListAnalogueN "require"
-	(\n -> do
-		EL.require n
-		EL.consume)
-	(\n xs -> if n > toInteger (length xs)
-		then Left (Exc.ErrorCall "require: Unexpected EOF")
-		else Right xs)
-	(\n -> do
-		ET.require n
-		ET.consume)
-	(\n xs -> if n > toInteger (TL.length xs)
-		then Left (Exc.ErrorCall "require: Unexpected EOF")
-		else Right xs)
-	(\n -> do
-		EB.require n
-		EB.consume)
-	(\n xs -> if n > toInteger (BL.length xs)
-		then Left (Exc.ErrorCall "require: Unexpected EOF")
-		else Right xs)
-
-suite_Isolate :: Suite
-suite_Isolate = testListAnalogue "isolate"
-	(do
-		x <- E.joinI (EL.isolate 2 $$ EL.head)
-		extra <- EL.consume
-		return (x, extra))
-	(\xs -> Right $ case xs of
-		[] -> (Nothing, [])
-		(x:[]) -> (Just x, [])
-		(x:_:xs') -> (Just x, xs'))
-	(do
-		x <- E.joinI (ET.isolate 2 $$ ET.head)
-		extra <- ET.consume
-		return (x, extra))
-	(\text -> Right $ case TL.unpack text of
-		[] -> (Nothing, TL.empty)
-		(x:[]) -> (Just x, TL.empty)
-		(x:_:xs') -> (Just x, TL.pack xs'))
-	(do
-		x <- E.joinI (EB.isolate 2 $$ EB.head)
-		extra <- EB.consume
-		return (x, extra))
-	(\bytes -> Right $ case BL.unpack bytes of
-		[] -> (Nothing, BL.empty)
-		(x:[]) -> (Just x, BL.empty)
-		(x:_:xs) -> (Just x, BL.pack xs))
-
-suite_SplitWhen :: Suite
-suite_SplitWhen = testListAnalogueX "splitWhen"
-	(\x -> do
-		xs <- E.joinI (EL.splitWhen (== x) $$ EL.consume)
-		extra <- EL.consume
-		return (xs, extra))
-	(\x xs -> let
-		split = LS.split . LS.dropFinalBlank . LS.dropDelims . LS.whenElt
-		in Right (split (== x) xs, []))
-	(\c -> do
-		xs <- E.joinI (ET.splitWhen (== c) $$ EL.consume)
-		extra <- EL.consume
-		return (xs, extra))
-	(\c text -> let
-		split = LS.split . LS.dropFinalBlank . LS.dropDelims . LS.whenElt
-		chars = TL.unpack text
-		in Right (map T.pack (split (== c) chars), []))
-	(\x -> do
-		xs <- E.joinI (EB.splitWhen (== x) $$ EL.consume)
-		extra <- EL.consume
-		return (xs, extra))
-	(\x bytes -> let
-		split = LS.split . LS.dropFinalBlank . LS.dropDelims . LS.whenElt
-		words = BL.unpack bytes
-		in Right (map B.pack (split (== x) words), []))
-
-suite_Map :: Suite
-suite_Map = test_Enumeratee "map" (EL.map id)
-
-suite_ConcatMap :: Suite
-suite_ConcatMap = test_Enumeratee "concatMap" (EL.concatMap (:[]))
-
-suite_MapM :: Suite
-suite_MapM = test_Enumeratee "mapM" (EL.mapM return)
-
-suite_ConcatMapM :: Suite
-suite_ConcatMapM = test_Enumeratee "concatMapM" (EL.concatMapM (\x -> return [x]))
-
-suite_MapAccum :: Suite
-suite_MapAccum = testListAnalogue "mapAccum"
-	(do
-		let enee = EL.mapAccum (\s ao -> (s+1, (s, ao))) 10
-		a <- E.joinI (enee $$ EL.head)
-		b <- EL.consume
-		return (a, b))
-	(\xs -> Right $ case xs of
-		[] -> (Nothing, [])
-		(x:xs') -> (Just (10, x), xs'))
-	(do
-		let enee = ET.mapAccum (\s ao -> (s+1, succ ao)) 10
-		a <- E.joinI (enee $$ EL.head)
-		b <- ET.consume
-		return (a, b))
-	(\text -> Right $ case TL.uncons text of
-		Nothing -> (Nothing, TL.empty)
-		Just (c, text') -> (Just (T.singleton (succ c)), text'))
-	(do
-		let enee = EB.mapAccum (\s ao -> (s+1, ao + s)) 10
-		a <- E.joinI (enee $$ EL.head)
-		b <- EB.consume
-		return (a, b))
-	(\bytes -> Right $ case BL.uncons bytes of
-		Nothing -> (Nothing, BL.empty)
-		Just (b, bytes') -> (Just (B.singleton (b + 10)), bytes'))
-
-
-suite_MapAccumM :: Suite
-suite_MapAccumM = testListAnalogue "mapAccumM"
-	(do
-		let enee = EL.mapAccumM (\s ao -> return (s+1, (s, ao))) 10
-		a <- E.joinI (enee $$ EL.head)
-		b <- EL.consume
-		return (a, b))
-	(\xs -> Right $ case xs of
-		[] -> (Nothing, [])
-		(x:xs') -> (Just (10, x), xs'))
-	(do
-		let enee = ET.mapAccumM (\s ao -> return (s+1, succ ao)) 10
-		a <- E.joinI (enee $$ EL.head)
-		b <- ET.consume
-		return (a, b))
-	(\text -> Right $ case TL.uncons text of
-		Nothing -> (Nothing, TL.empty)
-		Just (c, text') -> (Just (T.singleton (succ c)), text'))
-	(do
-		let enee = EB.mapAccumM (\s ao -> return (s+1, ao + s)) 10
-		a <- E.joinI (enee $$ EL.head)
-		b <- EB.consume
-		return (a, b))
-	(\bytes -> Right $ case BL.uncons bytes of
-		Nothing -> (Nothing, BL.empty)
-		Just (b, bytes') -> (Just (B.singleton (b + 10)), bytes'))
-
-suite_Filter :: Suite
-suite_Filter = test_Enumeratee "filter" (EL.filter (\_ -> True))
-
-suite_FilterM :: Suite
-suite_FilterM = test_Enumeratee "filterM" (EL.filterM (\_ -> return True))
-
--- }}}
-
--- Specific functions
-
-suite_Other :: Suite
-suite_Other = suite "other"
-	[ test_Sequence
-	, test_joinE
-	, suite "catchError"
-		[ test test_CatchError_WithoutContinue
-		, test test_CatchError_NotDivergent
-		, test test_CatchError_Interleaved
-		]
-	, test test_Zip
-	, test test_ZipBytes
-	, test test_ZipText
-	]
-
-test_Sequence :: Suite
-test_Sequence = property "sequence" prop where
-	prop :: Positive Integer -> [A] -> Bool
-	prop (Positive n) xs = result == expected where
-		result = runIdentity (E.run_ iter)
-		expected = map Just xs
-		
-		iter = E.enumList n xs $$ E.joinI (E.sequence EL.head $$ EL.consume)
-
-test_joinE :: Suite
-test_joinE = property "joinE" prop where
-	prop :: [Integer] -> Bool
-	prop xs = result == expected where
-		result = runIdentity (E.run_ iter)
-		expected = map (* 10) xs
-		
-		iter = (E.joinE (E.enumList 1 xs) (EL.map (* 10))) $$ EL.consume
-
-test_CatchError_WithoutContinue :: Test
-test_CatchError_WithoutContinue = assertions "without-continue" $ do
-	let iter = E.catchError
-	    	(E.throwError (Exc.ErrorCall "error"))
-	    	(\_ -> EL.require 1)
-	
-	res <- E.run (E.enumList 1 [] $$ iter)
-	$assert (left res)
-	
-	let Left err = res
-	$assert $ equal (Exc.fromException err) (Just (Exc.ErrorCall "require: Unexpected EOF"))
-
-test_CatchError_NotDivergent :: Test
-test_CatchError_NotDivergent = assertions "not-divergent" $ do
-	let iter = E.catchError
-	    	(do
-	    		EL.head
-	    		E.throwError (Exc.ErrorCall "error"))
-	    	(\_ -> EL.require 1)
-	
-	res <- E.run (E.enumList 1 [] $$ iter)
-	$assert (left res)
-	
-	let Left err = res
-	$assert $ equal (Exc.fromException err) (Just (Exc.ErrorCall "require: Unexpected EOF"))
-
-test_CatchError_Interleaved :: Test
-test_CatchError_Interleaved = within 1000 $ assertions "interleaved" $ do
-	let enumMVar mvar = EL.repeatM (liftIO (takeMVar mvar))
-	let iter mvar = do
-	    	liftIO (putMVar mvar ())
-	    	EL.head
-	    	return True
-	let onError err = return False
-	
-	mvar <- liftIO newEmptyMVar
-	E.run_ (enumMVar mvar $$ E.catchError (iter mvar) onError)
-
-test_Zip :: Test
-test_Zip = assertions "zip" $ do
-	let iterTup = do
-		Just x <- EL.head
-		Just y <- EL.head
-		return (x, y)
-	let iterTupFlip = do
-		Just x <- EL.head
-		Just y <- EL.head
-		return (y, x)
-	
-	let check i1 i2 = E.run_ (E.enumList 4 [1, 2, 3, 4, 5] $$ do
-		(x, y) <- EL.zip i1 i2
-		extra <- EL.consume
-		return (x, y, extra))
-	
-	-- Both sides have same behavior
-	(tup, tup2, extra) <- check iterTup iterTupFlip
-	$expect (equal tup (1, 2))
-	$expect (equal tup2 (2, 1))
-	$expect (equal extra [3, 4, 5])
-	
-	-- First side has more extra data
-	(took, tup, extra) <- check (EL.take 1) iterTup
-	$expect (equal took [1])
-	$expect (equal tup (1, 2))
-	$expect (equal extra [3, 4, 5])
-	
-	-- Second side has more extra data
-	(tup, took, extra) <- check iterTup (EL.take 1)
-	$expect (equal tup (1, 2))
-	$expect (equal took [1])
-	$expect (equal extra [3, 4, 5])
-
-test_ZipBytes :: Test
-test_ZipBytes = assertions "zip-bytes" $ do
-	let iterTup = do
-		Just x <- EB.head
-		Just y <- EB.head
-		return (x, y)
-	let iterTupFlip = do
-		Just x <- EB.head
-		Just y <- EB.head
-		return (y, x)
-	
-	let check i1 i2 = E.run_ (E.enumList 2 ["abc", "def", "ghi"] $$ do
-		(x, y) <- EB.zip i1 i2
-		extra <- EL.consume
-		return (x, y, extra))
-	
-	-- Both sides have same behavior
-	(tup, tup2, extra) <- check iterTup iterTupFlip
-	$expect (equal tup (0x61, 0x62))
-	$expect (equal tup2 (0x62, 0x61))
-	$expect (equal extra ["c", "def", "ghi"])
-	
-	-- First side has more extra data
-	(took, tup, extra) <- check (EB.take 1) iterTup
-	$expect (equal took "a")
-	$expect (equal tup (0x61, 0x62))
-	$expect (equal extra ["c", "def", "ghi"])
-	
-	-- Second side has more extra data
-	(tup, took, extra) <- check iterTup (EB.take 1)
-	$expect (equal tup (0x61, 0x62))
-	$expect (equal took "a")
-	$expect (equal extra ["c", "def", "ghi"])
-
-test_ZipText :: Test
-test_ZipText = assertions "zip-text" $ do
-	let iterTup = do
-		Just x <- ET.head
-		Just y <- ET.head
-		return (x, y)
-	let iterTupFlip = do
-		Just x <- ET.head
-		Just y <- ET.head
-		return (y, x)
-	
-	let check i1 i2 = E.run_ (E.enumList 2 ["abc", "def", "ghi"] $$ do
-		(x, y) <- ET.zip i1 i2
-		extra <- EL.consume
-		return (x, y, extra))
-	
-	-- Both sides have same behavior
-	(tup, tup2, extra) <- check iterTup iterTupFlip
-	$expect (equal tup ('a', 'b'))
-	$expect (equal tup2 ('b', 'a'))
-	$expect (equal extra ["c", "def", "ghi"])
-	
-	-- First side has more extra data
-	(took, tup, extra) <- check (ET.take 1) iterTup
-	$expect (equal took "a")
-	$expect (equal tup ('a', 'b'))
-	$expect (equal extra ["c", "def", "ghi"])
-	
-	-- Second side has more extra data
-	(tup, took, extra) <- check iterTup (ET.take 1)
-	$expect (equal tup ('a', 'b'))
-	$expect (equal took "a")
-	$expect (equal extra ["c", "def", "ghi"])
-
--- misc
-
-genASCII :: IsString a => Gen a
-genASCII = fmap fromString string where
-	string = sized $ \n -> do
-		k <- choose (0,n)
-		sequence [ char | _ <- [1..k] ]
-	
-	char = chr `fmap` choose (0,0x7F)
-
-genISO8859 :: IsString a => Gen a
-genISO8859 = fmap fromString string where
-	string = sized $ \n -> do
-		k <- choose (0,n)
-		sequence [ char | _ <- [1..k] ]
-	
-	char = chr `fmap` choose (0,0xFF)
-
-genUnicode :: IsString a => Gen a
-genUnicode = fmap fromString string where
-	string = sized $ \n -> do
-		k <- choose (0,n)
-		sequence [ char | _ <- [1..k] ]
-	
-	excluding :: [a -> Bool] -> Gen a -> Gen a
-	excluding bad gen = loop where
-		loop = do
-			x <- gen
-			if or (map ($ x) bad)
-				then loop
-				else return x
-	
-	reserved = [lowSurrogate, highSurrogate, noncharacter]
-	lowSurrogate c = c >= 0xDC00 && c <= 0xDFFF
-	highSurrogate c = c >= 0xD800 && c <= 0xDBFF
-	noncharacter c = masked == 0xFFFE || masked == 0xFFFF where
-		masked = c .&. 0xFFFF
-	
-	ascii = choose (0,0x7F)
-	plane0 = choose (0xF0, 0xFFFF)
-	plane1 = oneof [ choose (0x10000, 0x10FFF)
-	               , choose (0x11000, 0x11FFF)
-	               , choose (0x12000, 0x12FFF)
-	               , choose (0x13000, 0x13FFF)
-	               , choose (0x1D000, 0x1DFFF)
-	               , choose (0x1F000, 0x1FFFF)
-	               ]
-	plane2 = oneof [ choose (0x20000, 0x20FFF)
-	               , choose (0x21000, 0x21FFF)
-	               , choose (0x22000, 0x22FFF)
-	               , choose (0x23000, 0x23FFF)
-	               , choose (0x24000, 0x24FFF)
-	               , choose (0x25000, 0x25FFF)
-	               , choose (0x26000, 0x26FFF)
-	               , choose (0x27000, 0x27FFF)
-	               , choose (0x28000, 0x28FFF)
-	               , choose (0x29000, 0x29FFF)
-	               , choose (0x2A000, 0x2AFFF)
-	               , choose (0x2B000, 0x2BFFF)
-	               , choose (0x2F000, 0x2FFFF)
-	               ]
-	plane14 = choose (0xE0000, 0xE0FFF)
-	planes = [ascii, plane0, plane1, plane2, plane14]
-	
-	char = chr `fmap` excluding reserved (oneof planes)
-
-instance Arbitrary a => Arbitrary (E.Stream a) where
-	arbitrary = frequency
-		[ (10, return E.EOF)
-		, (90, fmap E.Chunks arbitrary)
-		]
-
-instance Arbitrary T.Text where
-	arbitrary = genUnicode
-
-instance Arbitrary B.ByteString where
-	arbitrary = genUnicode
-
-instance Eq Exc.ErrorCall where
-	(Exc.ErrorCall s1) == (Exc.ErrorCall s2) = s1 == s2
-
--- | Require a test to complete within /n/ milliseconds.
-within :: Int -> Test -> Test
-within time (Test name io) = Test name $ \opts -> do
-	res <- timeout (time * 1000) (io opts)
-	case res of
-		Just res' -> return res'
-		Nothing -> return (TestAborted [] (T.pack ("Test timed out after " ++ show time ++ " milliseconds")))
diff --git a/tests/enumerator-tests.cabal b/tests/enumerator-tests.cabal
--- a/tests/enumerator-tests.cabal
+++ b/tests/enumerator-tests.cabal
@@ -3,16 +3,38 @@
 build-type: Simple
 cabal-version: >= 1.6
 
+flag coverage
+  default: False
+  manual: True
+
+flag test-io-functions
+  default: True
+
 executable enumerator_tests
-  main-is: Tests.hs
-  ghc-options: -Wall -O2
+  main-is: EnumeratorTests.hs
+  ghc-options: -Wall
+  hs-source-dirs: ../lib,.
 
+  if flag(coverage)
+    ghc-options: -fhpc
+
+  if flag(test-io-functions)
+    build-depends:
+        base >= 4.2 && < 5.0
+      , knob >= 0.1 && < 0.2
+
+    if !os(windows)
+      build-depends:
+            silently
+  else
+    build-depends:
+        base >= 4.0 && < 5.0
+
   build-depends:
-      base > 3 && < 5
-    , bytestring
-    , chell >= 0.1 && < 0.2
+      bytestring
+    , chell >= 0.2 && < 0.3
     , chell-quickcheck >= 0.1 && < 0.2
-    , enumerator
+    , containers
     , QuickCheck
     , split
     , text
