blob: 41284eaaec7a91a5fb47768d4711e349c2f904ec [file] [log] [blame]
suite('saneKeyboardEvents', function() {
var el;
var Event = jQuery.Event
function supportsSelectionAPI() {
return 'selectionStart' in el[0];
}
setup(function() {
el = $('<textarea>').appendTo('#mock');
});
test('normal keys', function(done) {
var counter = 0;
saneKeyboardEvents(el, {
keystroke: noop,
typedText: function(text, keydown, keypress) {
counter += 1;
assert.ok(counter <= 1, 'callback is only called once');
assert.equal(text, 'a', 'text comes back as a');
assert.equal(el.val(), '', 'the textarea remains empty');
done();
}
});
el.trigger(Event('keydown', { which: 97 }));
el.trigger(Event('keypress', { which: 97 }));
el.val('a');
});
test('one keydown only', function(done) {
var counter = 0;
saneKeyboardEvents(el, {
keystroke: function(key, evt) {
counter += 1;
assert.ok(counter <= 1, 'callback is called only once');
assert.equal(key, 'Backspace', 'key is correctly set');
done();
}
});
el.trigger(Event('keydown', { which: 8 }));
});
test('a series of keydowns only', function(done) {
var counter = 0;
saneKeyboardEvents(el, {
keystroke: function(key, keydown) {
counter += 1;
assert.ok(counter <= 3, 'callback is called at most 3 times');
assert.ok(keydown);
assert.equal(key, 'Left');
if (counter === 3) done();
}
});
el.trigger(Event('keydown', { which: 37 }));
el.trigger(Event('keydown', { which: 37 }));
el.trigger(Event('keydown', { which: 37 }));
});
test('one keydown and a series of keypresses', function(done) {
var counter = 0;
saneKeyboardEvents(el, {
keystroke: function(key, keydown) {
counter += 1;
assert.ok(counter <= 3, 'callback is called at most 3 times');
assert.ok(keydown);
assert.equal(key, 'Backspace');
if (counter === 3) done();
}
});
el.trigger(Event('keydown', { which: 8 }));
el.trigger(Event('keypress', { which: 8 }));
el.trigger(Event('keypress', { which: 8 }));
el.trigger(Event('keypress', { which: 8 }));
});
suite('select', function() {
test('select populates the textarea but doesn\'t call .typedText()', function() {
var shim = saneKeyboardEvents(el, { keystroke: noop });
shim.select('foobar');
assert.equal(el.val(), 'foobar');
el.trigger('keydown');
assert.equal(el.val(), 'foobar', 'value remains after keydown');
if (supportsSelectionAPI()) {
el.trigger('keypress');
assert.equal(el.val(), 'foobar', 'value remains after keypress');
el.trigger('input');
assert.equal(el.val(), 'foobar', 'value remains after flush after keypress');
}
});
test('select populates the textarea but doesn\'t call text' +
' on keydown, even when the selection is not properly' +
' detectable', function() {
var shim = saneKeyboardEvents(el, { keystroke: noop });
shim.select('foobar');
// monkey-patch the dom-level selection so that hasSelection()
// returns false, as in IE < 9.
el[0].selectionStart = el[0].selectionEnd = 0;
el.trigger('keydown');
assert.equal(el.val(), 'foobar', 'value remains after keydown');
});
test('blurring', function() {
var shim = saneKeyboardEvents(el, { keystroke: noop });
shim.select('foobar');
el.trigger('blur');
el.focus();
// IE < 9 doesn't support selection{Start,End}
if (supportsSelectionAPI()) {
assert.equal(el[0].selectionStart, 0, 'it\'s selected from the start');
assert.equal(el[0].selectionEnd, 6, 'it\'s selected to the end');
}
assert.equal(el.val(), 'foobar', 'it still has content');
});
test('blur then empty selection', function() {
var shim = saneKeyboardEvents(el, { keystroke: noop });
shim.select('foobar');
el.blur();
shim.select('');
assert.ok(document.activeElement !== el[0], 'textarea remains blurred');
});
test('blur in keystroke handler', function(done) {
if (!document.hasFocus()) {
console.warn(
'The test "blur in keystroke handler" needs the document to have ' +
'focus. Only when the document has focus does .select() on an ' +
'element also focus it, which is part of the problematic behavior ' +
'we are testing robustness against. (Specifically, erroneously ' +
'calling .select() in a timeout after the textarea has blurred, ' +
'"stealing back" focus.)\n' +
'Normally, the page being open and focused is enough to have focus, ' +
'but with the Developer Tools open, it depends on whether you last ' +
'clicked on something in the Developer Tools or on the page itself. ' +
'Click the page, or close the Developer Tools, and Refresh.'
);
el.remove(); // LOL next line skips teardown https://git.io/vaUWq
this.skip();
}
var shim = saneKeyboardEvents(el, {
keystroke: function(key) {
assert.equal(key, 'Left');
el[0].blur();
}
});
shim.select('foobar');
assert.ok(document.activeElement === el[0], 'textarea focused');
el.trigger(Event('keydown', { which: 37 }));
assert.ok(document.activeElement !== el[0], 'textarea blurred');
setTimeout(function() {
assert.ok(document.activeElement !== el[0], 'textarea remains blurred');
done();
});
});
suite('selected text after keypress or paste doesn\'t get mistaken' +
' for inputted text', function() {
test('select() immediately after paste', function() {
var pastedText;
var onPaste = function(text) { pastedText = text; };
var shim = saneKeyboardEvents(el, {
paste: function(text) { onPaste(text); }
});
el.trigger('paste').val('$x^2+1$');
shim.select('$\\frac{x^2+1}{2}$');
assert.equal(pastedText, '$x^2+1$');
assert.equal(el.val(), '$\\frac{x^2+1}{2}$');
onPaste = null;
shim.select('$2$');
assert.equal(el.val(), '$2$');
});
test('select() after paste/input', function() {
var pastedText;
var onPaste = function(text) { pastedText = text; };
var shim = saneKeyboardEvents(el, {
paste: function(text) { onPaste(text); }
});
el.trigger('paste').val('$x^2+1$');
el.trigger('input');
assert.equal(pastedText, '$x^2+1$');
assert.equal(el.val(), '');
onPaste = null;
shim.select('$\\frac{x^2+1}{2}$');
assert.equal(el.val(), '$\\frac{x^2+1}{2}$');
shim.select('$2$');
assert.equal(el.val(), '$2$');
});
test('select() immediately after keydown/keypress', function() {
var typedText;
var onText = function(text) { typedText = text; };
var shim = saneKeyboardEvents(el, {
keystroke: noop,
typedText: function(text) { onText(text); }
});
el.trigger(Event('keydown', { which: 97 }));
el.trigger(Event('keypress', { which: 97 }));
el.val('a');
shim.select('$\\frac{a}{2}$');
assert.equal(typedText, 'a');
assert.equal(el.val(), '$\\frac{a}{2}$');
onText = null;
shim.select('$2$');
assert.equal(el.val(), '$2$');
});
test('select() after keydown/keypress/input', function() {
var typedText;
var onText = function(text) { typedText = text; };
var shim = saneKeyboardEvents(el, {
keystroke: noop,
typedText: function(text) { onText(text); }
});
el.trigger(Event('keydown', { which: 97 }));
el.trigger(Event('keypress', { which: 97 }));
el.val('a');
el.trigger('input');
assert.equal(typedText, 'a');
onText = null;
shim.select('$\\frac{a}{2}$');
assert.equal(el.val(), '$\\frac{a}{2}$');
shim.select('$2$');
assert.equal(el.val(), '$2$');
});
suite('unrecognized keys that move cursor and clear selection', function() {
test('without keypress', function() {
var shim = saneKeyboardEvents(el, { keystroke: noop });
shim.select('a');
assert.equal(el.val(), 'a');
if (!supportsSelectionAPI()) return;
el.trigger(Event('keydown', { which: 37, altKey: true }));
el[0].selectionEnd = 0;
el.trigger(Event('keyup', { which: 37, altKey: true }));
assert.ok(el[0].selectionStart !== el[0].selectionEnd);
el.blur();
shim.select('');
assert.ok(document.activeElement !== el[0], 'textarea remains blurred');
});
test('with keypress, many characters selected', function() {
var shim = saneKeyboardEvents(el, { keystroke: noop });
shim.select('many characters');
assert.equal(el.val(), 'many characters');
if (!supportsSelectionAPI()) return;
el.trigger(Event('keydown', { which: 37, altKey: true }));
el.trigger(Event('keypress', { which: 37, altKey: true }));
el[0].selectionEnd = 0;
el.trigger('keyup');
assert.ok(el[0].selectionStart !== el[0].selectionEnd);
el.blur();
shim.select('');
assert.ok(document.activeElement !== el[0], 'textarea remains blurred');
});
test('with keypress, only 1 character selected', function() {
var count = 0;
var shim = saneKeyboardEvents(el, {
keystroke: noop,
typedText: function(ch) {
assert.equal(ch, 'a');
assert.equal(el.val(), '');
count += 1;
}
});
shim.select('a');
assert.equal(el.val(), 'a');
if (!supportsSelectionAPI()) return;
el.trigger(Event('keydown', { which: 37, altKey: true }));
el.trigger(Event('keypress', { which: 37, altKey: true }));
el[0].selectionEnd = 0;
el.trigger('keyup');
assert.equal(count, 1);
el.blur();
shim.select('');
assert.ok(document.activeElement !== el[0], 'textarea remains blurred');
});
});
});
});
suite('paste', function() {
test('paste event only', function(done) {
saneKeyboardEvents(el, {
paste: function(text) {
assert.equal(text, '$x^2+1$');
done();
}
});
el.trigger('paste');
el.val('$x^2+1$');
});
test('paste after keydown/keypress', function(done) {
saneKeyboardEvents(el, {
keystroke: noop,
paste: function(text) {
assert.equal(text, 'foobar');
done();
}
});
// Ctrl-V in Firefox or Opera, according to unixpapa.com/js/key.html
// without an `input` event
el.trigger('keydown', { which: 86, ctrlKey: true });
el.trigger('keypress', { which: 118, ctrlKey: true });
el.trigger('paste');
el.val('foobar');
});
test('paste after keydown/keypress/input', function(done) {
saneKeyboardEvents(el, {
keystroke: noop,
paste: function(text) {
assert.equal(text, 'foobar');
done();
}
});
// Ctrl-V in Firefox or Opera, according to unixpapa.com/js/key.html
// with an `input` event
el.trigger('keydown', { which: 86, ctrlKey: true });
el.trigger('keypress', { which: 118, ctrlKey: true });
el.trigger('paste');
el.val('foobar');
el.trigger('input');
});
test('keypress timeout happening before paste timeout', function(done) {
saneKeyboardEvents(el, {
keystroke: noop,
paste: function(text) {
assert.equal(text, 'foobar');
done();
}
});
el.trigger('keydown', { which: 86, ctrlKey: true });
el.trigger('keypress', { which: 118, ctrlKey: true });
el.trigger('paste');
el.val('foobar');
// this synthesizes the keypress timeout calling handleText()
// before the paste timeout happens.
el.trigger('input');
});
});
suite('copy', function() {
test('only runs handler once even if handler synchronously selects', function() {
// ...which MathQuill does and resulted in a stack overflow: https://git.io/vosm0
var shim = saneKeyboardEvents(el, {
copy: function() {
shim.select();
}
});
el.trigger('copy');
});
});
});