local G=require("\109\111\115\101\115")local z=(require("\115\117\112\112\111\114\116\046\102\097\099\116\111\114\121")).create local f=require("\105\110\118\111\107\097\116\105\111\110\046\099\111\109\098\111\115\046\067\111\109\098\111")local u=require("\105\110\118\111\107\097\116\105\111\110\046\099\111\109\098\111\115\046\104\101\114\111")local p=require("\105\110\118\111\107\097\116\105\111\110\046\099\111\110\115\116\046\108\105\109\105\116\115")local q=require("\105\110\118\111\107\097\116\105\111\110\046\099\111\110\115\116\046\105\110\118\111\107\101\114")local c=require("\105\110\118\111\107\097\116\105\111\110\046\099\111\110\115\116\046\115\111\117\110\100\095\101\118\101\110\116\115")describe("\099\111\109\098\111\115\046\104\101\114\111",function()describe("\046\115\101\116\117\112",function()local p=f({id=13,heroLevel=6,gold=1300;items={"\105\116\101\109\095\116\114\097\118\101\108\095\098\111\111\116\115","\105\116\101\109\095\098\108\097\099\107\095\107\105\110\103\095\098\097\114","\105\116\101\109\095\115\104\105\118\097\115\095\103\117\097\114\100";"\105\116\101\109\095\114\101\102\114\101\115\104\101\114";"\105\116\101\109\095\111\099\116\097\114\105\110\101\095\099\111\114\101";"\105\116\101\109\095\117\108\116\105\109\097\116\101\095\115\099\101\112\116\101\114"};sequence={{id=1,name="\097\098\105\108\105\116\121"}}})it("\114\117\110\115 \116\104\101 \099\111\109\098\111 \115\101\116\117\112",function()local f=z("\104\101\114\111\095\105\110\118\111\107\101\114")local q=f.entity local c=z("\100\111\116\097\095\112\108\097\121\101\114",{id=13;hero=q})u.setup(c,p)assert.equal(6,q:GetLevel())assert.equal(1300,q:GetGold())assert.same(G.sort(p.items),f:ItemNames({sort=true}))end)describe("\119\104\101\110 \104\101\114\111 \097\108\114\101\097\100\121 \104\097\115 \115\111\109\101 \105\116\101\109\115",function()it("\103\105\118\101\115 \116\104\101 \104\101\114\111 \111\110\108\121 \116\104\101 \109\105\115\115\105\110\103 \105\116\101\109\115",function()local f={"\105\116\101\109\095\115\104\105\118\097\115\095\103\117\097\114\100","\105\116\101\109\095\114\101\102\114\101\115\104\101\114"}local q=z("\104\101\114\111\095\105\110\118\111\107\101\114",{},{items=f})local c=z("\100\111\116\097\095\112\108\097\121\101\114",{id=13,hero=q.entity})u.setup(c,p)assert.same(G.sort(p.items),q:ItemNames({sort=true}))end)end)end)describe("\046\116\101\097\114\100\111\119\110",function()it("\115\116\111\112\115 \099\117\114\114\101\110\116\108\121 \112\108\097\121\105\110\103 \115\111\117\110\100\115 \111\110 \116\104\101 \104\101\114\111",function()local G=z("\100\111\116\097\095\104\101\114\111\095\105\110\118\111\107\101\114")local f=z("\100\111\116\097\095\112\108\097\121\101\114",{id=13;hero=G})local p=spy.on(G,"\083\116\111\112\083\111\117\110\100")u.teardown(f);(assert.spy(p)).was.self.called_with(c.SNDEVT_INVOKER_METEOR_LOOP)end)it("\114\101\115\101\116\115 \104\101\114\111 \097\098\105\108\105\116\105\101\115 \099\111\111\108\100\111\119\110\115",function()local f=z("\104\101\114\111\095\105\110\118\111\107\101\114")local p=z("\100\111\116\097\095\112\108\097\121\101\114",{id=13;hero=f.entity})local q={}f:ForEachAbility(function(G)q[G:GetAbilityName()]={EndCooldown=spy.on(G,"\069\110\100\067\111\111\108\100\111\119\110")}end)assert.is_false(G.isEmpty(q))u.teardown(p)for G,z in pairs(q)do(assert.spy(z.EndCooldown)).was.self.called()end end)it("\114\101\115\101\116\115 \104\101\114\111 \105\116\101\109\115 \099\111\111\108\100\111\119\110\115",function()local f={"\105\116\101\109\095\115\104\105\118\097\115\095\103\117\097\114\100";"\105\116\101\109\095\114\101\102\114\101\115\104\101\114"}local p=z("\104\101\114\111\095\105\110\118\111\107\101\114",{},{items=f})local q=p.entity assert.is_false(G.isEmpty(q.inventory))local c=z("\100\111\116\097\095\112\108\097\121\101\114",{id=13;hero=q})local w={}p:ForEachItem(function(G)w[G:GetAbilityName()]={EndCooldown=spy.on(G,"\069\110\100\067\111\111\108\100\111\119\110")}end)assert.is_false(G.isEmpty(w))u.teardown(c)for G,z in pairs(w)do(assert.spy(z.EndCooldown)).was.self.called()end end)describe("\119\105\116\104 \100\114\111\112\112\101\100 \105\116\101\109\115",function()local f=z("\100\111\116\097\095\104\101\114\111\095\105\110\118\111\107\101\114")local p=z("\100\111\116\097\095\112\108\097\121\101\114",{id=13,hero=f})local q={z("\100\111\116\097\095\105\116\101\109",{name="\105\116\101\109\095\098\108\105\110\107",purchaser=f}),z("\100\111\116\097\095\105\116\101\109",{name="\105\116\101\109\095\114\101\102\114\101\115\104\101\114";purchaser=f});z("\100\111\116\097\095\105\116\101\109",{name="\105\116\101\109\095\116\114\097\118\101\108\095\098\111\111\116\115";purchaser=f})}before_each(function()stub.new(GameRules,"\078\117\109\068\114\111\112\112\101\100\073\116\101\109\115",#q)stub.new(GameRules,"\071\101\116\068\114\111\112\112\101\100\073\116\101\109",function(G,z)return q[z+1]end)end)after_each(function()GameRules.NumDroppedItems:revert()GameRules.GetDroppedItem:revert()end)it("\114\101\109\111\118\101\115 \100\114\111\112\112\101\100 \105\116\101\109\115 \111\119\110\101\100 \098\121 \116\104\101 \104\101\114\111",function()local z=G.map(q,function(G)return G,{RemoveSelf=spy.on(G,"\082\101\109\111\118\101\083\101\108\102")}end)u.teardown(p)for G,z in pairs(z)do(assert.spy(z.RemoveSelf)).was.self.called()assert.is_true(G:IsNull())end end)end)describe("\119\105\116\104 \115\112\097\119\110\101\100 \117\110\105\116\115 \098\121 \116\104\101 \104\101\114\111",function()local f=z("\100\111\116\097\095\104\101\114\111\095\105\110\118\111\107\101\114")local p=z("\100\111\116\097\095\112\108\097\121\101\114",{id=13;hero=f})local c={}local w={}for G,f in pairs(q.SPAWNED_UNITS)do for G=0,1,1 do local u=z("\100\111\116\097\095\117\110\105\116",{name=f;playerOwner=p})c[f]=c[f]or{}table.insert(c[f],u)w[f]=w[f]or{}w[f][u]={RemoveSelf=spy.on(u,"\082\101\109\111\118\101\083\101\108\102")}end end before_each(function()stub.new(Entities,"\070\105\110\100\066\121\078\097\109\101",function(z,f,u)local p=f and f:GetEntityIndex()or 0 local q=G.findIndex(c[u],function(G)return G:GetEntityIndex()>p end)return c[u][q]end)end)after_each(function()Entities.FindByName:revert()end)it("\114\101\109\111\118\101\115 \115\112\097\119\110\101\100 \117\110\105\116\115 \098\121 \116\104\101 \104\101\114\111",function()u.teardown(p)for G,z in pairs(w)do for G,z in pairs(z)do(assert.spy(z.RemoveSelf)).was.self.called_with()assert.is_true(G:IsNull())end end end)end)describe("\119\105\116\104 \115\111\102\116 \114\101\115\101\116",function()it("\112\117\114\103\101\115 \116\104\101 \104\101\114\111",function()local G=z("\100\111\116\097\095\104\101\114\111\095\105\110\118\111\107\101\114")local f=z("\100\111\116\097\095\112\108\097\121\101\114",{id=13,hero=G})local p=spy.on(G,"\080\117\114\103\101")u.teardown(f);(assert.spy(p)).was.self.called_with(true,true,false,true,true)end)it("\104\101\097\108\115 \116\104\101 \104\101\114\111 \116\111 \109\097\120 \104\101\097\108\116\104",function()local G=z("\100\111\116\097\095\104\101\114\111\095\105\110\118\111\107\101\114",{maxHealth=1000.0,health=301.25})local f=z("\100\111\116\097\095\112\108\097\121\101\114",{id=13;hero=G})assert.equal(301.25,G:GetHealth())u.teardown(f)assert.equal(1000.0,G:GetHealth())end)it("\103\105\118\101\115 \116\104\101 \104\101\114\111 \109\097\120 \109\097\110\097",function()local G=z("\100\111\116\097\095\104\101\114\111\095\105\110\118\111\107\101\114",{maxMana=1000.0,mana=301.25})local f=z("\100\111\116\097\095\112\108\097\121\101\114",{id=13;hero=G})assert.equal(301.25,G:GetMana())u.teardown(f)assert.equal(1000.0,G:GetMana())end)end)describe("\119\105\116\104 \104\097\114\100 \114\101\115\101\116",function()before_each(function()stub.new(PlayerResource,"\082\101\112\108\097\099\101\072\101\114\111\087\105\116\104")end)after_each(function()PlayerResource.ReplaceHeroWith:revert()end)it("\114\101\109\111\118\101\115 \097\108\108 \104\101\114\111\'\115 \105\116\101\109\115",function()local G={"\105\116\101\109\095\115\104\105\118\097\115\095\103\117\097\114\100";"\105\116\101\109\095\114\101\102\114\101\115\104\101\114"}local f=z("\100\111\116\097\095\104\101\114\111\095\105\110\118\111\107\101\114",{},{items=G})local p=z("\100\111\116\097\095\112\108\097\121\101\114",{id=13;hero=f})u.teardown(p,{hardReset=true})assert.same({},f.inventory)end)it("\114\101\115\101\116\115 \104\101\114\111 \097\098\105\108\105\116\105\101\115",function()local G={[q.ABILITY_QUAS]=7,[q.ABILITY_WEX]=4;[q.ABILITY_EXORT]=6,[q.ABILITY_COLD_SNAP]=1;[q.ABILITY_SUN_STRIKE]=1;[q.ABILITY_TALENT_L10_RIGHT]=1,[q.ABILITY_TALENT_L10_LEFT]=0;[q.ABILITY_TALENT_L15_RIGHT]=1,[q.ABILITY_TALENT_L15_LEFT]=0;[q.ABILITY_TALENT_L20_RIGHT]=1;[q.ABILITY_TALENT_L20_LEFT]=0,[q.ABILITY_TALENT_L25_RIGHT]=1,[q.ABILITY_TALENT_L25_LEFT]=0}local f=z("\100\111\116\097\095\104\101\114\111\095\105\110\118\111\107\101\114",{},{abilities=G})local p=z("\100\111\116\097\095\112\108\097\121\101\114",{id=13;hero=f})u.teardown(p,{hardReset=true})for G,z in ipairs(q.ORB_ABILITIES)do assert.equal(0,(f:FindAbilityByName(z)):GetLevel())end for G,z in ipairs(q.TALENT_ABILITIES)do assert.equal(0,(f:FindAbilityByName(z)):GetLevel())end assert.equal(1,(f:FindAbilityByName(q.ABILITY_COLD_SNAP)):GetLevel())assert.equal(1,(f:FindAbilityByName(q.ABILITY_SUN_STRIKE)):GetLevel())assert.equal(1,f:GetAbilityPoints())end)it("\114\101\112\108\097\099\101\115 \104\101\114\111 \119\105\116\104 \097 \110\101\119 \111\110\101",function()local G=z("\100\111\116\097\095\104\101\114\111\095\105\110\118\111\107\101\114")local f=z("\100\111\116\097\095\112\108\097\121\101\114",{id=13,hero=G})local p=spy.on(G,"\082\101\109\111\118\101\083\101\108\102")u.teardown(f,{hardReset=true});(assert.stub(PlayerResource.ReplaceHeroWith)).was.self.called_with(13,q.UNIT_NAME,0,0);(assert.spy(p)).was.self.called()assert.is_true(G:IsNull())end)end)end)describe("\046\114\101\102\117\110\100\080\117\114\099\104\097\115\101",function()it("\114\101\102\117\110\100\115 \097 \112\117\114\099\104\097\115\101",function()local G=z("\100\111\116\097\095\104\101\114\111\095\105\110\118\111\107\101\114")local f=z("\100\111\116\097\095\112\108\097\121\101\114",{id=13;hero=G})local p={item="\105\116\101\109\095\098\108\105\110\107",cost=1234.5}G:SetGold(0,true)G:SetGold(0,false)assert.equal(0,G:GetGold())u.refundPurchase(f,p)assert.equal(1234.5,G:GetGold())end)end)describe("\046\108\101\118\101\108\085\112",function()local G={[q.ABILITY_QUAS]=1,[q.ABILITY_WEX]=1,[q.ABILITY_EXORT]=1,[q.ABILITY_COLD_SNAP]=1;[q.ABILITY_SUN_STRIKE]=1}local f,c before_each(function()f=z("\100\111\116\097\095\104\101\114\111\095\105\110\118\111\107\101\114",{level=3,abilityPoints=0},{abilities=G})c=z("\100\111\116\097\095\112\108\097\121\101\114",{id=13,hero=f})end)describe("\119\105\116\104 \115\112\101\099\105\102\105\099 \108\101\118\101\108 \111\112\116\105\111\110",function()it("\108\101\118\101\108\115 \117\112 \116\104\101 \112\108\097\121\101\114 \104\101\114\111 \116\111 \116\104\101 \115\112\101\099\105\102\105\101\100 \108\101\118\101\108",function()assert.equal(3,f:GetLevel())assert.equal(0,f:GetAbilityPoints())u.levelUp(c,{level=6})assert.equal(6,f:GetLevel())assert.equal(3,f:GetAbilityPoints())end)end)describe("\119\105\116\104 \101\109\112\116\121 \108\101\118\101\108 \111\112\116\105\111\110",function()it("\108\101\118\101\108\115 \117\112 \116\104\101 \112\108\097\121\101\114 \104\101\114\111 \098\121 \111\110\101 \108\101\118\101\108",function()assert.equal(3,f:GetLevel())assert.equal(0,f:GetAbilityPoints())u.levelUp(c)assert.equal(4,f:GetLevel())assert.equal(1,f:GetAbilityPoints())end)end)describe("\119\105\116\104 \109\097\120\076\101\118\101\108 \111\112\116\105\111\110",function()it("\108\101\118\101\108\115 \117\112 \116\104\101 \112\108\097\121\101\114 \104\101\114\111\044 \097\098\105\108\105\116\105\101\115 \097\110\100 \116\097\108\101\110\116\115 \116\111 \109\097\120 \108\101\118\101\108",function()assert.equal(3,f:GetLevel())assert.equal(0,f:GetAbilityPoints())u.levelUp(c,{maxLevel=true})assert.equal(p.MAX_HERO_LEVEL,f:GetLevel())assert.equal(0,f:GetAbilityPoints())for G,z in ipairs(q.ORB_ABILITIES)do local u=f:FindAbilityByName(z)assert.equal(u:GetMaxLevel(),u:GetLevel())end for G,z in ipairs(q.TALENT_ABILITIES)do local u=f:FindAbilityByName(z)assert.equal(u:GetMaxLevel(),u:GetLevel())end assert.equal(1,(f:FindAbilityByName(q.ABILITY_COLD_SNAP)):GetLevel())assert.equal(1,(f:FindAbilityByName(q.ABILITY_SUN_STRIKE)):GetLevel())end)end)end)end)
local m = require("moses")
local create = require("support.factory").create

local Combo = require("invokation.combos.Combo")
local CombosHero = require("invokation.combos.hero")

local LIMITS = require("invokation.const.limits")
local INVOKER = require("invokation.const.invoker")
local SOUND_EVENTS = require("invokation.const.sound_events")

describe("combos.hero", function()
  describe(".setup", function()
    local combo = Combo({
      id = 13,
      heroLevel = 6,
      gold = 1300,
      items = {
        "item_travel_boots",
        "item_black_king_bar",
        "item_shivas_guard",
        "item_refresher",
        "item_octarine_core",
        "item_ultimate_scepter",
      },
      sequence = { { id = 1, name = "ability" } },
    })

    it("runs the combo setup", function()
      local unit = create("hero_invoker")
      local hero = unit.entity
      local player = create("dota_player", { id = 13, hero = hero })

      CombosHero.setup(player, combo)

      assert.equal(6, hero:GetLevel())
      assert.equal(1300, hero:GetGold())
      assert.same(m.sort(combo.items), unit:ItemNames({ sort = true }))
    end)

    describe("when hero already has some items", function()
      it("gives the hero only the missing items", function()
        local items = { "item_shivas_guard", "item_refresher" }
        local unit = create("hero_invoker", {}, { items = items })
        local player = create("dota_player", { id = 13, hero = unit.entity })

        CombosHero.setup(player, combo)

        assert.same(m.sort(combo.items), unit:ItemNames({ sort = true }))
      end)
    end)
  end)

  describe(".teardown", function()
    it("stops currently playing sounds on the hero", function()
      local hero = create("dota_hero_invoker")
      local player = create("dota_player", { id = 13, hero = hero })
      local spyStopSound = spy.on(hero, "StopSound")

      CombosHero.teardown(player)

      assert.spy(spyStopSound).was.self.called_with(SOUND_EVENTS.SNDEVT_INVOKER_METEOR_LOOP)
    end)

    it("resets hero abilities cooldowns", function()
      local unit = create("hero_invoker")
      local player = create("dota_player", { id = 13, hero = unit.entity })
      local spies = {}

      unit:ForEachAbility(function(ability)
        spies[ability:GetAbilityName()] = { EndCooldown = spy.on(ability, "EndCooldown") }
      end)

      assert.is_false(m.isEmpty(spies))

      CombosHero.teardown(player)

      for _, s in pairs(spies) do
        assert.spy(s.EndCooldown).was.self.called()
      end
    end)

    it("resets hero items cooldowns", function()
      local items = { "item_shivas_guard", "item_refresher" }
      local unit = create("hero_invoker", {}, { items = items })
      local hero = unit.entity

      assert.is_false(m.isEmpty(hero.inventory))

      local player = create("dota_player", { id = 13, hero = hero })
      local spies = {}

      unit:ForEachItem(function(item)
        spies[item:GetAbilityName()] = { EndCooldown = spy.on(item, "EndCooldown") }
      end)

      assert.is_false(m.isEmpty(spies))

      CombosHero.teardown(player)

      for _, s in pairs(spies) do
        assert.spy(s.EndCooldown).was.self.called()
      end
    end)

    describe("with dropped items", function()
      local hero = create("dota_hero_invoker")
      local player = create("dota_player", { id = 13, hero = hero })
      local items = {
        create("dota_item", { name = "item_blink", purchaser = hero }),
        create("dota_item", { name = "item_refresher", purchaser = hero }),
        create("dota_item", { name = "item_travel_boots", purchaser = hero }),
      }

      before_each(function()
        stub.new(GameRules, "NumDroppedItems", #items)
        stub.new(GameRules, "GetDroppedItem", function(_, i)
          return items[i + 1]
        end)
      end)

      after_each(function()
        -- selene: allow(incorrect_standard_library_use)
        GameRules.NumDroppedItems:revert()
        -- selene: allow(incorrect_standard_library_use)
        GameRules.GetDroppedItem:revert()
      end)

      it("removes dropped items owned by the hero", function()
        local itemSpies = m.map(items, function(item)
          return item, { RemoveSelf = spy.on(item, "RemoveSelf") }
        end)

        CombosHero.teardown(player)

        for item, spies in pairs(itemSpies) do
          assert.spy(spies.RemoveSelf).was.self.called()
          assert.is_true(item:IsNull())
        end
      end)
    end)

    describe("with spawned units by the hero", function()
      local hero = create("dota_hero_invoker")
      local player = create("dota_player", { id = 13, hero = hero })
      local units = {}
      local unitSpies = {}

      for _, name in pairs(INVOKER.SPAWNED_UNITS) do
        for _ = 0, 1 do
          local unit = create("dota_unit", { name = name, playerOwner = player })

          units[name] = units[name] or {}

          table.insert(units[name], unit)

          unitSpies[name] = unitSpies[name] or {}
          unitSpies[name][unit] = { RemoveSelf = spy.on(unit, "RemoveSelf") }
        end
      end

      before_each(function()
        stub.new(Entities, "FindByName", function(_, startEnt, name)
          local entIdx = startEnt and startEnt:GetEntityIndex() or 0

          local unitIdx = m.findIndex(units[name], function(unit)
            return unit:GetEntityIndex() > entIdx
          end)

          return units[name][unitIdx]
        end)
      end)

      after_each(function()
        -- selene: allow(incorrect_standard_library_use)
        Entities.FindByName:revert()
      end)

      it("removes spawned units by the hero", function()
        CombosHero.teardown(player)

        for _, group in pairs(unitSpies) do
          for unit, spies in pairs(group) do
            assert.spy(spies.RemoveSelf).was.self.called_with()
            assert.is_true(unit:IsNull())
          end
        end
      end)
    end)

    describe("with soft reset", function()
      it("purges the hero", function()
        local hero = create("dota_hero_invoker")
        local player = create("dota_player", { id = 13, hero = hero })
        local spyHeroPurge = spy.on(hero, "Purge")

        CombosHero.teardown(player)

        assert.spy(spyHeroPurge).was.self.called_with(true, true, false, true, true)
      end)

      it("heals the hero to max health", function()
        local hero = create("dota_hero_invoker", { maxHealth = 1000.0, health = 301.25 })
        local player = create("dota_player", { id = 13, hero = hero })

        assert.equal(301.25, hero:GetHealth())

        CombosHero.teardown(player)

        assert.equal(1000.0, hero:GetHealth())
      end)

      it("gives the hero max mana", function()
        local hero = create("dota_hero_invoker", { maxMana = 1000.0, mana = 301.25 })
        local player = create("dota_player", { id = 13, hero = hero })

        assert.equal(301.25, hero:GetMana())

        CombosHero.teardown(player)

        assert.equal(1000.0, hero:GetMana())
      end)
    end)

    describe("with hard reset", function()
      before_each(function()
        stub.new(PlayerResource, "ReplaceHeroWith")
      end)

      after_each(function()
        -- selene: allow(incorrect_standard_library_use)
        PlayerResource.ReplaceHeroWith:revert()
      end)

      it("removes all hero's items", function()
        local items = { "item_shivas_guard", "item_refresher" }
        local hero = create("dota_hero_invoker", {}, { items = items })
        local player = create("dota_player", { id = 13, hero = hero })

        CombosHero.teardown(player, { hardReset = true })

        assert.same({}, hero.inventory)
      end)

      it("resets hero abilities", function()
        local abilities = {
          [INVOKER.ABILITY_QUAS] = 7,
          [INVOKER.ABILITY_WEX] = 4,
          [INVOKER.ABILITY_EXORT] = 6,
          [INVOKER.ABILITY_COLD_SNAP] = 1,
          [INVOKER.ABILITY_SUN_STRIKE] = 1,
          [INVOKER.ABILITY_TALENT_L10_RIGHT] = 1,
          [INVOKER.ABILITY_TALENT_L10_LEFT] = 0,
          [INVOKER.ABILITY_TALENT_L15_RIGHT] = 1,
          [INVOKER.ABILITY_TALENT_L15_LEFT] = 0,
          [INVOKER.ABILITY_TALENT_L20_RIGHT] = 1,
          [INVOKER.ABILITY_TALENT_L20_LEFT] = 0,
          [INVOKER.ABILITY_TALENT_L25_RIGHT] = 1,
          [INVOKER.ABILITY_TALENT_L25_LEFT] = 0,
        }

        local hero = create("dota_hero_invoker", {}, { abilities = abilities })
        local player = create("dota_player", { id = 13, hero = hero })

        CombosHero.teardown(player, { hardReset = true })

        for _, name in ipairs(INVOKER.ORB_ABILITIES) do
          assert.equal(0, hero:FindAbilityByName(name):GetLevel())
        end

        for _, name in ipairs(INVOKER.TALENT_ABILITIES) do
          assert.equal(0, hero:FindAbilityByName(name):GetLevel())
        end

        assert.equal(1, hero:FindAbilityByName(INVOKER.ABILITY_COLD_SNAP):GetLevel())
        assert.equal(1, hero:FindAbilityByName(INVOKER.ABILITY_SUN_STRIKE):GetLevel())
        assert.equal(1, hero:GetAbilityPoints())
      end)

      it("replaces hero with a new one", function()
        local hero = create("dota_hero_invoker")
        local player = create("dota_player", { id = 13, hero = hero })
        local spyHeroRemoveSelf = spy.on(hero, "RemoveSelf")

        CombosHero.teardown(player, { hardReset = true })

        assert.stub(PlayerResource.ReplaceHeroWith).was.self.called_with(13, INVOKER.UNIT_NAME, 0, 0)

        assert.spy(spyHeroRemoveSelf).was.self.called()
        assert.is_true(hero:IsNull())
      end)
    end)
  end)

  describe(".refundPurchase", function()
    it("refunds a purchase", function()
      local hero = create("dota_hero_invoker")
      local player = create("dota_player", { id = 13, hero = hero })
      local purchase = { item = "item_blink", cost = 1234.5 }

      hero:SetGold(0, true)
      hero:SetGold(0, false)

      assert.equal(0, hero:GetGold())

      CombosHero.refundPurchase(player, purchase)

      assert.equal(1234.5, hero:GetGold())
    end)
  end)

  describe(".levelUp", function()
    local abilities = {
      [INVOKER.ABILITY_QUAS] = 1,
      [INVOKER.ABILITY_WEX] = 1,
      [INVOKER.ABILITY_EXORT] = 1,
      [INVOKER.ABILITY_COLD_SNAP] = 1,
      [INVOKER.ABILITY_SUN_STRIKE] = 1,
    }

    local hero, player

    before_each(function()
      hero = create("dota_hero_invoker", { level = 3, abilityPoints = 0 }, { abilities = abilities })
      player = create("dota_player", { id = 13, hero = hero })
    end)

    describe("with specific level option", function()
      it("levels up the player hero to the specified level", function()
        assert.equal(3, hero:GetLevel())
        assert.equal(0, hero:GetAbilityPoints())

        CombosHero.levelUp(player, { level = 6 })

        assert.equal(6, hero:GetLevel())
        assert.equal(3, hero:GetAbilityPoints())
      end)
    end)

    describe("with empty level option", function()
      it("levels up the player hero by one level", function()
        assert.equal(3, hero:GetLevel())
        assert.equal(0, hero:GetAbilityPoints())

        CombosHero.levelUp(player)

        assert.equal(4, hero:GetLevel())
        assert.equal(1, hero:GetAbilityPoints())
      end)
    end)

    describe("with maxLevel option", function()
      it("levels up the player hero, abilities and talents to max level", function()
        assert.equal(3, hero:GetLevel())
        assert.equal(0, hero:GetAbilityPoints())

        CombosHero.levelUp(player, { maxLevel = true })

        assert.equal(LIMITS.MAX_HERO_LEVEL, hero:GetLevel())
        assert.equal(0, hero:GetAbilityPoints())

        for _, name in ipairs(INVOKER.ORB_ABILITIES) do
          local ability = hero:FindAbilityByName(name)
          assert.equal(ability:GetMaxLevel(), ability:GetLevel())
        end

        for _, name in ipairs(INVOKER.TALENT_ABILITIES) do
          local ability = hero:FindAbilityByName(name)
          assert.equal(ability:GetMaxLevel(), ability:GetLevel())
        end

        assert.equal(1, hero:FindAbilityByName(INVOKER.ABILITY_COLD_SNAP):GetLevel())
        assert.equal(1, hero:FindAbilityByName(INVOKER.ABILITY_SUN_STRIKE):GetLevel())
      end)
    end)
  end)
end)
