diff options
Diffstat (limited to 'javascript/videojs/test/unit/tracks/text-track-display.test.js')
| -rw-r--r-- | javascript/videojs/test/unit/tracks/text-track-display.test.js | 622 |
1 files changed, 622 insertions, 0 deletions
diff --git a/javascript/videojs/test/unit/tracks/text-track-display.test.js b/javascript/videojs/test/unit/tracks/text-track-display.test.js new file mode 100644 index 0000000..6dcfb01 --- /dev/null +++ b/javascript/videojs/test/unit/tracks/text-track-display.test.js @@ -0,0 +1,622 @@ +/* eslint-env qunit */ +import window from 'global/window'; +import Html5 from '../../../src/js/tech/html5.js'; +import { constructColor } from '../../../src/js/tracks/text-track-display.js'; +import Component from '../../../src/js/component.js'; + +import * as browser from '../../../src/js/utils/browser.js'; +import TestHelpers from '../test-helpers.js'; +import document from 'global/document'; +import sinon from 'sinon'; + +QUnit.module('Text Track Display', { + beforeEach(assert) { + this.clock = sinon.useFakeTimers(); + }, + afterEach(assert) { + this.clock.restore(); + } +}); + +const getMenuItemByLanguage = function(items, language) { + for (let i = items.length - 1; i > 0; i--) { + const captionMenuItem = items[i]; + const trackLanguage = captionMenuItem.track.language; + + if (trackLanguage && trackLanguage === language) { + return captionMenuItem; + } + } +}; + +QUnit.test('if native text tracks are not supported, create a texttrackdisplay', function(assert) { + const oldTestVid = Html5.TEST_VID; + const oldIsFirefox = browser.IS_FIREFOX; + const oldTextTrackDisplay = Component.getComponent('TextTrackDisplay'); + const tag = document.createElement('video'); + const track1 = document.createElement('track'); + const track2 = document.createElement('track'); + + track1.kind = 'captions'; + track1.label = 'en'; + track1.language = 'English'; + track1.src = 'en.vtt'; + tag.appendChild(track1); + + track2.kind = 'captions'; + track2.label = 'es'; + track2.language = 'Spanish'; + track2.src = 'es.vtt'; + tag.appendChild(track2); + + Html5.TEST_VID = { + textTracks: [] + }; + + browser.stub_IS_FIREFOX(true); + + const fakeTTDSpy = sinon.spy(); + + class FakeTTD extends Component { + constructor(player, options) { + super(player, options); + fakeTTDSpy(); + } + } + + Component.registerComponent('TextTrackDisplay', FakeTTD); + + const player = TestHelpers.makePlayer({}, tag); + + assert.strictEqual(fakeTTDSpy.callCount, 1, 'text track display was created'); + + Html5.TEST_VID = oldTestVid; + browser.stub_IS_FIREFOX(oldIsFirefox); + Component.registerComponent('TextTrackDisplay', oldTextTrackDisplay); + + player.dispose(); +}); + +QUnit.test('shows the default caption track first', function(assert) { + const player = TestHelpers.makePlayer(); + const track1 = { + kind: 'captions', + label: 'English', + language: 'en', + src: 'en.vtt', + default: true + }; + const track2 = { + kind: 'captions', + label: 'Spanish', + language: 'es', + src: 'es.vtt' + }; + + // Add the text tracks + const englishTrack = player.addRemoteTextTrack(track1, true).track; + const spanishTrack = player.addRemoteTextTrack(track2, true).track; + + // Make sure the ready handler runs + this.clock.tick(1); + + assert.ok(englishTrack.mode === 'showing', 'English track should be showing'); + assert.ok(spanishTrack.mode === 'disabled', 'Spanish track should not be showing'); + player.dispose(); +}); + +if (!Html5.supportsNativeTextTracks()) { + QUnit.test('text track display should attach screen orientation change event handler', function(assert) { + const oldScreen = window.screen; + const removeHandlerSpy = sinon.spy(); + let changeHandlerSpy; + let changeHandlerAttached; + + window.screen = { + orientation: { + addEventListener: (type, func) => { + changeHandlerAttached = true; + changeHandlerSpy = sinon.spy(); + }, + dispatchEvent: (type) => changeHandlerSpy(), + removeEventListener: removeHandlerSpy + } + }; + + const player = TestHelpers.makePlayer(); + + this.clock.tick(1); + + assert.true(changeHandlerAttached, 'screen orientation change event handler was not attached'); + assert.strictEqual(changeHandlerSpy.callCount, 0, 'screen orientation change event handler should not be called'); + + window.screen.orientation.dispatchEvent('change'); + + assert.strictEqual(changeHandlerSpy.callCount, 1, 'screen orientation change event handler was not called'); + + player.dispose(); + + assert.strictEqual( + removeHandlerSpy.callCount, + 1, + 'screen orientation change event handler was not removed during player dispose' + ); + window.screen = oldScreen; + }); + + QUnit.test('selectedlanguagechange is triggered by a track mode change', function(assert) { + const player = TestHelpers.makePlayer(); + const track1 = { + kind: 'captions', + label: 'English', + language: 'en', + src: 'en.vtt' + }; + const spy = sinon.spy(); + const selectedLanguageHandler = function(event) { + spy(); + }; + const englishTrack = player.addRemoteTextTrack(track1, true).track; + + player.textTracks().addEventListener('selectedlanguagechange', selectedLanguageHandler); + englishTrack.mode = 'showing'; + + assert.strictEqual(spy.callCount, 1, 'selectedlanguagechange event was fired'); + player.dispose(); + player.textTracks().removeEventListener('selectedlanguagechange', selectedLanguageHandler); + }); + + QUnit.test("if user-selected language is unavailable, don't pick a track to show", function(assert) { + // The video has no default language but has ‘English’ captions only + const player = TestHelpers.makePlayer(); + const track1 = { + kind: 'captions', + label: 'English', + language: 'en', + src: 'en.vtt' + }; + const captionsButton = player.controlBar.getChild('SubsCapsButton'); + + player.src({type: 'video/mp4', src: 'http://google.com'}); + // manualCleanUp = true by default + const englishTrack = player.addRemoteTextTrack(track1, true).track; + + // Force 'es' as user-selected track + player.cache_.selectedLanguage = { language: 'es', kind: 'captions' }; + + this.clock.tick(1); + player.play(); + + assert.ok(!captionsButton.hasClass('vjs-hidden'), 'The captions button is shown'); + assert.ok(englishTrack.mode === 'disabled', 'English track should be disabled'); + player.dispose(); + }); + + QUnit.test('the user-selected language takes priority over default language', function(assert) { + // The video has ‘English’ captions as default, but has ‘Spanish’ captions also + const player = TestHelpers.makePlayer(); + const track1 = { + kind: 'captions', + label: 'English', + language: 'en', + src: 'en.vtt', + default: true + }; + const track2 = { + kind: 'captions', + label: 'Spanish', + language: 'es', + src: 'es.vtt' + }; + + player.src({type: 'video/mp4', src: 'http://google.com'}); + // manualCleanUp = true by default + const englishTrack = player.addRemoteTextTrack(track1, true).track; + const spanishTrack = player.addRemoteTextTrack(track2, true).track; + + // Force 'es' as user-selected track + player.cache_.selectedLanguage = { enabled: true, language: 'es', kind: 'captions' }; + this.clock.tick(1); + + assert.ok(spanishTrack.mode === 'showing', 'Spanish captions should be shown'); + assert.ok(englishTrack.mode === 'disabled', 'English captions should be hidden'); + player.dispose(); + }); + + QUnit.test("don't select user language if it is an empty string", function(assert) { + const player = TestHelpers.makePlayer(); + const track1 = { + kind: 'captions', + label: 'English', + language: 'en', + src: 'en.vtt' + }; + const track2 = { + kind: 'captions', + label: 'Spanish', + language: 'es', + src: 'es.vtt' + }; + const track3 = { + kind: 'metadata', + label: 'segment-metadata' + }; + + player.src({type: 'video/mp4', src: 'http://google.com'}); + // manualCleanUp = true by default + const englishTrack = player.addRemoteTextTrack(track1, true).track; + const spanishTrack = player.addRemoteTextTrack(track2, true).track; + const metadataTrack = player.addRemoteTextTrack(track3, true).track; + + // Force empty string ('') as "user-selected" track + player.cache_.selectedLanguage = { enabled: true, language: '', kind: 'captions' }; + this.clock.tick(1); + + assert.equal(spanishTrack.mode, 'disabled', 'Spanish captions should be disabled'); + assert.equal(englishTrack.mode, 'disabled', 'English captions should be disabled'); + assert.notEqual(metadataTrack.mode, 'showing', 'Metadata track should not be showing'); + + // Force es as "user-selected" track + player.cache_.selectedLanguage = { enabled: true, language: 'es', kind: 'captions' }; + player.trigger('loadedmetadata'); + + assert.equal(spanishTrack.mode, 'showing', 'Spanish captions should be showing'); + assert.equal(englishTrack.mode, 'disabled', 'English captions should be disabled'); + assert.notEqual(metadataTrack.mode, 'showing', 'Metadata track should not be showing'); + + player.dispose(); + }); + + QUnit.test("matching both the selectedLanguage's language and kind takes priority over just matching the language", function(assert) { + const player = TestHelpers.makePlayer(); + const track1 = { + kind: 'captions', + label: 'English', + language: 'en', + src: 'en.vtt' + }; + const track2 = { + kind: 'subtitles', + label: 'English', + language: 'en', + src: 'en.vtt' + }; + + player.src({type: 'video/mp4', src: 'http://google.com'}); + // manualCleanUp = true by default + const captionTrack = player.addRemoteTextTrack(track1, true).track; + const subsTrack = player.addRemoteTextTrack(track2, true).track; + + // Force English captions as user-selected track + player.cache_.selectedLanguage = { enabled: true, language: 'en', kind: 'captions' }; + this.clock.tick(1); + + assert.ok(captionTrack.mode === 'showing', 'Captions track should be preselected'); + assert.ok(subsTrack.mode === 'disabled', 'Subtitles track should remain disabled'); + player.dispose(); + }); + + QUnit.test('the user-selected language is used for subsequent source changes', function(assert) { + // Start with two captions tracks: English and Spanish + const player = TestHelpers.makePlayer(); + const track1 = { + kind: 'captions', + label: 'English', + language: 'en', + src: 'en.vtt' + }; + const track2 = { + kind: 'captions', + label: 'Spanish', + language: 'es', + src: 'es.vtt' + }; + const tracks = player.tech_.remoteTextTracks(); + const captionsButton = player.controlBar.getChild('SubsCapsButton'); + let esCaptionMenuItem; + let enCaptionMenuItem; + + player.src({type: 'video/mp4', src: 'http://google.com'}); + // manualCleanUp = true by default + player.addRemoteTextTrack(track1, true); + player.addRemoteTextTrack(track2, true); + + // Keep track of menu items + esCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'es'); + enCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'en'); + + // The user chooses Spanish + player.play(); + esCaptionMenuItem.trigger('click'); + + // Track mode changes on user-selection + assert.ok( + esCaptionMenuItem.track.mode === 'showing', + 'Spanish should be showing after selection' + ); + assert.ok( + enCaptionMenuItem.track.mode === 'disabled', + 'English should be disabled after selecting Spanish' + ); + assert.deepEqual( + player.cache_.selectedLanguage, + { enabled: true, language: 'es', kind: 'captions' } + ); + + // Switch source and remove old tracks + player.tech_.src({type: 'video/mp4', src: 'http://example.com'}); + while (tracks.length > 0) { + player.removeRemoteTextTrack(tracks[0]); + } + // Add tracks for the new source + // change the kind of track to subtitles + track1.kind = 'subtitles'; + track2.kind = 'subtitles'; + const englishTrack = player.addRemoteTextTrack(track1, true).track; + const spanishTrack = player.addRemoteTextTrack(track2, true).track; + + // Make sure player ready handler runs + this.clock.tick(1); + + // Keep track of menu items + esCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'es'); + enCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'en'); + + // The user-selection should have persisted + assert.ok( + esCaptionMenuItem.track.mode === 'showing', + 'Spanish should remain showing' + ); + assert.ok( + enCaptionMenuItem.track.mode === 'disabled', + 'English should remain disabled' + ); + assert.deepEqual( + player.cache_.selectedLanguage, + { enabled: true, language: 'es', kind: 'captions' } + ); + + assert.ok(spanishTrack.mode === 'showing', 'Spanish track remains showing'); + assert.ok(englishTrack.mode === 'disabled', 'English track remains disabled'); + player.dispose(); + }); + + QUnit.test('the user-selected language is cleared on turning off captions', function(assert) { + const player = TestHelpers.makePlayer(); + const track1 = { + kind: 'captions', + label: 'English', + language: 'en', + src: 'en.vtt' + }; + const captionsButton = player.controlBar.getChild('SubsCapsButton'); + + player.src({type: 'video/mp4', src: 'http://google.com'}); + // manualCleanUp = true by default + const englishTrack = player.addRemoteTextTrack(track1, true).track; + // Keep track of menu items + const enCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'en'); + // we know the position of the OffTextTrackMenuItem + const offMenuItem = captionsButton.items[1]; + + // Select English initially + player.play(); + enCaptionMenuItem.trigger('click'); + + assert.deepEqual( + player.cache_.selectedLanguage, + { enabled: true, language: 'en', kind: 'captions' }, 'English track is selected' + ); + assert.ok(englishTrack.mode === 'showing', 'English track should be showing'); + + // Select the off button + offMenuItem.trigger('click'); + + assert.deepEqual( + player.cache_.selectedLanguage, + { enabled: false }, 'selectedLanguage is cleared' + ); + assert.ok(englishTrack.mode === 'disabled', 'English track is disabled'); + player.dispose(); + }); + + QUnit.test('a color can be constructed from a three digit hex code', function(assert) { + const hex = '#f0e'; + + // f gets mapped to ff -> 255 in decimal, + // 0 gets mapped to 00 -> 0 in decimal, + // e gets mapped to ee -> 238 in decimal. + assert.equal(constructColor(hex, 1), 'rgba(255,0,238,1)'); + }); + + QUnit.test('a color can be constructed from a six digit hex code', function(assert) { + const hex = '#f604e2'; + + // f6 -> 246 in decimal, + // 04 -> 4 in decimal, + // e2 -> 226 in decimal. + assert.equal(constructColor(hex, 1), 'rgba(246,4,226,1)'); + }); + + QUnit.test('an invalid hex code will throw an error', function(assert) { + const hex = '#f'; + + assert.throws( + function() { + constructColor(hex, 1); + }, + new Error('Invalid color code provided, #f; must be formatted as e.g. #f0e or #f604e2.'), + 'colors must be valid hex codes.' + ); + }); + + const skipOnOldChrome = window.CSS.supports('inset-inline: 10px') ? 'test' : 'skip'; + + QUnit[skipOnOldChrome]('text track display should overlay a video', function(assert) { + const tag = document.createElement('video'); + + tag.width = 320; + tag.height = 180; + const player = TestHelpers.makePlayer({}, tag); + const textTrackDisplay = player.getChild('TextTrackDisplay'); + const textTrackDisplayStyle = textTrackDisplay.el().style; + + assert.ok(textTrackDisplayStyle.insetInline === '', 'text track display style insetInline equal to empty string'); + assert.ok(textTrackDisplayStyle.insetBlock === '', 'text track display style insetBlock equal to empty string'); + + // video aspect ratio equal to NaN + player.tech_.videoWidth = () => 0; + player.tech_.videoHeight = () => 0; + + assert.ok(textTrackDisplayStyle.insetInline === '', 'text track display style insetInline equal to empty string'); + assert.ok(textTrackDisplayStyle.insetBlock === '', 'text track display style insetBlock equal to empty string'); + + // video aspect ratio 2:1 + player.tech_.videoWidth = () => 100; + player.tech_.videoHeight = () => 50; + + textTrackDisplay.updateDisplayOverlay(); + + assert.ok(textTrackDisplayStyle.insetInline === '', 'text track display style insetInline equal to empty string'); + assert.ok(textTrackDisplayStyle.insetBlock === '10px', 'text track display style insetBlock equal to 10px'); + + // video aspect ratio 4:3 + player.tech_.videoWidth = () => 100; + player.tech_.videoHeight = () => 75; + + textTrackDisplay.updateDisplayOverlay(); + + assert.ok(textTrackDisplayStyle.insetInline === '40px', 'text track display style insetInline equal to 40px'); + assert.ok(textTrackDisplayStyle.insetBlock === '', 'text track display style insetBlock equal to empty string'); + + // video aspect ratio 16:9 + player.tech_.videoWidth = () => 320; + player.tech_.videoHeight = () => 180; + + textTrackDisplay.updateDisplayOverlay(); + + assert.ok(textTrackDisplayStyle.insetInline === '', 'text track display style insetInline equal to empty string'); + assert.ok(textTrackDisplayStyle.insetBlock === '', 'text track display style insetBlock equal to empty string'); + + player.dispose(); + }); + + QUnit.test('should use relative position for vjs-text-track-display element if environment does not support window.CSS', function(assert) { + const cssDescriptor = Object.getOwnPropertyDescriptor(window, 'CSS'); + + try { + // Set conditions for the use of the style modifications. Temporarily + // remove window.CSS to match the environment created by jsdom. + // https://github.com/jsdom/jsdom/issues/3991 + delete window.CSS; + + const player = TestHelpers.makePlayer(); + const track1 = { + kind: 'captions', + label: 'English', + language: 'en', + src: 'en.vtt', + default: true + }; + + // Add the text track + player.addRemoteTextTrack(track1, true); + + player.src({type: 'video/mp4', src: 'http://google.com'}); + player.play(); + + // as if metadata was loaded + player.textTrackDisplay.updateDisplayOverlay(); + + // Make sure the ready handler runs + this.clock.tick(1); + + const textTrack = window.document.querySelector('.vjs-text-track-display'); + + assert.ok(textTrack.style.position === 'relative', 'Style of position for vjs-text-track-display element should be relative'); + assert.ok(textTrack.style.top === 'unset', 'Style of position for vjs-text-track-display element should be unset'); + assert.ok(textTrack.style.bottom === '0px', 'Style of bottom for vjs-text-track-display element should be 0px'); + player.dispose(); + } finally { + // Restore window.CSS + Object.defineProperty(window, 'CSS', cssDescriptor); + } + }); + + QUnit.test('should use relative position for vjs-text-track-display element if browser does not support inset property', function(assert) { + // Set conditions for the use of the style modifications + window.CSS.supports = () => false; + browser.IS_SMART_TV = () => true; + + const player = TestHelpers.makePlayer(); + const track1 = { + kind: 'captions', + label: 'English', + language: 'en', + src: 'en.vtt', + default: true + }; + + // Add the text track + player.addRemoteTextTrack(track1, true); + + player.src({type: 'video/mp4', src: 'http://google.com'}); + player.play(); + + // as if metadata was loaded + player.textTrackDisplay.updateDisplayOverlay(); + + // Make sure the ready handler runs + this.clock.tick(1); + + const textTrack = window.document.querySelector('.vjs-text-track-display'); + + assert.ok(textTrack.style.position === 'relative', 'Style of position for vjs-text-track-display element should be relative'); + assert.ok(textTrack.style.top === 'unset', 'Style of position for vjs-text-track-display element should be unset'); + assert.ok(textTrack.style.bottom === '0px', 'Style of bottom for vjs-text-track-display element should be 0px'); + player.dispose(); + }); + + QUnit.test('track cue should use values of top, right, botton, left if browser does not support inset property', function(assert) { + // Set conditions for the use of the style modifications + window.CSS.supports = () => false; + browser.IS_SMART_TV = () => true; + + const player = TestHelpers.makePlayer(); + const track1 = { + kind: 'captions', + label: 'English', + language: 'en', + src: 'en.vtt', + default: true + }; + + // Add the text track + player.addRemoteTextTrack(track1, true); + + player.src({type: 'video/mp4', src: 'http://google.com'}); + player.play(); + + // mock caption + const textTrackDisplay = window.document.querySelector('.vjs-text-track-display').firstChild; + const node = document.createElement('div'); + + node.classList.add('vjs-text-track-cue'); + node.style.inset = '1px 2px 3px'; + const textnode = document.createTextNode('Sample text'); + + node.appendChild(textnode); + textTrackDisplay.appendChild(node); + + // avoid captions clear + player.textTrackDisplay.clearDisplay = () => ''; + + // as if metadata was loaded + player.textTrackDisplay.updateDisplay(); + + assert.ok(player.textTrackDisplay.el_.querySelector('.vjs-text-track-cue').style.left === 'unset', 'Style of left for vjs-text-track-cue element should be unset'); + assert.ok(player.textTrackDisplay.el_.querySelector('.vjs-text-track-cue').style.top === '1px', 'Style of top for vjs-text-track-cue element should be 1px'); + assert.ok(player.textTrackDisplay.el_.querySelector('.vjs-text-track-cue').style.right === '2px', 'Style of right for vjs-text-track-cue element should be 2px'); + player.dispose(); + }); +} |
