Back to Repositories

Testing Basic Plugin Architecture in Video.js

This test suite validates the basic plugin functionality in Video.js, focusing on plugin registration, setup, and event handling. It ensures proper implementation of the plugin architecture and verifies core plugin behaviors.

Test Coverage Overview

The test suite provides comprehensive coverage of Video.js plugin system fundamentals.

Key areas tested include:
  • Plugin registration and initialization
  • Plugin setup and interface verification
  • Event handling for plugin setup lifecycle
  • Property copying and preservation
Edge cases cover multiple plugin initializations and property inheritance verification.

Implementation Analysis

The testing approach uses QUnit framework with sinon for spying and mocking capabilities. Tests follow a structured pattern with beforeEach/afterEach hooks for proper test isolation.

Implementation focuses on:
  • Spy-based verification of plugin calls and context
  • Event emission validation
  • Plugin interface consistency checks
  • Property inheritance verification

Technical Details

Testing infrastructure includes:
  • QUnit as the primary testing framework
  • Sinon.js for spies and test doubles
  • Custom TestHelpers utility for player instantiation
  • ESLint configuration for maintaining code quality
  • Plugin registration/deregistration helpers

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices through proper test isolation and comprehensive coverage.

Notable practices include:
  • Proper test setup and teardown
  • Isolated test cases with clear assertions
  • Comprehensive event handling verification
  • Thorough interface contract testing
  • Clean and maintainable test organization

videojs/videoJs

test/unit/plugin-basic.test.js

            
/* eslint-env qunit */
import sinon from 'sinon';
import Plugin from '../../src/js/plugin';
import TestHelpers from './test-helpers';

QUnit.module('Plugin: basic', {

  beforeEach() {
    this.basic = sinon.spy();
    this.player = TestHelpers.makePlayer();

    Plugin.registerPlugin('basic', this.basic);
  },

  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.basic, 'function', 'basic plugins are a function on a player');
  assert.ok(this.player.hasPlugin('basic'), 'player has the plugin available');
  assert.notStrictEqual(this.player.basic, this.basic, 'basic plugins are wrapped');
  assert.strictEqual(this.player.basic.dispose, undefined, 'unlike advanced plugins, basic plugins do not have a dispose method');
  assert.notOk(this.player.usingPlugin('basic'), 'the player is not using the plugin');
});

QUnit.test('setup', function(assert) {
  this.player.basic({foo: 'bar'}, 123);
  assert.strictEqual(this.basic.callCount, 1, 'the plugin was called once');
  assert.strictEqual(this.basic.firstCall.thisValue, this.player, 'the plugin `this` value was the player');
  assert.deepEqual(this.basic.firstCall.args, [{foo: 'bar'}, 123], 'the plugin had the correct arguments');
  assert.ok(this.player.usingPlugin('basic'), 'the player now recognizes that the plugin was set up');
  assert.ok(this.player.hasPlugin('basic'), 'player has the plugin available');

  this.player.basic({foo: 'bar'}, 123);
  assert.strictEqual(this.basic.callCount, 2, 'the plugin was called twice');
  assert.strictEqual(this.basic.firstCall.thisValue, this.player, 'the plugin `this` value was the player');
  assert.deepEqual(this.basic.firstCall.args, [{foo: 'bar'}, 123], 'the plugin had the correct arguments');
  assert.ok(this.player.usingPlugin('basic'), 'the player now recognizes that the plugin was set up');
  assert.ok(this.player.hasPlugin('basic'), 'player has the plugin available');
});

QUnit.test('all "pluginsetup" events', function(assert) {
  const setupSpy = sinon.spy();
  const events = [
    'beforepluginsetup',
    'beforepluginsetup:basic',
    'pluginsetup',
    'pluginsetup:basic'
  ];

  this.player.on(events, setupSpy);

  const instance = this.player.basic();

  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: 'basic',

      // 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.basic
    }, 'the event hash object is correct');
  });
});

QUnit.test('properties are copied', function(assert) {
  const foo = () => {};

  foo.someProp = () => {};
  foo.VERSION = '9.9.9';

  Plugin.registerPlugin('foo', foo);

  assert.strictEqual(this.player.foo.VERSION, foo.VERSION, 'properties are copied');
  assert.strictEqual(this.player.foo.someProp, foo.someProp, 'properties are copied');

  this.player.foo();

  assert.strictEqual(this.player.foo.VERSION, foo.VERSION, 'properties still exist after init');
  assert.strictEqual(this.player.foo.someProp, foo.someProp, 'properties still exist after init');
});