mediawiki-extensions-Visual.../modules/ve/test/ui/ve.ui.CommandFactory.test.js
Timo Tijhof 34cbc729db ve.ui.CommandFactory: Initial implementation
Based on https://github.com/ccampbell/mousetrap.

Cleaned up to fit our coding standards, pass JSHint, and assume
jQuery's fixes where possible (e.g. no need for an addEvent
utility, no need for filling e.target, e.which, etc. cross-browser
which jQuery.Event already does).

Initially all were local functions in the constructor, but to
allow some customisations in subclasses moved various methods
to the prototype instead and marked with @private.

Really, only .register() must be called from the outside. The rest
assumes normalisation etc. or might break things if called
incorrectly.

Change-Id: Ic69a3c70759052094aefbeab623d885f8b118e14
2012-10-24 17:48:16 +00:00

164 lines
4.9 KiB
JavaScript

/**
* VisualEditor CommandFactory tests.
*
* @copyright 2011-2012 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
( function () {
// When runSequence is called multiple times,
// be sure to keep track of how far in the future we've scheduled,
// since mixed sequences are (intentionally) invalidated.
var sequenceSimulatorOffset = 0;
QUnit.module( 've.ui.CommandFactory', {
setup: function () {
// Clear registry after each test
ve.ui.commandFactory = new ve.ui.CommandFactory();
},
teardown: function () {
// Can't restore because the constructor does .off(), so the
// old one is destroyed
ve.ui.commandFactory = new ve.ui.CommandFactory();
}
} );
/**
* @param {string|undefined} action
* @param {string} char Single character.
* @param {Object} props [optional]
*/
function runAction( action, char, props ) {
props = props || {};
props.which = char.charCodeAt( 0 );
$( 'body' ).trigger(
$.Event( action || 'keypress', props )
);
}
/**
* @param {string} action
* @param {number} interval Time between actions (in milliseconds).
* @param {string} chars
*/
function runSequence( action, interval, chars ) {
var i;
chars = chars.split( ' ' );
/**
* Utility function to avoid making functions
* in a loop, causing scope issues with 'i'.
*/
function schedule( char, delay ) {
setTimeout( function () {
runAction( action, char );
}, delay );
}
for ( i = 0; i < chars.length; i++ ) {
sequenceSimulatorOffset += interval;
schedule( chars[i], sequenceSimulatorOffset );
}
}
QUnit.test( 'register: Single characters', 18, function ( assert ) {
$.each( {
// Default should work with keypress
'default': [ undefined, 'keypress' ],
'keypress': [ 'keypress', 'keypress' ]
}, function ( action, event ) {
$.each( '0 9 a Z ! > - +'.split( ' ' ), function ( i, char ) {
ve.ui.commandFactory.register( char, function () {
assert.ok( true, action + ': "' + char + '" - ' + event[1] + ' trigger without modifier keys' );
}, event[0] );
runAction( event[1], char );
} );
} );
ve.ui.commandFactory.register( 'a', function () {
assert.ok( false, 'keypress: "a" - trigger A is ignored' );
}, 'keypress' );
runAction( 'keypress', 'A' );
ve.ui.commandFactory.register( 'a', function () {
assert.ok( false, 'keypress: "a" - trigger A with Shift is ignored' );
}, 'keypress' );
runAction( 'keypress', 'A', { shiftKey: true } );
ve.ui.commandFactory.register( 'Z', function () {
assert.ok( true, 'keypress: "Z" - trigger Z with Shift' );
}, 'keypress' );
runAction( 'keypress', 'Z', { shiftKey: true } );
ve.ui.commandFactory.register( 'g', function () {
assert.ok( false, 'keypress: "g" - register removes old callbacks' );
}, 'keypress' );
ve.ui.commandFactory.register( 'g', function () {
// Undocumented behavior, tested so we detect if/when it changes
assert.ok( true, 'keypress: "g" - register keeps only the last callback' );
}, 'keypress' );
runAction( 'keypress', 'g' );
} );
QUnit.asyncTest( 'register: Sequences', 3, function ( assert ) {
ve.ui.commandFactory.register( '1 2 3', function () {
assert.ok( true, 'Number sequence with 20ms interval' );
} );
runSequence( 'keypress', 20, '1 2 3' );
ve.ui.commandFactory.register( 'a b c Z', function () {
assert.ok( true, 'Letter sequence with 10ms interval' );
} );
runSequence( 'keypress', 10, 'a b c Z' );
ve.ui.commandFactory.register( 'o r d e r', function () {
assert.ok( false, 'Out of order is invalid' );
} );
runSequence( 'keypress', 10, 'o r d r e' );
ve.ui.commandFactory.register( 'm i s s', function () {
assert.ok( false, 'Missing in-between is invalid' );
} );
runSequence( 'keypress', 10, 'm s s' );
ve.ui.commandFactory.register( 'e x t', function () {
assert.ok( false, 'Extra in-between is invalid' );
} );
runSequence( 'keypress', 10, 'e r x t' );
ve.ui.commandFactory.register( 's l', function () {
assert.ok( true, 'Total sequence may take over a second (2 * 600ms)' );
} );
runSequence( 'keypress', 600, 's l' );
ve.ui.commandFactory.register( 'g a', function () {
assert.ok( false, 'Sequences with gaps > 1 second are invalid (2 x 1100ms)' );
} );
runSequence( 'keypress', 1100, 'g a' );
setTimeout( QUnit.start, sequenceSimulatorOffset + 1 );
} );
QUnit.test( 'register: Combinations', 2, function ( assert ) {
ve.ui.commandFactory.register( 'ctrl+i', function ( e, combo ) {
assert.ok( true, combo );
} );
// XXX: Apparently i/ctrl doesn't work, needs to be uppercase I.
// This is internally normalized back to 'a'.
// Google Chrome (and others) use keycode for 'A' when typing a + modifier.
runAction( 'keydown', 'I', { ctrlKey: true } );
ve.ui.commandFactory.register( 'cmd+b', function ( e, combo ) {
assert.ok( true, combo );
} );
runAction( 'keydown', 'B', { metaKey: true } );
} );
}() );