Back to Repositories

Testing Text Track List Converter Implementation in Video.js

This test suite validates the text track list converter functionality in Video.js, focusing on the conversion between JSON and TextTrack objects, handling both native and emulated text tracks. It ensures proper track management and conversion across different implementations.

Test Coverage Overview

The test suite provides comprehensive coverage of text track conversion functionality.

Key areas tested include:
  • Native track object to JSON conversion
  • Emulated track object to JSON conversion
  • Text tracks list to JSON conversion
  • JSON to text tracks conversion
  • Track addition and removal operations
Integration points cover both native HTML5 text tracks and Video.js emulated tracks.

Implementation Analysis

The testing approach utilizes QUnit’s module and test structure to organize related test cases. The implementation employs mock tech objects and track creation to simulate both native and emulated environments.

Key patterns include:
  • Track cleanup utility functions
  • Native track feature detection
  • Mock tech object creation
  • TextTrackList manipulation

Technical Details

Testing tools and setup:
  • QUnit testing framework
  • ESLint for code quality
  • HTML5 native track API
  • Video.js TextTrack and TextTrackList implementations
  • Mock DOM elements via document.createElement
  • Custom track cleanup utilities

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices through comprehensive coverage and careful organization.

Notable practices include:
  • Isolation of native vs emulated track testing
  • Proper cleanup after each test
  • Thorough edge case handling
  • Consistent assertion patterns
  • Mock object usage for controlled testing

videojs/videoJs

test/unit/tracks/text-track-list-converter.test.js

            
/* eslint-env qunit */
import c from '../../../src/js/tracks/text-track-list-converter.js';
import TextTrack from '../../../src/js/tracks/text-track.js';
import TextTrackList from '../../../src/js/tracks/text-track-list.js';
import Html5 from '../../../src/js/tech/html5.js';
import document from 'global/document';

QUnit.module('Text Track List Converter', {});

const clean = (item) => {
  delete item.id;
  delete item.inBandMetadataTrackDispatchType;
  delete item.cues;
};

const cleanup = (item) => {
  if (Array.isArray(item)) {
    item.forEach(clean);
  } else {
    clean(item);
  }

  return item;
};

if (Html5.supportsNativeTextTracks()) {
  QUnit.test('trackToJson_ produces correct representation for native track object', function(assert) {
    const track = document.createElement('track');

    track.src = 'example.com/english.vtt';
    track.kind = 'captions';
    track.srclang = 'en';
    track.label = 'English';

    assert.deepEqual(cleanup(c.trackToJson_(track.track)), {
      kind: 'captions',
      label: 'English',
      language: 'en',
      mode: 'disabled'
    }, 'the json output is same');
  });

  QUnit.test('textTracksToJson produces good json output', function(assert) {
    const emulatedTrack = new TextTrack({
      kind: 'captions',
      label: 'English',
      language: 'en',
      tech: {
        crossOrigin() {
          return null;
        },
        on() {},
        one() {}
      }
    });

    const nativeTrack = document.createElement('track');

    nativeTrack.kind = 'captions';
    nativeTrack.srclang = 'es';
    nativeTrack.label = 'Spanish';

    const tt = new TextTrackList();

    tt.addTrack(nativeTrack.track);
    tt.addTrack(emulatedTrack);

    const tech = {
      $$() {
        return [nativeTrack];
      },

      el() {
        return {
          querySelectorAll() {
            return [nativeTrack];
          }
        };
      },
      on() {},
      crossOrigin() {
        return null;
      },
      textTracks() {
        return tt;
      }
    };

    assert.deepEqual(cleanup(c.textTracksToJson(tech)), [{
      kind: 'captions',
      label: 'Spanish',
      language: 'es',
      mode: 'disabled'
    }, {
      kind: 'captions',
      label: 'English',
      language: 'en',
      mode: 'disabled'
    }], 'the output is correct');

    tt.removeTrack(nativeTrack.track);
    tt.removeTrack(emulatedTrack);
  });

  QUnit.test('jsonToTextTracks calls addRemoteTextTrack on the tech with mixed tracks', function(assert) {
    const emulatedTrack = new TextTrack({
      kind: 'captions',
      label: 'English',
      language: 'en',
      src: 'example.com/english.vtt',
      tech: {
        crossOrigin() {
          return null;
        },
        on() {},
        one() {}
      }
    });

    const nativeTrack = document.createElement('track');

    nativeTrack.src = 'example.com/spanish.vtt';
    nativeTrack.kind = 'captions';
    nativeTrack.srclang = 'es';
    nativeTrack.label = 'Spanish';

    const tt = new TextTrackList();

    tt.addTrack(nativeTrack.track);
    tt.addTrack(emulatedTrack);

    let addRemotes = 0;
    const tech = {
      $$() {
        return [nativeTrack];
      },

      el() {
        return {
          querySelectorAll() {
            return [nativeTrack];
          }
        };
      },
      crossOrigin() {
        return null;
      },
      on() {},
      textTracks() {
        return tt;
      },
      addRemoteTextTrack() {
        addRemotes++;
        return {
          track: {}
        };
      }
    };

    c.jsonToTextTracks(cleanup(c.textTracksToJson(tech)), tech);

    assert.equal(addRemotes, 2, 'we added two text tracks');

    tt.removeTrack(nativeTrack.track);
    tt.removeTrack(emulatedTrack);
  });
}

QUnit.test('trackToJson_ produces correct representation for emulated track object', function(assert) {
  const track = new TextTrack({
    kind: 'captions',
    label: 'English',
    language: 'en',
    src: 'example.com/english.vtt',
    tech: {
      crossOrigin() {
        return null;
      },
      on() {},
      one() {}
    }
  });

  assert.deepEqual(cleanup(c.trackToJson_(track)), {
    src: 'example.com/english.vtt',
    kind: 'captions',
    label: 'English',
    language: 'en',
    mode: 'disabled'
  }, 'the json output is same');
});

QUnit.test('textTracksToJson produces good json output for emulated only', function(assert) {
  const emulatedTrack = new TextTrack({
    kind: 'captions',
    label: 'English',
    language: 'en',
    src: 'example.com/english.vtt',
    tech: {
      crossOrigin() {
        return null;
      },
      on() {},
      one() {}
    }
  });

  const anotherTrack = new TextTrack({
    src: 'example.com/spanish.vtt',
    kind: 'captions',
    srclang: 'es',
    label: 'Spanish',
    tech: {
      crossOrigin() {
        return null;
      },
      on() {},
      one() {}
    }
  });

  const tt = new TextTrackList();

  tt.addTrack(anotherTrack);
  tt.addTrack(emulatedTrack);

  const tech = {
    $$() {
      return [];
    },

    el() {
      return {
        querySelectorAll() {
          return [];
        }
      };
    },
    crossOrigin() {
      return null;
    },
    on() {},
    one() {},
    textTracks() {
      return tt;
    }
  };

  assert.deepEqual(cleanup(c.textTracksToJson(tech)), [{
    src: 'example.com/spanish.vtt',
    kind: 'captions',
    label: 'Spanish',
    language: 'es',
    mode: 'disabled'
  }, {
    src: 'example.com/english.vtt',
    kind: 'captions',
    label: 'English',
    language: 'en',
    mode: 'disabled'
  }], 'the output is correct');

  tt.removeTrack(anotherTrack);
  tt.removeTrack(emulatedTrack);
});

QUnit.test('jsonToTextTracks calls addRemoteTextTrack on the tech with emulated tracks only', function(assert) {
  const emulatedTrack = new TextTrack({
    kind: 'captions',
    label: 'English',
    language: 'en',
    src: 'example.com/english.vtt',
    tech: {
      crossOrigin() {
        return null;
      },
      on() {},
      one() {}
    }
  });

  const anotherTrack = new TextTrack({
    src: 'example.com/spanish.vtt',
    kind: 'captions',
    srclang: 'es',
    label: 'Spanish',
    tech: {
      crossOrigin() {
        return null;
      },
      on() {},
      one() {}
    }
  });

  const tt = new TextTrackList();

  tt.addTrack(anotherTrack);
  tt.addTrack(emulatedTrack);

  let addRemotes = 0;
  const tech = {
    $$() {
      return [];
    },

    el() {
      return {
        querySelectorAll() {
          return [];
        }
      };
    },
    crossOrigin() {
      return null;
    },
    on() {},
    one() {},
    textTracks() {
      return tt;
    },
    addRemoteTextTrack() {
      addRemotes++;
      return {
        track: {}
      };
    }
  };

  c.jsonToTextTracks(cleanup(c.textTracksToJson(tech)), tech);

  assert.equal(addRemotes, 2, 'we added two text tracks');

  tt.removeTrack(anotherTrack);
  tt.removeTrack(emulatedTrack);
});