{- mtlstats Copyright (C) 1984, 1985, 2019, 2020, 2021 Rhéal Lamothe This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . -} {-# LANGUAGE FlexibleInstances, OverloadedStrings, RankNTypes #-} module TypesSpec ( Comparable (..) , spec , makePlayer , makeGoalie , makePlayerStats , makeGoalieStats ) where import Control.Monad (replicateM) import Data.Aeson (FromJSON, ToJSON, decode, encode, toJSON) import Data.Aeson.Types (Value (Object)) import qualified Data.Map.Lazy as M import qualified Data.HashMap.Strict as HM import Data.Ratio ((%)) import Lens.Micro (Lens', (&), (^.), (.~), (?~)) import System.Random (randomIO, randomRIO) import Test.Hspec (Spec, context, describe, it, shouldBe) import Mtlstats.Config import Mtlstats.Types import qualified Types.MenuSpec as Menu class Comparable a where compareTest :: a -> a -> Spec spec :: Spec spec = describe "Mtlstats.Types" $ do playerSpec goalieSpec gameStatsSpec databaseSpec gameStateLSpec createPlayerStateLSpec createGoalieStateLSpec editPlayerStateLSpec editGoalieStateLSpec editStandingsModeLSpec esmSubModeLSpec teamScoreSpec otherScoreSpec homeTeamSpec awayTeamSpec gameWonSpec gameLostSpec gameTiedSpec unaccountedPointsSpec gmsGamesSpec gmsPointsSpec addGameStatsSpec playerSearchSpec activePlayerSearchSpec playerSearchExactSpec modifyPlayerSpec playerSummarySpec playerIsActiveSpec psPointsSpec addPlayerStatsSpec goalieSearchSpec activeGoalieSearchSpec goalieSearchExactSpec goalieSummarySpec goalieIsActiveSpec addGoalieStatsSpec gsAverageSpec Menu.spec playerSpec :: Spec playerSpec = describe "Player" $ jsonSpec player playerJSON goalieSpec :: Spec goalieSpec = describe "Goalie" $ jsonSpec goalie goalieJSON gameStatsSpec :: Spec gameStatsSpec = describe "GameStats" $ jsonSpec (gameStats 1) (gameStatsJSON 1) databaseSpec :: Spec databaseSpec = describe "Database" $ jsonSpec db dbJSON gameStateLSpec :: Spec gameStateLSpec = describe "gameStateL" $ lensSpec gameStateL -- getters [ ( "missing state", MainMenu, newGameState ) , ( "home game", NewGame $ gs HomeGame, gs HomeGame ) , ( "away game", NewGame $ gs AwayGame, gs AwayGame ) ] -- setters [ ( "set home", MainMenu, gs HomeGame ) , ( "home to away", NewGame $ gs HomeGame, gs AwayGame ) , ( "away to home", NewGame $ gs AwayGame, gs HomeGame ) , ( "clear home", NewGame $ gs HomeGame, newGameState ) , ( "clear away", NewGame $ gs AwayGame, newGameState ) ] where gs t = newGameState & gameType ?~ t createPlayerStateLSpec :: Spec createPlayerStateLSpec = describe "createPlayerStateL" $ lensSpec createPlayerStateL -- getters [ ( "missing state", MainMenu, newCreatePlayerState ) , ( "with state", CreatePlayer cps1, cps1 ) ] -- setters [ ( "missing state", MainMenu, cps1 ) , ( "change state", CreatePlayer cps1, cps2 ) , ( "clear state", CreatePlayer cps1, newCreatePlayerState ) ] where cps1 = newCreatePlayerState & cpsNumber ?~ 1 & cpsName .~ "Joe" & cpsPosition .~ "centre" cps2 = newCreatePlayerState & cpsNumber ?~ 2 & cpsName .~ "Bob" & cpsPosition .~ "defense" createGoalieStateLSpec :: Spec createGoalieStateLSpec = describe "createGoalieStateL" $ lensSpec createGoalieStateL -- getters [ ( "missing state", MainMenu, newCreateGoalieState ) , ( "with state", CreateGoalie cgs1, cgs1 ) ] -- setters [ ( "set state", MainMenu, cgs1 ) , ( "change state", CreateGoalie cgs1, cgs2 ) , ( "clear state", CreateGoalie cgs1, newCreateGoalieState ) ] where cgs1 = newCreateGoalieState & cgsNumber ?~ 1 & cgsName .~ "Joe" cgs2 = newCreateGoalieState & cgsNumber ?~ 2 & cgsName .~ "Bob" editPlayerStateLSpec :: Spec editPlayerStateLSpec = describe "editPlayerStateL" $ lensSpec editPlayerStateL -- getters [ ( "missing state", MainMenu, newEditPlayerState ) , ( "withState", EditPlayer eps1, eps1 ) ] -- setters [ ( "set state", MainMenu, eps1 ) , ( "change state", EditPlayer eps1, eps2 ) , ( "clear state", EditPlayer eps1, newEditPlayerState ) ] where eps1 = newEditPlayerState & epsSelectedPlayer ?~ 1 eps2 = newEditPlayerState & epsSelectedPlayer ?~ 2 editGoalieStateLSpec :: Spec editGoalieStateLSpec = describe "editGoalieStateL" $ lensSpec editGoalieStateL -- getters [ ( "missing state", MainMenu, newEditGoalieState ) , ( "with state", EditGoalie egs1, egs1 ) ] -- setters [ ( "set state", MainMenu, egs1 ) , ( "change state", EditGoalie egs1, egs2 ) , ( "clear state", EditGoalie egs1, newEditGoalieState ) ] where egs1 = newEditGoalieState & egsSelectedGoalie ?~ 1 egs2 = newEditGoalieState & egsSelectedGoalie ?~ 2 editStandingsModeLSpec :: Spec editStandingsModeLSpec = describe "editStandingsModeL" $ lensSpec editStandingsModeL -- getters [ ( "missing mode", MainMenu, menu ) , ( "with mode", EditStandings home, home ) ] -- setters [ ( "set mode", MainMenu, home ) , ( "change mode", EditStandings home, away ) ] where menu = ESMMenu home = ESMHome ESMSubMenu away = ESMAway ESMSubMenu esmSubModeLSpec :: Spec esmSubModeLSpec = describe "esmSubModeL" $ do context "getters" $ mapM_ (\(label, mode, expected) -> context label $ it ("should be " ++ show expected) $ mode^.esmSubModeL `shouldBe` expected) -- label, mode, expected [ ( "no state", ESMMenu, ESMSubMenu ) , ( "with state", ESMHome ESMEditWins, ESMEditWins ) ] context "setters" $ mapM_ (\(label, mode, expected) -> context label $ it ("should be " ++ show expected) $ let mode' = mode & esmSubModeL .~ ESMEditWins in mode' `shouldBe` expected) -- label, mode, expected [ ( "no state", ESMMenu, ESMMenu ) , ( "home mode", ESMHome ESMSubMenu, ESMHome ESMEditWins ) , ( "away mode", ESMAway ESMSubMenu, ESMAway ESMEditWins ) ] teamScoreSpec :: Spec teamScoreSpec = describe "teamScore" $ do let s t = newGameState & gameType ?~ t & homeScore ?~ 1 & awayScore ?~ 2 context "unknown game type" $ it "should return Nothing" $ teamScore newGameState `shouldBe` Nothing context "HomeGame" $ it "should return 1" $ teamScore (s HomeGame) `shouldBe` Just 1 context "AwayGame" $ it "should return 2" $ teamScore (s AwayGame) `shouldBe` Just 2 otherScoreSpec :: Spec otherScoreSpec = describe "otherScore" $ do let s t = newGameState & gameType ?~ t & homeScore ?~ 1 & awayScore ?~ 2 context "unknown game type" $ it "should return Nothing" $ otherScore newGameState `shouldBe` Nothing context "HomeGame" $ it "should return 2" $ otherScore (s HomeGame) `shouldBe` Just 2 context "AwayGame" $ it "should return 1" $ otherScore (s AwayGame) `shouldBe` Just 1 jsonSpec :: (Eq a, Show a, FromJSON a, ToJSON a) => a -> Value -> Spec jsonSpec x j = do describe "decode" $ it "should decode" $ decode (encode j) `shouldBe` Just x describe "toJSON" $ it "should encode" $ decode (encode $ toJSON x) `shouldBe` Just x describe "toEncoding" $ it "should encode" $ decode (encode x) `shouldBe` Just x lensSpec :: Comparable a => Lens' s a -> [(String, s, a)] -> [(String, s, a)] -> Spec lensSpec lens getters setters = do context "getters" $ mapM_ (\(label, s, x) -> context label $ compareTest (s^.lens) x) getters context "setters" $ mapM_ (\(label, s, x) -> context label $ let s' = s & lens .~ x in compareTest (s'^.lens) x) setters player :: Player player = newPlayer 1 "Joe" "centre" & pRookie .~ False & pYtd .~ playerStats 1 & pLifetime .~ playerStats 2 playerJSON :: Value playerJSON = Object $ HM.fromList [ ( "number", toJSON (1 :: Int) ) , ( "name", toJSON ("Joe" :: String) ) , ( "position", toJSON ("centre" :: String) ) , ( "rookie", toJSON False ) , ( "active", toJSON True ) , ( "ytd", playerStatsJSON 1 ) , ( "lifetime", playerStatsJSON 2 ) ] playerStats :: Int -> PlayerStats playerStats n = newPlayerStats & psGoals .~ n & psAssists .~ n + 1 & psPMin .~ n + 2 playerStatsJSON :: Int -> Value playerStatsJSON n = Object $ HM.fromList [ ( "goals", toJSON n ) , ( "assists", toJSON $ n + 1 ) , ( "penalty_mins", toJSON $ n + 2 ) ] goalie :: Goalie goalie = newGoalie 1 "Joe" & gRookie .~ False & gYtd .~ goalieStats 1 & gLifetime .~ goalieStats 2 goalieJSON :: Value goalieJSON = Object $ HM.fromList [ ( "number", toJSON (1 :: Int) ) , ( "name", toJSON ("Joe" :: String ) ) , ( "rookie", toJSON False ) , ( "active", toJSON True ) , ( "ytd", goalieStatsJSON 1 ) , ( "lifetime", goalieStatsJSON 2 ) ] goalieStats :: Int -> GoalieStats goalieStats n = newGoalieStats & gsGames .~ n & gsMinsPlayed .~ n + 1 & gsGoalsAllowed .~ n + 2 & gsShutouts .~ n + 3 & gsWins .~ n + 4 & gsLosses .~ n + 5 & gsTies .~ n + 6 goalieStatsJSON :: Int -> Value goalieStatsJSON n = Object $ HM.fromList [ ( "games", toJSON n ) , ( "mins_played", toJSON $ n + 1 ) , ( "goals_allowed", toJSON $ n + 2 ) , ( "shutouts", toJSON $ n + 3 ) , ( "wins", toJSON $ n + 4 ) , ( "losses", toJSON $ n + 5 ) , ( "ties", toJSON $ n + 6 ) ] gameStats :: Int -> GameStats gameStats n = GameStats { _gmsWins = n , _gmsLosses = n + 1 , _gmsOvertime = n + 2 , _gmsGoalsFor = n + 3 , _gmsGoalsAgainst = n + 4 } gameStatsJSON :: Int -> Value gameStatsJSON n = Object $ HM.fromList [ ( "wins", toJSON n ) , ( "losses", toJSON $ n + 1 ) , ( "overtime", toJSON $ n + 2 ) , ( "goals_for", toJSON $ n + 3 ) , ( "goals_against", toJSON $ n + 4 ) ] db :: Database db = newDatabase & dbPlayers .~ [player] & dbGoalies .~ [goalie] & dbGames .~ 1 & dbHomeGameStats .~ gameStats 1 & dbAwayGameStats .~ gameStats 2 dbJSON :: Value dbJSON = Object $ HM.fromList [ ( "players", toJSON [playerJSON] ) , ( "goalies", toJSON [goalieJSON] ) , ( "games", toJSON (1 :: Int) ) , ( "home_game_stats", gameStatsJSON 1 ) , ( "away_game_stats", gameStatsJSON 2 ) ] homeTeamSpec :: Spec homeTeamSpec = describe "homeTeam" $ do let gs gt = newGameState & gameType .~ gt & otherTeam .~ "foo" context "unknown game type" $ it "should return an empty string" $ homeTeam (gs Nothing) `shouldBe` "" context "home game" $ it ("should return " ++ show myTeam) $ homeTeam (gs $ Just HomeGame) `shouldBe` myTeam context "away game" $ it "should return \"foo\"" $ homeTeam (gs $ Just AwayGame) `shouldBe` "foo" awayTeamSpec :: Spec awayTeamSpec = describe "awayTeam" $ do let gs gt = newGameState & gameType .~ gt & otherTeam .~ "foo" context "unknown game type" $ it "should return an empty string" $ awayTeam (gs Nothing) `shouldBe` "" context "home game" $ it "should return \"foo\"" $ awayTeam (gs $ Just HomeGame) `shouldBe` "foo" context "away game" $ it ("should return " ++ show myTeam) $ awayTeam (gs $ Just AwayGame) `shouldBe` myTeam gameWonSpec :: Spec gameWonSpec = describe "gameWon" $ mapM_ (\(t, h, a, expected) -> let desc = "game type: " ++ show t ++ ", home score: " ++ show h ++ ", away score: " ++ show a gs = newGameState & gameType .~ t & homeScore .~ h & awayScore .~ a in context desc $ it ("should be " ++ show expected) $ gameWon gs `shouldBe` expected) -- gameType, homeScore, awayScore, expected [ ( Just HomeGame, Just 1, Just 1, Just False ) , ( Just HomeGame, Just 1, Just 2, Just False ) , ( Just HomeGame, Just 2, Just 1, Just True ) , ( Just AwayGame, Just 1, Just 1, Just False ) , ( Just AwayGame, Just 1, Just 2, Just True ) , ( Just AwayGame, Just 2, Just 1, Just False ) , ( Nothing, Just 1, Just 2, Nothing ) , ( Just HomeGame, Nothing, Just 1, Nothing ) , ( Just AwayGame, Nothing, Just 1, Nothing ) , ( Just HomeGame, Just 1, Nothing, Nothing ) , ( Just AwayGame, Just 1, Nothing, Nothing ) , ( Nothing, Nothing, Nothing, Nothing ) ] gameLostSpec :: Spec gameLostSpec = describe "gameLost" $ mapM_ (\(t, h, a, ot, expected) -> let desc = "game type: " ++ show t ++ ", home score: " ++ show h ++ ", away score: " ++ show a ++ ", overtimr flag: " ++ show ot gs = newGameState & gameType .~ t & homeScore .~ h & awayScore .~ a & overtimeFlag .~ ot in context desc $ it ("should be " ++ show expected) $ gameLost gs `shouldBe` expected) -- gameType, homeScore, awayScore, overtimeFlag, expected [ ( Just HomeGame, Just 1, Just 1, Just False, Just False ) , ( Just HomeGame, Just 1, Just 2, Just False, Just True ) , ( Just HomeGame, Just 1, Just 2, Just True, Just False ) , ( Just HomeGame, Just 2, Just 1, Just False, Just False ) , ( Just AwayGame, Just 1, Just 1, Just False, Just False ) , ( Just AwayGame, Just 1, Just 2, Just False, Just False ) , ( Just AwayGame, Just 2, Just 1, Just False, Just True ) , ( Just AwayGame, Just 2, Just 1, Just True, Just False ) , ( Nothing, Just 1, Just 2, Just False, Nothing ) , ( Just HomeGame, Nothing, Just 1, Just False, Nothing ) , ( Just AwayGame, Nothing, Just 1, Just False, Nothing ) , ( Just HomeGame, Just 1, Nothing, Just False, Nothing ) , ( Just AwayGame, Just 1, Nothing, Just False, Nothing ) , ( Just HomeGame, Just 1, Just 2, Nothing, Nothing ) , ( Just AwayGame, Just 1, Just 2, Nothing, Nothing ) , ( Nothing, Nothing, Nothing, Just False, Nothing ) ] gameTiedSpec :: Spec gameTiedSpec = describe "gameTied" $ mapM_ (\(home, away, expected) -> let desc = "home score: " ++ show home ++ ", away score: " ++ show away gs = newGameState & homeScore .~ home & awayScore .~ away in context desc $ it ("should be " ++ show expected) $ gameTied gs `shouldBe` expected) [ ( Nothing, Nothing, Nothing ) , ( Nothing, Just 1, Nothing ) , ( Just 1, Nothing, Nothing ) , ( Just 1, Just 1, Just True ) , ( Just 1, Just 2, Just False ) ] unaccountedPointsSpec :: Spec unaccountedPointsSpec = describe "unaccounted points" $ do context "no data" $ it "should return Nothing" $ unaccountedPoints newGameState `shouldBe` Nothing context "unaccounted points" $ it "should return True" $ let gs = newGameState & gameType ?~ HomeGame & homeScore ?~ 1 in unaccountedPoints gs `shouldBe` Just True context "all points accounted" $ it "should return False" $ let gs = newGameState & gameType ?~ HomeGame & homeScore ?~ 1 & pointsAccounted .~ 1 in unaccountedPoints gs `shouldBe` Just False context "more points accounted" $ it "should return True" $ let gs = newGameState & gameType ?~ HomeGame & homeScore ?~ 1 & pointsAccounted .~ 2 in unaccountedPoints gs `shouldBe` Just False gmsGamesSpec :: Spec gmsGamesSpec = describe "gmsGames" $ mapM_ (\(w, l, ot, expected) -> let desc = "wins: " ++ show w ++ ", losses: " ++ show l ++ ", overtime: " ++ show ot gs = newGameStats & gmsWins .~ w & gmsLosses .~ l & gmsOvertime .~ ot in context desc $ it ("should be " ++ show expected) $ gmsGames gs `shouldBe` expected) -- wins, losses, overtime, expected [ ( 0, 0, 0, 0 ) , ( 1, 0, 0, 1 ) , ( 0, 1, 0, 1 ) , ( 0, 0, 1, 1 ) , ( 1, 1, 1, 3 ) , ( 2, 3, 5, 10 ) ] gmsPointsSpec :: Spec gmsPointsSpec = describe "gmsPoints" $ mapM_ (\(w, l, ot, expected) -> let gs = newGameStats & gmsWins .~ w & gmsLosses .~ l & gmsOvertime .~ ot in context (show gs) $ it ("should be " ++ show expected) $ gmsPoints gs `shouldBe` expected) -- wins, losses, overtime, expected [ ( 0, 0, 0, 0 ) , ( 1, 0, 0, 2 ) , ( 0, 1, 0, 0 ) , ( 0, 1, 1, 1 ) , ( 1, 1, 1, 3 ) , ( 2, 4, 3, 7 ) ] addGameStatsSpec :: Spec addGameStatsSpec = describe "addGameStats" $ it "should add the values" $ let s1 = GameStats { _gmsWins = 1 , _gmsLosses = 2 , _gmsOvertime = 3 , _gmsGoalsFor = 4 , _gmsGoalsAgainst = 5 } s2 = GameStats { _gmsWins = 6 , _gmsLosses = 7 , _gmsOvertime = 8 , _gmsGoalsFor = 9 , _gmsGoalsAgainst = 10 } expected = GameStats { _gmsWins = 7 , _gmsLosses = 9 , _gmsOvertime = 11 , _gmsGoalsFor = 13 , _gmsGoalsAgainst = 15 } in addGameStats s1 s2 `shouldBe` expected playerSearchSpec :: Spec playerSearchSpec = describe "playerSearch" $ mapM_ (\(sStr, expected) -> context sStr $ it ("should return " ++ show expected) $ let ps = [joe, bob, steve] in playerSearch sStr ps `shouldBe` expected) -- search, result [ ( "joe", [(0, joe)] ) , ( "o", [(0, joe), (1, bob)] ) , ( "e", [(0, joe), (2, steve)] ) , ( "x", [] ) ] activePlayerSearchSpec :: Spec activePlayerSearchSpec = describe "activePlayerSearch" $ mapM_ (\(sStr, expected) -> context sStr $ it ("should return " ++ show expected) $ let ps = [joe, bob, steve & pActive .~ False] in activePlayerSearch sStr ps `shouldBe` expected) -- search, result [ ( "joe", [(0, joe)] ) , ( "o", [(0, joe), (1, bob)] ) , ( "e", [(0, joe)] ) , ( "x", [] ) ] playerSearchExactSpec :: Spec playerSearchExactSpec = describe "playerSearchExact" $ mapM_ (\(sStr, expected) -> context sStr $ it ("should be " ++ show expected) $ let ps = [joe, bob, steve] in playerSearchExact sStr ps `shouldBe` expected) -- search, result [ ( "Joe", Just (0, joe) ) , ( "Bob", Just (1, bob) ) , ( "Steve", Just (2, steve) ) , ( "Sam", Nothing ) , ( "", Nothing ) ] modifyPlayerSpec :: Spec modifyPlayerSpec = describe "modifyPlayer" $ mapM_ (\(name, j, b, s) -> let modifier = pLifetime.psGoals .~ 1 players = modifyPlayer modifier name [joe, bob, steve] in context ("modify " ++ name) $ do context "Joe's lifetime goals" $ it ("should be " ++ show j) $ head players^.pLifetime.psGoals `shouldBe` j context "Bob's lifetime goals" $ it ("should be " ++ show b) $ (players !! 1)^.pLifetime.psGoals `shouldBe` b context "Steve's lifetime goals" $ it ("should be " ++ show s) $ last players^.pLifetime.psGoals `shouldBe` s) -- player name, Joe's goals, Bob's goals, Steve's goals [ ( "Joe", 1, 0, 0 ) , ( "Bob", 0, 1, 0 ) , ( "Steve", 0, 0, 1 ) , ( "Sam", 0, 0, 0 ) ] playerSummarySpec :: Spec playerSummarySpec = describe "playerSummary" $ it "should be \"Joe (2) center\"" $ playerSummary joe `shouldBe` "Joe (2) center" playerIsActiveSpec :: Spec playerIsActiveSpec = describe "playerIsActive" $ do let pStats = newPlayerStats & psGoals .~ 10 & psAssists .~ 11 & psPMin .~ 12 p = newPlayer 1 "Joe" "centre" & pLifetime .~ pStats mapM_ (\(label, p', expected) -> context label $ it ("should be " ++ show expected) $ playerIsActive p' `shouldBe` expected) -- label, player, expected [ ( "not active", p, False ) , ( "has goal", p & pYtd.psGoals .~ 1, True ) , ( "has assist", p & pYtd.psAssists .~ 1, True ) , ( "has penalty minute", p & pYtd.psPMin .~ 1, True ) ] psPointsSpec :: Spec psPointsSpec = describe "psPoints" $ mapM_ (\(goals, assists, points) -> let desc = "goals: " ++ show goals ++ ", assists: " ++ show assists stats = newPlayerStats & psGoals .~ goals & psAssists .~ assists in context desc $ it ("should be " ++ show points) $ psPoints stats `shouldBe` points) -- goals, assists, points [ ( 0, 0, 0 ) , ( 1, 0, 1 ) , ( 0, 1, 1 ) , ( 2, 3, 5 ) ] addPlayerStatsSpec :: Spec addPlayerStatsSpec = describe "addPlayerStats" $ do let s1 = newPlayerStats & psGoals .~ 1 & psAssists .~ 2 & psPMin .~ 3 s2 = newPlayerStats & psGoals .~ 4 & psAssists .~ 5 & psPMin .~ 6 s3 = addPlayerStats s1 s2 describe "psGoals" $ it "should be 5" $ s3^.psGoals `shouldBe` 5 describe "psAssists" $ it "should be 7" $ s3^.psAssists `shouldBe` 7 describe "psPMin" $ it "should be 9" $ s3^.psPMin `shouldBe` 9 goalieSearchSpec :: Spec goalieSearchSpec = describe "goalieSearch" $ do let goalies = [ newGoalie 2 "Joe" , newGoalie 3 "Bob" , newGoalie 5 "Steve" ] result n = (n, goalies!!n) context "partial match" $ it "should return Joe and Steve" $ goalieSearch "e" goalies `shouldBe` [result 0, result 2] context "no match" $ it "should return an empty list" $ goalieSearch "x" goalies `shouldBe` [] context "exact match" $ it "should return Bob" $ goalieSearch "bob" goalies `shouldBe` [result 1] activeGoalieSearchSpec :: Spec activeGoalieSearchSpec = describe "activeGoalieSearch" $ do let goalies = [ newGoalie 2 "Joe" , newGoalie 3 "Bob" , newGoalie 5 "Steve" & gActive .~ False ] result n = (n, goalies!!n) context "partial match" $ it "should return Joe" $ activeGoalieSearch "e" goalies `shouldBe` [result 0] context "no match" $ it "should return an empty list" $ activeGoalieSearch "x" goalies `shouldBe` [] context "exact match" $ it "should return Bob" $ activeGoalieSearch "bob" goalies `shouldBe` [result 1] goalieSearchExactSpec :: Spec goalieSearchExactSpec = describe "goalieSearchExact" $ do let goalies = [ newGoalie 2 "Joe" , newGoalie 3 "Bob" , newGoalie 5 "Steve" ] result n = (n, goalies!!n) mapM_ (\(name, num) -> context name $ it ("should return " ++ name) $ goalieSearchExact name goalies `shouldBe` Just (result num)) -- name, num [ ( "Joe", 0 ) , ( "Bob", 1 ) , ( "Steve", 2 ) ] context "Greg" $ it "should return Nothing" $ goalieSearchExact "Greg" goalies `shouldBe` Nothing goalieSummarySpec :: Spec goalieSummarySpec = describe "goalieSummary" $ it "should provide a summary string" $ goalieSummary (newGoalie 2 "Joe") `shouldBe` "Joe (2)" goalieIsActiveSpec :: Spec goalieIsActiveSpec = describe "goalieIsActive" $ mapM_ (\(label, input, expected) -> context label $ it ("should be " ++ show expected) $ goalieIsActive input `shouldBe` expected) -- label, input, expected [ ( "inactive", inactive, False ) , ( "active", active, True ) ] where inactive = newGoalie 1 "Joe" & gLifetime.gsMinsPlayed .~ 1 active = inactive & gYtd.gsMinsPlayed .~ 1 addGoalieStatsSpec :: Spec addGoalieStatsSpec = describe "addGoalieStats" $ let g1 = GoalieStats { _gsGames = 1 , _gsMinsPlayed = 2 , _gsGoalsAllowed = 3 , _gsShutouts = 4 , _gsWins = 5 , _gsLosses = 6 , _gsTies = 7 } g2 = GoalieStats { _gsGames = 8 , _gsMinsPlayed = 9 , _gsGoalsAllowed = 10 , _gsShutouts = 11 , _gsWins = 12 , _gsLosses = 13 , _gsTies = 14 } expected = GoalieStats { _gsGames = 9 , _gsMinsPlayed = 11 , _gsGoalsAllowed = 13 , _gsShutouts = 15 , _gsWins = 17 , _gsLosses = 19 , _gsTies = 21 } actual = g1 `addGoalieStats` g2 in it ("should be " ++ show expected) $ actual `shouldBe` expected gsAverageSpec :: Spec gsAverageSpec = describe "gsAverage" $ mapM_ (\(label, stats, expected) -> context label $ it ("should be " ++ show expected) $ gsAverage stats `shouldBe` expected) -- label, stats, expected [ ( "with minutes", gs, 3 % 2 ) , ( "no minutes", newGoalieStats , 0 ) ] where gs = newGoalieStats & gsMinsPlayed .~ 2 * gameLength & gsGoalsAllowed .~ 3 joe :: Player joe = newPlayer 2 "Joe" "center" bob :: Player bob = newPlayer 3 "Bob" "defense" steve :: Player steve = newPlayer 5 "Steve" "forward" -- | Creates a 'Player' makePlayer :: IO Player makePlayer = Player <$> makeNum <*> makeName <*> makeName <*> makeBool <*> makeBool <*> makePlayerStats <*> makePlayerStats -- | Creates a 'Goalie' makeGoalie :: IO Goalie makeGoalie = Goalie <$> makeNum <*> makeName <*> makeBool <*> makeBool <*> makeGoalieStats <*> makeGoalieStats -- | Creates a 'PlayerStats' value makePlayerStats :: IO PlayerStats makePlayerStats = PlayerStats <$> makeNum <*> makeNum <*> makeNum -- | Creates a 'GoalieStats' value makeGoalieStats :: IO GoalieStats makeGoalieStats = GoalieStats <$> makeNum <*> makeNum <*> makeNum <*> makeNum <*> makeNum <*> makeNum <*> makeNum makeNum :: IO Int makeNum = randomRIO (1, 10) makeBool :: IO Bool makeBool = randomIO makeName :: IO String makeName = replicateM 10 $ randomRIO ('A', 'Z') instance Comparable GoalieStats where compareTest actual expected = mapM_ (\(name, lens) -> describe name $ it ("should be " ++ show (expected^.lens)) $ actual^.lens `shouldBe` expected^.lens) -- name, lens [ ( "gsGames", gsGames ) , ( "gsMinsPlayed", gsMinsPlayed ) , ( "gsGoalsAllowed", gsGoalsAllowed ) , ( "gsWins", gsWins ) , ( "gsLosses", gsLosses ) , ( "gsTies", gsTies ) ] instance Comparable GameState where compareTest actual expected = it ("should be " ++ show expected) $ actual `shouldBe` expected instance Comparable CreatePlayerState where compareTest actual expected = do describe "cpsNumber" $ it ("should be " ++ show (expected^.cpsNumber)) $ actual^.cpsNumber `shouldBe` expected^.cpsNumber describe "cpsName" $ it ("should be " ++ expected^.cpsName) $ actual^.cpsName `shouldBe` expected^.cpsName describe "cpsPosition" $ it ("should be " ++ expected^.cpsPosition) $ actual^.cpsPosition `shouldBe` expected^.cpsPosition instance Comparable EditPlayerState where compareTest actual expected = do describe "epsSelectedPlayer" $ it ("should be " ++ show (expected^.epsSelectedPlayer)) $ actual^.epsSelectedPlayer `shouldBe` expected^.epsSelectedPlayer describe "epsMode" $ it ("should be " ++ show (expected^.epsMode)) $ actual^.epsMode `shouldBe` expected^.epsMode instance Comparable EditGoalieState where compareTest actual expected = do describe "egsSelectedGoalie" $ it ("should be " ++ show (expected^.egsSelectedGoalie)) $ actual^.egsSelectedGoalie `shouldBe` expected^.egsSelectedGoalie describe "egsMode" $ it ("should be " ++ show (expected^.egsMode)) $ actual^.egsMode `shouldBe` expected^.egsMode instance Comparable CreateGoalieState where compareTest actual expected = do describe "cgsNuber" $ it("should be " ++ show (expected^.cgsNumber)) $ actual^.cgsNumber `shouldBe` expected^.cgsNumber describe "cgsName" $ it ("should be " ++ expected^.cgsName) $ actual^.cgsName `shouldBe` expected^.cgsName instance Comparable EditStandingsMode where compareTest actual expected = it ("should be " ++ show expected) $ actual `shouldBe` expected instance Comparable Goalie where compareTest actual expected = do describe "gNumber" $ it ("should be " ++ show (expected^.gNumber)) $ actual^.gNumber `shouldBe` expected^.gNumber describe "gName" $ it ("should be " ++ show (expected^.gName)) $ actual^.gName `shouldBe` expected^.gName describe "gRookie" $ it ("should be " ++ show (expected^.gRookie)) $ actual^.gRookie `shouldBe` expected^.gRookie describe "gActive" $ it ("should be " ++ show (expected^.gActive)) $ actual^.gActive `shouldBe` expected^.gActive describe "gYtd" $ (actual^.gYtd) `compareTest` (expected^.gYtd) describe "gLifetime" $ (actual^.gLifetime) `compareTest` (expected^.gLifetime) instance Comparable (M.Map Int GoalieStats) where compareTest actual expected = do let aList = M.toList actual eList = M.toList expected it "should have the correct number of elements" $ length aList `shouldBe` length eList mapM_ (\(n, (ka, va), (ke, ve)) -> context ("element " ++ show n) $ do context "key" $ it ("should be " ++ show ke) $ ka `shouldBe` ke context "value" $ va `compareTest` ve) (zip3 ([0..] :: [Int]) aList eList)