diff --git a/semver-range.cabal b/semver-range.cabal
--- a/semver-range.cabal
+++ b/semver-range.cabal
@@ -1,5 +1,5 @@
 name:                semver-range
-version:             0.2.2
+version:             0.2.7
 synopsis:            An implementation of semver and semantic version ranges.
 license:             MIT
 license-file:        LICENSE
diff --git a/src/Data/SemVer/Parser.hs b/src/Data/SemVer/Parser.hs
--- a/src/Data/SemVer/Parser.hs
+++ b/src/Data/SemVer/Parser.hs
@@ -3,10 +3,10 @@
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedLists #-}
 
-module Data.SemVer.Parser where -- (
-  --   parseSemVer, parseSemVerRange, pSemVerRange, pSemVer, p
-  --   fromHaskellVersion, matchText
-  -- ) where
+module Data.SemVer.Parser (
+    parseSemVer, parseSemVerRange, pSemVerRange, pSemVer,
+    fromHaskellVersion, matchText, splitWS
+  ) where
 
 import qualified Prelude as P
 import ClassyPrelude hiding (try, many)
@@ -20,22 +20,26 @@
 
 type Parser = ParsecT String () Identity
 
+-- | Split a text on whitespace. Why isn't this in the stdlib.
+splitWS :: Text -> [Text]
+splitWS = filter (/= "") . T.split (flip elem (" \t\n\r" :: String))
+
 -------------------------------------------------------------------------------
 -- Wildcards: intermediate representations of semvers
 --
 -- | A partially specified semantic version. Implicitly defines
 -- a range of acceptable versions, as seen in @wildcardToRange@.
 data Wildcard = Any
-              | One Int
-              | Two Int Int
+              | Maj Int
+              | Min Int Int
               | Full SemVer
               deriving (Show, Eq)
 
 -- | Fills in zeros in a wildcard.
 wildcardToSemver :: Wildcard -> SemVer
 wildcardToSemver Any = semver 0 0 0
-wildcardToSemver (One n) = semver n 0 0
-wildcardToSemver (Two n m) = semver n m 0
+wildcardToSemver (Maj n) = semver n 0 0
+wildcardToSemver (Min n m) = semver n m 0
 wildcardToSemver (Full sv) = sv
 
 -- | Translates a wildcard (partially specified version) to a range.
@@ -44,8 +48,8 @@
 wildcardToRange :: Wildcard -> SemVerRange
 wildcardToRange = \case
   Any -> Geq $ semver 0 0 0
-  One n -> Geq (semver n 0 0) `And` Lt (semver (n+1) 0 0)
-  Two n m -> Geq (semver n m 0) `And` Lt (semver n (m+1) 0)
+  Maj n -> Geq (semver n 0 0) `And` Lt (semver (n+1) 0 0)
+  Min n m -> Geq (semver n m 0) `And` Lt (semver n (m+1) 0)
   Full sv -> Eq sv
 
 -- | Translates a ~wildcard to a range.
@@ -55,9 +59,9 @@
   -- I'm not sure this is officially supported, but just in case...
   Any -> tildeToRange (Full $ semver 0 0 0)
   -- ~1 := >=1.0.0 <(1+1).0.0 := >=1.0.0 <2.0.0 (Same as 1.x)
-  One n -> Geq (semver n 0 0) `And` Lt (semver (n+1) 0 0)
+  Maj n -> Geq (semver n 0 0) `And` Lt (semver (n+1) 0 0)
   -- ~1.2 := >=1.2.0 <1.(2+1).0 := >=1.2.0 <1.3.0 (Same as 1.2.x)
-  Two n m -> Geq (semver n m 0) `And` Lt (semver n (m+1) 0)
+  Min n m -> Geq (semver n m 0) `And` Lt (semver n (m+1) 0)
   -- ~1.2.3 := >=1.2.3 <1.(2+1).0 := >=1.2.3 <1.3.0
   Full (SemVer n m o [] _) -> Geq (semver n m o) `And` Lt (semver n (m+1) 0)
   -- ~1.2.3-beta.2 := >=1.2.3-beta.2 <1.3.0
@@ -67,8 +71,8 @@
 -- Ex: ^1.2.x := >=1.2.0 <2.0.0
 caratToRange :: Wildcard -> SemVerRange
 caratToRange = \case
-  One n -> Geq (semver n 0 0) `And` Lt (semver (n+1) 0 0)
-  Two n m -> Geq (semver n m 0) `And` Lt (semver (n+1) 0 0)
+  Maj n -> Geq (semver n 0 0) `And` Lt (semver (n+1) 0 0)
+  Min n m -> Geq (semver n m 0) `And` Lt (semver (n+1) 0 0)
   Full (SemVer 0 n m tags _) -> Geq (semver' 0 n m tags) `And` Lt (semver' 0 (n+1) 0 tags)
   Full (SemVer n m o tags _) -> Geq (semver' n m o tags) `And` Lt (semver' (n+1) 0 0 tags)
 
@@ -79,12 +83,12 @@
 hyphenatedRange :: Wildcard -> Wildcard -> SemVerRange
 hyphenatedRange wc1 wc2 = And sv1 sv2 where
   sv1 = case wc1 of Any -> anyVersion
-                    One n -> Geq (semver n 0 0)
-                    Two n m -> Geq (semver n m 0)
+                    Maj n -> Geq (semver n 0 0)
+                    Min n m -> Geq (semver n m 0)
                     Full sv -> Geq sv
   sv2 = case wc2 of Any -> anyVersion
-                    One n -> Lt (semver (n+1) 0 0)
-                    Two n m -> Lt (semver n (m+1) 0)
+                    Maj n -> Lt (semver (n+1) 0 0)
+                    Min n m -> Lt (semver n (m+1) 0)
                     Full sv -> Lt sv
 
 -- | Given a parser and a string, attempts to parse the string.
@@ -96,11 +100,11 @@
 
 -- | Consumes any spaces (not other whitespace).
 spaces :: Parser String
-spaces = many $ oneOf [' ', '\t']
+spaces = many $ oneOf [' ', '\t', '\n', '\r']
 
 -- | Consumes at least one space (not other whitespace).
 spaces1 :: Parser String
-spaces1 = many1 $ oneOf [' ', '\t']
+spaces1 = many1 $ oneOf [' ', '\t', '\n', '\r']
 
 -- | Parses the given string and any trailing spaces.
 sstring :: String -> Parser String
@@ -128,15 +132,17 @@
   -- Handle a few special cases
   "" -> return anyVersion
   "||" -> return anyVersion
-  t -> parse pSemVerRange t
+  t -> parse (pSemVerRange <* eof) t
 
 -- | Parse a string as an explicit version, or return an error.
 parseSemVer :: Text -> Either ParseError SemVer
-parseSemVer = parse pSemVer
+parseSemVer = parse pSemVer . T.strip
 
 -- | Parses a semantic version.
 pSemVer :: Parser SemVer
-pSemVer = wildcardToSemver <$> pWildCard
+pSemVer = do
+  optional (char '=')
+  wildcardToSemver <$> pWildCard
 
 pVersionComp :: Parser SemVerRange
 pVersionComp = cmp >>= \case
@@ -154,8 +160,8 @@
   where
     topOf = \case
       Any -> semver 0 0 0
-      One n -> semver (n+1) 0 0
-      Two n m -> semver n (m+1) 0
+      Maj n -> semver (n+1) 0 0
+      Min n m -> semver n (m+1) 0
       Full sv -> sv
 
 -- | Parses a comparison operator.
@@ -176,8 +182,10 @@
 pJoinedSemVerRange = do
   first <- pSemVerRangeSingle
   option first $ do
-    lookAhead (sstring "||" <|> cmp) >>= \case
+    let next = choice [sstring "||", sstring "&&", map singleton anyChar]
+    lookAhead next >>= \case
       "||" -> Or first <$> (sstring "||" *> pJoinedSemVerRange)
+      "&&" -> And first <$> (sstring "&&" *> pJoinedSemVerRange)
       _ -> And first <$> pJoinedSemVerRange
 
 -- | Parses a hyphenated range.
@@ -197,17 +205,18 @@
   optional (char 'v')
   res <- takeWhile isJust <$> sepBy1 bound (sstring ".") >>= \case
     [] -> return Any
-    [Just n] -> return $ One n
-    [Just n, Just m] -> return $ Two n m
+    [Just n] -> return $ Maj n
+    [Just n, Just m] -> return $ Min n m
     [Just n, Just m, Just o] -> option (Full $ semver n m o) $ do
-      -- Release tags might be separated by a hyphen, or not.
-      optional (char '-')
-      tags <- PrereleaseTags <$> (tag `sepBy1` char '.')
+      tags <- option [] $ do
+        -- Release tags might be separated by a hyphen, or not.
+        optional (char '-')
+        PrereleaseTags <$> (tag `sepBy1` char '.')
       -- Grab metadata if there is any
-      option (Full $ semver'' n m o tags []) $ do
+      metadata <- option [] $ do
         char '+'
-        metadata <- many1 (letter <|> digit <|> char '-') `sepBy1` char '.'
-        return $ Full $ semver'' n m o tags (map pack metadata)
+        many1 (letter <|> digit <|> char '-') `sepBy1` char '.'
+      return $ Full $ semver'' n m o tags (map pack metadata)
     w -> unexpected ("Invalid version " ++ show w)
   spaces *> return res
 
diff --git a/src/Data/SemVer/Types.hs b/src/Data/SemVer/Types.hs
--- a/src/Data/SemVer/Types.hs
+++ b/src/Data/SemVer/Types.hs
@@ -150,6 +150,17 @@
   And svr1 svr2 -> versionsOf svr1 <> versionsOf svr2
   Or svr1 svr2 -> versionsOf svr1 <> versionsOf svr2
 
+-- | Strip out all prerelease tags from a given 'SemVerRange'.
+stripRangeTags :: SemVerRange -> SemVerRange
+stripRangeTags = \case
+  Eq  sv -> Eq  (sv { svTags = [] })
+  Geq sv -> Geq (sv { svTags = [] })
+  Leq sv -> Leq (sv { svTags = [] })
+  Lt  sv -> Lt  (sv { svTags = [] })
+  Gt  sv -> Gt  (sv { svTags = [] })
+  And svr1 svr2 -> And (stripRangeTags svr1) (stripRangeTags svr2)
+  Or  svr1 svr2 -> Or  (stripRangeTags svr1) (stripRangeTags svr2)
+
 -- | Create a SemVer with no version tags.
 semver :: Int -> Int -> Int -> SemVer
 semver major minor patch = semver' major minor patch []
@@ -195,22 +206,77 @@
 -- | Returns whether a given semantic version matches a range.
 -- Note that there are special cases when there are prerelease tags. For
 -- details see https://github.com/npm/node-semver#prerelease-tags.
--- matches :: SemVerRange -> SemVer -> Bool
--- matches range version = case (sharedTags range, svTags version) of
---   -- This is the simple case, where neither the range nor the version has given
---   -- prerelease tags. Then we can just do regular predicate calculus.
---   (Nothing, PrereleaseTags []) -> matchesSimple range version
---   _ -> undefined
---   -- (Just rTags, PrereleaseTags vTags)
---   --   | rTags == vTags -> matchesSimple range version
---   --   | tuplesOf range /= [toTuple version] -> False
---   --   | otherwise -> matchesTags range rTags vTags
---   -- (_, _) -> False
+matches :: SemVerRange -> SemVer -> Bool
+matches range version =
+  case (sharedTags range, svTags version) of
 
+    (Nothing, PrereleaseTags vTags)
+      -- Neither the range nor the version have prerelease tags
+      | null vTags -> matchesSimple range version
+
+      -- If there is no prerelease tag in the range but there is in
+      -- the version reject it
+      | otherwise  -> False
+
+    -- A range with a prerelease tag can match a version without a
+    -- prerelease tag provided it *does* meet the semantic version
+    -- tuple's constraint criteria
+    (Just _, PrereleaseTags []) ->
+      matchesSimple range version
+
+    -- The most important invariant when considering a comparison
+    -- between a range with prerelease tags and a version with
+    -- prerelease tags is whether the semantic version in both is the
+    -- same; if it is not, then we must reject the version.
+    --
+    -- Note that we could have a conjunction or a disjunction, so we
+    -- want to see if our version tuple is in the list of tuples for
+    -- the range. However, it would be possible to then match with,
+    -- say, the upper-bound version tuple which may be constrained by
+    -- a less-than relation. Therefore, if there is an equivalent
+    -- range tuple to the version tuple, we want to check if it
+    -- satisfies the constraints with the goal of rejecting early.
+    --
+    -- For example, if we assume a range constraint of "^1.2.3-alpha"
+    -- this translates to ">=1.2.3-alpha <2.0.0-alpha". Also assume we
+    -- have the version "1.2.3-alpha". In the trivial case, we check
+    -- to see if the version's tuple ("1.2.3") is in the set of
+    -- version tuples for the range ([ (1.2.3), (2.0.0) ]). We can
+    -- clearly see that it is, therefore we proceed with a match check
+    -- on the tags.
+    --
+    -- However, consider matching "2.0.0-alpha" against the range
+    -- constraint we've already given. If we only check for membership
+    -- of our version tuple ("2.0.0") in the set of range tuples ([
+    -- (1.2.3), (2.0.0) ]) then we would get a match, this is not
+    -- correct. Thus, if the version tuple is a member of the set of
+    -- range tuples we must also check that it satisfies the range
+    -- constraints sans prerelease tags.
+    (Just rTags, vTags)
+
+      -- Explicit rejection, e.g. "^1.2.3-alpha" must reject
+      -- "1.2.4-alpha" and "2.0.0-alpha", anything else is safe to
+      -- compare based on tags so we can let it "fall through".
+      | versionTuple `notElem` rangeTuple || not (matchesSimple rangeNoTags versionNoTags)
+        -> False
+
+      | rTags == vTags
+        -> True
+
+      | rTags /= vTags
+        -> matchesTags range rTags vTags
+
+  where
+    rangeTuple   = tuplesOf range
+    versionTuple = toTuple version
+
+    rangeNoTags   = stripRangeTags range
+    versionNoTags = version { svTags = [] }
+
 -- | Simple predicate calculus matching, doing AND and OR combination with
 -- numerical comparison.
-matches :: SemVerRange -> SemVer -> Bool
-matches range ver = case range of
+matchesSimple :: SemVerRange -> SemVer -> Bool
+matchesSimple range ver = case range of
   Eq sv -> ver == sv
   Gt sv -> ver > sv
   Lt sv -> ver < sv
@@ -223,18 +289,19 @@
 
 -- | Given a range and two sets of tags, the first being a bound on the second,
 -- uses the range to compare the tags and see if they match.
-matchesTags :: SemVerRange -> [PrereleaseTag] -> [PrereleaseTag] -> Bool
-matchesTags range rangeTags verTags = case range of
-  Eq _ -> verTags == rangeTags
-  Gt _ -> verTags > rangeTags
-  Lt _ -> verTags < rangeTags
-  Geq _ -> verTags >= rangeTags
-  Leq _ -> verTags <= rangeTags
-  -- Note that as we're currently doing things, these cases won't get hit.
-  And svr1 svr2 -> matchesTags svr1 verTags rangeTags &&
-                     matchesTags svr2 verTags rangeTags
-  Or svr1 svr2 -> matchesTags svr1 verTags rangeTags ||
-                     matchesTags svr2 verTags rangeTags
+matchesTags :: SemVerRange -> PrereleaseTags -> PrereleaseTags -> Bool
+matchesTags range rangeTags verTags =
+  case range of
+    Eq _  -> verTags == rangeTags
+    Gt _  -> verTags >  rangeTags
+    Lt _  -> verTags <  rangeTags
+    Geq _ -> verTags >= rangeTags
+    Leq _ -> verTags <= rangeTags
+
+    And svr1 svr2 ->
+      matchesTags svr1 rangeTags verTags
+    Or svr1 svr2 ->
+      matchesTags svr1 rangeTags verTags || matchesTags svr2 rangeTags verTags
 
 -- | Gets the highest-matching semver in a range.
 bestMatch :: SemVerRange -> [SemVer] -> Either String SemVer
diff --git a/tests/Unit.hs b/tests/Unit.hs
--- a/tests/Unit.hs
+++ b/tests/Unit.hs
@@ -12,23 +12,13 @@
 module Main (main) where
 
 import ClassyPrelude
-import Data.Either
+import Data.Either (isRight, isLeft)
 import Test.Hspec
 import Test.QuickCheck (property, Arbitrary(..), oneof)
 import qualified Data.Text as T
 
 import Data.SemVer
 
--- | This instance seems to be missing :(
-instance (Arbitrary a, Arbitrary b, Arbitrary c,
-          Arbitrary d, Arbitrary e, Arbitrary f)
-         => Arbitrary (a, b, c, d, e, f) where
-  arbitrary = (,,,,,) <$> arbitrary
-                      <*> arbitrary
-                      <*> arbitrary
-                      <*> arbitrary
-                      <*> arbitrary
-                      <*> arbitrary
 -- | Arbitrary semver
 instance Arbitrary SemVer where
   arbitrary = semver' <$> arb <*> arb <*> arb <*> arbitrary where
@@ -73,6 +63,9 @@
 
 infixl 1 `shouldBeR`
 
+shouldBeL :: (Show a, Show b, Eq a) => Either a b -> IO ()
+shouldBeL x = shouldSatisfy x isLeft
+
 main :: IO ()
 main = hspec $ do
   describe "semver parsing" $ do
@@ -138,124 +131,191 @@
         `shouldBeR` Geq (semver'' 1 2 3 ["pre"] ["asdf"])
                      `And` Lt (semver'' 2 4 3 ["pre"] ["asdf"])
 
-  it "poo" $ do
-    let svNoTags = semver 1 2 3
-        svTags = semver' 1 2 3 ["pre"]
-        svTags' = semver' 2 4 3 ["pre"]
-    svNoTags >= svTags `shouldBe` True
-    let range = Geq svTags `And` Lt svTags'
-    range `matches` svNoTags `shouldBe` True
+    it "should parse semvers with && instead of spaces" $ do
+      let expected = Geq (semver 2 0 0) `And` Leq (semver 2 15 0)
+      parseSemVerRange ">= 2 && <= 2.14" `shouldBeR` expected
+
+    it "should fail when it's wrong" $ do
+      shouldBeL (parseSemVerRange "xyz")
+
   rangeTests
+  cleanTests
 
+-- | These test cases were adapted from
+-- https://github.com/npm/node-semver/blob/master/test/clean.js
+cleanTests :: Spec
+cleanTests = describe "unclean version strings" $ do
+  let examples :: [(Text, Maybe Text)] = [
+        ("1.2.3", Just "1.2.3"),
+        (" 1.2.3 ", Just "1.2.3"),
+        (" 1.2.3-4 ", Just "1.2.3-4"),
+        (" 1.2.3-pre ", Just "1.2.3-pre"),
+        ("  =v1.2.3   ", Just "1.2.3"),
+        ("v1.2.3", Just "1.2.3"),
+        (" v1.2.3 ", Just "1.2.3"),
+        ("\t1.2.3", Just "1.2.3"),
+        (">1.2.3", Nothing),
+        ("~1.2.3", Nothing),
+        ("<=1.2.3", Nothing)
+        -- The example below is given in the tests but this doesn't
+        -- seem like an error to me, so there.
+        -- ("1.2.x", Nothing)
+        ]
+  forM_ examples $ \(string, result) -> case result of
+    Just string' -> do
+      it ("should parse " <> show string <> " same as " <> show string') $ do
+        parseSemVer string `shouldSatisfy` isRight
+        parseSemVer string `shouldBe` parseSemVer string'
 
+    Nothing -> do
+      it ("should not parse " <> show string) $ do
+        parseSemVer string `shouldSatisfy` isLeft
+
 -- | These test cases were adapted from
--- https://github.com/npm/node-semver/blob/
---   d21444a0658224b152ce54965d02dbe0856afb84/test/index.js#L134
+-- https://github.com/npm/node-semver/blob/master/test/index.js#L134
 rangeTests :: Spec
 rangeTests = describe "range tests" $ do
   -- In each case, the range described in the first element of the
   -- tuple should be satisfied by the concrete version described in
   -- the second element of the tuple.
-  let testCases :: [(Text, Text)] = [
-        ("1.0.0 - 2.0.0", "1.2.3"),
-        ("^1.2.3+build", "1.2.3"),
-        ("^1.2.3+build", "1.3.0"),
-        ("1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3"),
-        ("1.2.3-pre+asdf - 2.4.3pre+asdf", "1.2.3"),
-        ("1.2.3pre+asdf - 2.4.3pre+asdf", "1.2.3"),
-        ("1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3-pre.2"),
-        ("1.2.3-pre+asdf - 2.4.3-pre+asdf", "2.4.3-alpha"),
-        ("1.2.3+asdf - 2.4.3+asdf", "1.2.3"),
-        ("1.0.0", "1.0.0"),
-        (">=*", "0.2.4"),
-        ("", "1.0.0"),
-        ("*", "1.2.3"),
-        ("*", "v1.2.3"),
-        (">=1.0.0", "1.0.0"),
-        (">=1.0.0", "1.0.1"),
-        (">=1.0.0", "1.1.0"),
-        (">1.0.0", "1.0.1"),
-        (">1.0.0", "1.1.0"),
-        ("<=2.0.0", "2.0.0"),
-        ("<=2.0.0", "1.9999.9999"),
-        ("<=2.0.0", "0.2.9"),
-        ("<2.0.0", "1.9999.9999"),
-        ("<2.0.0", "0.2.9"),
-        (">= 1.0.0", "1.0.0"),
-        (">=  1.0.0", "1.0.1"),
-        (">=   1.0.0", "1.1.0"),
-        ("> 1.0.0", "1.0.1"),
-        (">  1.0.0", "1.1.0"),
-        ("<=   2.0.0", "2.0.0"),
-        ("<= 2.0.0", "1.9999.9999"),
-        ("<=  2.0.0", "0.2.9"),
-        ("<    2.0.0", "1.9999.9999"),
-        ("<\t2.0.0", "0.2.9"),
-        (">=0.1.97", "v0.1.97"),
-        (">=0.1.97", "0.1.97"),
-        ("0.1.20 || 1.2.4", "1.2.4"),
-        (">=0.2.3 || <0.0.1", "0.0.0"),
-        (">=0.2.3 || <0.0.1", "0.2.3"),
-        (">=0.2.3 || <0.0.1", "0.2.4"),
-        ("||", "1.3.4"),
-        ("2.x.x", "2.1.3"),
-        ("1.2.x", "1.2.3"),
-        ("1.2.x || 2.x", "2.1.3"),
-        ("1.2.x || 2.x", "1.2.3"),
-        ("x", "1.2.3"),
-        ("2.*.*", "2.1.3"),
-        ("1.2.*", "1.2.3"),
-        ("1.2.* || 2.*", "2.1.3"),
-        ("1.2.* || 2.*", "1.2.3"),
-        ("*", "1.2.3"),
-        ("2", "2.1.2"),
-        ("2.3", "2.3.1"),
-        ("~2.4", "2.4.0"),
-        ("~2.4", "2.4.5"),
-        ("~>3.2.1", "3.2.2"),
-        ("~1", "1.2.3"),
-        ("~>1", "1.2.3"),
-        ("~> 1", "1.2.3"),
-        ("~1.0", "1.0.2"),
-        ("~ 1.0", "1.0.2"),
-        ("~ 1.0.3", "1.0.12"),
-        (">=1", "1.0.0"),
-        (">= 1", "1.0.0"),
-        ("<1.2", "1.1.1"),
-        ("< 1.2", "1.1.1"),
-        ("~v0.5.4-pre", "0.5.5"),
-        ("~v0.5.4-pre", "0.5.4"),
-        ("=0.7.x", "0.7.2"),
-        ("<=0.7.x", "0.7.2"),
-        (">=0.7.x", "0.7.2"),
-        ("<=0.7.x", "0.6.2"),
-        ("~1.2.1 >=1.2.3", "1.2.3"),
-        ("~1.2.1 =1.2.3", "1.2.3"),
-        ("~1.2.1 1.2.3", "1.2.3"),
-        ("~1.2.1 >=1.2.3 1.2.3", "1.2.3"),
-        ("~1.2.1 1.2.3 >=1.2.3", "1.2.3"),
-        ("~1.2.1 1.2.3", "1.2.3"),
-        (">=1.2.1 1.2.3", "1.2.3"),
-        ("1.2.3 >=1.2.1", "1.2.3"),
-        (">=1.2.3 >=1.2.1", "1.2.3"),
-        (">=1.2.1 >=1.2.3", "1.2.3"),
-        (">=1.2", "1.2.8"),
-        ("^1.2.3", "1.8.1"),
-        ("^0.1.2", "0.1.2"),
-        ("^0.1", "0.1.2"),
-        ("^1.2", "1.4.2"),
-        ("^1.2 ^1", "1.4.2"),
-        ("^1.2.3-alpha", "1.2.3-pre"),
-        ("^1.2.0-alpha", "1.2.0-pre"),
-        ("^0.0.1-alpha", "0.0.1-beta")
+  let testCases :: [(Bool, Text, Text)] = [
+
+        -- Range constraints with pre-release tags require that any
+        -- version satisfying the constraint must be equivalent (in
+        -- its semantic version tuple) to the minimum of all semantic
+        -- versions within the range. In this case the minimum of the
+        -- range is "1.2.3" and the version's semantic version tuple
+        -- is "1.2.4", therefore it does not satisfy the constraints
+        -- of the range given the presence of pre-release tags.
+        (False, "1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.4-pre+asdf"),
+        (False, "1.2.3-pre+asdf - 2.4.3-pre+asdf", "2.4.3-alpha"),
+        (False, ">=0.0.1-alpha <0.2.0-alpha", "0.1.1-alpha"),
+        (False, "^0.0.1-alpha", "0.0.4-alpha"),
+
+        -- Range constraints without prerelease tags are very strict
+        -- about not admitting versions *with* prerelease tags
+        (False, "^0.1.2", "0.1.2-beta1"),
+        (False, "^0.1.2", "0.1.4-beta1"),
+
+        -- Despite the numeric quantity, these versions have
+        -- prerelease tags and are therefore subjected to the same
+        -- invariant checking.
+        (False, "^1.2.3-1", "1.8.1-1"),
+        (False, "^1.2.3-1", "1.8.1-4"),
+
+        -- If we ever have an exact version tuple match at the top of
+        -- a given range then it must satisfy the range constraint!
+        --
+        -- e.g. "^1.2.3-alpha" translates to ">=1.2.3-alpha
+        -- <2.0.0-alpha" and the version to check is "2.0.0-alpha". In
+        -- this case the version tuples are equivalent, sans
+        -- prerelease tags, but it does not satisfy the upper-bound
+        -- less-than relation.
+        (False, "^1.2.3-alpha", "2.0.0-alpha"),
+
+        (True,  "1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3-pre+asdf"),
+        (True, "", "1.0.0"),
+        (True, "*", "1.2.3"),
+        (True, "*", "1.2.3"),
+        (True, "*", "v1.2.3"),
+        (True, "0.1.20 || 1.2.4", "1.2.4"),
+        (True, "1.0.0 - 2.0.0", "1.2.3"),
+        (True, "1.0.0", "1.0.0"),
+        (True, "1.2.* || 2.*", "1.2.3"),
+        (True, "1.2.* || 2.*", "2.1.3"),
+        (True, "1.2.*", "1.2.3"),
+        (True, "1.2.3 - 2.4.3", "1.2.4"),
+        (True, "1.2.3 >=1.2.1", "1.2.3"),
+        (True, "1.2.3+asdf - 2.4.3+asdf", "1.2.3"),
+        (True, "1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3"),
+        (True, "1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3-pre.2"),
+        (True, "1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3-pred"),
+        (True, "1.2.3-pre+asdf - 2.4.3pre+asdf", "1.2.3"),
+        (True, "1.2.3pre+asdf - 2.4.3pre+asdf", "1.2.3"),
+        (True, "1.2.x || 2.x", "1.2.3"),
+        (True, "1.2.x || 2.x", "2.1.3"),
+        (True, "1.2.x", "1.2.3"),
+        (True, "2", "2.1.2"),
+        (True, "2.*.*", "2.1.3"),
+        (True, "2.3", "2.3.1"),
+        (True, "2.x.x", "2.1.3"),
+        (True, "<    2.0.0", "1.9999.9999"),
+        (True, "< 1.2", "1.1.1"),
+        (True, "<1.2", "1.1.1"),
+        (True, "<2.0.0", "0.2.9"),
+        (True, "<2.0.0", "1.9999.9999"),
+        (True, "<=   2.0.0", "2.0.0"),
+        (True, "<=  2.0.0", "0.2.9"),
+        (True, "<= 2.0.0", "1.9999.9999"),
+        (True, "<=0.7.x", "0.6.2"),
+        (True, "<=0.7.x", "0.7.2"),
+        (True, "<=2.0.0", "0.2.9"),
+        (True, "<=2.0.0", "1.9999.9999"),
+        (True, "<=2.0.0", "2.0.0"),
+        (True, "<\t2.0.0", "0.2.9"),
+        (True, "=0.7.x", "0.7.2"),
+        (True, ">  1.0.0", "1.1.0"),
+        (True, "> 1.0.0", "1.0.1"),
+        (True, ">1.0.0", "1.0.1"),
+        (True, ">1.0.0", "1.1.0"),
+        (True, ">=   1.0.0", "1.1.0"),
+        (True, ">=  1.0.0", "1.0.1"),
+        (True, ">= 1", "1.0.0"),
+        (True, ">= 1.0.0", "1.0.0"),
+        (True, ">= 4.0.0 <4.1.0-0", "4.0.1"),
+        (True, ">=*", "0.2.4"),
+        (True, ">=0.1.97", "0.1.97"),
+        (True, ">=0.1.97", "v0.1.97"),
+        (True, ">=0.2.3 || <0.0.1", "0.0.0"),
+        (True, ">=0.2.3 || <0.0.1", "0.2.3"),
+        (True, ">=0.2.3 || <0.0.1", "0.2.4"),
+        (True, ">=0.7.x", "0.7.2"),
+        (True, ">=1", "1.0.0"),
+        (True, ">=1.0.0", "1.0.0"),
+        (True, ">=1.0.0", "1.0.1"),
+        (True, ">=1.0.0", "1.1.0"),
+        (True, ">=1.2", "1.2.8"),
+        (True, ">=1.2.1 1.2.3", "1.2.3"),
+        (True, ">=1.2.1 >=1.2.3", "1.2.3"),
+        (True, ">=1.2.3 >=1.2.1", "1.2.3"),
+        (True, "^0.0.1-alpha", "0.0.1-beta"),
+        (True, "^0.0.1-alpha.1", "0.0.1-alpha.t"),
+        (True, "^0.0.1-alpha.1", "0.0.1-alpha.tdff.dddddddddd"),
+        (True, "^0.1", "0.1.2"),
+        (True, "^0.1.2", "0.1.2"),
+        (True, "^1.2 ^1", "1.4.2"),
+        (True, "^1.2", "1.4.2"),
+        (True, "^1.2.0-alpha", "1.2.0-pre"),
+        (True, "^1.2.3", "1.8.1"),
+        (True, "^1.2.3+build", "1.2.3"),
+        (True, "^1.2.3+build", "1.3.0"),
+        (True, "^1.2.3-alpha", "1.2.3-pre"),
+        (True, "^1.2.3-alpha.1", "1.2.3-alpha.7"),
+        (True, "^1.2.3-boop", "1.2.4"),
+        (True, "x", "1.2.3"),
+        (True, "||", "1.3.4"),
+        (True, "~ 1.0", "1.0.2"),
+        (True, "~ 1.0.3", "1.0.12"),
+        (True, "~1", "1.2.3"),
+        (True, "~1.0", "1.0.2"),
+        (True, "~1.2.1 1.2.3 >=1.2.3", "1.2.3"),
+        (True, "~1.2.1 1.2.3", "1.2.3"),
+        (True, "~1.2.1 1.2.3", "1.2.3"),
+        (True, "~1.2.1 =1.2.3", "1.2.3"),
+        (True, "~1.2.1 >=1.2.3 1.2.3", "1.2.3"),
+        (True, "~1.2.1 >=1.2.3", "1.2.3"),
+        (True, "~2.4", "2.4.0"),
+        (True, "~2.4", "2.4.5"),
+        (True, "~> 1", "1.2.3"),
+        (True, "~>1", "1.2.3"),
+        (True, "~v0.5.4-pre", "0.5.4"),
+        (True, "~v0.5.4-pre", "0.5.5"),
+        (True, "~>3.2.1", "3.2.2")
         ]
-  forM_ testCases $ \(range, version) -> do
+  forM_ testCases $ \(expectedMatchBool, range, version) -> do
     let fail_ = expectationFailure
     it (show version <> " satisfies range " <> show range) $ do
       case (parseSemVerRange range, parseSemVer version) of
         (Left err, _) -> fail $ "Semver range parse failed: " <> show err
         (_, Left err) -> fail $ "Semver parse failed: " <> show err
-        (Right range, Right version) -> case matches range version of
-          True -> True `shouldBe` True -- return ()
-          False -> fail $ "Version " <> show version <> " didn't match range "
-                       <> show range --`shouldBe` True
+        (Right range, Right version) -> matches range version `shouldBe` expectedMatchBool
