Back to Repositories

Testing Function Utility Implementation in Video.js

This test suite validates core function utilities in Video.js, focusing on context binding, throttling, and debouncing implementations. The tests ensure reliable function behavior control and timing mechanisms essential for video player performance.

Test Coverage Overview

The test suite provides comprehensive coverage of function utility methods including bind_, throttle, and debounce implementations.

Key areas tested include:
  • Function context binding verification
  • Throttle timing and call frequency control
  • Debounce behavior with immediate and delayed execution
  • Cancellation of debounced operations

Implementation Analysis

Tests utilize QUnit framework with sinon for time manipulation and spy functionality. The implementation follows a modular approach with isolated test cases for each utility method.

Testing patterns include:
  • Clock manipulation for timing verification
  • Spy functions to track execution counts
  • Assertion-based validation of function behavior
  • Before/after hooks for test environment setup

Technical Details

Testing infrastructure includes:
  • QUnit as the primary test framework
  • Sinon.js for mocking and time manipulation
  • ESLint configuration for code quality
  • Modular test organization with beforeEach/afterEach hooks
  • Fake timers for precise timing control

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices through clear organization and thorough validation.

Notable practices include:
  • Isolated test cases with specific assertions
  • Proper cleanup of test environment
  • Comprehensive edge case coverage
  • Clear test descriptions and expectations
  • Effective use of spy functions for behavior verification

videojs/videoJs

test/unit/utils/fn.test.js

            
/* eslint-env qunit */
import sinon from 'sinon';
import * as Fn from '../../../src/js/utils/fn';

QUnit.module('utils/fn', {
  beforeEach() {
    this.clock = sinon.useFakeTimers();
  },
  afterEach() {
    this.clock.restore();
  }
});

QUnit.test('should add context to a function', function(assert) {
  assert.expect(1);

  const newContext = { test: 'obj'};
  const asdf = function() {
    assert.ok(this === newContext);
  };
  const fdsa = Fn.bind_(newContext, asdf);

  fdsa();
});

QUnit.test('should throttle functions properly', function(assert) {
  assert.expect(3);

  const tester = sinon.spy();
  const throttled = Fn.throttle(tester, 100);

  // We must wait a full wait period before the function can be called.
  this.clock.tick(100);
  throttled();
  throttled();
  this.clock.tick(50);
  throttled();

  assert.strictEqual(tester.callCount, 1, 'the throttled function has been called the correct number of times');

  this.clock.tick(50);
  throttled();

  assert.strictEqual(tester.callCount, 2, 'the throttled function has been called the correct number of times');

  throttled();
  this.clock.tick(100);
  throttled();

  assert.strictEqual(tester.callCount, 3, 'the throttled function has been called the correct number of times');
});

QUnit.test('should debounce functions properly', function(assert) {
  assert.expect(6);

  const tester = sinon.spy();
  const debounced = Fn.debounce(tester, 100);

  // Called twice on each assertion to ensure that multiple calls only result
  // in a call to the inner function.
  debounced();
  debounced();
  assert.strictEqual(tester.callCount, 0, 'the debounced function was NOT called because no time has elapsed');

  this.clock.tick(100);
  assert.strictEqual(tester.callCount, 1, 'the debounced function was called because enough time elapsed');

  this.clock.tick(100);
  assert.strictEqual(tester.callCount, 1, 'the debounced function was NOT called again even though time elapsed');

  debounced();
  debounced();
  assert.strictEqual(tester.callCount, 1, 'the debounced function was NOT called because no time has elapsed since invocation');

  this.clock.tick(50);
  assert.strictEqual(tester.callCount, 1, 'the debounced function was NOT called because the clock has NOT ticket forward enough');

  this.clock.tick(50);
  assert.strictEqual(tester.callCount, 2, 'the debounced function was called because the clock ticked forward enough');
});

QUnit.test('may immediately invoke debounced functions', function(assert) {
  assert.expect(2);

  const tester = sinon.spy();
  const debounced = Fn.debounce(tester, 100, true);

  // Called twice on each assertion to ensure that multiple calls only result
  // in a call to the inner function.
  debounced();
  debounced();
  assert.strictEqual(tester.callCount, 1, 'the debounced function was called because true was passed');

  this.clock.tick(100);
  assert.strictEqual(tester.callCount, 1, 'the debounced function was NOT called because it has only been invoked once');
});

QUnit.test('may cancel debounced functions', function(assert) {
  assert.expect(2);

  const tester = sinon.spy();
  const debounced = Fn.debounce(tester, 100);

  debounced();
  this.clock.tick(50);
  debounced.cancel();
  this.clock.tick(50);
  assert.strictEqual(tester.callCount, 0, 'the debounced function was NOT called because it was cancelled');

  debounced();
  this.clock.tick(100);
  assert.strictEqual(tester.callCount, 1, 'the debounced function was called because it was NOT cancelled');
});