{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverlappingInstances #-} module Isotope.BaseSpec (spec) where import Isotope import Test.Hspec import Test.QuickCheck import Data.Char import Data.List import Data.Map (Map, fromList) spec :: Spec spec = do describe "ElementSymbol" $ do it "should not have more than two characters" $ ((<= 2) . length . show) <$> elementSymbolList `shouldSatisfy` and it "second character of a two character ElementSymbol should not be upper case" $ (\x -> length x /= 2 || (isLower . last) x) . show <$> elementSymbolList `shouldSatisfy` and describe "lookupElement" $ do it "should not contain duplicate elements" $ lookupElement <$> elementSymbolList `shouldSatisfy` allUnique describe "elementName" $ do it "should not be an empty string" $ elementName <$> elementSymbolList `shouldSatisfy` notElem "" it "should not start with a capital letter" $ isLower . head . elementName <$> elementSymbolList `shouldSatisfy` and it "element names should not be duplicated" $ elementName <$> elementSymbolList `shouldSatisfy` allUnique describe "atomicNumber" $ do it "should be between 1 and 92" $ (\x -> x >= 1 && x <= 92) . atomicNumber <$> elementSymbolList `shouldSatisfy` and it "should not have duplicated atomic numbers" $ atomicNumber <$> elementSymbolList `shouldSatisfy` allUnique describe "isotopes" $ do it "should not have duplicate isotopes" $ isotopes <$> elementSymbolList `shouldSatisfy` allUnique describe "mostAbundantIsotope" $ do it "C should be C12 (six protons and six neutrons)" $ (nucleons . mostAbundantIsotope) C `shouldBe` (6, 6) describe "selectIsotope" $ do it "calling fuction with the arguments C and 12 should select C12" $ nucleons (selectIsotope C 12) `shouldBe` (6, 6) describe "monoisotopicMass" $ do it "calling function with C should be 12.0" $ monoisotopicMass C `shouldBe` 12.0 describe "nominalMass" $ do it "calling function with C should return 12" $ nominalMass C `shouldBe` 12 describe "isotopicMasses" $ do it "calling function with H should return a list containing 1.007... and 2.014...." $ isotopicMasses H `shouldBe` [1.00782503223, 2.01410177812] describe "integerMasses" $ do it "calling function with H should return [1, 2]" $ integerMasses H `shouldBe` [1, 2] it "proton number should be equal to atomic number" $ all protonNumEqAtomicNum elementSymbolList describe "averageAtomicMass" $ do it "calling function with C should return 12.0107" $ withinTolerance (averageMass C) 12.0107 0.0001 `shouldBe` True describe "isotopicAbundances" $ do it "calling function with C should return [0.9893, 0.0107]" $ isotopicAbundances C `shouldBe` [0.9893, 0.0107] it "sum of isotopic abundances for an element should equal 1" $ (\sym -> withinTolerance (sumIsotopicAbundance sym) 1 0.0001) <$> elementSymbolList `shouldSatisfy` and describe "Monoid instance for MolecularFormula" $ do it "associativity" $ property $ \a b c -> (a |+| b) |+| c == a |+| (b |+| c) it "identity element" $ property $ \a -> a |+| emptyFormula == a describe "Addition of molecular formulae is commutative" $ do it "commutative" $ property $ \a b -> a |+| b == b |+| a describe "properties of |+|, |*| and |-|)" $ do it "a |-| a = emptyFormula" $ property $ \a -> a |-| a == emptyFormula it "0 |*| a == emptyFormula" $ property $ \a -> 0 |*| a == emptyFormula it "a |+| a == 2 |*| a" $ property $ \a -> a |+| a == 2 |*| a describe "toEmpiricalFormula" $ do it "Empty MolecularFormula should return an empty EmpiricalFormula" $ toEmpiricalFormula (emptyFormula :: MolecularFormula) `shouldBe` mkEmpiricalFormula [] it "\"C6H6\" should be \"CH\"" $ toEmpiricalFormula [mol|C6H6|] `shouldBe` [emp|CH|] describe "renderFormula" $ do it "\"C6H6O\" should be \"C6H6O\"" $ renderFormula [mol|C6H6O|] `shouldBe` "C6H6O" it "\"CCl4\" should be \"CCl4\"" $ renderFormula [mol|CCl4|] `shouldBe` "CCl4" it "\"H2O4S\" should be \"H2O4S\"" $ renderFormula [mol|H2O4S|] `shouldBe` "H2O4S" allUnique :: (Eq a) => [a] -> Bool allUnique l = l == nub l withinTolerance :: (Num a, Ord a) => a -> a -> a -> Bool withinTolerance n1 n2 err = abs (n1 - n2) < err protonNumEqAtomicNum :: ElementSymbol -> Bool protonNumEqAtomicNum sym = and $ (== atomicNumber sym) . fst . nucleons <$> isotopes sym sumIsotopicAbundance :: ElementSymbol -> IsotopicAbundance sumIsotopicAbundance = sum . isotopicAbundances instance Arbitrary ElementSymbol where arbitrary = oneof $ return <$> elementSymbolList instance Arbitrary (ElementSymbol, Int) where arbitrary = do elemSym <- arbitrary :: Gen ElementSymbol n <- choose (1,100) return (elemSym, n) instance Arbitrary MolecularFormula where arbitrary = do symNums <- arbitrary :: Gen [(ElementSymbol, Int)] return $ MolecularFormula . fromList $ symNums