diff --git a/src/adUnit/VpaidAdUnit.js b/src/adUnit/VpaidAdUnit.js index f78497ab..1452d3ce 100644 --- a/src/adUnit/VpaidAdUnit.js +++ b/src/adUnit/VpaidAdUnit.js @@ -11,6 +11,7 @@ import loadCreative from './helpers/vpaid/loadCreative'; import { adLoaded, adStarted, + adStopped, adPlaying, adPaused, startAd, @@ -163,6 +164,14 @@ class VpaidAdUnit extends VideoAdUnit { type: creativeView }); }, + [adStopped]: () => { + this.emit(adStopped, { + adUnit: this, + type: adStopped + }); + + this[_protected].finish(); + }, [adUserAcceptInvitation]: () => { this.emit(acceptInvitation, { adUnit: this, @@ -412,12 +421,17 @@ class VpaidAdUnit extends VideoAdUnit { * * @throws if ad unit is finished. */ - cancel () { + async cancel () { this[_protected].throwIfFinished(); - this.creativeAd[stopAd](); + try { + const adStoppedPromise = waitFor(this.creativeAd, adStopped, 3000); - this[_protected].finish(); + this.creativeAd[stopAd](); + await adStoppedPromise; + } catch (error) { + this[_protected].finish(); + } } /** diff --git a/src/adUnit/__tests__/VpaidAdUnit.spec.js b/src/adUnit/__tests__/VpaidAdUnit.spec.js index af3d40eb..f8c27b92 100644 --- a/src/adUnit/__tests__/VpaidAdUnit.spec.js +++ b/src/adUnit/__tests__/VpaidAdUnit.spec.js @@ -134,6 +134,10 @@ describe('VpaidAdUnit', () => { mockCreativeAd.emit(adStarted); }); + mockCreativeAd.stopAd.mockImplementationOnce(() => { + mockCreativeAd.emit(adStopped); + }); + loadCreative.mockReturnValue(Promise.resolve(mockCreativeAd)); retrieveIcons.mockReturnValue(null); @@ -304,6 +308,10 @@ describe('VpaidAdUnit', () => { mockCreativeAd.emit(adStarted); }); + mockCreativeAd.stopAd.mockImplementationOnce(() => { + mockCreativeAd.emit(adStopped); + }); + mockCreativeAd.resizeAd.mockImplementationOnce(() => { mockCreativeAd.emit(adSizeChange); }); @@ -330,7 +338,7 @@ describe('VpaidAdUnit', () => { await adUnit.start(); - adUnit.cancel(); + await adUnit.cancel(); expect(mockRemoveIcons).toHaveBeenCalledTimes(1); }); @@ -466,7 +474,7 @@ describe('VpaidAdUnit', () => { await adUnit.start(); expect(mockDrawIcons).toHaveBeenCalledTimes(1); - adUnit.cancel(); + await adUnit.cancel(); expect(mockDrawIcons).toHaveBeenCalledTimes(1); @@ -559,11 +567,18 @@ describe('VpaidAdUnit', () => { }); describe('cancel', () => { + jest.useFakeTimers(); + test('must throw if the adUnit is finished', async () => { + expect.assertions(1); await adUnit.start(); await adUnit.cancel(); - expect(() => adUnit.cancel()).toThrow('VideoAdUnit is finished'); + try { + await adUnit.cancel(); + } catch (error) { + expect(error.message).toBe('VideoAdUnit is finished'); + } }); test('must call stopAd and finish the adUnit', async () => { @@ -573,6 +588,22 @@ describe('VpaidAdUnit', () => { expect(mockCreativeAd.stopAd).toHaveBeenCalledTimes(1); expect(adUnit.isFinished()).toBe(true); }); + + it('must finish the adUnit if the creative does not emit adStopped event after some time', async () => { + mockCreativeAd.stopAd = jest.fn(); + mockCreativeAd.stopAd.mockImplementationOnce(() => { + // empty on purpose + }); + + await adUnit.start(); + const cancelPromise = adUnit.cancel(); + + expect(mockCreativeAd.stopAd).toHaveBeenCalledTimes(1); + expect(adUnit.isFinished()).toBe(false); + jest.runOnlyPendingTimers(); + await cancelPromise; + expect(adUnit.isFinished()).toBe(true); + }); }); describe('onFinish', () => { @@ -610,6 +641,20 @@ describe('VpaidAdUnit', () => { expect(callback).toHaveBeenCalledTimes(1); }); + test('must be called once the ad unit stops', async () => { + const callback = jest.fn(); + + adUnit.onFinish(callback); + + await adUnit.start(); + + expect(callback).not.toHaveBeenCalled(); + + adUnit.creativeAd.emit(adStopped); + + expect(callback).toHaveBeenCalledTimes(1); + }); + test('must be called if the user closes the ad unit', async () => { const callback = jest.fn(); @@ -687,6 +732,10 @@ describe('VpaidAdUnit', () => { mockCreativeAd.emit(adStarted); }); + mockCreativeAd.stopAd.mockImplementationOnce(() => { + mockCreativeAd.emit(adStopped); + }); + loadCreative.mockReturnValue(Promise.resolve(mockCreativeAd)); adUnit = new VpaidAdUnit(vpaidChain, videoAdContainer); }); @@ -814,7 +863,7 @@ describe('VpaidAdUnit', () => { adUnit.creativeAd.emit(adPlaying); expect(adUnit.paused()).toBe(false); - adUnit.cancel(); + await adUnit.cancel(); expect(adUnit.paused()).toBe(true); }); });