// Module 4: Interceptors and "trigger twice". Each test matches a heading in scenarios.md. use engine::harness::*; fn game_with_deck() -> Game { new_game(GameOpts { decks: Some([vec!["Test Filler".to_string(); 5], vec![]]), }) } fn damaged(target: Id, amount: i32) -> GameEvent { GameEvent::Damaged { target, amount } } fn triggered(source: Id, trigger: &str) -> GameEvent { GameEvent::Triggered { source, trigger: trigger.to_string(), } } fn spell_cast(card: &str) -> GameEvent { GameEvent::SpellCast { card: card.to_string(), } } #[test] fn test_04_baron_doubles() { let mut g = game_with_deck(); let b = g.summon("Baron Rivendare", 0); let h = g.summon("Loot Hoarder", 0); let golem = g.summon("Harvest Golem", 0); g.deal_damage(golem, 2); g.clear_log(); g.cast("Whirlwind", 0, None); let hand = g.hand(0); assert_eq!(hand.len(), 2); let (c1, c2) = (hand[0], hand[1]); let summoned: Vec = g .log() .into_iter() .filter_map(|e| match e { GameEvent::Summoned { id } => Some(id), _ => None, }) .collect(); assert_eq!(summoned.len(), 2); let (d1, d2) = (summoned[0], summoned[1]); assert_eq!( g.log(), vec![ spell_cast("Whirlwind"), damaged(b, 1), damaged(h, 1), damaged(golem, 1), GameEvent::Destroyed { id: h }, GameEvent::Destroyed { id: golem }, GameEvent::DeathPhase { round: 1 }, triggered(h, "deathrattle"), GameEvent::Drew { player: 0, id: c1 }, triggered(h, "deathrattle"), GameEvent::Drew { player: 0, id: c2 }, triggered(golem, "deathrattle"), GameEvent::Summoned { id: d1 }, triggered(golem, "deathrattle"), GameEvent::Summoned { id: d2 }, ] ); assert_eq!(g.name_of(d1), "Damaged Golem"); assert_eq!(g.name_of(d2), "Damaged Golem"); let board = g.board(0); assert_eq!(board.len(), 3); assert_eq!(board[0], b); let mut golems = vec![board[1], board[2]]; golems.sort(); let mut expected = vec![d1, d2]; expected.sort(); assert_eq!(golems, expected); assert_eq!(g.health(b), 6); } #[test] fn test_04_simultaneous_death_not_doubled() { let mut g = game_with_deck(); let b = g.summon("Baron Rivendare", 0); let h = g.summon("Loot Hoarder", 0); g.clear_log(); g.cast("Twisting Nether", 0, None); assert_eq!(g.hand(0).len(), 1); let hoarder_triggers = g .log() .into_iter() .filter(|e| *e == triggered(h, "deathrattle")) .count(); assert_eq!(hoarder_triggers, 1); assert_eq!(g.zone_of(b), Zone::Graveyard); assert_eq!(g.zone_of(h), Zone::Graveyard); } #[test] fn test_04_two_barons_do_not_stack() { let mut g = game_with_deck(); g.summon("Baron Rivendare", 0); g.summon("Baron Rivendare", 0); let h = g.summon("Loot Hoarder", 0); g.clear_log(); g.deal_damage(h, 1); let hand = g.hand(0); assert_eq!(hand.len(), 2); let (c1, c2) = (hand[0], hand[1]); assert_eq!( g.log(), vec![ damaged(h, 1), GameEvent::Destroyed { id: h }, GameEvent::DeathPhase { round: 1 }, triggered(h, "deathrattle"), GameEvent::Drew { player: 0, id: c1 }, triggered(h, "deathrattle"), GameEvent::Drew { player: 0, id: c2 }, ] ); } #[test] fn test_04_aura_timing_order() { let mut g = game_with_deck(); let h = g.summon("Loot Hoarder", 0); let b = g.summon("Baron Rivendare", 0); g.clear_log(); g.cast("Twisting Nether", 0, None); let hand = g.hand(0); assert_eq!(hand.len(), 1); let c = hand[0]; assert_eq!( g.log(), vec![ spell_cast("Twisting Nether"), GameEvent::Destroyed { id: h }, GameEvent::Destroyed { id: b }, GameEvent::DeathPhase { round: 1 }, triggered(h, "deathrattle"), GameEvent::Drew { player: 0, id: c }, ] ); } #[test] fn test_04_battlecry_on_play() { let mut g = game_with_deck(); let e = g.add_to_hand("Novice Engineer", 0); g.clear_log(); g.play(e); let hand = g.hand(0); assert_eq!(hand.len(), 1); let c = hand[0]; assert_eq!( g.log(), vec![ GameEvent::Summoned { id: e }, triggered(e, "battlecry"), GameEvent::Drew { player: 0, id: c }, ] ); assert_eq!(g.zone_of(e), Zone::Board); assert_eq!(g.board(0), vec![e]); } #[test] fn test_04_armor_absorbs_before_health() { let mut g = game_with_deck(); let a = g.summon("Armorsmith", 0); let r = g.summon("Test Reactor", 0); g.deal_damage(a, 1); g.deal_damage(a, 1); let hero = g.hero(0); assert_eq!(g.armor(0), 2); g.clear_log(); g.deal_damage(hero, 1); assert_eq!( g.log(), vec![ GameEvent::ArmorLost { player: 0, amount: 1 }, damaged(hero, 1), triggered(r, "damaged"), GameEvent::StatsChanged { id: r, attack: 2, health: 5 }, ] ); assert_eq!(g.armor(0), 1); assert_eq!(g.health(hero), 30); g.clear_log(); g.deal_damage(hero, 5); assert_eq!( g.log(), vec![ GameEvent::ArmorLost { player: 0, amount: 1 }, damaged(hero, 5), triggered(r, "damaged"), GameEvent::StatsChanged { id: r, attack: 3, health: 5 }, ] ); assert_eq!(g.armor(0), 0); assert_eq!(g.health(hero), 26); } #[test] fn test_04_redirect_replaces_hero_damage() { let mut g = game_with_deck(); let b = g.summon("Bolf Ramshield", 0); let a = g.summon("Armorsmith", 0); g.summon("Test Reactor", 0); let hero = g.hero(0); g.clear_log(); g.deal_damage(hero, 4); assert_eq!( g.log(), vec![ damaged(b, 4), triggered(a, "damaged"), GameEvent::ArmorGained { player: 0, amount: 1 }, ] ); assert_eq!(g.health(hero), 30); assert_eq!(g.health(b), 5); assert_eq!(g.armor(0), 1); } #[test] fn test_04_redirect_applies_once() { let mut g = game_with_deck(); let b1 = g.summon("Bolf Ramshield", 0); let b2 = g.summon("Bolf Ramshield", 0); let hero = g.hero(0); g.clear_log(); g.deal_damage(hero, 3); assert_eq!(g.log(), vec![damaged(b1, 3)]); assert_eq!(g.health(b1), 6); assert_eq!(g.health(b2), 9); assert_eq!(g.health(hero), 30); } #[test] fn test_04_redirect_before_armor() { let mut g = game_with_deck(); let a = g.summon("Armorsmith", 0); g.deal_damage(a, 1); g.destroy(a); let b = g.summon("Bolf Ramshield", 0); let hero = g.hero(0); assert_eq!(g.armor(0), 1); g.clear_log(); g.deal_damage(hero, 2); assert_eq!(g.log(), vec![damaged(b, 2)]); assert_eq!(g.armor(0), 1); assert_eq!(g.health(hero), 30); assert_eq!(g.health(b), 7); }