In a recent project I needed a auto complete plugin that worked in a textarea, and only based on certain keywords / context. What I found was the jQuery TextComplete plugin, which is awesome! I recommend others try it out.
What I did not find was automated JavaScript tests for the auto complete behavior. Since my use case was pretty complicated (involving multiple strategies and complex regular expressions), I wanted to be sure I had tests for all the scenarios I wanted to cover.
Using Jasmine + PhantomJS, I got the tests down to the following succinct structure:
var textarea;
beforeEach(function() {
// render UI
textarea = $('#auto_complete_test textarea');
});
it("should auto complete string operators when after a string node that is after a number node", function() {
inputTextAndInvokeKeyPressEvent(textarea, 'bike_ride > 5 min and user_name ');
var dropdown = assertDropdown();
expect(autoCompleteList(dropdown)).toEqual([ '=', 'contains', 'present' ]);
});
it("should auto complete number operators when after a number node that is after a string node", function() {
inputTextAndInvokeKeyPressEvent(textarea, 'user_name contains "Bobby" and bike_ride ');
var dropdown = assertDropdown();
expect(autoCompleteList(dropdown)).toEqual([ '>', '<', '>=', '<=', '=' ]);
});
Test Helpers
The real power is in the test helper functions I created. The trick to get these tests to work was:
- Focus must be set on the element
- Ensure the caret is at the right point in textarea (just setting the value via jQuery is not sufficient)
- Simulate a true keyup event for the plugin
- Make it easy and DRY to both set the value and the caret
Set the text value and correct caret position
Accepts the jQuery reference to a textarea, and the existing string input to simulate the user has already typed into the textarea:
function inputTextAndInvokeKeyPressEvent(textarea, input) {
textarea.val(input);
setCaret(textarea, input.length);
textarea.trigger({ type : 'keyup', keyCode : input.charCodeAt(input.length - 1) });
}
Utility: set the focus & caret
function setCaret($el, index) {
if ($el.length === 0) {
throw("No dom element found for $el in setCaret");
}
var el = $el[0];
if (el.createTextRange) {
var range = el.createTextRange();
range.move("character", index);
range.select();
} else if (el.selectionStart !== null) {
el.focus();
el.setSelectionRange(index, index);
}
}
Utility: Assert an auto complete dropdown exists, and return it
function assertDropdown() {
var dropdown = $('.textcomplete-dropdown');
expect(dropdown.length).toEqual(1, "Should be 1 dropdown DOM element");
return dropdown;
}
Utility: Return array of items in dropdown
function autoCompleteList(dropdown) {
return dropdown.find('li').map(function() {
return $(this).text();
}).get();
}