497 lines
13 KiB
JavaScript
497 lines
13 KiB
JavaScript
describe('Dataset', function() {
|
|
var www = WWW(), mockSuggestions, mockSuggestionsDisplayFn;
|
|
|
|
mockSuggestions = [
|
|
{ value: 'one', raw: { value: 'one' } },
|
|
{ value: 'two', raw: { value: 'two' } },
|
|
{ value: 'html', raw: { value: '<b>html</b>' } }
|
|
];
|
|
|
|
mockSuggestionsDisplayFn = [
|
|
{ display: '4' },
|
|
{ display: '5' },
|
|
{ display: '6' }
|
|
];
|
|
|
|
beforeEach(function() {
|
|
this.dataset = new Dataset({
|
|
name: 'test',
|
|
node: $('<div>'),
|
|
source: this.source = jasmine.createSpy('source')
|
|
}, www);
|
|
});
|
|
|
|
it('should throw an error if source is missing', function() {
|
|
expect(noSource).toThrow();
|
|
|
|
function noSource() { new Dataset({}, www); }
|
|
});
|
|
|
|
it('should throw an error if the name is not a valid class name', function() {
|
|
expect(fn).toThrow();
|
|
|
|
function fn() {
|
|
var d = new Dataset({
|
|
name: 'a space',
|
|
node: $('<div>'),
|
|
source: $.noop
|
|
}, www);
|
|
}
|
|
});
|
|
|
|
describe('#getRoot', function() {
|
|
it('should return the root element', function() {
|
|
var sel = 'div' + www.selectors.dataset + www.selectors.dataset + '-test';
|
|
expect(this.dataset.$el).toBe(sel);
|
|
});
|
|
});
|
|
|
|
describe('#update', function() {
|
|
it('should render suggestions', function() {
|
|
this.source.andCallFake(syncMockSuggestions);
|
|
this.dataset.update('woah');
|
|
|
|
expect(this.dataset.$el).toContainText('one');
|
|
expect(this.dataset.$el).toContainText('two');
|
|
expect(this.dataset.$el).toContainText('html');
|
|
});
|
|
|
|
it('should escape html chars from display value when using default template', function() {
|
|
this.source.andCallFake(syncMockSuggestions);
|
|
this.dataset.update('woah');
|
|
|
|
expect(this.dataset.$el).toContainText('<b>html</b>');
|
|
});
|
|
|
|
it('should respect limit option', function() {
|
|
this.dataset.limit = 2;
|
|
this.source.andCallFake(syncMockSuggestions);
|
|
this.dataset.update('woah');
|
|
|
|
expect(this.dataset.$el).toContainText('one');
|
|
expect(this.dataset.$el).toContainText('two');
|
|
expect(this.dataset.$el).not.toContainText('three');
|
|
});
|
|
|
|
it('should allow custom display functions', function() {
|
|
this.dataset = new Dataset({
|
|
name: 'test',
|
|
node: $('<div>'),
|
|
display: function(o) { return o.display; },
|
|
source: this.source = jasmine.createSpy('source')
|
|
}, www);
|
|
|
|
this.source.andCallFake(syncMockSuggestionsDisplayFn);
|
|
this.dataset.update('woah');
|
|
|
|
expect(this.dataset.$el).toContainText('4');
|
|
expect(this.dataset.$el).toContainText('5');
|
|
expect(this.dataset.$el).toContainText('6');
|
|
});
|
|
|
|
it('should ignore async invocations of sync', function() {
|
|
this.source.andCallFake(asyncSync);
|
|
this.dataset.update('woah');
|
|
|
|
expect(this.dataset.$el).not.toContainText('one');
|
|
});
|
|
|
|
it('should ignore subesequent invocations of sync', function() {
|
|
this.source.andCallFake(multipleSync);
|
|
this.dataset.update('woah');
|
|
|
|
expect(this.dataset.$el.find('.tt-suggestion')).toHaveLength(3);
|
|
});
|
|
|
|
it('should trigger asyncRequested when needing/expecting backfill', function() {
|
|
var spy = jasmine.createSpy();
|
|
|
|
this.dataset.async = true;
|
|
this.dataset.onSync('asyncRequested', spy);
|
|
this.source.andCallFake(fakeGetWithAsyncSuggestions);
|
|
|
|
this.dataset.update('woah');
|
|
|
|
expect(spy).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not trigger asyncRequested when not expecting backfill', function() {
|
|
var spy = jasmine.createSpy();
|
|
|
|
this.dataset.async = false;
|
|
this.dataset.onSync('asyncRequested', spy);
|
|
this.source.andCallFake(fakeGetWithAsyncSuggestions);
|
|
|
|
this.dataset.update('woah');
|
|
|
|
expect(spy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not trigger asyncRequested when not expecting backfill', function() {
|
|
var spy = jasmine.createSpy();
|
|
|
|
this.dataset.limit = 2;
|
|
this.dataset.async = true;
|
|
this.dataset.onSync('asyncRequested', spy);
|
|
this.source.andCallFake(fakeGetWithAsyncSuggestions);
|
|
|
|
this.dataset.update('woah');
|
|
|
|
expect(spy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should trigger asyncCanceled when pending aysnc is canceled', function() {
|
|
var spy = jasmine.createSpy();
|
|
|
|
this.dataset.async = true;
|
|
this.dataset.onSync('asyncCanceled', spy);
|
|
this.source.andCallFake(fakeGetWithAsyncSuggestions);
|
|
|
|
this.dataset.update('woah');
|
|
this.dataset.cancel();
|
|
|
|
waits(100);
|
|
|
|
runs(function() {
|
|
expect(spy).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it('should not trigger asyncCanceled when cancel happens after update', function() {
|
|
var spy = jasmine.createSpy();
|
|
|
|
this.dataset.async = true;
|
|
this.dataset.onSync('asyncCanceled', spy);
|
|
this.source.andCallFake(fakeGetWithAsyncSuggestions);
|
|
|
|
this.dataset.update('woah');
|
|
|
|
waits(100);
|
|
|
|
runs(function() {
|
|
this.dataset.cancel();
|
|
expect(spy).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it('should trigger asyncReceived when aysnc is received', function() {
|
|
var spy = jasmine.createSpy();
|
|
|
|
this.dataset.async = true;
|
|
this.dataset.onSync('asyncReceived', spy);
|
|
this.source.andCallFake(fakeGetWithAsyncSuggestions);
|
|
|
|
this.dataset.update('woah');
|
|
|
|
waits(100);
|
|
|
|
runs(function() {
|
|
expect(spy).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it('should not trigger asyncReceived if canceled', function() {
|
|
var spy = jasmine.createSpy();
|
|
|
|
this.dataset.async = true;
|
|
this.dataset.onSync('asyncReceived', spy);
|
|
this.source.andCallFake(fakeGetWithAsyncSuggestions);
|
|
|
|
this.dataset.update('woah');
|
|
this.dataset.cancel();
|
|
|
|
waits(100);
|
|
|
|
runs(function() {
|
|
expect(spy).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it('should not modify sync when async is added', function() {
|
|
var $test;
|
|
|
|
this.dataset.async = true;
|
|
this.source.andCallFake(fakeGetWithAsyncSuggestions);
|
|
|
|
this.dataset.update('woah');
|
|
$test = this.dataset.$el.find('.tt-suggestion').first();
|
|
$test.addClass('test');
|
|
|
|
waits(100);
|
|
|
|
runs(function() {
|
|
expect($test).toHaveClass('test');
|
|
});
|
|
});
|
|
|
|
it('should respect limit option in regard to async', function() {
|
|
this.dataset.async = true;
|
|
this.source.andCallFake(fakeGetWithAsyncSuggestions);
|
|
|
|
this.dataset.update('woah');
|
|
|
|
waits(100);
|
|
|
|
runs(function() {
|
|
expect(this.dataset.$el.find('.tt-suggestion')).toHaveLength(5);
|
|
});
|
|
});
|
|
|
|
it('should cancel pending async', function() {
|
|
var spy1 = jasmine.createSpy(), spy2 = jasmine.createSpy();
|
|
|
|
this.dataset.async = true;
|
|
this.dataset.onSync('asyncCanceled', spy1);
|
|
this.dataset.onSync('asyncReceived', spy2);
|
|
this.source.andCallFake(fakeGetWithAsyncSuggestions);
|
|
|
|
|
|
this.dataset.update('woah');
|
|
this.dataset.update('woah again');
|
|
|
|
waits(100);
|
|
|
|
runs(function() {
|
|
expect(spy1.callCount).toBe(1);
|
|
expect(spy2.callCount).toBe(1);
|
|
});
|
|
});
|
|
|
|
it('should render notFound when no suggestions are available', function() {
|
|
this.dataset = new Dataset({
|
|
source: this.source,
|
|
node: $('<div>'),
|
|
templates: {
|
|
notFound: '<h2>empty</h2>'
|
|
}
|
|
}, www);
|
|
|
|
this.source.andCallFake(syncEmptySuggestions);
|
|
this.dataset.update('woah');
|
|
|
|
expect(this.dataset.$el).toContainText('empty');
|
|
});
|
|
|
|
it('should render pending when no suggestions are available but async is pending', function() {
|
|
this.dataset = new Dataset({
|
|
source: this.source,
|
|
node: $('<div>'),
|
|
async: true,
|
|
templates: {
|
|
pending: '<h2>pending</h2>'
|
|
}
|
|
}, www);
|
|
|
|
this.source.andCallFake(syncEmptySuggestions);
|
|
this.dataset.update('woah');
|
|
|
|
expect(this.dataset.$el).toContainText('pending');
|
|
});
|
|
|
|
it('should render header when suggestions are rendered', function() {
|
|
this.dataset = new Dataset({
|
|
source: this.source,
|
|
node: $('<div>'),
|
|
templates: {
|
|
header: '<h2>header</h2>'
|
|
}
|
|
}, www);
|
|
|
|
this.source.andCallFake(syncMockSuggestions);
|
|
this.dataset.update('woah');
|
|
|
|
expect(this.dataset.$el).toContainText('header');
|
|
});
|
|
|
|
it('should render footer when suggestions are rendered', function() {
|
|
this.dataset = new Dataset({
|
|
source: this.source,
|
|
node: $('<div>'),
|
|
templates: {
|
|
footer: function(c) { return '<p>' + c.query + '</p>'; }
|
|
}
|
|
}, www);
|
|
|
|
this.source.andCallFake(syncMockSuggestions);
|
|
this.dataset.update('woah');
|
|
|
|
expect(this.dataset.$el).toContainText('woah');
|
|
});
|
|
|
|
it('should not render header/footer if there is no content', function() {
|
|
this.dataset = new Dataset({
|
|
source: this.source,
|
|
node: $('<div>'),
|
|
templates: {
|
|
header: '<h2>header</h2>',
|
|
footer: '<h2>footer</h2>'
|
|
}
|
|
}, www);
|
|
|
|
this.source.andCallFake(syncEmptySuggestions);
|
|
this.dataset.update('woah');
|
|
|
|
expect(this.dataset.$el).not.toContainText('header');
|
|
expect(this.dataset.$el).not.toContainText('footer');
|
|
});
|
|
|
|
it('should not render stale suggestions', function() {
|
|
this.source.andCallFake(fakeGetWithAsyncSuggestions);
|
|
this.dataset.update('woah');
|
|
|
|
this.source.andCallFake(syncMockSuggestions);
|
|
this.dataset.update('nelly');
|
|
|
|
waits(100);
|
|
|
|
runs(function() {
|
|
expect(this.dataset.$el).toContainText('one');
|
|
expect(this.dataset.$el).toContainText('two');
|
|
expect(this.dataset.$el).toContainText('html');
|
|
expect(this.dataset.$el).not.toContainText('four');
|
|
expect(this.dataset.$el).not.toContainText('five');
|
|
});
|
|
});
|
|
|
|
it('should not render async suggestions if update was canceled', function() {
|
|
this.source.andCallFake(fakeGetWithAsyncSuggestions);
|
|
this.dataset.update('woah');
|
|
this.dataset.cancel();
|
|
|
|
waits(100);
|
|
|
|
runs(function() {
|
|
var rendered = this.dataset.$el.find('.tt-suggestion');
|
|
expect(rendered).toHaveLength(3);
|
|
});
|
|
});
|
|
|
|
it('should render all async suggestions if sync had no content', function() {
|
|
this.source.andCallFake(fakeGetWithEmptySyncAndAsyncSuggestions);
|
|
this.dataset.update('woah');
|
|
|
|
waits(100);
|
|
|
|
runs(function() {
|
|
var rendered = this.dataset.$el.find('.tt-suggestion');
|
|
expect(rendered).toHaveLength(5);
|
|
});
|
|
});
|
|
|
|
it('should trigger rendered after suggestions are rendered', function() {
|
|
var spy;
|
|
|
|
this.dataset.onSync('rendered', spy = jasmine.createSpy());
|
|
|
|
this.source.andCallFake(syncMockSuggestions);
|
|
this.dataset.update('woah');
|
|
|
|
waitsFor(function() { return spy.callCount; });
|
|
});
|
|
});
|
|
|
|
describe('#clear', function() {
|
|
it('should clear suggestions', function() {
|
|
this.source.andCallFake(syncMockSuggestions);
|
|
this.dataset.update('woah');
|
|
|
|
this.dataset.clear();
|
|
expect(this.dataset.$el).toBeEmpty();
|
|
});
|
|
|
|
it('should cancel pending updates', function() {
|
|
var spy;
|
|
|
|
this.source.andCallFake(syncMockSuggestions);
|
|
this.dataset.update('woah');
|
|
spy = spyOn(this.dataset, 'cancel');
|
|
|
|
this.dataset.clear();
|
|
expect(spy).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should trigger cleared', function() {
|
|
var spy;
|
|
|
|
this.dataset.onSync('cleared', spy = jasmine.createSpy());
|
|
this.dataset.clear();
|
|
expect(spy).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('#isEmpty', function() {
|
|
it('should return true when empty', function() {
|
|
expect(this.dataset.isEmpty()).toBe(true);
|
|
});
|
|
|
|
it('should return false when not empty', function() {
|
|
this.source.andCallFake(syncMockSuggestions);
|
|
this.dataset.update('woah');
|
|
|
|
expect(this.dataset.isEmpty()).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('#destroy', function() {
|
|
it('should set dataset element to dummy element', function() {
|
|
var $prevEl = this.dataset.$el;
|
|
|
|
this.dataset.destroy();
|
|
expect(this.dataset.$el).not.toBe($prevEl);
|
|
});
|
|
});
|
|
|
|
// helper functions
|
|
// ----------------
|
|
|
|
function syncEmptySuggestions(q, sync, async) {
|
|
sync([]);
|
|
}
|
|
|
|
function syncMockSuggestions(q, sync, async) {
|
|
sync(mockSuggestions);
|
|
}
|
|
|
|
function syncMockSuggestionsDisplayFn(q, sync, async) {
|
|
sync(mockSuggestionsDisplayFn);
|
|
}
|
|
|
|
function asyncSync(q, sync, async) {
|
|
setTimeout(function() { sync(mockSuggestions); }, 0);
|
|
}
|
|
|
|
function multipleSync(q, sync, async) {
|
|
sync(mockSuggestions);
|
|
sync(mockSuggestions);
|
|
}
|
|
|
|
function fakeGetWithAsyncSuggestions(query, sync, async) {
|
|
sync(mockSuggestions);
|
|
|
|
setTimeout(function() {
|
|
async([
|
|
{ value: 'four', raw: { value: 'four' } },
|
|
{ value: 'five', raw: { value: 'five' } },
|
|
{ value: 'six', raw: { value: 'six' } },
|
|
{ value: 'seven', raw: { value: 'seven' } },
|
|
{ value: 'eight', raw: { value: 'eight' } },
|
|
]);
|
|
}, 0);
|
|
}
|
|
|
|
function fakeGetWithEmptySyncAndAsyncSuggestions(query, sync, async) {
|
|
sync([]);
|
|
|
|
setTimeout(function() {
|
|
async([
|
|
{ value: 'four', raw: { value: 'four' } },
|
|
{ value: 'five', raw: { value: 'five' } },
|
|
{ value: 'six', raw: { value: 'six' } },
|
|
{ value: 'seven', raw: { value: 'seven' } },
|
|
{ value: 'eight', raw: { value: 'eight' } },
|
|
]);
|
|
}, 0);
|
|
}
|
|
|
|
});
|