From 78c6a38ca642efb5fc40db6ec3ee6945157f4f79 Mon Sep 17 00:00:00 2001 From: Chris Rodriguez Date: Tue, 29 Sep 2015 13:54:45 -0400 Subject: [PATCH] LMS: new UI for video player + AFontGarde iconfonts --- .../xmodule/xmodule/css/video/display.scss | 696 +++--- .../spec/video/video_accessible_menu_spec.js | 4 +- .../js/spec/video/video_caption_spec.js | 184 +- .../js/spec/video/video_context_menu_spec.js | 16 +- .../js/spec/video/video_full_screen_spec.js | 6 - .../video/video_play_pause_control_spec.js | 6 - .../video/video_play_skip_control_spec.js | 2 - .../js/spec/video/video_player_spec.js | 10 - .../spec/video/video_quality_control_spec.js | 4 +- .../js/spec/video/video_skip_control_spec.js | 2 - .../js/spec/video/video_speed_control_spec.js | 45 +- .../spec/video/video_volume_control_spec.js | 44 +- .../js/src/video/04_video_full_screen.js | 28 +- .../js/src/video/05_video_quality_control.js | 32 +- .../js/src/video/07_video_volume_control.js | 76 +- .../js/src/video/08_video_speed_control.js | 50 +- .../js/src/video/09_play_pause_control.js | 34 +- .../js/src/video/09_play_skip_control.js | 21 +- .../xmodule/js/src/video/09_skip_control.js | 14 +- .../xmodule/js/src/video/09_video_caption.js | 1937 +++++++++-------- .../static/images/fontawesome/arrows-alt.svg | 6 + .../static/images/fontawesome/caret-left.svg | 6 + .../static/images/fontawesome/caret-right.svg | 6 + common/static/images/fontawesome/caret-up.svg | 6 + common/static/images/fontawesome/cc.svg | 6 + common/static/images/fontawesome/compress.svg | 6 + common/static/images/fontawesome/list-alt.svg | 6 + common/static/images/fontawesome/pause.svg | 6 + common/static/images/fontawesome/play.svg | 6 + .../static/images/fontawesome/quote-left.svg | 6 + .../images/fontawesome/step-forward.svg | 6 + .../static/images/fontawesome/volume-down.svg | 6 + .../static/images/fontawesome/volume-off.svg | 6 + .../static/images/fontawesome/volume-up.svg | 6 + .../js/vendor/afontgarde/afontgarde.css | 60 + .../static/js/vendor/afontgarde/afontgarde.js | 287 +++ .../static/js/vendor/afontgarde/edx-icons.js | 3 + .../modernizr.fontface-generatedcontent.js | 4 + .../test/acceptance/pages/lms/video/video.py | 29 +- .../tests/video/test_studio_video_module.py | 4 +- .../tests/video/test_video_module.py | 8 +- lms/envs/common.py | 4 + 42 files changed, 2223 insertions(+), 1471 deletions(-) create mode 100755 common/static/images/fontawesome/arrows-alt.svg create mode 100755 common/static/images/fontawesome/caret-left.svg create mode 100755 common/static/images/fontawesome/caret-right.svg create mode 100755 common/static/images/fontawesome/caret-up.svg create mode 100755 common/static/images/fontawesome/cc.svg create mode 100755 common/static/images/fontawesome/compress.svg create mode 100755 common/static/images/fontawesome/list-alt.svg create mode 100755 common/static/images/fontawesome/pause.svg create mode 100755 common/static/images/fontawesome/play.svg create mode 100755 common/static/images/fontawesome/quote-left.svg create mode 100755 common/static/images/fontawesome/step-forward.svg create mode 100755 common/static/images/fontawesome/volume-down.svg create mode 100755 common/static/images/fontawesome/volume-off.svg create mode 100755 common/static/images/fontawesome/volume-up.svg create mode 100644 common/static/js/vendor/afontgarde/afontgarde.css create mode 100644 common/static/js/vendor/afontgarde/afontgarde.js create mode 100644 common/static/js/vendor/afontgarde/edx-icons.js create mode 100644 common/static/js/vendor/afontgarde/modernizr.fontface-generatedcontent.js diff --git a/common/lib/xmodule/xmodule/css/video/display.scss b/common/lib/xmodule/xmodule/css/video/display.scss index 5f531435bf..c54591a7ff 100644 --- a/common/lib/xmodule/xmodule/css/video/display.scss +++ b/common/lib/xmodule/xmodule/css/video/display.scss @@ -1,3 +1,66 @@ +// This is for A Font Garde +// It loads the icon font only when it's available. +// --- +// It is scoped to the video player for now, but we +// will eventually want to move this to the main font +// sheet, globally, so it applies to all use cases. + +// -------- +// Defaults: what displays if the icon font doesn't load. +// -------- + +// the html target is necessary for xblocks and xmodules, but works across the board +html:not('.afontgarde') .icon-fallback-img { + + .fa-play { + background: url('#{$static-path}/images/fontawesome/play.svg') center center no-repeat; + } + + .fa-pause { + background: url('#{$static-path}/images/fontawesome/pause.svg') center center no-repeat; + } + + .fa-step-forward { + background: url('#{$static-path}/images/fontawesome/step-forward.svg') center center no-repeat; + } + + .fa-arrows-alt { + background: url('#{$static-path}/images/fontawesome/arrows-alt.svg') center center no-repeat; + } + + .fa-caret-right { + background: url('#{$static-path}/images/fontawesome/caret-right.svg') center center no-repeat; + } + + .fa-caret-left { + background: url('#{$static-path}/images/fontawesome/caret-left.svg') center center no-repeat; + } + + .fa-caret-up { + background: url('#{$static-path}/images/fontawesome/caret-up.svg') center center no-repeat; + } + + .fa-compress { + background: url('#{$static-path}/images/fontawesome/compress.svg') center center no-repeat; + } + + .fa-quote-left { + background: url('#{$static-path}/images/fontawesome/quote-left.svg') center center no-repeat; + } + + .fa-volume-up { + background: url('#{$static-path}/images/fontawesome/volume-up.svg') center center no-repeat; + } + + .fa-volume-down { + background: url('#{$static-path}/images/fontawesome/volume-down.svg') center center no-repeat; + } + + .fa-volume-off { + background: url('#{$static-path}/images/fontawesome/volume-off.svg') center center no-repeat; + } +} + & { margin-bottom: ($baseline*1.5); } @@ -6,21 +69,25 @@ display: none; } -div.video { +.video { @include clearfix(); - background: #f3f3f3; + background: rgb(240, 243, 245); // UXPL grayscale-cool xx-light; display: block; margin: 0 -12px; padding: 12px; border-radius: 5px; outline: none; - &:focus, &:active, &:hover { + &:focus, + &:active, + &:hover { border: 0; } &.is-initialized { - article.video-wrapper { + + .video-wrapper { + .spinner { display: none; } @@ -29,12 +96,14 @@ div.video { // CASE: video pre-roll state &.is-pre-roll { + .slider { visibility: hidden; } .video-player { position: relative; + &:before { display: block; content: ""; @@ -44,12 +113,12 @@ div.video { } } - div.tc-wrapper { + .tc-wrapper { @include clearfix(); position: relative; } - div.focus_grabber { + .focus_grabber { position: relative; display: inline; width: 0px; @@ -60,7 +129,7 @@ div.video { margin: 0; padding: 0; - .video-download-button{ + .video-download-button { display: inline-block; vertical-align: top; margin: ($baseline*0.75) ($baseline/2) 0 0; @@ -75,16 +144,20 @@ div.video { padding: ($baseline*0.75); color: $lighter-base-font-color; - &:hover, &:focus { + &:hover, + &:focus { background-color: $action-primary-active-bg; color: $very-light-text; } } } + .video-tracks { + > a { border-radius: 3px 0 0 3px; } + > a.external-track { border-radius: 3px; } @@ -116,16 +189,17 @@ div.video { } } - article.video-wrapper { - float: left; - margin-right: flex-gutter(9); + .video-wrapper { + @include float(left); + @include margin-right(flex-gutter(9)); width: flex-grid(6, 9); background-color: black; position: relative; - div.video-player-pre, div.video-player-post { + .video-player-pre, + .video-player-post { height: 50px; - background-color: black; + background-color: rgb(17, 16, 16) // UXPL grayscale black; } .spinner { @@ -173,7 +247,7 @@ div.video { } } - section.video-player { + .video-player { overflow: hidden; min-height: 300px; @@ -185,7 +259,9 @@ div.video { } } - object, iframe, video { + object, + iframe, + video { display: block; border: none; width: 100%; @@ -201,285 +277,272 @@ div.video { } } - section.video-controls { + .video-controls { @include clearfix(); - background: #333; - border: 1px solid $black; - border-top: 0; - color: $gray-l3; position: relative; + border: 0; + background: rgb(40, 44, 46); // UXPL grayscale-cool x-dark + color: rgb(240, 243, 245); // UXPL grayscale-cool xx-light - &:hover, &:focus { - ul, div { + &:hover, + &:focus { + + ul, + div { opacity: 1; } } - %video-button { - @extend %ui-fake-link; - @include transition(none); - display: block; - font-weight: 700; - line-height: 46px; + %video-control { + @extend %t-strong; + @extend %t-title7; + display: inline-block; + vertical-align: middle; margin: 0; - padding: 0 0 0 15px; - overflow: hidden; - text-indent: -9999px; - -webkit-font-smoothing: antialiased; - box-shadow: 1px 0 0 #555, inset 1px 0 0 #555; - color: $white; - border-width: 0 1px; - border-style: solid; - border-color: $black; + border: 0; + border-radius: 0; + padding: ($baseline / 2) ($baseline / 1.5); + background: rgb(40, 44, 46); // UXPL grayscale-cool x-dark + box-shadow: none; + text-shadow: none; + color: rgb(207, 216, 220); // UXPL grayscale-cool light - &:hover, &:focus { - background-color: #444; - color: $white; - text-decoration: none; + &:hover, + &:focus { + background: darken(rgb(40, 44, 46), 7%); // UXPL secondary } &:active, - &:focus { - color: $white; - background-color: #444; - text-decoration: none; + &.is-active, + &.active { + color: rgb(14, 166, 236); // UXPL primary accent } } - div.slider { + .control { + @extend %video-control; + + .icon-fallback-img { + + .icon { + // if the icon font doesn't render, we need to provide dimensions for the svg's + width: 1em; + height: 1em; + + &.icon-hd { + // except when it's text, like with HD + // otherwise it's shifted to the right because "HD" is wider than 1em + width: auto; + } + } + } + } + + .slider { @include clearfix(); - @include transform(scaleY(0.5) translate3d(0, 50%, 0)); - background: #c2c2c2; - border: 1px solid $black; - border-radius: 0; - border-top: 1px solid $black; - box-shadow: inset 0 1px 0 #eee, 0 1px 0 #555; + @include transform-origin(bottom left); + @include transition(height .7s ease-in-out 0s); position: absolute; - z-index: 1; bottom: 100%; left: 0; right: 0; - height: 14px; - margin-left: -1px; - margin-right: -1px; - -webkit-transition: -webkit-transform 0.7s ease-in-out; - -moz-transition: -moz-transform 0.7s ease-in-out; - -ms-transition: -ms-transform 0.7s ease-in-out; - transition: transform 0.7s ease-in-out; + z-index: 1; + height: ($baseline / 4); + margin-left: 0; + border: 0; + border-radius: 0; + background: rgb(79, 89, 93); // UXPL grayscale-cool dark - div.ui-widget-header { - background: #777; - box-shadow: inset 0 1px 0 #999; + .ui-widget-header { + background: rgb(142, 62, 99); // UXPL secondary dark + box-shadow: none; } - div.ui-corner-all.slider-range { - background-color: #1e91d3; + .ui-corner-all.slider-range { opacity: 0.3; + background-color: #1e91d3; } - a.ui-slider-handle { + .ui-slider-handle { @extend %ui-fake-link; - @include transform(scale(.7, 1.3) translate3d(-80%, -15%, 0)); - background: $pink url('#{$static-path}/images/slider-handle.png') center center no-repeat; - background-size: 50%; - border: 1px solid darken($pink, 20%); - border-radius: 50%; - box-shadow: inset 0 1px 0 lighten($pink, 10%); - height: 20px; - margin-left: 0; + @include transform-origin(bottom left); + @include transition(all .7s ease-in-out 0s); top: 0; - -webkit-transition: -webkit-transform 0.7s ease-in-out; - -moz-transition: -moz-transform 0.7s ease-in-out; - -ms-transition: -ms-transform 0.7s ease-in-out; - transition: transform 0.7s ease-in-out; - width: 20px; + height: ($baseline / 4); + width: ($baseline / 4); + margin-left: -($baseline / 8); // center-center causes the control to be beyond the end of the sider + border: 0; + border-radius: ($baseline / 5); + background: rgb(203, 89, 141); // UXPL secondary base + box-shadow: none; - &:focus, &:hover { - background-color: lighten($pink, 10%); + &:focus, + &:hover { + background-color: rgb(219, 139, 175); // UXPL secondary light } } } .vcr { - float: left; + @include float(left); list-style: none; - margin: 0 lh() 0 0; + @include border-right(1px solid rgb(40, 44, 46)); // UXPL grayscale-cool x-dark padding: 0; @media (max-width: 1120px) { - margin-right: lh(0.5); + @include margin-right(lh(0.5)); font-size: em(14); } .video_control { - @extend %video-button; - float: left; - background-image: url('#{$static-path}/images/vcr.png'); - background-position: 15px 15px ; - background-repeat: no-repeat; - border-left: none; - padding: 0 lh(.75); - width: 14px; &:focus { - @extend %ui-depth4; position: relative; - outline: $white dotted thin; - outline-offset: -2px; - } - - &:empty { - height: 46px; - background-position: 15px 15px; - } - - &.play { - background-position: 17px -114px; - } - - &.pause { - background-position: 16px -50px; } &.skip { - background-image: none; - text-indent: 0; - width: initial; white-space: nowrap; } } - div.vidtime { + .vidtime { @extend %t-strong; - float: left; - line-height: 46px; //height of play pause buttons - -webkit-font-smoothing: antialiased; - padding-left: lh(.75); + @extend %t-title7; + @include padding-left(lh(.75)); + display: inline-block; + color: rgb(207, 216, 220); // UXPL grayscale-cool light + -webkit-font-smoothing: antialiased;; + @media (max-width: 1120px) { - padding-left: lh(0.5); + @include padding-left(lh(0.5)); } } } - div.secondary-controls { - float: right; + .secondary-controls { + @include float(right); + @include border-left(1px dotted rgb(79, 89, 93)); // UXPL grayscale-cool x-dark + + .volume, + .add-fullscreen, + .grouped-controls, + .quality-control { + @include border-left(1px dotted rgb(79, 89, 93)); // UXPL grayscale-cool x-dark + } + + .speed-button, + .volume > .control, + .add-fullscreen, + .quality-control, + .toggle-transcript { - a.speed-button, - div.volume > a, - a.add-fullscreen, - a.quality-control, - a.hide-subtitles { - // overflow is used to bypass Firefox CSS :focus outline bug - // http://johndoesdesign.com/blog/2012/css/firefox-and-its-css-focus-outline-bug/ &:focus { - @extend %ui-depth5; position: relative; - outline: $white dotted thin; - outline-offset: -2px; - overflow: auto; } } .menu-container { - float: left; position: relative; - &.is-opened { - .menu { - display: block; - opacity: 1; - padding: 0; - margin: 0; - list-style: none; - } - } - .menu { @include transition(none); @extend %ui-depth1; - box-shadow: inset 1px 0 0 #555, 0 1px 0 #444; - background-color: #444; - border: 1px solid $black; - bottom: 46px; - display: none; - opacity: 0; position: absolute; + display: none; + bottom: ($baseline * 2); + @include right(0); // right-align menus since this whole collection is on the right + width: 120px; + margin: 0; + border: none; + padding: 0; + box-shadow: none; + background-color: rgb(40, 44, 46); // UXPL grayscale-cool x-dark + list-style: none; li { @extend %ui-fake-link; - box-shadow: 0 1px 0 #555; - border-bottom: 1px solid $black; - color: $white; + color: rgb(231, 236, 238); // UXPL grayscale-cool x-light - a { - border: 0; - color: $white; + .speed-option, + .control-lang { + @include text-align(left); display: block; + width: 100%; + border: 0; + border-radius: 0; padding: lh(0.5); + background: rgb(40, 44, 46); // UXPL grayscale-cool x-dark + box-shadow: none; + color: rgb(231, 236, 238); // UXPL grayscale-cool x-light overflow: hidden; + text-shadow: none; text-overflow: ellipsis; white-space: nowrap; - &:hover, &:focus { - background-color: #666; - color: #aaa; - outline-offset: -4px; + &:hover, + &:focus { + background-color: rgb(79, 89, 93); // UXPL grayscale-cool dark + color: rgb(252, 252, 252); // UXPL grayscale white } } - &.is-active{ - a { - font-weight: bold; + &.is-active { + + .speed-option, + .control-lang { + color: rgb(14, 166, 236); // UXPL primary accent } } + } + } - &:last-child { - box-shadow: none; - border-bottom: 0; - margin-top: 0; - } + &.is-opened { + + .menu { + display: block; } } } - div.speeds { - &.is-opened { - .speed-button { - background-image: url('#{$static-path}/images/open-arrow.png'); + .speeds, + .lang, + .grouped-controls { + display: inline-block; + + .control { + + .icon-fallback-img { + @include float(left); + @include transform-origin(center center); } } + } - .menu{ - width: 131px; + .speeds { - @media (max-width: 1120px) { - width: 80px; + &.is-opened { + + .control { + + .icon { + + @include ltr { + @include transform(rotate(-90deg)); + } + + @include rtl { + @include transform(rotate(90deg)); + } + } } } .speed-button { - @extend %video-button; - @include clearfix(); - background-image: url('#{$static-path}/images/closed-arrow.png'); - background-position: 10px center; - background-repeat: no-repeat; - min-width: 116px; - text-indent: 0; - - @media (max-width: 1120px) { - min-width: 0; - width: 60px; - } .label { - float: left; - font-size: em(14); - font-weight: normal; - letter-spacing: 1px; - padding: 0 lh(0.25) 0 lh(0.5); - line-height: 46px; - text-transform: uppercase; - color: #999; + @include padding(0 ($baseline/3) 0 0); + font-family: $body-font-family; + color: rgb(231, 236, 238); // UXPL grayscale-cool x-light @media (max-width: 1120px) { display: none; @@ -487,117 +550,115 @@ div.video { } .value { - float: left; + @include padding(0, lh(0.5), 0, 0); + color: rgb(231, 236, 238); // UXPL grayscale-cool x-light font-weight: bold; - margin-bottom: 0; - padding: 0 lh(0.5) 0 0; @media (max-width: 1120px) { - padding: 0 lh(0.5) 0 lh(0.5); + padding: 0 lh(0.5); } - - line-height: 46px; - color: $white; } } } - div.volume { - float: left; + .lang { + + .language-menu { + width: $baseline; + padding: ($baseline / 2) 0; + } + + .control { + + .icon { + + @include rtl { + @include transform(rotate(-180deg)); + } + } + } + + &.is-opened { + + .control { + + .icon { + + @include ltr { + @include transform(rotate(90deg)); + } + + @include rtl { + @include transform(rotate(90deg)); + } + } + } + } + } + + .volume { + display: inline-block; position: relative; &.is-opened { + .volume-slider-container { display: block; opacity: 1; } } - &.is-muted { - & > a { - background-image: url('#{$static-path}/images/mute.png'); - } - } - - & > a { - @extend %video-button; - @include clearfix(); - background-image: url('#{$static-path}/images/volume.png'); - background-position: 10px center; - background-repeat: no-repeat; - width: 30px; - height: 46px; - } - &:not(:first-child) > a { - border-left: none; + @include border-left(none); } .volume-slider-container { @include transition(none); @extend %ui-depth1; - box-shadow: inset 1px 0 0 #555, 0 3px 0 #444; - background-color: #444; - border: 1px solid $black; - bottom: 46px; display: none; - opacity: 0; position: absolute; - width: 45px; - height: 125px; - margin-left: -1px; + bottom: ($baseline * 2); + @include right(0); + width: 41px; + height: 120px; + background-color: rgb(40, 44, 46); // UXPL grayscale-cool x-dark .volume-slider { height: 100px; - border: 0; - width: 5px; + width: ($baseline / 4); margin: 14px auto; - background: #666; - border: 1px solid $black; - box-shadow: 0 1px 0 #333; + border: 0; + background: rgb(79, 89, 93); // UXPL grayscale-cool dark - a.ui-slider-handle { + .ui-slider-handle { @extend %ui-fake-link; @include transition(height $tmg-s2 ease-in-out 0s, width $tmg-s2 ease-in-out 0s); - background: $pink url('#{$static-path}/images/slider-handle.png') center center no-repeat; - background-size: 50%; - border: 1px solid darken($pink, 20%); - border-radius: 15px; - box-shadow: inset 0 1px 0 lighten($pink, 10%); + @include left(-5px); height: 15px; - left: -6px; width: 15px; + background: rgb(203, 89, 141); // UXPL secondary base + border: 0; + border-radius: ($baseline / 5); + + &:hover, + &:focus { + background: rgb(219, 139, 175); // UXPL secondary light + } } .ui-slider-range { - background: #ddd; + background: rgb(142, 62, 99); // UXPL secondary dark } } } } - a.add-fullscreen { - @extend %video-button; - background: url('#{$static-path}/images/fullscreen.png') center no-repeat; - border-left: none; - float: left; - padding: 0 11px; - width: 30px; - } - - - a.quality-control { - @extend %video-button; - background: url('#{$static-path}/images/hd.png') center no-repeat; - border-left: none; - float: left; - padding: 0 11px; - width: 30px; + .quality-control { + font-weight: 700; + letter-spacing: -1px; &.active { - background-color: #F44; - color: #0ff; - text-decoration: none; + color: rgb(14, 166, 236); // UXPL primary accent } &.is-hidden { @@ -605,62 +666,55 @@ div.video { } } - div.lang { - & > a.hide-subtitles { - @extend %video-button; - @include transition(none); - box-shadow: inset 1px 0 0 #555; - background: url('#{$static-path}/images/cc.png') center no-repeat; - border-left: none; - border-right: none; - padding: 0 11px; - width: 30px; + .toggle-transcript { - &.off { - opacity: 0.7; + &.is-active { + color: rgb(14, 166, 236); // UXPL primary accent } - } + } - .menu.langs-list { - right: -1px; - width: 150px; + .lang { + + & > .hide-subtitles { + @include transition(none); } } } } - &:hover section.video-controls { - ul, div { - opacity: 1; - } + &:hover { - div.slider { - @include transform(scaleY(1) translate3d(0, 0, 0)); + .video-controls { - a.ui-slider-handle { - @include transform(scale(1) translate3d(-50%, -15%, 0)); + .slider { + height: ($baseline / 1.5); + + .ui-slider-handle { + height: ($baseline / 1.5); + width: ($baseline / 1.5); + } } } } } - ol.subtitles { - padding-left: 0; - float: left; - max-height: 460px; + .subtitles { + @include float(left); overflow: auto; - width: flex-grid(3, 9); margin: 0; + max-height: 460px; + width: flex-grid(3, 9); + padding: 0; font-size: 14px; list-style: none; visibility: visible; li { @extend %ui-fake-link; - border: 0; - color: rgb(29,157,217); margin-bottom: 8px; + border: 0; padding: 0; + color: #0074b5; // AA compliant line-height: lh(); &.current { @@ -673,7 +727,8 @@ div.video { outline-offset: -1px; } - &:hover, &:focus { + &:hover, + &:focus { text-decoration: underline; } @@ -685,13 +740,12 @@ div.video { &.closed { - article.video-wrapper { + .video-wrapper { width: flex-grid(9,9); - background-color: inherit; } - article.video-wrapper section.video-controls.html5 { + .video-wrapper .video-controls.html5 { bottom: 0; left: 0; right: 0; @@ -699,21 +753,22 @@ div.video { z-index: 1; } - article.video-wrapper div.video-player-pre, article.video-wrapper div.video-player-post { + .video-wrapper .video-player-pre, + .video-wrapper .video-player-post { height: 0; } - article.video-wrapper section.video-player { + .video-wrapper .video-player { h3 { color: black; } } - ol.subtitles { + .subtitles { @extend .is-hidden; } - ol.subtitles.html5 { + .subtitles.html5 { @extend %ui-depth0; background-color: rgba(243, 243, 243, 0.8); height: 100%; @@ -743,63 +798,66 @@ div.video { border-radius: 0; &.closed { - div.tc-wrapper { - article.video-wrapper { + .tc-wrapper { + .video-wrapper { width: 100%; } } } - article.video-wrapper div.video-player-pre, article.video-wrapper div.video-player-post { + .video-wrapper .video-player-pre, + .video-wrapper .video-player-post { height: 0; } - article.video-wrapper { + .video-wrapper { position: static; } - article.video-wrapper section.video-player { + .video-wrapper .video-player { h3 { color: white; } } - div.tc-wrapper { + .tc-wrapper { @include clearfix(); width: 100%; height: 100%; position: static; - article.video-wrapper { + .video-wrapper { height: 100%; width: 75%; + @include margin-right(0); vertical-align: middle; - margin-right: 0; - object, iframe, video{ + object, + iframe, + video{ position: absolute; width: auto; height: auto; } } - section.video-controls { + .video-controls { @extend %ui-depth4; + position: absolute; bottom: 0; left: 0; - position: absolute; width: 100%; } } - ol.subtitles { - @include box-sizing(border-box); - @include transition(none); - background: $black; + .subtitles { height: 100%; width: 25%; padding: lh(); + @include box-sizing(border-box); + @include transition(none); + background: $black; visibility: visible; li { @@ -813,9 +871,11 @@ div.video { } &.is-touch { - div.tc-wrapper { - article.video-wrapper { - object, iframe, video { + .tc-wrapper { + .video-wrapper { + object, + iframe, + video { width: 100%; height: 100%; } @@ -864,5 +924,3 @@ div.video { } } } - - diff --git a/common/lib/xmodule/xmodule/js/spec/video/video_accessible_menu_spec.js b/common/lib/xmodule/xmodule/js/spec/video/video_accessible_menu_spec.js index a790f8ecae..feae0e58f3 100644 --- a/common/lib/xmodule/xmodule/js/spec/video/video_accessible_menu_spec.js +++ b/common/lib/xmodule/xmodule/js/spec/video/video_accessible_menu_spec.js @@ -260,7 +260,7 @@ state.videoSpeedControl.setSpeed(1.0); spyOn(state.videoPlayer, 'onSpeedChange').andCallThrough(); - $('li[data-speed="0.75"] a').click(); + $('li[data-speed="0.75"] .speed-link').click(); }); it('trigger speedChange event', function () { @@ -274,7 +274,7 @@ xdescribe('onSpeedChange', function () { beforeEach(function () { state = jasmine.initializePlayer(); - $('li[data-speed="1.0"] a').addClass('active'); + $('li[data-speed="1.0"] .speed-link').addClass('active'); state.videoSpeedControl.setSpeed(0.75); }); diff --git a/common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js b/common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js index b8d489d861..9d822bd8c1 100644 --- a/common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js +++ b/common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js @@ -23,39 +23,39 @@ }); describe('constructor', function () { + describe('always', function () { + beforeEach(function () { spyOn($, 'ajaxWithPrefix').andCallThrough(); }); - it('create the caption element', function () { + it('create the transcript element', function () { state = jasmine.initializePlayer(); - expect($('.video')).toContain('ol.subtitles'); + expect($('.video')).toContain('.subtitles'); }); - it('add caption control to video player', function () { + it('add transcript control to video player', function () { state = jasmine.initializePlayer(); - expect($('.video')).toContain('a.hide-subtitles'); + expect($('.video')).toContain('.toggle-transcript'); }); - it('add ARIA attributes to caption control', function () { + it('add ARIA attributes to transcript control', function () { state = jasmine.initializePlayer(); - var captionControl = $('a.hide-subtitles'); + var captionControl = $('.toggle-transcript'); expect(captionControl).toHaveAttrs({ - 'role': 'button', - 'title': 'Turn off captions', 'aria-disabled': 'false' }); }); - it('fetch the caption in HTML5 mode', function () { + it('fetch the transcript in HTML5 mode', function () { runs(function () { state = jasmine.initializePlayer(); }); waitsFor(function () { return state.videoCaption.loaded; - }, 'Expect captions to be loaded.', WAIT_TIMEOUT); + }, 'Expect transcript to be loaded.', WAIT_TIMEOUT); runs(function () { expect($.ajaxWithPrefix).toHaveBeenCalledWith({ @@ -70,7 +70,7 @@ }); }); - it('fetch the caption in Flash mode', function () { + it('fetch the transcript in Flash mode', function () { runs(function () { state = jasmine.initializePlayerYouTube(); spyOn(state, 'isFlashMode').andReturn(true); @@ -79,7 +79,7 @@ waitsFor(function () { return state.videoCaption.loaded; - }, 'Expect captions to be loaded.', WAIT_TIMEOUT); + }, 'Expect transcript to be loaded.', WAIT_TIMEOUT); runs(function () { expect($.ajaxWithPrefix).toHaveBeenCalledWith({ @@ -96,14 +96,14 @@ }); }); - it('fetch the caption in Youtube mode', function () { + it('fetch the transcript in Youtube mode', function () { runs(function () { state = jasmine.initializePlayerYouTube(); }); waitsFor(function () { return state.videoCaption.loaded; - }, 'Expect captions to be loaded.', WAIT_TIMEOUT); + }, 'Expect transcript to be loaded.', WAIT_TIMEOUT); runs(function () { expect($.ajaxWithPrefix).toHaveBeenCalledWith({ @@ -159,7 +159,14 @@ }); describe('renderLanguageMenu', function () { + describe('is rendered', function () { + var KEY = $.ui.keyCode, + + keyPressEvent = function(key) { + return $.Event('keydown', { keyCode: key }); + }; + it('if languages more than 1', function () { state = jasmine.initializePlayer(); var transcripts = state.config.transcriptLanguages, @@ -172,7 +179,7 @@ $('.langs-list li').each(function(index) { var code = $(this).data('lang-code'), - link = $(this).find('a'), + link = $(this).find('.control'), label = link.text(); expect(code).toBeInArray(langCodes); @@ -183,7 +190,7 @@ it('when clicking on link with new language', function () { state = jasmine.initializePlayer(); var Caption = state.videoCaption, - link = $('.langs-list li[data-lang-code="de"] a'); + link = $('.langs-list li[data-lang-code="de"] .control-lang'); spyOn(Caption, 'fetchCaption'); spyOn(state.storage, 'setItem'); @@ -201,7 +208,7 @@ it('when clicking on link with current language', function () { state = jasmine.initializePlayer(); var Caption = state.videoCaption, - link = $('.langs-list li[data-lang-code="en"] a'); + link = $('.langs-list li[data-lang-code="en"] .control-lang'); spyOn(Caption, 'fetchCaption'); spyOn(state.storage, 'setItem'); @@ -223,6 +230,23 @@ $('.lang').mouseleave(); expect($('.lang')).not.toHaveClass('is-opened'); }); + + it('opens the language menu on arrow up', function() { + state = jasmine.initializePlayer(); + $('.language-menu').focus(); + $('.language-menu').trigger(keyPressEvent(KEY.UP)); + expect($('.lang')).toHaveClass('is-opened'); + expect($('.langs-list').find('li').last().find('.control-lang')).toBeFocused(); + }); + + it('closes the language menu on ESC', function() { + state = jasmine.initializePlayer(); + $('.language-menu').trigger(keyPressEvent(KEY.UP)); + expect($('.lang')).toHaveClass('is-opened'); + $('.language-menu').trigger(keyPressEvent(KEY.ESCAPE)); + expect($('.lang')).not.toHaveClass('is-opened'); + expect($('.language-menu')).toBeFocused(); + }); }); describe('is not rendered', function () { @@ -246,10 +270,10 @@ waitsFor(function () { return state.videoCaption.rendered; - }, 'Captions are not rendered', WAIT_TIMEOUT); + }, 'Transcripts are not rendered', WAIT_TIMEOUT); }); - it('render the caption', function () { + it('render the transcript', function () { runs(function () { var captionsData = jasmine.stubbedCaption, items = $('.subtitles li[data-index]'); @@ -267,7 +291,7 @@ }); }); - it('add a padding element to caption', function () { + it('add a padding element to transcript', function () { runs(function () { expect($('.subtitles li:first').hasClass('spacing')) .toBe(true); @@ -277,7 +301,7 @@ }); - it('bind all the caption link', function () { + it('bind all the transcript link', function () { runs(function () { var handlerList = ['captionMouseOverOut', 'captionClick', 'captionMouseDown', 'captionFocus', 'captionBlur', @@ -323,7 +347,7 @@ waitsFor(function () { return state.videoCaption.rendered; - }, 'Captions are not rendered', WAIT_TIMEOUT); + }, 'Transcripts are not rendered', WAIT_TIMEOUT); runs(function () { expect(state.videoCaption.rendered).toBeTruthy(); @@ -346,14 +370,14 @@ ); }); - it('show captions on play', function () { + it('show transcript on play', function () { runs(function () { state.el.trigger('play'); }); waitsFor(function () { return state.videoCaption.rendered; - }, 'Captions are not rendered', WAIT_TIMEOUT); + }, 'Transcripts are not rendered', WAIT_TIMEOUT); runs(function () { var captionsData = jasmine.stubbedCaption, @@ -377,7 +401,7 @@ }); }); - describe('when no captions file was specified', function () { + describe('when no transcripts file was specified', function () { beforeEach(function () { state = jasmine.initializePlayer('video_all.html', { 'sub': '', @@ -385,8 +409,8 @@ }); }); - it('captions panel is not shown', function () { - expect(state.videoCaption.hideSubtitlesEl).toBeHidden(); + it('transcript panel is not shown', function () { + expect(state.videoCaption.languageChooserEl).toBeHidden(); }); }); }); @@ -403,10 +427,10 @@ waitsFor(function () { return state.videoCaption.rendered; - }, 'Captions are not rendered', WAIT_TIMEOUT); + }, 'Transcripts are not rendered', WAIT_TIMEOUT); }); - describe('when cursor is outside of the caption box', function () { + describe('when cursor is outside of the transcript box', function () { it('does not set freezing timeout', function () { runs(function () { expect(state.videoCaption.frozen).toBeFalsy(); @@ -414,7 +438,7 @@ }); }); - describe('when cursor is in the caption box', function () { + describe('when cursor is in the transcript box', function () { beforeEach(function () { spyOn(state.videoCaption, 'onMouseLeave'); runs(function () { @@ -452,7 +476,7 @@ }); describe( - 'when cursor is moving out of the caption box', + 'when cursor is moving out of the transcript box', function () { beforeEach(function () { @@ -469,7 +493,7 @@ expect(window.clearTimeout).toHaveBeenCalledWith(100); }); - it('unfreeze the caption', function () { + it('unfreeze the transcript', function () { expect(state.videoCaption.frozen).toBeNull(); }); }); @@ -482,7 +506,7 @@ $('.subtitles').trigger(jQuery.Event('mouseout')); }); - it('scroll the caption', function () { + it('scroll the transcript', function () { expect($.fn.scrollTo).toHaveBeenCalled(); }); }); @@ -493,7 +517,7 @@ $('.subtitles').trigger(jQuery.Event('mouseout')); }); - it('does not scroll the caption', function () { + it('does not scroll the transcript', function () { expect($.fn.scrollTo).not.toHaveBeenCalled(); }); }); @@ -514,7 +538,7 @@ spyOn(state, 'youtubeId').andReturn('Z5KLxerq05Y'); }); - it('show caption on language change', function () { + it('show transcript on language change', function () { Caption.loaded = true; Caption.fetchCaption(); @@ -522,7 +546,7 @@ expect(Caption.hideCaptions).toHaveBeenCalledWith(false); }); - msg = 'use cookie to show/hide captions if they have not been ' + + msg = 'use cookie to show/hide transcripts if they have not been ' + 'loaded yet'; it(msg, function () { Caption.loaded = false; @@ -554,7 +578,7 @@ }); msg = 'on success: change language on touch devices when ' + - 'captions have not been rendered yet'; + 'transcripts have not been rendered yet'; it(msg, function () { state.isTouch = true; Caption.loaded = true; @@ -604,7 +628,7 @@ expect(Caption.loaded).toBeTruthy(); }); - msg = 'on error: captions are hidden if there are no transcripts'; + msg = 'on error: transcripts are hidden if there are no transcripts'; it(msg, function () { spyOn(Caption, 'fetchAvailableTranslations'); $.ajax.andCallFake(function (settings) { @@ -619,7 +643,6 @@ expect(Caption.fetchAvailableTranslations).not.toHaveBeenCalled(); expect(Caption.hideCaptions.mostRecentCall.args) .toEqual([true, false]); - expect(Caption.hideSubtitlesEl).toBeHidden(); }); msg = 'on error: for Html5 player an attempt to fetch transcript ' + @@ -667,7 +690,7 @@ msg = 'on error: fetch available translations if there are ' + 'additional transcripts'; - xit(msg, function () { + it(msg, function () { $.ajax .andCallFake(function (settings) { _.result(settings, 'error'); @@ -683,7 +706,6 @@ expect($.ajaxWithPrefix).toHaveBeenCalled(); expect(Caption.fetchAvailableTranslations).toHaveBeenCalled(); - expect(Caption.hideCaptions).not.toHaveBeenCalled(); }); }); @@ -745,7 +767,7 @@ expect(Caption.renderLanguageMenu).not.toHaveBeenCalled(); }); - msg = 'on error: captions are hidden if there are no transcript'; + msg = 'on error: transcripts are hidden if there are no transcript'; it(msg, function () { $.ajax.andCallFake(function (settings) { _.result(settings, 'error'); @@ -754,12 +776,12 @@ expect($.ajaxWithPrefix).toHaveBeenCalled(); expect(Caption.hideCaptions).toHaveBeenCalledWith(true, false); - expect(Caption.hideSubtitlesEl).toBeHidden(); + expect(Caption.subtitlesEl).toBeHidden(); }); }); describe('play', function () { - describe('when the caption was not rendered', function () { + describe('when the transcript was not rendered', function () { beforeEach(function () { window.onTouchBasedDevice.andReturn(['iPad']); @@ -770,10 +792,10 @@ waitsFor(function () { return state.videoCaption.rendered; - }, 'Captions are not rendered', WAIT_TIMEOUT); + }, 'Transcripts are not rendered', WAIT_TIMEOUT); }); - it('render the caption', function () { + it('render the transcript', function () { runs(function () { var captionsData; @@ -792,7 +814,7 @@ }); - it('add a padding element to caption', function () { + it('add a padding element to transcript', function () { runs(function () { expect($('.subtitles li:first')).toBe('.spacing'); expect($('.subtitles li:last')).toBe('.spacing'); @@ -833,7 +855,7 @@ waitsFor(function () { return state.videoCaption.rendered; - }, 'Captions are not rendered', WAIT_TIMEOUT); + }, 'Transcripts are not rendered', WAIT_TIMEOUT); }); describe('when the video speed is 1.0x', function () { @@ -852,7 +874,7 @@ }); describe('when the video speed is not 1.0x', function () { - it('search the caption based on 1.0x speed', function () { + it('search the transcript based on 1.0x speed', function () { runs(function () { state.videoCaption.updatePlayTime(25.000); expect(state.videoCaption.currentIndex).toEqual(5); @@ -882,14 +904,14 @@ }); }); - it('deactivate the previous caption', function () { + it('deactivate the previous transcript', function () { runs(function () { expect($('.subtitles li[data-index=1]')) .not.toHaveClass('current'); }); }); - it('activate new caption', function () { + it('activate new transcript', function () { runs(function () { expect($('.subtitles li[data-index=5]')) .toHaveClass('current'); @@ -902,7 +924,7 @@ }); }); - it('scroll caption to new position', function () { + it('scroll transcript to new position', function () { runs(function () { expect($.fn.scrollTo).toHaveBeenCalled(); }); @@ -930,7 +952,7 @@ waitsFor(function () { return state.videoCaption.rendered; - }, 'Captions are not rendered', WAIT_TIMEOUT); + }, 'Transcripts are not rendered', WAIT_TIMEOUT); runs(function () { videoControl = state.videoControl; @@ -939,8 +961,8 @@ }); }); - describe('set the height of caption container', function () { - it('when CC button is enabled', function () { + describe('set the height of transcript container', function () { + it('when transcript button is enabled', function () { runs(function () { var realHeight = parseInt( $('.subtitles').css('maxHeight'), 10 @@ -953,7 +975,7 @@ }); }); - it('when CC button is disabled ', function () { + it('when transcript button is disabled ', function () { runs(function () { var realHeight, videoWrapperHeight, progressSliderHeight, controlHeight, shouldBeHeight; @@ -976,7 +998,7 @@ }); }); - it('set the height of caption spacing', function () { + it('set the height of transcript spacing', function () { runs(function () { var firstSpacing, lastSpacing; @@ -994,7 +1016,7 @@ }); }); - it('scroll caption to new position', function () { + it('scroll transcript to new position', function () { runs(function () { expect($.fn.scrollTo).toHaveBeenCalled(); }); @@ -1009,11 +1031,11 @@ waitsFor(function () { return state.videoCaption.rendered; - }, 'Captions are not rendered', WAIT_TIMEOUT); + }, 'Transcripts are not rendered', WAIT_TIMEOUT); }); describe('when frozen', function () { - it('does not scroll the caption', function () { + it('does not scroll the transcript', function () { runs(function () { state.videoCaption.frozen = true; $('.subtitles li[data-index=1]').addClass('current'); @@ -1030,8 +1052,8 @@ }); }); - describe('when there is no current caption', function () { - it('does not scroll the caption', function () { + describe('when there is no current transcript', function () { + it('does not scroll the transcript', function () { runs(function () { state.videoCaption.scrollCaption(); expect($.fn.scrollTo).not.toHaveBeenCalled(); @@ -1039,8 +1061,8 @@ }); }); - describe('when there is a current caption', function () { - it('scroll to current caption', function () { + describe('when there is a current transcript', function () { + it('scroll to current transcript', function () { runs(function () { $('.subtitles li[data-index=1]').addClass('current'); state.videoCaption.scrollCaption(); @@ -1062,7 +1084,7 @@ isRendered = state.videoCaption.rendered; return isRendered && duration; - }, 'Captions are not rendered', WAIT_TIMEOUT); + }, 'Transcripts are not rendered', WAIT_TIMEOUT); }); describe('when the video speed is 1.0x', function () { @@ -1104,40 +1126,30 @@ $('.subtitles li[data-index=1]').addClass('current'); }); - describe('when the caption is visible', function () { + describe('when the transcript is visible', function () { beforeEach(function () { state.el.removeClass('closed'); state.videoCaption.toggle(jQuery.Event('click')); }); - it('hide the caption', function () { + it('hide the transcript', function () { expect(state.el).toHaveClass('closed'); }); - - it('changes ARIA attribute of caption control', function () { - expect($('a.hide-subtitles')) - .toHaveAttr('title', 'Turn on captions'); - }); }); - describe('when the caption is hidden', function () { + describe('when the transcript is hidden', function () { beforeEach(function () { state.el.addClass('closed'); state.videoCaption.toggle(jQuery.Event('click')); jasmine.Clock.useMock(); }); - it('show the caption', function () { + it('show the transcript', function () { expect(state.el).not.toHaveClass('closed'); }); - it('changes ARIA attribute of caption control', function () { - expect($('a.hide-subtitles')) - .toHaveAttr('title', 'Turn off captions'); - }); - // Test turned off due to flakiness (11/25/13) - xit('scroll the caption', function () { + xit('scroll the transcript', function () { // After transcripts are shown, and the video plays for a // bit. jasmine.Clock.tick(1000); @@ -1153,7 +1165,7 @@ }); }); - describe('caption accessibility', function () { + describe('transcript accessibility', function () { beforeEach(function () { runs(function () { state = jasmine.initializePlayer(); @@ -1161,7 +1173,7 @@ waitsFor(function () { return state.videoCaption.rendered; - }, 'Captions are not rendered', WAIT_TIMEOUT); + }, 'Transcripts are not rendered', WAIT_TIMEOUT); }); describe('when getting focus through TAB key', function () { @@ -1174,7 +1186,7 @@ }); }); - it('shows an outline around the caption', function () { + it('shows an outline around the transcript', function () { runs(function () { expect($('.subtitles li[data-index=0]')) .toHaveClass('focused'); @@ -1197,7 +1209,7 @@ }); }); - it('does not show an outline around the caption', function () { + it('does not show an outline around the transcript', function () { runs(function () { expect($('.subtitles li[data-index=0]')) .not.toHaveClass('focused'); @@ -1212,7 +1224,7 @@ }); describe( - 'when same caption gets the focus through mouse after ' + + 'when same transcript gets the focus through mouse after ' + 'having focus through TAB key', function () { @@ -1241,7 +1253,7 @@ }); describe( - 'when a second caption gets focus through mouse after ' + + 'when a second transcript gets focus through mouse after ' + 'first had focus through TAB key', function () { diff --git a/common/lib/xmodule/xmodule/js/spec/video/video_context_menu_spec.js b/common/lib/xmodule/xmodule/js/spec/video/video_context_menu_spec.js index 295b151a4f..96b854340b 100644 --- a/common/lib/xmodule/xmodule/js/spec/video/video_context_menu_spec.js +++ b/common/lib/xmodule/xmodule/js/spec/video/video_context_menu_spec.js @@ -5,16 +5,16 @@ closeSubmenuKeyboard, menu, menuItems, menuSubmenuItem, submenu, submenuItems, overlay, playButton; openMenu = function () { - var container = $('div.video'); + var container = $('.video'); jasmine.Clock.useMock(); container.find('video').trigger('contextmenu'); - menu = container.children('ol.contextmenu'); - menuItems = menu.children('li.menu-item').not('.submenu-item'); - menuSubmenuItem = menu.children('li.menu-item.submenu-item'); - submenu = menuSubmenuItem.children('ol.submenu'); - submenuItems = submenu.children('li.menu-item'); - overlay = container.children('div.overlay'); - playButton = $('a.video_control.play'); + menu = container.children('.contextmenu'); + menuItems = menu.children('.menu-item').not('.submenu-item'); + menuSubmenuItem = menu.children('.menu-item.submenu-item'); + submenu = menuSubmenuItem.children('.submenu'); + submenuItems = submenu.children('.menu-item'); + overlay = container.children('.overlay'); + playButton = $('.video_control.play'); }; keyPressEvent = function(key) { diff --git a/common/lib/xmodule/xmodule/js/spec/video/video_full_screen_spec.js b/common/lib/xmodule/xmodule/js/spec/video/video_full_screen_spec.js index 215b891f41..6aa858b73f 100644 --- a/common/lib/xmodule/xmodule/js/spec/video/video_full_screen_spec.js +++ b/common/lib/xmodule/xmodule/js/spec/video/video_full_screen_spec.js @@ -30,8 +30,6 @@ var fullScreenControl = $('.add-fullscreen'); expect(fullScreenControl).toHaveAttrs({ - 'role': 'button', - 'title': 'Fill browser', 'aria-disabled': 'false' }); }); @@ -53,14 +51,10 @@ var fullScreenControl = $('.add-fullscreen'); fullScreenControl.click(); expect(fullScreenControl).toHaveAttrs({ - 'role': 'button', - 'title': 'Exit full browser', 'aria-disabled': 'false' }); fullScreenControl.click(); expect(fullScreenControl).toHaveAttrs({ - 'role': 'button', - 'title': 'Fill browser', 'aria-disabled': 'false' }); }); diff --git a/common/lib/xmodule/xmodule/js/spec/video/video_play_pause_control_spec.js b/common/lib/xmodule/xmodule/js/spec/video/video_play_pause_control_spec.js index 877dc9861e..0cbb23c1f1 100644 --- a/common/lib/xmodule/xmodule/js/spec/video/video_play_pause_control_spec.js +++ b/common/lib/xmodule/xmodule/js/spec/video/video_play_pause_control_spec.js @@ -25,8 +25,6 @@ it('add ARIA attributes to play control', function () { expect($('.video_control.play')).toHaveAttrs({ - 'role': 'button', - 'title': 'Play', 'aria-disabled': 'false' }); }); @@ -34,8 +32,6 @@ it('can update ARIA state on play', function () { state.el.trigger('play'); expect($('.video_control.pause')).toHaveAttrs({ - 'role': 'button', - 'title': 'Pause', 'aria-disabled': 'false' }); }); @@ -44,8 +40,6 @@ state.el.trigger('play'); state.el.trigger('ended'); expect($('.video_control.play')).toHaveAttrs({ - 'role': 'button', - 'title': 'Play', 'aria-disabled': 'false' }); }); diff --git a/common/lib/xmodule/xmodule/js/spec/video/video_play_skip_control_spec.js b/common/lib/xmodule/xmodule/js/spec/video/video_play_skip_control_spec.js index 9ccea6a0ab..725bdd5c8a 100644 --- a/common/lib/xmodule/xmodule/js/spec/video/video_play_skip_control_spec.js +++ b/common/lib/xmodule/xmodule/js/spec/video/video_play_skip_control_spec.js @@ -27,8 +27,6 @@ it('add ARIA attributes to play control', function () { expect($('.video_control.play')).toHaveAttrs({ - 'role': 'button', - 'title': 'Play', 'aria-disabled': 'false' }); }); diff --git a/common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js b/common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js index 843b6c3f65..e521282ec8 100644 --- a/common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js +++ b/common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js @@ -745,11 +745,6 @@ function (VideoPlayer) { $('.add-fullscreen').click(); }); - it('replace the full screen button tooltip', function () { - expect($('.add-fullscreen')) - .toHaveAttr('title', 'Exit full browser'); - }); - it('add the video-fullscreen class', function () { expect(state.el).toHaveClass('video-fullscreen'); }); @@ -773,11 +768,6 @@ function (VideoPlayer) { $('.add-fullscreen').click(); }); - it('replace the full screen button tooltip', function () { - expect($('.add-fullscreen')) - .toHaveAttr('title', 'Fill browser'); - }); - it('remove the video-fullscreen class', function () { expect(state.el).not.toHaveClass('video-fullscreen'); }); diff --git a/common/lib/xmodule/xmodule/js/spec/video/video_quality_control_spec.js b/common/lib/xmodule/xmodule/js/spec/video/video_quality_control_spec.js index 0bf3722a4c..5ce7375aee 100644 --- a/common/lib/xmodule/xmodule/js/spec/video/video_quality_control_spec.js +++ b/common/lib/xmodule/xmodule/js/spec/video/video_quality_control_spec.js @@ -33,8 +33,6 @@ it('add ARIA attributes to quality control', function () { expect(qualityControl.el).toHaveAttrs({ - 'role': 'button', - 'title': 'HD off', 'aria-disabled': 'false' }); }); @@ -117,7 +115,7 @@ it('does not contain the quality control', function () { state = jasmine.initializePlayer(); - expect(state.el.find('a.quality-control').length).toBe(0); + expect(state.el.find('.quality-control').length).toBe(0); }); }); }); diff --git a/common/lib/xmodule/xmodule/js/spec/video/video_skip_control_spec.js b/common/lib/xmodule/xmodule/js/spec/video/video_skip_control_spec.js index da3a87845b..d3c0b47d52 100644 --- a/common/lib/xmodule/xmodule/js/spec/video/video_skip_control_spec.js +++ b/common/lib/xmodule/xmodule/js/spec/video/video_skip_control_spec.js @@ -33,8 +33,6 @@ it('add ARIA attributes to play control', function () { state.el.trigger('play'); expect($('.skip-control')).toHaveAttrs({ - 'role': 'button', - 'title': 'Do not show again', 'aria-disabled': 'false' }); }); diff --git a/common/lib/xmodule/xmodule/js/spec/video/video_speed_control_spec.js b/common/lib/xmodule/xmodule/js/spec/video/video_speed_control_spec.js index d5b14e6b2d..2ea4a7e365 100644 --- a/common/lib/xmodule/xmodule/js/spec/video/video_speed_control_spec.js +++ b/common/lib/xmodule/xmodule/js/spec/video/video_speed_control_spec.js @@ -1,4 +1,5 @@ (function (undefined) { + 'use strict'; describe('VideoSpeedControl', function () { var state, oldOTBD; @@ -38,21 +39,11 @@ expect($(link)).toHaveData( 'speed', state.speeds[index] ); - expect($(link).find('a').text()).toBe( + expect($(link).find('.speed-option').text()).toBe( state.speeds[index] + 'x' ); }); }); - - it('add ARIA attributes to speed control', function () { - var speedControl = $('div.speeds>a'); - - expect(speedControl).toHaveAttrs({ - 'role': 'button', - 'title': 'Speeds', - 'aria-disabled': 'false' - }); - }); }); describe('when running on touch based device', function () { @@ -61,33 +52,17 @@ window.onTouchBasedDevice.andReturn([device]); state = jasmine.initializePlayer(); - expect(state.el.find('div.speeds')).not.toExist(); + expect(state.el.find('.speeds')).not.toExist(); }); }); }); describe('when running on non-touch based device', function () { - var speedControl, speedEntries, speedButton, + var speedControl, speedEntries, speedButton, speedsContainer, KEY = $.ui.keyCode, keyPressEvent = function(key) { return $.Event('keydown', {keyCode: key}); - }, - - // Get previous element in array or cyles back to the last - // if it is the first. - previousSpeed = function(index) { - return speedEntries.eq(index < 1 ? - speedEntries.length - 1 : - index - 1); - }, - - // Get next element in array or cyles back to the first if - // it is the last. - nextSpeed = function(index) { - return speedEntries.eq(index >= speedEntries.length-1 ? - 0 : - index + 1); }; beforeEach(function () { @@ -95,7 +70,7 @@ speedControl = $('.speeds'); speedButton = $('.speed-button'); speedsContainer = $('.video-speeds'); - speedEntries = speedsContainer.find('a'); + speedEntries = speedsContainer.find('.speed-option'); }); it('open/close the speed menu on mouseenter/mouseleave', @@ -114,11 +89,6 @@ expect(speedControl).toHaveClass('is-opened'); }); - it('close the speed menu on click', function () { - speedControl.mouseenter().click(); - expect(speedControl).not.toHaveClass('is-opened'); - }); - it('close the speed menu on outside click', function () { speedControl.trigger(keyPressEvent(KEY.ENTER)); $(window).click(); @@ -150,8 +120,7 @@ it('UP and DOWN keydown function as expected on speed entries', function () { - var lastEntry = speedEntries.length-1, - speed_0_75 = speedEntries.filter(':contains("0.75x")'), + var speed_0_75 = speedEntries.filter(':contains("0.75x")'), speed_1_0 = speedEntries.filter(':contains("1.0x")'); // First open menu @@ -226,7 +195,7 @@ it('trigger speedChange event', function () { spyOnEvent(state.el, 'speedchange'); - $('li[data-speed="0.75"] a').click(); + $('li[data-speed="0.75"] .speed-option').click(); expect('speedchange').toHaveBeenTriggeredOn(state.el); expect(state.videoSpeedControl.currentSpeed).toEqual('0.75'); }); diff --git a/common/lib/xmodule/xmodule/js/spec/video/video_volume_control_spec.js b/common/lib/xmodule/xmodule/js/spec/video/video_volume_control_spec.js index e1edb571d3..5c3f782e85 100644 --- a/common/lib/xmodule/xmodule/js/spec/video/video_volume_control_spec.js +++ b/common/lib/xmodule/xmodule/js/spec/video/video_volume_control_spec.js @@ -3,6 +3,12 @@ describe('VideoVolumeControl', function () { var state, oldOTBD, volumeControl; + var KEY = $.ui.keyCode, + + keyPressEvent = function(key) { + return $.Event('keydown', { keyCode: key }); + }; + beforeEach(function () { oldOTBD = window.onTouchBasedDevice; window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice') @@ -56,24 +62,20 @@ describe('VideoVolumeControl', function () { var liveRegion = $('.video-live-region'); expect(liveRegion).toHaveAttrs({ - 'role': 'status', - 'aria-live': 'polite', - 'aria-atomic': 'false' + 'aria-live': 'polite' }); }); it('add ARIA attributes to volume control', function () { - var button = $('.volume > a'); + var button = $('.volume .control'); expect(button).toHaveAttrs({ - 'role': 'button', - 'title': 'Volume', 'aria-disabled': 'false' }); }); it('bind the volume control', function () { - var button = $('.volume > a'); + var button = $('.volume .control'); expect(button).toHandle('keydown'); expect(button).toHandle('mousedown'); @@ -185,16 +187,19 @@ describe('VideoVolumeControl', function () { }); describe('increaseVolume', function () { + beforeEach(function () { state = jasmine.initializePlayer(); volumeControl = state.videoVolumeControl; }); it('volume is increased correctly', function () { + var button = $('.volume .control'); volumeControl.volume = 60; - state.el.trigger(jQuery.Event("keydown", { - keyCode: $.ui.keyCode.UP - })); + + // adjust the volume + button.focus(); + button.trigger(keyPressEvent(KEY.UP)); expect(volumeControl.volume).toEqual(80); }); @@ -206,16 +211,19 @@ describe('VideoVolumeControl', function () { }); describe('decreaseVolume', function () { + beforeEach(function () { state = jasmine.initializePlayer(); volumeControl = state.videoVolumeControl; }); it('volume is decreased correctly', function () { + var button = $('.volume .control'); volumeControl.volume = 60; - state.el.trigger(jQuery.Event("keydown", { - keyCode: $.ui.keyCode.DOWN - })); + + // adjust the volume + button.focus(); + button.trigger(keyPressEvent(KEY.DOWN)); expect(volumeControl.volume).toEqual(40); }); @@ -274,21 +282,21 @@ describe('VideoVolumeControl', function () { it('nothing happens if ALT+keyUp are pushed down', function () { assertVolumeIsNotChanged({ - keyCode: $.ui.keyCode.UP, + keyCode: KEY.UP, altKey: true }); }); it('nothing happens if SHIFT+keyUp are pushed down', function () { assertVolumeIsNotChanged({ - keyCode: $.ui.keyCode.UP, + keyCode: KEY.UP, shiftKey: true }); }); it('nothing happens if SHIFT+keyDown are pushed down', function () { assertVolumeIsNotChanged({ - keyCode: $.ui.keyCode.DOWN, + keyCode: KEY.DOWN, shiftKey: true }); }); @@ -302,8 +310,8 @@ describe('VideoVolumeControl', function () { it('nothing happens if ALT+ENTER are pushed down', function () { var isMuted = volumeControl.getMuteStatus(); - $('.volume > a').trigger(jQuery.Event("keydown", { - keyCode: $.ui.keyCode.ENTER, + $('.volume .control').trigger(jQuery.Event("keydown", { + keyCode: KEY.ENTER, altKey: true })); expect(volumeControl.getMuteStatus()).toEqual(isMuted); diff --git a/common/lib/xmodule/xmodule/js/src/video/04_video_full_screen.js b/common/lib/xmodule/xmodule/js/src/video/04_video_full_screen.js index e561852057..916f840a9f 100644 --- a/common/lib/xmodule/xmodule/js/src/video/04_video_full_screen.js +++ b/common/lib/xmodule/xmodule/js/src/video/04_video_full_screen.js @@ -2,10 +2,14 @@ 'use strict'; define('video/04_video_full_screen.js', [], function () { var template = [ - '', - gettext('Fill browser'), - '' + '' ].join(''); // VideoControl() function - what this module "exports". @@ -133,8 +137,12 @@ define('video/04_video_full_screen.js', [], function () { fullScreenClassNameEl.removeClass('video-fullscreen'); $(window).scrollTop(this.scrollPos); this.videoFullScreen.fullScreenEl - .attr('title', gettext('Fill browser')) - .text(gettext('Fill browser')); + .find('.icon') + .removeClass('fa-compress') + .addClass('fa-arrows-alt') + .find('.control-text') + .text(gettext('Fill browser')); + this.el.trigger('fullscreen', [this.isFullScreen]); } @@ -146,8 +154,12 @@ define('video/04_video_full_screen.js', [], function () { this.videoFullScreen.fullScreenState = this.isFullScreen = true; fullScreenClassNameEl.addClass('video-fullscreen'); this.videoFullScreen.fullScreenEl - .attr('title', gettext('Exit full browser')) - .text(gettext('Exit full browser')); + .find('.icon') + .removeClass('fa-arrows-alt') + .addClass('fa-compress') + .find('.control-text') + .text(gettext('Exit full browser')); + this.el.trigger('fullscreen', [this.isFullScreen]); } diff --git a/common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js b/common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js index 11965fe31e..10e119b1ad 100644 --- a/common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js +++ b/common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js @@ -1,15 +1,27 @@ (function (requirejs, require, define) { // VideoQualityControl module. +'use strict'; define( 'video/05_video_quality_control.js', [], function () { var template = [ - '' + '' ].join(''); // VideoQualityControl() function - what this module "exports". @@ -134,17 +146,17 @@ function () { var controlStateStr; this.videoQualityControl.quality = value; if (_.contains(this.config.availableHDQualities, value)) { - controlStateStr = gettext('HD on'); + controlStateStr = gettext('on'); this.videoQualityControl.el .addClass('active') - .attr('title', controlStateStr) - .text(controlStateStr); + .find('.control-text') + .text(controlStateStr); } else { - controlStateStr = gettext('HD off'); + controlStateStr = gettext('off'); this.videoQualityControl.el .removeClass('active') - .attr('title', controlStateStr) - .text(controlStateStr); + .find('.control-text') + .text(controlStateStr); } } diff --git a/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js b/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js index 7177ee9215..aff29a4459 100644 --- a/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js +++ b/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js @@ -38,13 +38,25 @@ function() { step: 20, template: [ - '
', - '', - '