autocomplete-test.js revision 4397b5a5bcaf8a291905306dbcc7f2b0fd2e60ee
var ArrayAssert = Y.ArrayAssert,
ObjectAssert = Y.ObjectAssert,
YArray = Y.Array,
// Simple, bare AutoCompleteBase implementation for testing.
initializer: function () {
this._bindUIACBase();
this._syncUIACBase();
},
destructor: function () {
this._destructorACBase();
}
});
// Helper functions.
function arrayToResults(array) {
return {
};
});
}
return {
};
}
if (!key) {
key = 'text';
}
});
}
function setUpACInstance() {
});
// Helper method that synchronously simulates a valueChange event on the
// inputNode.
this.simulateInput = function (value) {
};
}
function setUpACListInstance() {
this.ac = new Y.AutoComplete({
});
// Helper method that synchronously simulates a valueChange event on the
// inputNode.
this.simulateInput = function (value) {
};
}
function tearDownACInstance() {
delete this.ac;
delete this.inputNode;
}
function tearDownACListInstance() {
delete this.ac;
delete this.inputNode;
}
// -- Global Suite -------------------------------------------------------------
// -- Base Suite ---------------------------------------------------------------
// -- Base: Lifecycle ----------------------------------------------------------
name: 'Lifecycle',
_should: {
// error: {
// 'Initializer should require an inputNode': 'No inputNode specified.'
// }
},
setUp: function () {
},
tearDown: function () {
delete this.inputNode;
},
'Initializer should accept an inputNode': 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 () {
// Should fail.
// var ac = new ACBase();
// }
}));
// -- Base: Attributes ---------------------------------------------------------
name: 'Attributes',
'Browser autocomplete should be off by default': function () {
},
'Browser autocomplete should be turned on when enabled': function () {
allowBrowserAutocomplete: true
});
},
'Browser autocomplete should be settable after init': function () {
},
'inputNode should be writable only on init': function () {
},
'maxResults should enforce a maximum number of results': function () {
});
},
'maxResults should do nothing if <= 0': function () {
});
},
'minQueryLength should enforce a minimum query length': function () {
var fired = 0;
this.simulateInput('foo');
this.simulateInput('bar');
},
'minQueryLength should allow empty queries if set to 0': function () {
var fired = 0;
this.simulateInput('foo');
this.simulateInput('');
},
'minQueryLength should prevent queries if negative': function () {
var fired = 0;
this.simulateInput('foo');
this.simulateInput('bar');
},
'queryDelay should delay query events': function () {
var fired = 0;
fired += 1;
});
this.simulateInput('foo');
this.simulateInput('bar');
this.wait(function () {
}, 35);
},
'_parseValue should return the rightmost token when using a queryDelimiter': function () {
},
'requestTemplate should accept a custom template function': function () {
return 'query: ' + query;
};
},
'requestTemplate should generate a template function when set to a string': function () {
},
'requestTemplate function should replace {query} with the URI-encoded query': function () {
var rt;
},
'`this` object in requestTemplate functions should be the AutoComplete instance': function () {
calls = 0;
calls += 1;
});
},
'resultFilters should accept a function, array of functions, string, array of strings, or null': function () {
var filter = function () {};
ArrayAssert.itemsAreSame([Y.AutoCompleteFilters.phraseMatch, Y.AutoCompleteFilters.charMatch], this.ac.get('resultFilters'));
},
'result filters should receive the query and an array of result objects as parameters': function () {
var called = 0,
self = this,
called += 1;
display: 'foo&bar',
raw : 'foo&bar',
text : 'foo&bar'
}, results[0]);
return results;
};
},
'resultFormatter should accept a function or null': function () {
var formatter = function () {};
},
'result formatters should receive the query and an array of result objects as parameters': function () {
var called = 0,
self = this,
called += 1;
display: 'foo&bar',
raw : 'foo&bar',
text : 'foo&bar'
}, results[0]);
return ['|foo|'];
};
},
'resultHighlighter should accept a function, string, or null': function () {
var highlighter = function () {};
},
'result highlighters should receive the query and an array of result objects as parameters': function () {
var called = 0,
self = this,
called += 1;
display: 'foo&bar',
raw : 'foo&bar',
text : 'foo&bar'
}, results[0]);
return ['|foo|'];
};
},
'resultListLocator should accept a function, string, or null': function () {
var locator = function () {};
},
'resultListLocator should locate results': function () {
var self = this;
});
},
'resultTextLocator should locate result text': function () {
var self = this;
});
},
'_parseResponse should preserve duplicates in text when using resultTextLocator': function () {
var response = {
results: [
{"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"}
]
};
"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
// tests.
'value attribute should update the inputNode value when set via the API, and should not trigger a query event': function () {
});
},
// -- Generic setters and validators ---------------------------------------
'_functionValidator() should accept a function or null': function () {
}
}));
// -- Base: Events -------------------------------------------------------------
name: 'Events',
'clear event should fire when the query is cleared': function () {
var fired = 0;
fired += 1;
});
// Without delimiter.
this.simulateInput('foo');
this.simulateInput('');
// With delimiter.
this.simulateInput('bar,foo');
this.simulateInput('');
},
'clear event should be preventable': function () {
e.preventDefault();
});
this.simulateInput('foo');
this.simulateInput('');
},
'query event should fire when the value attribute is changed via the UI': function () {
var fired = 0;
fired += 1;
});
this.simulateInput('foo');
},
'query event should be preventable': function () {
this.ac.sendRequest = function () {
};
e.preventDefault();
});
this.simulateInput('foo');
},
'results event should fire when a source returns results': function () {
var fired = 0;
fired += 1;
});
this.simulateInput('foo');
},
'results event should be preventable': function () {
e.preventDefault();
});
this.simulateInput('foo');
}
}));
// -- Base: Methods ------------------------------------------------------------
name: 'Methods',
'sendRequest should provide a complete request object to source.sendRequest': function () {
var mockSource = Y.Mock();
// Create a mock DataSource-like source so we can test what gets passed
// to source.sendRequest().
method: 'sendRequest',
})]
});
}
}));
// -- Base: Built-in Sources ---------------------------------------------------
name: 'Built-in sources',
// -- Behavior -------------------------------------------------------------
'Array sources should return the full array regardless of query': function () {
},
'DataSource sources should work': function () {
});
},
'Function sources should support synchronous return values': function () {
var realQuery;
return ['foo', 'bar', 'baz'];
});
realQuery = 'foo';
},
'Function sources should support asynchronous return values': function () {
var realQuery;
setTimeout(function () {
}, 10);
});
realQuery = 'foo';
this.wait(function () {
}, 15);
},
'Object sources should work': function () {
foo: ['foo'],
bar: ['bar']
});
},
'sourceType should override source type detection for built-in types': function () {
}
}));
// -- 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>'),
html : 'foo & bar',
index : 0,
selected: true,
text : 'foo & bar',
value : 'abc'
}, result);
},
'XHR strings should be turned into IO source objects': function () {
// Absolute URL.
// Relative URL.
},
// TODO: mock io requests
'JSONP strings should be turned into JSONP source objects': function () {
// Absolute URL.
// Relative URL.
},
'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', 'use "http://example.com/foo.env"; select * from search.suggest where q="{query}"');
},
// TODO: mock YQL requests
// -- Other stuff ----------------------------------------------------------
'sourceType should override source type detection for extra types': function () {
},
'_jsonpFormatter should correctly format URLs both with and without a requestTemplate set': function () {
Assert.areSame('foo?q=bar%20baz&cb=callback', this.ac._jsonpFormatter('foo?q={query}&cb={callback}', 'callback', 'bar baz'));
Assert.areSame('foo?q=bar%20baz&cb=callback', this.ac._jsonpFormatter('foo', 'callback', 'bar baz'));
Assert.areSame('foo?q=bar%20baz&cb=callback', this.ac._jsonpFormatter('foo?q={query}', 'callback', 'bar baz'));
},
'requestTemplate should be appended to XHR source URLs': function () {
var query = 'monkey pants',
source = '/foo?q={query}';
})
);
}
}));
// -- Filters Suite ------------------------------------------------------------
// -- Filters: API -------------------------------------------------------------
name: 'API',
// -- charMatch() ----------------------------------------------------------
'charMatch() should match all characters in the query, in any order': function () {
);
['cab', 'taxi cab'],
);
},
'charMatch() should be case-insensitive': function () {
['Foo', 'foo'],
);
},
'charMatchCase() should be case-sensitive': function () {
['foo'],
);
},
'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 () {
);
['foo bar'],
);
['xxfoo barxx'],
);
['foo barxx'],
);
['xxfoo bar'],
);
},
'phraseMatch() should be case-insensitive': function () {
['Foo', 'foo'],
);
},
'phraseMatchCase() should be case-sensitive': function () {
['foo'],
);
},
'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 () {
);
['foo', 'foo bar'],
);
},
'startsWith() should be case-insensitive': function () {
['Foo', 'foo'],
);
},
'startsWithCase() should be case-sensitive': function () {
['foo'],
);
},
'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'],
resultsToArray(Filters.subWordMatch('baz foo bar', arrayToResults(['foo', 'bar', 'foo bar baz', 'foobar baz'])))
);
['John Doe'],
resultsToArray(Filters.subWordMatch('John', arrayToResults(['John Doe', 'Jon Doe', 'Richard Roe'])))
);
['John Doe', 'Jon Doe'],
resultsToArray(Filters.subWordMatch('J. Doe', arrayToResults(['John Doe', 'Jon Doe', 'Richard Roe'])))
);
['John Doe', 'Jon Doe'],
resultsToArray(Filters.subWordMatch('D., Jo.', arrayToResults(['John Doe', 'Jon Doe', 'Richard Roe'])))
);
['John Doe', 'Jon Doe', 'Richard Roe [12345]'],
resultsToArray(Filters.subWordMatch('oe', arrayToResults(['John Doe', 'Jon Doe', 'Richard Roe [12345]', 'O.E.'])))
);
['Anne-Sophie'],
resultsToArray(Filters.subWordMatch('(Sophie-Ann.)', arrayToResults(['Anne-Sophie', 'Ann-Christine'])))
);
},
'subWordMatch() should be case-insensitive': function () {
['Foo', 'foo'],
);
},
'subWordMatchCase() should be case-sensitive': function () {
['foo'],
);
},
'subWordMatchFold() should match accent-folded characters': function () {
[],
);
['fóó bar baz'],
);
['Anne-Sophie', 'Sophie, Anné'],
resultsToArray(Filters.subWordMatchFold('Anné S.', arrayToResults(['Anne-Sophie', 'Sophie, Anné', 'Ann Sophie'])))
);
},
// -- wordMatch() ----------------------------------------------------------
'wordMatch() should match results that contain all words in the query in any order': function () {
);
['foo bar baz'],
resultsToArray(Filters.wordMatch('baz foo bar', arrayToResults(['foo', 'bar', 'foo bar baz', 'foobar baz'])))
);
['foo', 'foo bar baz'],
resultsToArray(Filters.wordMatch('foo', arrayToResults(['foo', 'bar', 'foo bar baz', 'foobar baz'])))
);
},
'wordMatch() should be case-insensitive': function () {
['Foo', 'foo'],
);
},
'wordMatchCase() should be case-sensitive': function () {
['foo'],
);
},
'wordMatchFold() should match accent-folded characters': function () {
['fóó', 'föö', 'foo'],
);
// Accent-folded matches are always case-insensitive.
['FÓÓ', 'FÖÖ', 'FOO'],
);
}
}));
// -- Highlighters Suite -------------------------------------------------------
// -- Highlighters: API --------------------------------------------------------
name: '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 () {
['foobar', 'barbaz'],
);
['<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 ----------------------------------------------------------
name: '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 () {
},
'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');
},
// Ticket #2529692
'List should not appear automatically when attached to an inputNode with text': function () {
var ac = new Y.AutoComplete({
queryDelay: 0,
render: true,
});
},
'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 ---------------------------------------------------------
name: 'Attributes',
_should: {
ignore: {
}
},
'test: activateFirstItem': function () {
},
'test: activeItem': function () {
},
'test: alwaysShowList': function () {
},
'test: circular': function () {
// circular === true, going down.
this.ac._activateNextItem();
this.ac._activateNextItem();
this.ac._activateNextItem();
this.ac._activateNextItem();
// circular == true, going up
this.ac._activatePrevItem();
this.ac._activatePrevItem();
this.ac._activatePrevItem();
this.ac._activatePrevItem();
// circular === false, going down
this.ac._activateNextItem();
this.ac._activateNextItem();
this.ac._activateNextItem();
// circular === false, going up
this.ac._activatePrevItem();
this.ac._activatePrevItem();
this.ac._activatePrevItem();
},
'test: hoveredItem': function () {
},
'test: tabSelect': function () {
var fired = 0,
fired += 1;
});
},
'test: visible': function () {
}
}));
// -- List: Events -------------------------------------------------------------
name: 'Events',
'select event should fire when a result is selected': function () {
// Note: this test also covers the selectItem() method.
var fired = 0,
fired += 1;
});
}
}));
// -- List: API ----------------------------------------------------------------
name: 'API',
'hide() should hide the list, except when alwaysShowList is true': function () {
}
// Note: selectItem() is already covered by the select event test above. No
// need to retest it.
}));
// -- Plugin Suite -------------------------------------------------------------
// -- Plugin: Lifecycle --------------------------------------------------------
name: 'Lifecycle',
setUp: function () {
},
tearDown: function () {
delete this.inputNode;
},
'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 () {
}
}));
}, '@VERSION@', {
requires: [
'autocomplete', 'autocomplete-filters',
'autocomplete-filters-accentfold', 'autocomplete-highlighters',
'autocomplete-highlighters-accentfold', 'autocomplete-test-data',
'datasource-local', 'node', 'node-event-simulate', 'jsonp', 'test', 'yql'
]
});