autocomplete-test.js revision 817fd44279e274b210f7f29c8080c1f2d121f4f8
1537N/A// Simple, bare AutoCompleteBase implementation for testing. queryDelay:
0 // makes queries synchronous; easier for testing // Helper method that synchronously simulates a valueChange event on the queryDelay:
0 // makes queries synchronous; easier for testing, // Helper method that synchronously simulates a valueChange event on the // -- Global Suite ------------------------------------------------------------- // -- Base Suite --------------------------------------------------------------- // -- Base: Lifecycle ---------------------------------------------------------- // 'Initializer should require an inputNode': 'No inputNode specified.' 'Initializer should accept an inputNode':
function () {
'destructor should detach events':
function () {
// Note: This test is temporarily commented out since it appears to cause // the autocomplete:init event to remain on the event stack indefinitely, // preventing all queuable events from firing. It's not clear that this is // incorrect behavior, but it needs further investigation. // 'Initializer should require an inputNode': function () { // var ac = new ACBase(); // -- Base: Attributes --------------------------------------------------------- 'Browser autocomplete should be off by default':
function () {
'Browser autocomplete should be turned on when enabled':
function () {
'Browser autocomplete should be settable after init':
function () {
ac.
set(
'allowBrowserAutocomplete',
true);
'inputNode should be writable only on init':
function () {
'maxResults should enforce a maximum number of results':
function () {
this.
ac.
once(
'results',
function (e) {
this.
ac.
set(
'maxResults',
3);
'maxResults should do nothing if <= 0':
function () {
this.
ac.
on(
'results',
function (e) {
this.
ac.
set(
'maxResults',
0);
this.
ac.
set(
'maxResults', -
5);
'minQueryLength should enforce a minimum query length':
function () {
this.
ac.
on(
'query',
function (e) {
fired +=
1; });
this.
ac.
set(
'minQueryLength',
3);
this.
ac.
set(
'minQueryLength',
4);
'minQueryLength should allow empty queries if set to 0':
function () {
this.
ac.
on(
'query',
function (e) {
fired +=
1; });
this.
ac.
set(
'minQueryLength',
0);
'minQueryLength should prevent queries if negative':
function () {
this.
ac.
on(
'query',
function (e) {
fired +=
1; });
this.
ac.
set(
'minQueryLength', -
1);
'queryDelay should delay query events':
function () {
this.
ac.
on(
'query',
function (e) {
this.
ac.
set(
'queryDelay',
30);
'_parseValue should return the rightmost token when using a queryDelimiter':
function () {
this.
ac.
set(
'queryDelimiter',
',');
this.
ac.
set(
'queryDelimiter',
'@@@');
'requestTemplate should accept a custom template function':
function () {
return 'query: ' +
query;
this.
ac.
set(
'requestTemplate',
fn);
'requestTemplate should generate a template function when set to a string':
function () {
this.
ac.
set(
'requestTemplate',
'foo');
'requestTemplate function should replace {query} with the URI-encoded query':
function () {
this.
ac.
set(
'requestTemplate',
'/ac?q={query}&a=aardvark');
rt =
this.
ac.
get(
'requestTemplate');
'`this` object in requestTemplate functions should be the AutoComplete instance':
function () {
this.
ac.
set(
'requestTemplate',
function () {
this.
ac.
set(
'source', [
'foo',
'bar']);
'resultFilters should accept a function, array of functions, string, array of strings, or null':
function () {
this.
ac.
set(
'resultFilters',
null);
this.
ac.
set(
'resultFilters',
'phraseMatch');
this.
ac.
set(
'resultFilters', [
'phraseMatch',
'charMatch']);
this.
ac.
set(
'resultFilters',
null);
this.
ac.
set(
'resultFilters', [
'foo',
'bar']);
'result filters should receive the query and an array of result objects as parameters':
function () {
'resultFormatter should accept a function or null':
function () {
this.
ac.
set(
'resultFormatter',
null);
'result formatters should receive the query and an array of result objects as parameters':
function () {
Assert.
areSame(
self.
ac,
this,
'`this` object in formatters should be the AutoComplete instance');
'resultHighlighter should accept a function, string, or null':
function () {
this.
ac.
set(
'resultHighlighter',
null);
this.
ac.
set(
'resultHighlighter',
'phraseMatch');
'result highlighters should receive the query and an array of result objects as parameters':
function () {
Assert.
areSame(
self.
ac,
this,
'`this` object in highlighters should be the AutoComplete instance');
'resultListLocator should accept a function, string, or null':
function () {
this.
ac.
set(
'resultListLocator',
null);
this.
ac.
set(
'resultListLocator',
'foo.bar');
'resultListLocator should locate results':
function () {
this.
ac.
set(
'resultListLocator',
'foo.bar');
this.
ac.
set(
'resultListLocator',
function () {
'resultTextLocator should locate result text':
function () {
this.
ac.
set(
'resultTextLocator',
'foo.bar');
this.
ac.
set(
'resultTextLocator',
function () {
'_parseResponse should preserve duplicates in text when using resultTextLocator':
function () {
{
"City":
"La Habra",
"State":
"CA",
"County":
"Orange",
"Zip":
"90631"},
{
"City":
"La Habra Heights",
"State":
"CA",
"County":
"Orange",
"Zip":
"90631"},
{
"City":
"La Habra Hgts",
"State":
"CA",
"County":
"Orange",
"Zip":
"90631"}
this.
ac.
set(
'resultTextLocator',
'Zip');
this.
ac.
on(
'results',
function (e) {
"The raw result values should be different.");
"The raw result values should be different.");
// See the "Built-in Sources" test case below for source and sourceType 'value attribute should update the inputNode value and query attribute when set via the API, but should not fire a query event':
function () {
this.
ac.
on(
'query',
function () {
this.
ac.
set(
'value',
'foo');
this.
ac.
set(
'value',
'bar');
this.
ac.
set(
'value',
'');
// -- Generic setters and validators --------------------------------------- '_functionValidator() should accept a function or null':
function () {
// -- Base: Events ------------------------------------------------------------- 'clear event should fire when the query is cleared':
function () {
this.
ac.
on(
'clear',
function (e) {
this.
ac.
set(
'queryDelimiter',
',');
'clear event should fire when the value attribute is cleared via the API':
function () {
this.
ac.
on(
'clear',
function (e) {
this.
ac.
set(
'value',
'');
'clear event should be preventable':
function () {
this.
ac.
on(
'clear',
function (e) {
'query event should fire when the value attribute is changed via the UI':
function () {
this.
ac.
on(
'query',
function (e) {
'query event should be preventable':
function () {
this.
ac.
on(
'query',
function (e) {
'results event should fire when a source returns results':
function () {
this.
ac.
set(
'source', [
'foo',
'bar']);
this.
ac.
on(
'results',
function (e) {
'results event should be preventable':
function () {
this.
ac.
set(
'source', [
'foo',
'bar']);
this.
ac.
on(
'results',
function (e) {
// -- Base: Methods ------------------------------------------------------------ 'sendRequest should provide a complete request object to source.sendRequest':
function () {
// Create a mock DataSource-like source so we can test what gets passed // to source.sendRequest(). this.
ac.
set(
'requestTemplate',
'?q={query}&baz=quux');
// -- Base: Built-in Sources --------------------------------------------------- name:
'Built-in sources',
// -- Behavior ------------------------------------------------------------- 'Array sources should return the full array regardless of query':
function () {
this.
ac.
set(
'source', [
'foo',
'bar',
'baz']);
'DataSource sources should work':
function () {
'Function sources should support synchronous return values':
function () {
return [
'foo',
'bar',
'baz'];
'Function sources should support asynchronous return values':
function () {
'Object sources should work':
function () {
'sourceType should override source type detection for built-in types':
function () {
this.
ac.
set(
'source', [
'foo',
'bar']);
this.
ac.
set(
'sourceType',
'object');
// -- Base: Extra Sources ------------------------------------------------------ name:
'Extra sources (autocomplete-sources)',
// -- Source types --------------------------------------------------------- '<select> nodes should be turned into select source objects':
function () {
var select = Y.
Node.
create(
'<select><option>foo</option><option>bar</option><option>baz</option></select>');
'A <select> result should be an object with convenient properties':
function () {
var select = Y.
Node.
create(
'<select><option value="abc">foo & bar</option><option>bar</option><option>baz</option></select>'),
'XHR strings should be turned into IO source objects':
function () {
this.
ac.
set(
'source',
'foo');
this.
ac.
set(
'source',
'foo?q={query}');
// TODO: mock io requests 'JSONP strings should be turned into JSONP source objects':
function () {
this.
ac.
set(
'source',
'foo?callback={callback}');
this.
ac.
set(
'source',
'foo?q={query}&callback={callback}');
'Y.JSONPRequest instances should be turned into JSONP source objects':
function () {
// TODO: mock JSONP requests 'YQL strings should be turned into YQL source objects':
function () {
this.
ac.
set(
'source',
'select * from search.suggest where q="{query}"');
this.
ac.
set(
'source',
'select * from search.suggest where q="{request}"');
this.
ac.
set(
'source',
'set foo="bar" on search; select * from search.suggest where q="{query}"');
// TODO: mock YQL requests // -- Other stuff ---------------------------------------------------------- 'sourceType should override source type detection for extra types':
function () {
this.
ac.
set(
'source',
'moo');
this.
ac.
set(
'sourceType',
'jsonp');
'_jsonpFormatter should correctly format URLs both with and without a requestTemplate set':
function () {
this.
ac.
set(
'requestTemplate',
'?q={query}&cb={callback}');
this.
ac.
set(
'requestTemplate',
'&cb={callback}');
'requestTemplate should be appended to XHR source URLs':
function () {
var query =
'monkey pants',
this.
ac.
set(
'requestTemplate',
'&bar=baz');
// -- Filters Suite ------------------------------------------------------------ // -- Filters: API ------------------------------------------------------------- // -- charMatch() ---------------------------------------------------------- 'charMatch() should match all characters in the query, in any order':
function () {
'charMatch() should be case-insensitive':
function () {
'charMatchCase() should be case-sensitive':
function () {
'charMatchFold() should match accent-folded characters':
function () {
[
'fóó',
'föö',
'foo'],
// Accent-folded matches are always case-insensitive. [
'FÓÓ',
'FÖÖ',
'FOO'],
// -- phraseMatch() -------------------------------------------------------- 'phraseMatch() should match the complete query as a phrase':
function () {
'phraseMatch() should be case-insensitive':
function () {
'phraseMatchCase() should be case-sensitive':
function () {
'phraseMatchFold() should match accent-folded characters':
function () {
[
'fóó',
'föö',
'foo'],
// Accent-folded matches are always case-insensitive. [
'FÓÓ',
'FÖÖ',
'FOO'],
// -- startsWith() --------------------------------------------------------- 'startsWith() should match the complete query at the start of a result':
function () {
'startsWith() should be case-insensitive':
function () {
'startsWithCase() should be case-sensitive':
function () {
'startsWithFold() should match accent-folded characters':
function () {
[
'fóó',
'föö',
'foo'],
// Accent-folded matches are always case-insensitive. [
'FÓÓ',
'FÖÖ',
'FOO'],
// -- subWordMatch() ------------------------------------------------------- 'subWordMatch() should match results where all words in the query - ignoring whitespace and punctuation - occur partially in the text':
function () {
[
'foo bar baz',
'foobar baz'],
[
'John Doe',
'Jon Doe',
'Richard Roe [12345]'],
'subWordMatch() should be case-insensitive':
function () {
'subWordMatchCase() should be case-sensitive':
function () {
'subWordMatchFold() should match accent-folded characters':
function () {
[
'Anne-Sophie',
'Sophie, Anné'],
// -- wordMatch() ---------------------------------------------------------- 'wordMatch() should match results that contain all words in the query in any order':
function () {
'wordMatch() should be case-insensitive':
function () {
'wordMatchCase() should be case-sensitive':
function () {
'wordMatchFold() should match accent-folded characters':
function () {
[
'fóó',
'föö',
'foo'],
// Accent-folded matches are always case-insensitive. [
'FÓÓ',
'FÖÖ',
'FOO'],
// -- Highlighters Suite ------------------------------------------------------- // -- Highlighters: API -------------------------------------------------------- // -- charMatch() ---------------------------------------------------------- 'charMatch() should highlight all characters in the query, in any order':
function () {
[
'foo',
'<b class="yui3-highlight">b</b><b class="yui3-highlight">a</b>r',
'<b class="yui3-highlight">b</b><b class="yui3-highlight">a</b>z'],
'charMatch() should be case-insensitive':
function () {
[
'<b class="yui3-highlight">F</b>oo',
'<b class="yui3-highlight">f</b>oo'],
// -- charMatchCase() ------------------------------------------------------ 'charMatchCase() should be case-sensitive':
function () {
[
'Foo',
'<b class="yui3-highlight">f</b>oo'],
// -- charMatchFold() ------------------------------------------------------ 'charMatchFold() should highlight accent-folded characters':
function () {
[
'f<b class="yui3-highlight">ó</b><b class="yui3-highlight">ó</b>',
'f<b class="yui3-highlight">o</b><b class="yui3-highlight">o</b>',
'bar'],
[
'f<b class="yui3-highlight">o</b><b class="yui3-highlight">o</b>',
'f<b class="yui3-highlight">o</b><b class="yui3-highlight">o</b>',
'bar'],
// -- phraseMatch() -------------------------------------------------------- 'phraseMatch() should highlight the complete query as a phrase':
function () {
[
'foo',
'bar',
'foo bar'],
[
'foo',
'bar',
'<b class="yui3-highlight">foo bar</b>'],
[
'<b class="yui3-highlight">foo</b>',
'bar',
'<b class="yui3-highlight">foo</b> bar'],
[
'foo',
'bar',
'xx<b class="yui3-highlight">foo bar</b>'],
'phraseMatch() should be case-insensitive':
function () {
[
'<b class="yui3-highlight">Foo</b>',
'<b class="yui3-highlight">foo</b>'],
// -- phraseMatchCase() ---------------------------------------------------- 'phraseMatchCase() should be case-sensitive':
function () {
[
'Foo',
'<b class="yui3-highlight">foo</b>'],
// -- phraseMatchFold() ---------------------------------------------------- 'phraseMatchFold() should match accent-folded characters':
function () {
[
'<b class="yui3-highlight">fóó</b>bar',
'bar<b class="yui3-highlight">foo</b>',
'bar'],
[
'<b class="yui3-highlight">foo</b>bar',
'bar<b class="yui3-highlight">foo</b>',
'bar'],
// -- startsWith() --------------------------------------------------------- 'startsWith() should highlight the complete query at the start of a result':
function () {
[
'xx foo',
'bar',
'xx foo bar'],
[
'<b class="yui3-highlight">foo</b>',
'bar foo',
'<b class="yui3-highlight">foo</b> bar'],
'startsWith() should be case-insensitive':
function () {
[
'<b class="yui3-highlight">Foo</b>',
'<b class="yui3-highlight">foo</b>'],
// -- startsWithCase() ----------------------------------------------------- 'startsWithCase() should be case-sensitive':
function () {
[
'Foo',
'<b class="yui3-highlight">foo</b>'],
// -- startsWithFold() ----------------------------------------------------- 'startsWithFold() should match accent-folded characters':
function () {
[
'<b class="yui3-highlight">fóó</b>',
'barfoo',
'bar'],
[
'<b class="yui3-highlight">foo</b>',
'barfoo',
'bar'],
// -- subWordMatch() ------------------------------------------------------- 'subWordMatch() should highlight partial words in the query':
function () {
[
'foobar [12345]',
'.foo-baz!'],
[
'<b class="yui3-highlight">foo</b>',
'<b class="yui3-highlight">foo</b> <b class="yui3-highlight">bar</b> <b class="yui3-highlight">baz</b>'],
[
'<b class="yui3-highlight">Anne</b>-<b class="yui3-highlight">S</b>ophie',
'<b class="yui3-highlight">S</b>ophie, <b class="yui3-highlight">Anne</b>',
'Ann <b class="yui3-highlight">S</b>ophie'],
'subWordMatch() should be case-insensitive':
function () {
[
'<b class="yui3-highlight">Foo</b>',
'<b class="yui3-highlight">foo</b>'],
// -- subWordMatchCase() --------------------------------------------------- 'subWordMatchCase() should be case-sensitive':
function () {
[
'Foo',
'<b class="yui3-highlight">foo</b>'],
// -- subWordMatchFold() --------------------------------------------------- 'subWordMatchFold() should match accent-folded characters':
function () {
[
'fóóbar [12345]',
'.fóó-baz!'],
[
'<b class="yui3-highlight">fóó</b>',
'<b class="yui3-highlight">fóó</b> <b class="yui3-highlight">bar</b> <b class="yui3-highlight">baz</b>'],
[
'<b class="yui3-highlight">Anne</b>-<b class="yui3-highlight">S</b>ophie',
'<b class="yui3-highlight">S</b>ophie, <b class="yui3-highlight">Anné</b>',
'Ann <b class="yui3-highlight">S</b>ophie'],
// -- wordMatch() ---------------------------------------------------------- 'wordMatch() should highlight complete words in the query':
function () {
[
'<b class="yui3-highlight">foo</b>',
'<b class="yui3-highlight">bar</b>',
'<b class="yui3-highlight">foo</b> <b class="yui3-highlight">bar</b> <b class="yui3-highlight">baz</b>',
'foobar <b class="yui3-highlight">baz</b>'],
'wordMatch() should be case-insensitive':
function () {
[
'<b class="yui3-highlight">Foo</b>',
'<b class="yui3-highlight">foo</b>'],
// -- wordMatchCase() ------------------------------------------------------ 'wordMatchCase() should be case-sensitive':
function () {
[
'Foo',
'<b class="yui3-highlight">foo</b>'],
'wordMatchFold() should match accent-folded characters':
function () {
[
'<b class="yui3-highlight">fóó</b>',
'<b class="yui3-highlight">foo</b>',
'barfoo'],
[
'<b class="yui3-highlight">foo</b>',
'<b class="yui3-highlight">fóó</b>',
'barfoo'],
// -- List Suite --------------------------------------------------------------- // -- List: Lifecycle ---------------------------------------------------------- 'List should render inside the same parent as the inputNode by default':
function () {
'List width should match the width of the inputNode by default':
function () {
'Explicit list widths should be supported':
function () {
this.
ac.
set(
'width',
'142px');
'List should default to a sane width if the inputNode width is 0 or unknown':
function () {
Y.
assert(
this.
ac.
get(
'boundingBox').
get(
'offsetWidth') >
0,
'List widget width should be greater than 0px');
'List should not appear automatically when attached to an inputNode with text':
function () {
'test: verify list markup':
function () {
// Verify DOM hierarchy and class names. Assert.
isNotNull(Y.
one(
'#testbed > div.yui3-aclist.yui3-aclist-hidden > div.yui3-aclist-content > ul.yui3-aclist-list'));
// Verify ARIA markup on the input node. // Verify ARIA markup on the bounding box and list node. // Verify that a live region node exists. 'test: verify result item markup':
function () {
// -- List: Attributes --------------------------------------------------------- 'test: activateFirstItem':
function () {
this.
ac.
set(
'activateFirstItem',
true);
'test: activeItem':
function () {
var items =
this.
ac.
get(
'listNode').
all(
'> li.yui3-aclist-item');
this.
ac.
set(
'activeItem',
null);
'test: alwaysShowList':
function () {
this.
ac.
set(
'alwaysShowList',
true);
'test: circular':
function () {
var items =
this.
ac.
get(
'listNode').
all(
'> li.yui3-aclist-item');
// circular === true, going down. this.
ac.
set(
'activeItem',
null);
// circular == true, going up this.
ac.
set(
'activeItem',
null);
// circular === false, going down this.
ac.
set(
'circular',
false);
this.
ac.
set(
'activeItem',
null);
// circular === false, going up 'test: hoveredItem':
function () {
var items =
this.
ac.
get(
'listNode').
all(
'> li.yui3-aclist-item');
'test: tabSelect':
function () {
this.
ac.
set(
'alwaysShowList',
true);
this.
ac.
on(
'select',
function (e) {
this.
ac.
set(
'tabSelect',
false);
this.
ac.
set(
'tabSelect',
true);
'test: visible':
function () {
this.
ac.
set(
'visible',
true);
// -- List: Events ------------------------------------------------------------- 'select event should fire when a result is selected':
function () {
// Note: this test also covers the selectItem() method. this.
ac.
on(
'select',
function (e) {
// -- List: API ---------------------------------------------------------------- 'hide() should hide the list, except when alwaysShowList is true':
function () {
this.
ac.
set(
'alwaysShowList',
true);
// Note: selectItem() is already covered by the select event test above. No // -- Plugin Suite ------------------------------------------------------------- // -- Plugin: Lifecycle -------------------------------------------------------- 'inputNode.ac should be an instance of Y.Plugin.AutoComplete':
function () {
'inputNode.ac should extend Y.AutoCompleteList':
function () {
'The plugin should render itself immediately':
function () {
'autocomplete',
'autocomplete-filters',
'autocomplete-filters-accentfold',
'autocomplete-highlighters',
'autocomplete-highlighters-accentfold',
'autocomplete-test-data',
'datasource-local',
'node',
'node-event-simulate',
'jsonp',
'test',
'yql'