Sebastian Berlin (WMSE) has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/315218

Change subject: Skip back word
......................................................................

Skip back word

Enabled skipping back one word at a time while playing. Button and keyborad
shortcut (ctrl + up arrow by deafult) was added.

Also changed from spies to stubs in tests to isolate them further.

Bug: T133687
Change-Id: I0165d2c55680191160ec1f717f5b5452172aecd9
---
M extension.json
M modules/ext.wikispeech.css
M modules/ext.wikispeech.js
M tests/qunit/ext.wikispeech.test.js
4 files changed, 244 insertions(+), 56 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Wikispeech 
refs/changes/18/315218/1

diff --git a/extension.json b/extension.json
index 6f0c748..334d4f6 100644
--- a/extension.json
+++ b/extension.json
@@ -87,6 +87,10 @@
                        "skipAheadWord": {
                                "key": 40,
                                "modifiers": [ "ctrl" ]
+                       },
+                       "skipBackWord": {
+                               "key": 38,
+                               "modifiers": [ "ctrl" ]
                        }
                },
                "WikispeechSkipBackRewindsThreshold": 3.0
diff --git a/modules/ext.wikispeech.css b/modules/ext.wikispeech.css
index d362b04..18f6934 100644
--- a/modules/ext.wikispeech.css
+++ b/modules/ext.wikispeech.css
@@ -21,3 +21,7 @@
 .ext-wikispeech-skip-ahead-word:after {
        content: "Skip ahead word";
 }
+
+.ext-wikispeech-skip-back-word:after {
+       content: "Skip back word";
+}
diff --git a/modules/ext.wikispeech.js b/modules/ext.wikispeech.js
index bedfd7a..81ba3fa 100644
--- a/modules/ext.wikispeech.js
+++ b/modules/ext.wikispeech.js
@@ -30,6 +30,11 @@
                                'ext-wikispeech-skip-ahead-word',
                                self.skipAheadToken
                        );
+                       self.addButton(
+                               'ext-wikispeech-skip-back-word-button',
+                               'ext-wikispeech-skip-back-word',
+                               self.skipBackToken
+                       );
                };
 
                this.addButton = function ( id, cssClass, onClickFunction ) {
@@ -232,6 +237,74 @@
                };
 
                /**
+                * Skip to the previous token.
+                */
+
+               this.skipBackToken = function () {
+                       var $previousToken, $utterance, $audio;
+
+                       if ( self.isPlaying() ) {
+                               $previousToken =
+                                       self.getPreviousToken( 
self.getCurrentToken() );
+                               $utterance = $previousToken.parent().parent();
+                               if ( $utterance.get( 0 ) !== 
$currentUtterance.get( 0 ) ) {
+                                       self.playUtterance( $utterance );
+                               }
+                               $audio = $currentUtterance.children( 'audio' );
+                               $audio.prop( 'currentTime', 
$previousToken.attr( 'time' ) );
+                       }
+               };
+
+               /**
+                * Get the token before a given token.
+                *
+                * Tokens that are "silent" i.e. have a duration of zero or 
have no
+                * transcription, are ignored.
+                *
+                * @param $token Original token.
+                * @return The token before $token.
+                */
+
+               this.getPreviousToken = function ( $token ) {
+                       var $utterance, $followingToken, $tokens;
+
+                       $utterance = $token.parent().parent();
+                       do {
+                               $followingToken = $token;
+                               $token = $token.prev();
+                               if ( !$token.length ) {
+                                       $utterance = $utterance.prev();
+                                       if ( !$utterance.length ) {
+                                               return $();
+                                       }
+                                       $tokens = $utterance.find( 'token' );
+                                       $token = $( $utterance.find( 'token' )
+                                                               .get( 
$tokens.length - 1 ) );
+                               }
+                               // Ignore tokens that either have a duration of 
zero or that
+                               // have no text.
+                       } while (
+                               $token.attr( 'time' ) === $followingToken.attr( 
'time' ) ||
+                               $token.text() === ''
+                       );
+                       return $token;
+               };
+
+               /**
+                * Get the token element for the token currently playing.
+                *
+                * @return The token currently playing.
+                */
+
+               this.getCurrentToken = function () {
+                       var index, $tokens;
+
+                       index = self.getCurrentTokenIndex();
+                       $tokens = $currentUtterance.find( $( 'token' ) );
+                       return $( $tokens.get( index ) );
+               };
+
+               /**
                 * Register listeners for keyboard shortcuts.
                 */
 
@@ -256,6 +329,11 @@
                                        self.eventMatchShortcut( event, 
shortcuts.skipAheadWord )
                                ) {
                                        self.skipAheadToken();
+                                       return false;
+                               } else if (
+                                       self.eventMatchShortcut( event, 
shortcuts.skipBackWord )
+                               ) {
+                                       self.skipBackToken();
                                        return false;
                                }
                        } );
@@ -310,7 +388,6 @@
                                        $nextUtteranceAudio = 
$nextUtterance.children( 'audio' );
                                        $audio.on( {
                                                play: function () {
-                                                       $currentUtterance = 
$utterance;
                                                        self.prepareUtterance( 
$nextUtterance );
                                                },
                                                ended: function () {
@@ -337,7 +414,7 @@
                 *
                 * @param $utterance The original utterance.
                 * @param {number} offest The difference, in index, to the 
wanted
-                *  utterance. Can be negative for preceeding utterances.
+                *  utterance. Can be negative for preceding utterances.
                 * @return The utterance after the original utterance. Empty 
object if
                 *  $utterance isn't a valid utterance or if an uttrance 
couldn't be
                 *  found.
diff --git a/tests/qunit/ext.wikispeech.test.js 
b/tests/qunit/ext.wikispeech.test.js
index b416d7b..4136379 100644
--- a/tests/qunit/ext.wikispeech.test.js
+++ b/tests/qunit/ext.wikispeech.test.js
@@ -23,30 +23,6 @@
                        $( '#qunit-fixture' ).append(
                                $( '<h1></h1>' ).attr( 'id', 'firstHeading' )
                        );
-                       mw.config.set(
-                               'wgWikispeechKeyboardShortcuts', {
-                                       playStop: {
-                                               key: 32,
-                                               modifiers: [ 'ctrl' ]
-                                       },
-                                       skipAheadSentence: {
-                                               key: 39,
-                                               modifiers: [ 'ctrl' ]
-                                       },
-                                       skipBackSentence: {
-                                               key: 37,
-                                               modifiers: [ 'ctrl' ]
-                                       },
-                                       skipAheadWord: {
-                                               key: 40,
-                                               modifiers: [ 'ctrl' ]
-                                       }
-                               }
-                       );
-                       mw.config.set(
-                               'wgWikispeechSkipBackRewindsThreshold',
-                               3.0
-                       );
                },
                teardown: function () {
                        server.restore();
@@ -72,7 +48,7 @@
 
        QUnit.test( 'prepareUtterance()', function ( assert ) {
                assert.expect( 1 );
-               sinon.spy( wikispeech, 'loadAudio' );
+               sinon.stub( wikispeech, 'loadAudio', function () {} );
 
                wikispeech.prepareUtterance( $( '#utterance-0' ) );
 
@@ -86,7 +62,7 @@
        QUnit.test( "prepareUtterance(): don't request if already requested", 
function ( assert ) {
                // jscs:enable validateQuoteMarks
                assert.expect( 1 );
-               sinon.spy( wikispeech, 'loadAudio' );
+               sinon.stub( wikispeech, 'loadAudio', function () {} );
                $( '#utterance-0' ).prop( 'requested', true );
 
                wikispeech.prepareUtterance( $( '#utterance-0' ) );
@@ -98,7 +74,7 @@
                var $nextUtterance = $( '#utterance-1' );
                assert.expect( 1 );
                wikispeech.prepareUtterance( $( '#utterance-0' ) );
-               sinon.spy( wikispeech, 'prepareUtterance' );
+               sinon.stub( wikispeech, 'prepareUtterance', function () {} );
 
                $( '#utterance-0 audio' ).trigger( 'play' );
 
@@ -112,7 +88,7 @@
        QUnit.test( "prepareUtterance(): don't prepare next audio if it doesn't 
exist", function ( assert ) {
                // jscs:enable validateQuoteMarks
                assert.expect( 1 );
-               sinon.spy( wikispeech, 'prepareUtterance' );
+               sinon.stub( wikispeech, 'prepareUtterance', function () {} );
                wikispeech.prepareUtterance( $( '#utterance-1' ) );
 
                $( '#utterance-1 audio' ).trigger( 'play' );
@@ -129,7 +105,7 @@
                wikispeech.prepareUtterance( $( '#utterance-0' ) );
                wikispeech.prepareUtterance( $( '#utterance-1' ) );
                $nextAudio = $( '#utterance-1' ).children( 'audio' ).get( 0 );
-               sinon.spy( $nextAudio, 'play' );
+               sinon.stub( $nextAudio, 'play', function () {} );
                wikispeech.playUtterance( $( '#utterance-0' ) );
 
                $( '#utterance-0 audio' ).trigger( 'ended' );
@@ -140,7 +116,7 @@
        QUnit.test( 'prepareUtterance(): stop when end of text is reached', 
function ( assert ) {
                var $lastUtterance = $( '#utterance-1' );
                assert.expect( 1 );
-               sinon.spy( wikispeech, 'stop' );
+               sinon.stub( wikispeech, 'stop', function () {} );
                wikispeech.prepareUtterance( $lastUtterance );
                wikispeech.playUtterance( $lastUtterance );
 
@@ -151,7 +127,7 @@
 
        QUnit.test( 'loadAudio()', function ( assert ) {
                assert.expect( 4 );
-               sinon.spy( wikispeech, 'addTokenElements' );
+               sinon.stub( wikispeech, 'addTokenElements', function () {} );
 
                wikispeech.loadAudio( $( '#utterance-0' ) );
 
@@ -178,7 +154,8 @@
        } );
 
        QUnit.test( 'addButtons()', function ( assert ) {
-               assert.expect( 4 );
+               assert.expect( 5 );
+
                wikispeech.addButtons();
 
                assert.strictEqual(
@@ -195,6 +172,10 @@
                );
                assert.strictEqual(
                        $( '#firstHeading 
#ext-wikispeech-skip-ahead-word-button' ).length,
+                       1
+               );
+               assert.strictEqual(
+                       $( '#firstHeading 
#ext-wikispeech-skip-back-word-button' ).length,
                        1
                );
        } );
@@ -216,24 +197,13 @@
         */
 
        function testClickButton( assert, functionName, buttonId ) {
-               var baseFunction, called;
-
                assert.expect( 1 );
-               // Sinon has problems spying on the on click functions. It 
seems to
-               // be caused by function names getting lost at some point. This
-               // replaces the tested function and just sets a boolean if it's 
called.
-               baseFunction = wikispeech[ functionName ];
-               called = false;
-               wikispeech[ functionName ] = function () {
-                       called = true;
-               };
+               sinon.stub( wikispeech, functionName, function () {} );
                wikispeech.addButtons();
 
                $( buttonId ).click();
 
-               assert.strictEqual( called, true );
-               // Restore the tested function.
-               wikispeech[ functionName ] = baseFunction;
+               assert.strictEqual( wikispeech[ functionName ].called, true );
        }
 
        QUnit.test( 'Clicking skip ahead sentence button', function ( assert ) {
@@ -262,7 +232,7 @@
 
        QUnit.test( 'playOrStop(): play', function ( assert ) {
                assert.expect( 1 );
-               sinon.spy( wikispeech, 'play' );
+               sinon.stub( wikispeech, 'play', function () {} );
 
                wikispeech.playOrStop();
 
@@ -272,14 +242,14 @@
        QUnit.test( 'playOrStop(): stop', function ( assert ) {
                assert.expect( 1 );
                wikispeech.play();
-               sinon.spy( wikispeech, 'stop' );
+               sinon.stub( wikispeech, 'stop', function () {} );
 
                wikispeech.playOrStop();
 
                assert.strictEqual( wikispeech.stop.called, true );
        } );
 
-       QUnit.test( 'Pressing keyboard shortcut for playStop', function ( 
assert ) {
+       QUnit.test( 'Pressing keyboard shortcut for play/stop', function ( 
assert ) {
                testKeyboardShortcut( assert, 'playOrStop', 32, 'c' );
        } );
 
@@ -296,7 +266,7 @@
 
        function testKeyboardShortcut( assert, functionName, keyCode, modifiers 
) {
                assert.expect( 1 );
-               sinon.spy( wikispeech, functionName );
+               sinon.stub( wikispeech, functionName, function () {} );
                wikispeech.addKeyboardShortcuts();
 
                $( document ).trigger( createKeydownEvent( keyCode, modifiers ) 
);
@@ -314,16 +284,20 @@
         * @return The created keydown event.
         */
 
-       QUnit.test( 'Pressing keyboard shortcut for skipAheadSentence', 
function ( assert ) {
+       QUnit.test( 'Pressing keyboard shortcut for skipping ahead sentence', 
function ( assert ) {
                testKeyboardShortcut( assert, 'skipAheadUtterance', 39, 'c' );
        } );
 
-       QUnit.test( 'Pressing keyboard shortcut for skipBackSentence', function 
( assert ) {
+       QUnit.test( 'Pressing keyboard shortcut for skipping back sentence', 
function ( assert ) {
                testKeyboardShortcut( assert, 'skipBackUtterance', 37, 'c' );
        } );
 
-       QUnit.test( 'Pressing keyboard shortcut for skipAheadWord', function ( 
assert ) {
+       QUnit.test( 'Pressing keyboard shortcut for skipping ahead word', 
function ( assert ) {
                testKeyboardShortcut( assert, 'skipAheadToken', 40, 'c' );
+       } );
+
+       QUnit.test( 'Pressing keyboard shortcut for skipping back word', 
function ( assert ) {
+               testKeyboardShortcut( assert, 'skipBackToken', 38, 'c' );
        } );
 
        function createKeydownEvent( keyCode, modifiers ) {
@@ -402,7 +376,7 @@
 
        QUnit.test( 'skipAheadUtterance(): stop if no next utterance', function 
( assert ) {
                assert.expect( 1 );
-               sinon.spy( wikispeech, 'stop' );
+               sinon.stub( wikispeech, 'stop', function () {} );
                wikispeech.playUtterance( $( '#utterance-1' ) );
 
                wikispeech.skipAheadUtterance();
@@ -776,10 +750,139 @@
                );
                wikispeech.play();
                $( '#utterance-0 audio' ).prop( 'currentTime', 1.0 );
-               sinon.spy( wikispeech, 'skipAheadUtterance' );
+               sinon.stub( wikispeech, 'skipAheadUtterance', function () {} );
 
                wikispeech.skipAheadToken();
 
                assert.strictEqual( wikispeech.skipAheadUtterance.called, true 
);
        } );
+
+       QUnit.test( 'skipBackToken()', function ( assert ) {
+               assert.expect( 1 );
+               wikispeech.prepareUtterance( $( '#utterance-0' ) );
+               $( '#utterance-0' ).append(
+                       $( '<tokens></tokens>' )
+                               .append(
+                                       $( '<token></token>' )
+                                               .attr( 'time', 0.0 )
+                                               .text( '1' )
+                               )
+                               .append(
+                                       $( '<token></token>' )
+                                               .attr( 'time', 1.0 )
+                                               .text( '2' )
+                               )
+               );
+               wikispeech.play();
+               $( '#utterance-0 audio' ).prop( 'currentTime', 1.0 );
+
+               wikispeech.skipBackToken();
+
+               assert.strictEqual(
+                       $( '#utterance-0 audio' ).prop( 'currentTime' ),
+                       0.0
+               );
+       } );
+
+       QUnit.test( 'skipBackToken(): skip to last token in previous utterance 
if first token', function ( assert ) {
+               assert.expect( 2 );
+               wikispeech.prepareUtterance( $( '#utterance-0' ) );
+               wikispeech.prepareUtterance( $( '#utterance-1' ) );
+               $( '#utterance-0' ).append(
+                       $( '<tokens></tokens>' )
+                               .append(
+                                       $( '<token></token>' )
+                                               .attr( 'time', 0.0 )
+                                               .text( '1' )
+                               )
+                               .append(
+                                       $( '<token></token>' )
+                                               .attr( 'time', 1.0 )
+                                               .text( '2' )
+                               )
+               );
+               $( '#utterance-1' ).append(
+                       $( '<tokens></tokens>' )
+                               .append(
+                                       $( '<token></token>' )
+                                               .attr( 'time', 0.0 )
+                               )
+               );
+               wikispeech.playUtterance( $( '#utterance-1' ) );
+
+               wikispeech.skipBackToken();
+
+               assert.strictEqual(
+                       $( '#utterance-0 audio' ).prop( 'paused' ),
+                       false
+               );
+               assert.strictEqual(
+                       $( '#utterance-0 audio' ).prop( 'currentTime' ),
+                       1.0
+               );
+       } );
+
+       QUnit.test( 'skipBackToken(): ignore empty tokens', function ( assert ) 
{
+               assert.expect( 1 );
+               wikispeech.prepareUtterance( $( '#utterance-0' ) );
+               $( '#utterance-0' ).append(
+                       $( '<tokens></tokens>' )
+                               .append(
+                                       $( '<token></token>' )
+                                               .attr( 'time', 0.0 )
+                                               .text( '1' )
+                               )
+                               .append(
+                                       $( '<token></token>' )
+                                               .attr( 'time', 1.0 )
+                                               .text( '' )
+                               )
+                               .append(
+                                       $( '<token></token>' )
+                                               .attr( 'time', 2.0 )
+                                               .text( '3' )
+                               )
+               );
+               wikispeech.playUtterance( $( '#utterance-0' ) );
+               $( '#utterance-0 audio' ).prop( 'currentTime', 2.0 );
+
+               wikispeech.skipBackToken();
+
+               assert.strictEqual(
+                       $( '#utterance-0 audio' ).prop( 'currentTime' ),
+                       0.0
+               );
+       } );
+
+       QUnit.test( 'skipBackToken(): ignore tokens with no length', function ( 
assert ) {
+               assert.expect( 1 );
+               wikispeech.prepareUtterance( $( '#utterance-0' ) );
+               $( '#utterance-0' ).append(
+                       $( '<tokens></tokens>' )
+                               .append(
+                                       $( '<token></token>' )
+                                               .attr( 'time', 0.0 )
+                                               .text( '1' )
+                               )
+                               .append(
+                                       $( '<token></token>' )
+                                               .attr( 'time', 1.0 )
+                                               .text( '2' )
+                               )
+                               .append(
+                                       $( '<token></token>' )
+                                               .attr( 'time', 1.0 )
+                                               .text( '3' )
+                               )
+               );
+               wikispeech.playUtterance( $( '#utterance-0' ) );
+               $( '#utterance-0 audio' ).prop( 'currentTime', 1.0 );
+
+               wikispeech.skipBackToken();
+
+               assert.strictEqual(
+                       $( '#utterance-0 audio' ).prop( 'currentTime' ),
+                       0.0
+               );
+       } );
 } )( mediaWiki, jQuery );

-- 
To view, visit https://gerrit.wikimedia.org/r/315218
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I0165d2c55680191160ec1f717f5b5452172aecd9
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Wikispeech
Gerrit-Branch: master
Gerrit-Owner: Sebastian Berlin (WMSE) <sebastian.ber...@wikimedia.se>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to