MailOnline/videojs-vast-vpaid

View on GitHub
test/ads/vpaid/VPAIDIntegrator.spec.js

Summary

Maintainability
F
1 wk
Test Coverage
var VPAIDAdUnitWrapper = require('ads/vpaid/VPAIDAdUnitWrapper');
var VPAIDIntegrator = require('ads/vpaid/VPAIDIntegrator');
var VPAIDFlashTech = require('ads/vpaid/VPAIDFlashTech');
var VPAIDHTML5Tech = require('ads/vpaid/VPAIDHTML5Tech');

var MediaFile = require('ads/vast/MediaFile');
var VASTError = require('ads/vast/VASTError');
var VASTResponse = require('ads/vast/VASTResponse');
var VASTTracker = require('ads/vast/VASTTracker');
var vastUtil = require('ads/vast/vastUtil');

var dom = require('utils/dom');
var utilities = require('utils/utilityFunctions');
var xml = require('utils/xml');

var testUtils = require('../../test-utils');

describe("VPAIDIntegrator", function () {
  var player, vpaidAdUnit, adUnitWrapper, testDiv;

  function FakeAdUnit() {
    var events = {};
    this.options = {
      src: 'fakeSrc'
    };
    this.volume = 0;
    this.isSkippable = false;

    this.resizeAd = sinon.spy();
    this.skipAd = sinon.spy();
    this.setVolume = function(vol) {
      this.volume = vol;
    };

    this.getAdVolume = function(fn) {
      var vol = this.volume;
      window.setTimeout(function() {
        fn(null, vol);
      }, 0);

    };

    this.on = function(evtName, handler) {
      if(!events[evtName]){
        events[evtName] = [];
      }

      events[evtName].push(handler);
    };

    this.getAdSkippableState = function(fn) {
      var skippable = this.isSkippable;
      window.setTimeout(function() {
        fn(null, skippable);
      }, 0);
    };

    this.trigger = function() {
      var args = utilities.arrayLikeObjToArray(arguments);
      var evtName = args.shift();
      var handlers = events[evtName];

      if(handlers){
        handlers.forEach(function(handler) {
          handler.apply(null, args);
        });
      }
    };

    this.pauseAd = sinon.spy();
    this.resumeAd = sinon.spy();
  }

  beforeEach(function () {

    testDiv = document.createElement("div");
    document.body.appendChild(testDiv);
    var videoEl = document.createElement('video');
    testDiv.appendChild(videoEl);

    player = videojs(videoEl, {});
    dom.addClass(player.el(), 'vjs-test-player');
    vpaidAdUnit = {
      'handshakeVersion': utilities.noop,
      'initAd': utilities.noop,
      'startAd': utilities.noop,
      'stopAd': utilities.noop,
      'skipAd': utilities.noop,
      'resizeAd': utilities.noop,
      'pauseAd': utilities.noop,
      'expandAd': utilities.noop,
      'collapseAd': utilities.noop,
      'subscribe': utilities.noop,
      'unsubscribe': utilities.noop,
      'unloadAdUnit': utilities.noop,
      'on': sinon.spy()
    };
    adUnitWrapper = new VPAIDAdUnitWrapper(vpaidAdUnit, {responseTimeout: 5000});
  });

  afterEach(function () {
    dom.remove(testDiv);
  });

  it("must return an instance of itself", function () {
    assert.instanceOf(new VPAIDIntegrator(player), VPAIDIntegrator);
  });

  describe("instance", function () {
    var vpaidIntegrator, callback, FakeTech, vastResponse;

    function createMediaFile(url, type) {
      var xmlStr = '<MediaFile delivery="progressive" type="' + type + '" apiFramework="VPAID">' +
        '<![CDATA[' + url + ']]>' +
        '</MediaFile>';
      return new MediaFile(xml.toJXONTree(xmlStr));
    }

    beforeEach(function () {
      var mediaFile = createMediaFile('http://fakeMediaFile', 'application/x-fake');
      vpaidIntegrator = new VPAIDIntegrator(player, {autoResize: true});
      callback = sinon.spy();

      FakeTech = function () {

      };

      FakeTech.prototype.name = 'vpaid-fake';
      FakeTech.prototype.loadAdUnit = sinon.spy();
      FakeTech.prototype.unloadAdUnit = sinon.spy();
      FakeTech.prototype.mediaFile = mediaFile;
      FakeTech.supports = sinon.stub();

      vastResponse = new VASTResponse();
      vastResponse._addMediaFiles([mediaFile]);
      this.clock = sinon.useFakeTimers();

    });

    afterEach(function () {
      this.clock.restore();
    });

    describe("playAd", function () {
      var loadAdUnit, playAdUnit, finishPlaying;

      beforeEach(function () {
        FakeTech.supports.returns(true);
        vastUtil.VPAID_techs.unshift(FakeTech);

        loadAdUnit = testUtils.stubAsyncStep(vpaidIntegrator, '_loadAdUnit', this.clock);
        playAdUnit = testUtils.stubAsyncStep(vpaidIntegrator, '_playAdUnit', this.clock);
        finishPlaying = testUtils.stubAsyncStep(vpaidIntegrator, '_finishPlaying', this.clock);
      });

      afterEach(function () {
        vastUtil.VPAID_techs.shift();
      });

      it("must complain if you don't pass a VASTResponse", function () {
        vpaidIntegrator.playAd(null, callback);
        sinon.assert.calledOnce(callback);
        var error = testUtils.firstArg(callback);
        assert.instanceOf(error, VASTError);
        assert.equal(error.message, 'VAST Error: on VASTIntegrator.playAd, missing required VASTResponse');
      });

      it("must trigger an error if could not find a supported mediaFile", function () {
        sinon.stub(vastUtil, 'track');
        vastResponse._addErrorTrackUrl(xml.toJXONTree('<Error><![CDATA[https://fakeErrorUrl&error_code=[ERRORCODE]]]></Error>'));

        FakeTech.supports.returns(false);
        vpaidIntegrator.playAd(vastResponse, callback);
        sinon.assert.calledOnce(callback);
        var error = testUtils.firstArg(callback);
        assert.instanceOf(error, VASTError);
        assert.equal(error.message, 'VAST Error: on VPAIDIntegrator.playAd, could not find a supported mediaFile');
        sinon.assert.calledWithExactly(vastUtil.track, ["https://fakeErrorUrl&error_code=[ERRORCODE]"], { ERRORCODE: 403 });
        vastUtil.track.restore();
      });

      it("must trigger a vpaid.adEnd evt on vast.adsCancel evt", function(){
        var spy = sinon.spy();
        player.on('vpaid.adEnd', spy);
        vpaidIntegrator.playAd(vastResponse, callback);
        player.trigger('vast.adsCancel');
        assert.isTrue(spy.calledOnce);
      });

      it("must NOT trigger a vpaid.adEnd evt twice", function(){
        var spy = sinon.spy();
        player.on('vpaid.adEnd', spy);
        vpaidIntegrator.playAd(vastResponse, callback);
        player.trigger('vast.adsCancel');
        player.trigger('vast.adsCancel');
        assert.isTrue(spy.calledOnce);
      });

      it("must add 'vjs-vpaid-ad' class to the player element", function () {
        assert.isFalse(dom.hasClass(player.el(), 'vjs-vpaid-ad'));
        vpaidIntegrator.playAd(vastResponse, callback);
        assert.isTrue(dom.hasClass(player.el(), 'vjs-vpaid-ad'));
      });

      it("must remove 'vjs-vpaid-ad' class once the adUnit finish playing", function () {
        vpaidIntegrator.playAd(vastResponse, callback);
        this.clock.tick();

        loadAdUnit.flush(null, vpaidAdUnit, vastResponse);
        playAdUnit.flush(null, vpaidAdUnit, vastResponse);
        finishPlaying.flush(null, vpaidAdUnit, vastResponse);

        assert.isFalse(dom.hasClass(player.el(), 'vjs-vpaid-ad'));
      });

      ['vast.adsCancel', 'vpaid.adEnd'].forEach(function (evt) {
        it("must remove 'vjs-vpaid-ad' class if there is  an '"+evt+"' event", function () {
          vpaidIntegrator.playAd(vastResponse, callback);
          player.trigger(evt);
          this.clock.tick();
          assert.isFalse(dom.hasClass(player.el(), 'vjs-vpaid-ad'));
        });

        it("must unload the adUnit if there is an '"+evt+"' event", function () {
          vpaidIntegrator.playAd(vastResponse, callback);
          player.trigger(evt);
          this.clock.tick();
          sinon.assert.calledOnce(FakeTech.prototype.unloadAdUnit);
        });
      });

      it("must unload the adUnit if the ad finishes playing", function () {
        vpaidIntegrator.playAd(vastResponse, callback);
        this.clock.tick();
        loadAdUnit.flush(null, vpaidAdUnit, vastResponse);
        playAdUnit.flush(null, vpaidAdUnit, vastResponse);
        finishPlaying.flush(null, vpaidAdUnit, vastResponse);
        sinon.assert.calledOnce(FakeTech.prototype.unloadAdUnit);
      });

      it("must trigger 'vpaid.adEnd'", function () {
        player.on('vpaid.adEnd', callback);
        vpaidIntegrator.playAd(vastResponse, utilities.noop);
        this.clock.tick();
        loadAdUnit.flush(null, vpaidAdUnit, vastResponse);
        playAdUnit.flush(null, vpaidAdUnit, vastResponse);
        finishPlaying.flush(null, vpaidAdUnit, vastResponse);

        sinon.assert.calledOnce(callback);
      });

      describe("return obj", function(){
        it("must have the type VPAID", function(){
          var adUnit = vpaidIntegrator.playAd(vastResponse, utilities.noop);
          assert.equal(adUnit.type, 'VPAID');
        });

        it("must trigger the vpaid.pauseAd evt", function(){
          var adUnit = vpaidIntegrator.playAd(vastResponse, utilities.noop);
          var spy = sinon.spy();
          sinon.stub(player, 'pause');
          player.on('vpaid.pauseAd', spy);
          adUnit.pauseAd();
          sinon.assert.calledOnce(spy);
          sinon.assert.calledOnce(player.pause);
          player.pause.restore();
        });

        it("must trigger the vpaid.resumeAd evt", function(){
          var adUnit =vpaidIntegrator.playAd(vastResponse, utilities.noop);
          var spy = sinon.spy();
          player.on('vpaid.resumeAd', spy);
          adUnit.resumeAd();
          sinon.assert.calledOnce(spy);
        });

        it("must know if it is paused", function(){
          var adUnit = vpaidIntegrator.playAd(vastResponse, utilities.noop);
          assert(adUnit.isPaused());
          adUnit._paused = false;
          assert(!adUnit.isPaused());
        });

        it("must be able to return the source of the ad", function(){
          var adUnit = vpaidIntegrator.playAd(vastResponse, utilities.noop);
          assert.equal(adUnit.getSrc(), FakeTech.prototype.mediaFile);
        });
      });
    });

    describe("loadAdUnit", function () {
      it("must pass the containerEl", function () {
        var testTech = new FakeTech();
        vpaidIntegrator._loadAdUnit(testTech, vastResponse, callback);
        sinon.assert.calledWithExactly(testTech.loadAdUnit, vpaidIntegrator.containerEl, player.el().querySelector('.vjs-tech'), sinon.match.func);
      });

      it("must pass the error if there is an error loading the ad unit", function(){
        var testTech = new FakeTech();
        vpaidIntegrator._loadAdUnit(testTech, vastResponse, callback);
        var techLoadAdUnitCb = testUtils.thirdArg(testTech.loadAdUnit);
        var fakeTechNativeError = new Error('error loading the ad unit.');
        techLoadAdUnitCb(fakeTechNativeError, undefined);
        sinon.assert.calledWithExactly(callback, fakeTechNativeError, undefined, vastResponse);
      });

      it("must pass the error, a wrapped adUnit and the vast response to the callback", function () {
        var testTech = new FakeTech();
        vpaidIntegrator._loadAdUnit(testTech, vastResponse, callback);
        var techLoadAdUnitCb = testUtils.thirdArg(testTech.loadAdUnit);
        techLoadAdUnitCb(null, vpaidAdUnit);
        sinon.assert.calledWithExactly(callback, null, sinon.match.instanceOf(VPAIDAdUnitWrapper), vastResponse);
      });

      it("must pass the error if there is a problem creating the VPAIDAdUnitWrapper", function(){
        var testTech = new FakeTech();
        vpaidIntegrator._loadAdUnit(testTech, vastResponse, callback);
        var techLoadAdUnitCb = testUtils.thirdArg(testTech.loadAdUnit);
        //We make the adUnit invalid
        vpaidAdUnit.initAd = undefined;

        techLoadAdUnitCb(null, vpaidAdUnit);
        sinon.assert.calledWithExactly(callback, sinon.match.instanceOf(VASTError), vpaidAdUnit, vastResponse);
        var error = testUtils.firstArg(callback);
        assert.equal(error.message, 'VAST Error: on VPAIDAdUnitWrapper, the passed VPAID adUnit does not fully implement the VPAID interface');
      });

      it("must add the tech class to the player and remove it on 'vpaid.adEnd' event",function(){
        var testTech = new FakeTech();
        var fakeAdUnit = {};
        sinon.stub(VPAIDIntegrator.prototype, '_createVPAIDAdUnitWrapper').returns(fakeAdUnit);
        vpaidIntegrator._loadAdUnit(testTech, vastResponse, callback);
        var techLoadAdUnitCb = testUtils.thirdArg(testTech.loadAdUnit);
        //We make the adUnit invalid
        vpaidAdUnit.initAd = undefined;

        techLoadAdUnitCb(null, vpaidAdUnit);
        sinon.assert.calledWithExactly(callback, null, fakeAdUnit, vastResponse);
        assert.isTrue(dom.hasClass(player.el(), 'vjs-vpaid-fake-ad'));
        player.trigger('vpaid.adEnd');
        assert.isFalse(dom.hasClass(player.el(), 'vjs-vpaid-fake-ad'));
        VPAIDIntegrator.prototype._createVPAIDAdUnitWrapper.restore();
      });
    });

    describe("playAdUnit", function () {
      var initAd, setupEvents, startAd, handshake;

      beforeEach(function () {
        handshake = testUtils.stubAsyncStep(vpaidIntegrator, '_handshake', this.clock);
        initAd = testUtils.stubAsyncStep(vpaidIntegrator, '_initAd', this.clock);
        setupEvents = testUtils.stubAsyncStep(vpaidIntegrator, '_setupEvents', this.clock);
        startAd = testUtils.stubAsyncStep(vpaidIntegrator, '_startAd', this.clock);
      });

      it("must exec the steps to play the adUnit", function () {
        vpaidIntegrator._playAdUnit(vpaidAdUnit, vastResponse, callback);
        this.clock.tick();
        handshake.flush(null, vpaidAdUnit, vastResponse);
        initAd.flush(null, vpaidAdUnit, vastResponse);
        setupEvents.flush(null, vpaidAdUnit, vastResponse);
        startAd.flush(null, vpaidAdUnit, vastResponse);

        sinon.assert.calledOnce(handshake.stub());
        sinon.assert.calledOnce(initAd.stub());
        sinon.assert.calledOnce(setupEvents.stub());
        sinon.assert.calledOnce(startAd.stub());
      });

      it("must call the adUnit with the error, adUnit and vastResponse", function () {
        vpaidIntegrator._playAdUnit(vpaidAdUnit, vastResponse, callback);
        this.clock.tick();
        handshake.flush(null, vpaidAdUnit, vastResponse);
        initAd.flush(null, vpaidAdUnit, vastResponse);
        setupEvents.flush(null, vpaidAdUnit, vastResponse);
        startAd.flush(null, vpaidAdUnit, vastResponse);

        sinon.assert.calledWithExactly(callback, null, vpaidAdUnit, vastResponse);
      });
    });

    describe("handshake", function () {
      var next, response;
      beforeEach(function () {
        sinon.spy(vpaidAdUnit, 'handshakeVersion');
        next = sinon.spy();
        response = new VASTResponse();

      });

      it("must pass an error to the callback if the VPAID version is smaller than 1.0", function () {
        vpaidIntegrator._handshake(adUnitWrapper, response, next);
        sinon.assert.calledWith(vpaidAdUnit.handshakeVersion, '2.0');
        var respond = testUtils.secondArg(vpaidAdUnit.handshakeVersion);
        respond(null, '0.0.0');
        sinon.assert.calledOnce(next);
        var error = testUtils.firstArg(next);
        assert.instanceOf(error, VASTError);
        assert.equal(error.message, 'VAST Error: on VPAIDIntegrator._handshake, unsupported version "0.0.0"');
      });

      it("must pass an error to the callback if the VPAID version is bigger than 2.x.x", function () {
        vpaidIntegrator._handshake(adUnitWrapper, response, next);
        sinon.assert.calledWith(vpaidAdUnit.handshakeVersion, '2.0');
        var respond = testUtils.secondArg(vpaidAdUnit.handshakeVersion);
        respond(null, '3.0.0');
        sinon.assert.calledOnce(next);
        var error = testUtils.firstArg(next);
        assert.instanceOf(error, VASTError);
        assert.equal(error.message, 'VAST Error: on VPAIDIntegrator._handshake, unsupported version "3.0.0"');
      });

      it("must pass an error to the callback if the handshake returns an error", function () {
        vpaidIntegrator._handshake(adUnitWrapper, response, next);
        sinon.assert.calledWith(vpaidAdUnit.handshakeVersion, '2.0');
        var respond = testUtils.secondArg(vpaidAdUnit.handshakeVersion);
        var fakeError = new Error();
        respond(fakeError);
        sinon.assert.calledWith(next, fakeError);
      });

      it("must call the callback with null and the adUnit and the VASTResponse if the version is supported", function () {
        vpaidIntegrator._handshake(adUnitWrapper, response, next);
        sinon.assert.calledWith(vpaidAdUnit.handshakeVersion, '2.0');
        var respond = testUtils.secondArg(vpaidAdUnit.handshakeVersion);
        respond(null, '2.0');
        sinon.assert.calledOnce(next);
        sinon.assert.calledWithExactly(next, null, adUnitWrapper, response);
      });
    });

    describe("initAd", function () {
      var response;

      beforeEach(function () {
        response = new VASTResponse();
        sinon.spy(adUnitWrapper, 'initAd');
        sinon.stub(dom, 'getDimension').returns(
          {
            width: 720,
            height: 480
          }
        );
      });

      afterEach(function(){
        dom.getDimension.restore();
      });

      it("must call pass the with, height, viewmode, desired bitrate, and creativeData to the adUnit's initAd", function () {
        var next = sinon.spy();
        vpaidIntegrator._initAd(adUnitWrapper, response, next);
        sinon.assert.calledWithExactly(adUnitWrapper.initAd, 720, 480, 'normal', -1, sinon.match({AdParameters: ''}), sinon.match.func);
      });

      it("must pass the add parameters if present on the VASTResponse", function () {
        var next = sinon.spy();
        response.adParameters = 'some params';
        vpaidIntegrator._initAd(adUnitWrapper, response, next);
        sinon.assert.calledWithExactly(adUnitWrapper.initAd, 720, 480, 'normal', -1, sinon.match({AdParameters: 'some params'}), sinon.match.func);
      });

      it("must propagate any error that may come from the adUnit and pass the adUnit and the VASTResponse", function () {
        var next = sinon.spy();
        var fakeError = new Error();
        vpaidIntegrator._initAd(adUnitWrapper, response, next);
        var respond = testUtils.lastArg(adUnitWrapper.initAd);
        respond(fakeError);
        sinon.assert.calledWith(next, fakeError, adUnitWrapper, response);
      });
    });

    describe("setupEvents", function(){
      var tracker, adUnit, vastResponse, next;

      beforeEach(function(){
        tracker = sinon.createStubInstance(VASTTracker);
        sinon.stub(VPAIDIntegrator.prototype, '_createVASTTracker', function() {
          return tracker;
        });
        adUnit = new FakeAdUnit();
        vastResponse = new VASTResponse();
        next = sinon.spy();
        vpaidIntegrator._setupEvents(adUnit, vastResponse, next);
      });

      afterEach(function(){
        VPAIDIntegrator.prototype._createVASTTracker.restore();
      });

      it("must call next with no error and the passed adUnit and vastResponse", function(){
        sinon.assert.calledWithExactly(next, null, adUnit, vastResponse);
      });

      it("must create a tracker passing the adUnit src and the vast response", function(){
        sinon.assert.calledWithExactly(VPAIDIntegrator.prototype._createVASTTracker, adUnit.options.src, vastResponse);
      });

      it("must propagate adUnit events prepending the prefix 'vpaid.' to the evt type", function(){
        [
          'AdSkipped',
          'AdImpression',
          'AdStarted',
          'AdVideoStart',
          'AdPlaying',
          'AdPaused',
          'AdVideoFirstQuartile',
          'AdVideoMidpoint',
          'AdVideoThirdQuartile',
          'AdVideoComplete',
          'AdClickThru',
          'AdUserAcceptInvitation',
          'AdUserClose',
          'AdUserMinimize',
          'AdError',
          'AdVolumeChange'
        ].forEach(function (evtType) {
          var spy = sinon.spy();
          player.on('vpaid.'+ evtType, spy);
          adUnit.trigger(evtType, {});
          sinon.assert.calledOnce(spy);
        });

      });

      it("on 'AdSkipped' event, must track skip", function(){
        adUnit.trigger('AdSkipped');
        sinon.assert.calledOnce(tracker.trackSkip);
      });

      it("on 'AdImpression' event, must track impressions", function(){
        adUnit.trigger('AdImpression');
        sinon.assert.calledOnce(tracker.trackImpressions);
      });

      it("on 'AdVideoStart' event, must track start, resume the adUnit and trigger 'play' evt", function(){
        var playSpy = sinon.spy();
        player.on('play', playSpy);
        vpaidIntegrator._adUnit = {
          isPaused: utilities.echoFn(true)
        };
        adUnit.trigger('AdVideoStart');
        sinon.assert.calledOnce(tracker.trackStart);

        assert.isFalse(vpaidIntegrator._adUnit._paused);
        assert(playSpy.calledOnce);
      });

      it("on 'AdStarted' event, must track creativeView, resume the adUnit and trigger 'play' evt", function(){
        var playSpy = sinon.spy();
        player.on('play', playSpy);
        vpaidIntegrator._adUnit = {
          isPaused: utilities.echoFn(true)
        };
        adUnit.trigger('AdStarted');
        sinon.assert.calledOnce(tracker.trackCreativeView);
        assert.isFalse(vpaidIntegrator._adUnit._paused);
        assert(playSpy.calledOnce);
      });

      it("on 'AdVideoFirstQuartile' event, must track first quartile", function(){
        adUnit.trigger('AdVideoFirstQuartile');
        sinon.assert.calledOnce(tracker.trackFirstQuartile);
      });

      it("on 'AdVideoMidpoint' event, must track midpoint", function(){
        adUnit.trigger('AdVideoMidpoint');
        sinon.assert.calledOnce(tracker.trackMidpoint);
      });

      it("on 'AdVideoThirdQuartile' event, must track third quartile", function(){
        adUnit.trigger('AdVideoThirdQuartile');
        sinon.assert.calledOnce(tracker.trackThirdQuartile);
      });

      it("on 'AdVideoComplete' event, must track complete", function(){
        adUnit.trigger('AdVideoComplete');
        sinon.assert.calledOnce(tracker.trackComplete);
      });

      describe("on 'AdClickThru',", function(){
        beforeEach(function(){
          sinon.stub(window, 'open');
        });

        afterEach(function(){
          window.open.restore();
        });

        it("must track click", function(){
          adUnit.trigger('AdClickThru', {});
          sinon.assert.calledOnce(tracker.trackClick);
        });

        it("must not open a new window if the playerHandles is false", function(){
          adUnit.trigger('AdClickThru', {url: 'fake/click/thru/url', playerHandles: false });
          sinon.assert.notCalled(window.open);
        });

        it("must open the url passed if the playerHandles flag is true", function(){
          adUnit.trigger('AdClickThru', {url: 'fake/click/thru/url', playerHandles: true });
          sinon.assert.calledWithExactly(window.open, 'fake/click/thru/url', '_blank');
        });

        it("must no open any window if there is no click thru url passed or in the vastResponse", function(){
          adUnit.trigger('AdClickThru', '', 1, true);
          sinon.assert.notCalled(window.open);
        });

        it("must use the vastResponse clickThru macro if no url is passed", function(){
          vastResponse.clickThrough = 'fake/click/thru/url/[ASSETURI]';
          adUnit.trigger('AdClickThru', {url: null, playerHandles: true });
          sinon.assert.calledWithExactly(window.open, 'fake/click/thru/url/fakeSrc', '_blank');
        });
      });

      it("on 'AdUserAcceptInvitation' event, must track acceptInvitation", function(){
        adUnit.trigger('AdUserAcceptInvitation');
        sinon.assert.calledOnce(tracker.trackAcceptInvitation);
        sinon.assert.calledOnce(tracker.trackAcceptInvitationLinear);
      });

      it("on 'AdUserClose' event, must track close", function(){
        adUnit.trigger('AdUserClose');
        sinon.assert.calledOnce(tracker.trackClose);
        sinon.assert.calledOnce(tracker.trackCloseLinear);
      });

      it("on 'AdPaused' event, must track pause, pause the adUnit and trigger 'pause' evt", function(){
        var pauseSpy = sinon.spy();
        player.on('pause', pauseSpy);
        vpaidIntegrator._adUnit = {};
        adUnit.trigger('AdPaused');
        sinon.assert.calledOnce(tracker.trackPause);

        assert(vpaidIntegrator._adUnit._paused);
        assert(pauseSpy.calledOnce);
      });

      it("on 'AdUserMinimize' event, must track collapse", function(){
        adUnit.trigger('AdUserMinimize');
        sinon.assert.calledOnce(tracker.trackCollapse);
      });

      it("on 'AdError' event, must track error with code 901", function(){
        adUnit.trigger('AdError');
        sinon.assert.calledWithExactly(tracker.trackErrorWithCode, 901);
      });

      it("on 'AdPlaying' event, must track resume, resume the adUnit and trigger 'play' evt", function(){
          var playSpy = sinon.spy();
          player.on('play', playSpy);
          vpaidIntegrator._adUnit = {
            isPaused: utilities.echoFn(true)
          };
          adUnit.trigger('AdPlaying');
          sinon.assert.calledOnce(tracker.trackResume);

          assert.isFalse(vpaidIntegrator._adUnit._paused);
          assert(playSpy.calledOnce);
      });

      describe("on 'AdVolumeChange' evt,", function(){
        beforeEach(function(){
          this.clock = sinon.useFakeTimers();
          sinon.stub(player, 'volume');
        });

        afterEach(function(){
          this.clock.restore();
        });

        it("must track mute if the volume was not 0 but gets updated to 0", function(){
          player.volume.returns(10);
          adUnit.setVolume(0);
          adUnit.trigger('AdVolumeChange');
          this.clock.tick();
          sinon.assert.calledOnce(tracker.trackMute);
          sinon.assert.notCalled(tracker.trackUnmute);
          sinon.assert.calledWithExactly(player.volume, 0);
        });

        it("must not track mute if the volume was already 0", function(){
          player.volume.returns(0);
          adUnit.setVolume(0);
          adUnit.trigger('AdVolumeChange');
          this.clock.tick();
          sinon.assert.notCalled(tracker.trackMute);
          sinon.assert.notCalled(tracker.trackUnmute);
        });

        it("must tack unmute if the volume was 0 and changes to not cero", function(){
          player.volume.returns(0);
          adUnit.setVolume(10);
          adUnit.trigger('AdVolumeChange');
          this.clock.tick();
          sinon.assert.notCalled(tracker.trackMute);
          sinon.assert.calledOnce(tracker.trackUnmute);
          sinon.assert.calledWithExactly(player.volume, 10);
        });
        it("must not tack unmute if the volume was not 0 and changes to not cero", function(){
          player.volume.returns(5);
          adUnit.setVolume(10);
          adUnit.trigger('AdVolumeChange');
          this.clock.tick();
          sinon.assert.notCalled(tracker.trackMute);
          sinon.assert.notCalled(tracker.trackUnmute);
          sinon.assert.calledWithExactly(player.volume, 10);
        });
      });

      it("must pause the ad unit on 'vpaid.pauseAd' evt", function(){
        player.trigger('vpaid.pauseAd');
        sinon.assert.calledOnce(adUnit.pauseAd);
      });

      it("must pause the ad unit on 'vpaid.resumeAd' evt", function(){
        player.trigger('vpaid.resumeAd');
        sinon.assert.calledOnce(adUnit.resumeAd);
      });

      it("must not pause or resume the adUnit after 'vaid.adEnd' event", function(){
        player.trigger('vpaid.adEnd');
        player.trigger('vpaid.resumeAd');
        player.trigger('vpaid.pauseAd');
        sinon.assert.notCalled(adUnit.resumeAd);
        sinon.assert.notCalled(adUnit.pauseAd);
      });
    });

    describe("addSkipButton", function(){
      var adUnit, vastResponse, next;

      beforeEach(function(){
        this.clock = sinon.useFakeTimers();
        adUnit = new FakeAdUnit();
        vastResponse = new VASTResponse();
        next = sinon.spy();
        vpaidIntegrator._addSkipButton(adUnit, vastResponse, next);
      });

      afterEach(function(){
        this.clock.restore();
      });

      it("must call next with no error and the passed adUnit and vastResponse", function(){
        sinon.assert.calledWithExactly(next, null, adUnit, vastResponse);
      });

      describe("on 'AdSkippableStateChange'", function(){
        it("must add the skip button if the adUnit is skippable", function(){
          adUnit.isSkippable = true;
          adUnit.trigger('AdSkippableStateChange');
          this.clock.tick();

          assert.isNotNull(player.el().querySelector('.vast-skip-button'));
        });

        it("must only add one skip button no matter how many 'AdSkippableStateChange' evts we receive while the adUnit is skippable", function(){
          adUnit.isSkippable = true;
          adUnit.trigger('AdSkippableStateChange');
          this.clock.tick();
          adUnit.trigger('AdSkippableStateChange');
          this.clock.tick();
          adUnit.trigger('AdSkippableStateChange');
          this.clock.tick();
          adUnit.trigger('AdSkippableStateChange');
          this.clock.tick();

          assert.equal(player.el().querySelectorAll('.vast-skip-button').length, 1);
        });

        it("must remove the skip button if the adUnit is no longer skippable", function(){
          adUnit.isSkippable = true;
          adUnit.trigger('AdSkippableStateChange');
          this.clock.tick();
          adUnit.isSkippable = false;
          adUnit.trigger('AdSkippableStateChange');
          this.clock.tick();
          assert.isNull(player.el().querySelector('.vast-skip-button'));

        });

        it("must remove the adUnit when you click on the skip button", function(){
          adUnit.isSkippable = true;
          adUnit.trigger('AdSkippableStateChange');
          this.clock.tick();

          var skipButton = player.el().querySelector('.vast-skip-button');
          testUtils.click(skipButton);
          sinon.assert.calledOnce(adUnit.skipAd);
        });

        it("must remove the adUnit on 'vast.adEnd' event", function(){
          adUnit.isSkippable = true;
          adUnit.trigger('AdSkippableStateChange');
          this.clock.tick();
          adUnit.isSkippable = false;
          player.trigger('vast.adEnd');
          this.clock.tick();
          assert.isNull(player.el().querySelector('.vast-skip-button'));
        });

        it("must remove the adUnit on 'vast.adsCancel' event", function(){
          adUnit.isSkippable = true;
          adUnit.trigger('AdSkippableStateChange');
          this.clock.tick();
          adUnit.isSkippable = false;
          player.trigger('vast.adsCancel');
          this.clock.tick();
          assert.isNull(player.el().querySelector('.vast-skip-button'));
        });
      });

    });

    describe("findSupportedTech", function () {
      var FLASH_APP_MIME = 'application/x-shockwave-flash';
      var JS_APP_MIME = 'application/javascript';
      var originalFlash, originalHtml5;

      beforeEach(function() {
        originalFlash = VPAIDFlashTech.VPAIDFLASHClient;
        originalHtml5 = VPAIDHTML5Tech.VPAIDHTML5Client;
        VPAIDFlashTech.VPAIDFLASHClient = {
          isSupported: function() {
            return true;
          }
        };

        VPAIDHTML5Tech.VPAIDHTML5Client = {
          isSupported: function() {
            return true;
          }
        };
      });

      afterEach(function() {
        VPAIDFlashTech.VPAIDFLASHClient = originalFlash;
        VPAIDHTML5Tech.VPAIDHTML5Client = originalHtml5;
      });

      it("must return null if you pass a wrong vastREsponse", function () {
        [undefined, null, [], {}, ''].forEach(function (wrongResponse) {
          assert.isNull(vpaidIntegrator._findSupportedTech(wrongResponse));
        });
      });

      it("must return null if the passed vast response is not supported", function () {
        var vastResponse = new VASTResponse();
        assert.isNull(vpaidIntegrator._findSupportedTech(vastResponse));

        vastResponse._addMediaFiles([createMediaFile('http://fakeVideoFile', 'application/fake-type')]);
        assert.isNull(vpaidIntegrator._findSupportedTech(vastResponse));
      });

      it("must return null if the tech is not supported", function () {
        var vastResponse = new VASTResponse();
        vastResponse._addMediaFiles([createMediaFile('http://fakeVideoFile', FLASH_APP_MIME)]);

        sinon.stub(VPAIDFlashTech.VPAIDFLASHClient, 'isSupported', function () {return false;});

        assert.isNull(vpaidIntegrator._findSupportedTech(vastResponse));
      });

      it("must return an instance of the supported tech", function () {
        var vastResponse = new VASTResponse();

        vastResponse._addMediaFiles([createMediaFile('http://fakeVideoFile', FLASH_APP_MIME)]);

        assert.instanceOf(vpaidIntegrator._findSupportedTech(vastResponse), VPAIDFlashTech);
      });

      it("must pass the settings to the to the created tech", function(){
        var settings = {vpaidFlashLoaderPath: '/VPAIDFlash.swf'};
        var vastResponse = new VASTResponse();
        vastResponse._addMediaFiles([createMediaFile('http://fakeVideoFile', FLASH_APP_MIME)]);
        var flashTech = vpaidIntegrator._findSupportedTech(vastResponse, settings);
        assert.deepEqual(flashTech.settings, settings);
      });

      it("must return preferred tech if supported and available", function () {
        var settings = {preferredTech: 'html5'};
        var vastResponse = new VASTResponse();
        vastResponse._addMediaFiles([createMediaFile('http://fakeVideoFile', FLASH_APP_MIME), createMediaFile('http://anotherFakeVideoFile', JS_APP_MIME)]);
        assert.instanceOf(vpaidIntegrator._findSupportedTech(vastResponse, settings), VPAIDHTML5Tech);
      });

      it("must return preferred tech if supported and available", function () {
        var settings = {preferredTech: 'flash'};
        var vastResponse = new VASTResponse();
        vastResponse._addMediaFiles([createMediaFile('http://fakeVideoFile', FLASH_APP_MIME), createMediaFile('http://anotherFakeVideoFile', JS_APP_MIME)]);
        assert.instanceOf(vpaidIntegrator._findSupportedTech(vastResponse, settings), VPAIDFlashTech);
      });

      it("must return supported tech even if preferred tech is not available", function () {
        var settings = {preferredTech: 'html5'};
        var vastResponse = new VASTResponse();
        vastResponse._addMediaFiles([createMediaFile('http://fakeVideoFile', FLASH_APP_MIME)]);
        assert.instanceOf(vpaidIntegrator._findSupportedTech(vastResponse, settings), VPAIDFlashTech);
      });

      it("must return available, supported tech if preferred tech is not supported", function () {
        var settings = {preferredTech: 'flash'};

        VPAIDFlashTech.VPAIDFLASHClient = {
          isSupported: function() {
            return false;
          }
        };

        var vastResponse = new VASTResponse();
        vastResponse._addMediaFiles([createMediaFile('http://fakeVideoFile', FLASH_APP_MIME), createMediaFile('http://anotherFakeVideoFile', JS_APP_MIME)]);
        assert.instanceOf(vpaidIntegrator._findSupportedTech(vastResponse, settings), VPAIDHTML5Tech);
      });
    });

    describe("linkPlayerControls", function () {
      describe("volume control", function () {
        beforeEach(function () {
          sinon.stub(adUnitWrapper, 'setAdVolume');
          sinon.stub(adUnitWrapper, 'getAdVolume');
          sinon.stub(adUnitWrapper, 'on');
          sinon.stub(player, 'volume').returns(0.5);
        });

        it("must call the callback with null, adUnit and vast response", function () {
          vpaidIntegrator._linkPlayerControls(adUnitWrapper, vastResponse, callback);
          sinon.assert.calledWithExactly(callback, null, adUnitWrapper, vastResponse);
        });

        it("must update the adUnit volume on 'volumechange'", function () {
          vpaidIntegrator._linkPlayerControls(adUnitWrapper, vastResponse, callback);
          player.trigger('volumechange');
          sinon.assert.calledWith(adUnitWrapper.setAdVolume, 0.5);
        });

        it("must update the adUnit volume to 0 if the player is muted", function(){
          sinon.stub(player, 'muted').returns(true);
          vpaidIntegrator._linkPlayerControls(adUnitWrapper, vastResponse, callback);
          player.trigger('volumechange');
          sinon.assert.calledWith(adUnitWrapper.setAdVolume, 0);
        });

        it("must update the player volume on 'AdVolumeChange'", function () {
          vpaidIntegrator._linkPlayerControls(adUnitWrapper, vastResponse, callback);
          var triggerAdVolumeChange = testUtils.lastArg(adUnitWrapper.on);
          triggerAdVolumeChange();
          var getAdVolumeHandler = testUtils.lastArg(adUnitWrapper.getAdVolume);
          getAdVolumeHandler(null, 0.1);
          sinon.assert.calledWith(player.volume, 0.1);
        });

        it("must unsubscribe on 'vpaid.adEnd' events", function () {
          vpaidIntegrator._linkPlayerControls(adUnitWrapper, vastResponse, callback);
          player.trigger('vpaid.adEnd');
          player.trigger('volumechange');
          sinon.assert.notCalled(adUnitWrapper.setAdVolume);
        });
      });

      describe("fullscreen change", function () {
        beforeEach(function () {
          sinon.stub(adUnitWrapper, 'resizeAd');
        });

        it("must resize the adUnit on fullscreenchange", function () {
          sinon.stub(player, 'isFullscreen');
          vpaidIntegrator._linkPlayerControls(adUnitWrapper, vastResponse, callback);
          player.trigger('fullscreenchange');
          sinon.assert.calledWith(adUnitWrapper.resizeAd, 300, 150, vpaidIntegrator.VIEW_MODE.NORMAL);

          player.isFullscreen.returns(true);
          player.trigger('fullscreenchange');
          sinon.assert.calledWith(adUnitWrapper.resizeAd, 300, 150, vpaidIntegrator.VIEW_MODE.FULLSCREEN);
        });

        it("must unsubscribe on 'vpaid.adEnd' event", function () {
          sinon.stub(player, 'isFullscreen');
          vpaidIntegrator._linkPlayerControls(adUnitWrapper, vastResponse, callback);
          player.trigger('vpaid.adEnd');
          player.trigger('fullscreenchange');
          sinon.assert.notCalled(adUnitWrapper.resizeAd);
        });
      });
    });

    describe("startAd", function () {
      it("must call adUnit.startAd and pass the adUnit and vastResponse to the callback", function () {
        sinon.stub(adUnitWrapper, 'startAd');
        vpaidIntegrator._startAd(adUnitWrapper, vastResponse, callback);

        sinon.assert.calledOnce(adUnitWrapper.startAd);

        var startAdCb = testUtils.lastArg(adUnitWrapper.startAd);
        startAdCb(null);

        sinon.assert.calledWithExactly(callback, null, adUnitWrapper, vastResponse);
      });

      it("must trigger 'vast.adStart'", function () {
        sinon.stub(adUnitWrapper, 'startAd');
        vpaidIntegrator._startAd(adUnitWrapper, vastResponse, utilities.noop);
        sinon.assert.calledOnce(adUnitWrapper.startAd);

        player.on('vast.adStart', callback);
        var startAdCb = testUtils.lastArg(adUnitWrapper.startAd);
        startAdCb(null);

        sinon.assert.calledOnce(callback);
      });

      it("must not trigger 'vast.adStart' if there is an error on startAd", function () {
        sinon.stub(adUnitWrapper, 'startAd');
        vpaidIntegrator._startAd(adUnitWrapper, vastResponse, utilities.noop);
        sinon.assert.calledOnce(adUnitWrapper.startAd);

        player.on('vast.adStart', callback);
        var startAdCb = testUtils.lastArg(adUnitWrapper.startAd);
        startAdCb(new Error());

        sinon.assert.notCalled(callback);
      });
    });
  });

  describe("must support autoResize", function() {
    it("must handle window resize", function() {
      var vpaidIntegrator = new VPAIDIntegrator(player, {autoResize: true});
      var adUnit = new FakeAdUnit();
      vpaidIntegrator._setupEvents(adUnit, new VASTResponse(), utilities.noop);
      dom.dispatchEvent(window, new Event('resize'));
      assert(adUnit.resizeAd.calledOnce);
    });

    it("must handle window orientation change", function() {
      var vpaidIntegrator = new VPAIDIntegrator(player, {autoResize: true});
      var adUnit = new FakeAdUnit();
      vpaidIntegrator._setupEvents(adUnit, new VASTResponse(), utilities.noop);
      dom.dispatchEvent(window, new Event('orientationchange'));
      assert(adUnit.resizeAd.calledOnce);
    });

    it("must handle vast.resize", function() {
      var vpaidIntegrator = new VPAIDIntegrator(player, {autoResize: true});
      var adUnit = new FakeAdUnit();
      vpaidIntegrator._setupEvents(adUnit, new VASTResponse(), utilities.noop);
      player.trigger('vast.resize');
      assert(adUnit.resizeAd.calledOnce);
    });

    it("must ignore resize and orientation change when the flag is off", function() {
      var vpaidIntegrator = new VPAIDIntegrator(player, {autoResize: false});
      var adUnit = new FakeAdUnit();
      vpaidIntegrator._setupEvents(adUnit, new VASTResponse(), utilities.noop);
      dom.dispatchEvent(window, new Event('orientationchange'));
      dom.dispatchEvent(window, new Event('resize'));
      assert.equal(adUnit.resizeAd.callCount, 0);
    });

    it("must not handle vast.resize after 'vpaid.adEnd' events", function(){
      var vpaidIntegrator = new VPAIDIntegrator(player, {autoResize: true});
      var adUnit = new FakeAdUnit();
      vpaidIntegrator._setupEvents(adUnit, new VASTResponse(), utilities.noop);
      player.trigger('vpaid.adEnd');
      player.trigger('vast.resize');
      assert(adUnit.resizeAd.notCalled);
    });
  });

  describe("must finish destroy adUnit", function() {
    it("must destroy adUnit when AdStopped", function() {
      var vpaidIntegrator = new VPAIDIntegrator(player, {autoResize: true});
      var adUnit = new FakeAdUnit();
      var spy = sinon.spy();
      vpaidIntegrator._finishPlaying(adUnit, new VASTResponse(), spy);
      adUnit.trigger('AdStopped');
      assert(spy.calledOnce);
    });

    it("must destroy adUnit when AdStopped", function() {
      var vpaidIntegrator = new VPAIDIntegrator(player, {autoResize: true});
      var adUnit = new FakeAdUnit();
      var spy = sinon.spy();
      vpaidIntegrator._finishPlaying(adUnit, new VASTResponse(), spy);
      adUnit.trigger('AdError');
      assert(spy.calledOnce);
    });
  });
});