/* eslint-env qunit */ import sinon from 'sinon'; import Plugin from '../../src/js/plugin'; import TestHelpers from './test-helpers'; QUnit.module('Plugin: advanced', { beforeEach() { this.player = TestHelpers.makePlayer(); const spy = this.spy = sinon.spy(); class MockPlugin extends Plugin { constructor(...args) { super(...args); spy.apply(this, args); } } MockPlugin.VERSION = '1.0.0'; this.MockPlugin = MockPlugin; Plugin.registerPlugin('mock', MockPlugin); }, afterEach() { this.player.dispose(); Object.keys(Plugin.getPlugins()).forEach(key => { if (key !== Plugin.BASE_PLUGIN_NAME) { Plugin.deregisterPlugin(key); } }); } }); QUnit.test('pre-setup interface', function(assert) { assert.strictEqual(typeof this.player.plugin, 'undefined', 'the base Plugin does not add a method to the player'); assert.strictEqual(typeof this.player.mock, 'function', 'plugins are a factory function on a player'); assert.ok(this.player.hasPlugin('mock'), 'player has the plugin available'); assert.strictEqual(this.player.mock.dispose, undefined, 'class-based plugins are not populated on a player until the factory method creates them'); assert.notOk(this.player.usingPlugin('mock'), 'the player is not using the plugin'); }); QUnit.test('setup', function(assert) { const instance = this.player.mock({foo: 'bar'}, 123); assert.strictEqual(this.spy.callCount, 1, 'plugin was set up once'); assert.strictEqual(this.spy.firstCall.thisValue, instance, 'plugin constructor `this` value was the instance'); assert.deepEqual(this.spy.firstCall.args, [this.player, {foo: 'bar'}, 123], 'plugin had the correct arguments'); assert.ok(this.player.usingPlugin('mock'), 'player now recognizes that the plugin was set up'); assert.ok(this.player.hasPlugin('mock'), 'player has the plugin available'); assert.ok(instance instanceof this.MockPlugin, 'plugin instance has the correct constructor'); assert.strictEqual(instance, this.player.mock(), 'factory is replaced by method returning the instance'); assert.strictEqual(instance.player, this.player, 'instance has a reference to the player'); assert.strictEqual(instance.name, 'mock', 'instance knows its name'); assert.strictEqual(typeof instance.state, 'object', 'instance is stateful'); assert.strictEqual(typeof instance.setState, 'function', 'instance is stateful'); assert.strictEqual(typeof instance.off, 'function', 'instance is evented'); assert.strictEqual(typeof instance.on, 'function', 'instance is evented'); assert.strictEqual(typeof instance.one, 'function', 'instance is evented'); assert.strictEqual(typeof instance.trigger, 'function', 'instance is evented'); assert.strictEqual(typeof instance.dispose, 'function', 'instance has dispose method'); assert.strictEqual(typeof instance.version, 'function', 'instance has version method'); assert.strictEqual(instance.version(), '1.0.0', 'version function returns VERSION value'); assert.throws( () => new Plugin(this.player), new Error('Plugin must be sub-classed; not directly instantiated.'), 'the Plugin class cannot be directly instantiated' ); }); QUnit.test('log is added by default', function(assert) { const instance = this.player.mock(); assert.strictEqual(typeof instance.log, 'function', 'log is a function'); assert.strictEqual(typeof instance.log.debug, 'function', 'log.debug is a function'); assert.strictEqual(typeof instance.log.error, 'function', 'log.error is a function'); assert.strictEqual(typeof instance.log.history, 'function', 'log.history is a function'); assert.strictEqual(typeof instance.log.levels, 'object', 'log.levels is a object'); assert.strictEqual(typeof instance.log.warn, 'function', 'log.warn is a function'); }); QUnit.test('log will not clobber pre-existing log property', function(assert) { class MockLogPlugin extends Plugin { log() {} } MockLogPlugin.VERSION = '1.0.0'; Plugin.registerPlugin('mockLog', MockLogPlugin); const instance = this.player.mockLog(); assert.strictEqual(typeof instance.log, 'function', 'log is a function'); assert.strictEqual(instance.log, MockLogPlugin.prototype.log, 'log was not overridden'); }); QUnit.test('all "pluginsetup" events', function(assert) { const setupSpy = sinon.spy(); const events = [ 'beforepluginsetup', 'beforepluginsetup:mock', 'pluginsetup', 'pluginsetup:mock' ]; this.player.on(events, setupSpy); const instance = this.player.mock(); events.forEach((type, i) => { const event = setupSpy.getCall(i).args[0]; const hash = setupSpy.getCall(i).args[1]; assert.strictEqual(event.type, type, `the "${type}" event was triggered`); assert.strictEqual(event.target, this.player.el_, 'the event has the correct target'); assert.deepEqual(hash, { name: 'mock', // The "before" events have a `null` instance and the others have the // return value of the plugin factory. instance: i < 2 ? null : instance, plugin: this.MockPlugin }, 'the event hash object is correct'); }); }); QUnit.test('defaultState static property is used to populate state', function(assert) { class DefaultStateMock extends Plugin {} DefaultStateMock.defaultState = {foo: 1, bar: 2}; Plugin.registerPlugin('dsm', DefaultStateMock); const instance = this.player.dsm(); assert.deepEqual(instance.state, {foo: 1, bar: 2}, 'the plugin state has default properties'); }); QUnit.test('dispose', function(assert) { const instance = this.player.mock(); instance.dispose(); assert.notOk(this.player.usingPlugin('mock'), 'player recognizes that the plugin is NOT set up'); assert.ok(this.player.hasPlugin('mock'), 'player still has the plugin available'); assert.strictEqual(typeof this.player.mock, 'function', 'instance is replaced by factory'); assert.notStrictEqual(instance, this.player.mock, 'instance is replaced by factory'); assert.strictEqual(instance.player, null, 'instance no longer has a reference to the player'); assert.strictEqual(instance.state, null, 'state is now null'); }); QUnit.test('"dispose" event', function(assert) { const disposeSpy = sinon.spy(); const instance = this.player.mock(); instance.on('dispose', disposeSpy); instance.dispose(); assert.strictEqual(disposeSpy.callCount, 1, 'the "dispose" event was triggered'); const event = disposeSpy.firstCall.args[0]; const hash = disposeSpy.firstCall.args[1]; assert.strictEqual(event.type, 'dispose', 'the event has the correct type'); assert.strictEqual(event.target, instance.eventBusEl_, 'the event has the correct target'); assert.deepEqual(hash, { name: 'mock', instance, plugin: this.MockPlugin }, 'the event hash object is correct'); }); QUnit.test('arbitrary events', function(assert) { const fooSpy = sinon.spy(); const instance = this.player.mock(); instance.on('foo', fooSpy); instance.trigger('foo'); const event = fooSpy.firstCall.args[0]; const hash = fooSpy.firstCall.args[1]; assert.strictEqual(fooSpy.callCount, 1, 'the "foo" event was triggered'); assert.strictEqual(event.type, 'foo', 'the event has the correct type'); assert.strictEqual(event.target, instance.eventBusEl_, 'the event has the correct target'); assert.deepEqual(hash, { name: 'mock', instance, plugin: this.MockPlugin }, 'the event hash object is correct'); }); QUnit.test('handleStateChanged() method is automatically bound to the "statechanged" event', function(assert) { const spy = sinon.spy(); class TestHandler extends Plugin { handleStateChanged(...args) { spy.apply(this, args); } } Plugin.registerPlugin('testHandler', TestHandler); const instance = this.player.testHandler(); instance.setState({foo: 1}); assert.strictEqual(spy.callCount, 1, 'the handleStateChanged listener was called'); assert.strictEqual(spy.firstCall.args[0].type, 'statechanged', 'the event was "statechanged"'); assert.strictEqual(typeof spy.firstCall.args[0].changes, 'object', 'the event included a changes object'); });