'use strict'; /* eslint-env mocha, jasmine */ describe('Dataset', function() { require('../../src/common/dom.js').element = require('jquery'); require('../../src/jquery/plugin.js'); var $ = require('jquery'); require('jasmine-jquery'); var Dataset = require('../../src/autocomplete/dataset.js'); beforeEach(function() { this.dataset = new Dataset({ name: 'test', source: this.source = jasmine.createSpy('source') }); }); it('should throw an error if source is missing', function() { expect(noSource).toThrow(); function noSource() { new Dataset(); } }); 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', source: $.noop}); } }); describe('#getRoot', function() { it('should return the root element', function() { expect(this.dataset.getRoot()).toBeMatchedBy('div.aa-dataset-test'); }); }); describe('#update', function() { it('should render suggestions', function() { this.source.and.callFake(fakeGetWithSyncResults); this.dataset.update('woah'); expect(this.dataset.getRoot()).toContainText('one'); expect(this.dataset.getRoot()).toContainText('two'); expect(this.dataset.getRoot()).toContainText('three'); }); it('should allow custom display functions', function() { this.dataset = new Dataset({ name: 'test', display: function(o) { return o.display; }, source: this.source = jasmine.createSpy('source') }); this.source.and.callFake(fakeGetForDisplayFn); this.dataset.update('woah'); expect(this.dataset.getRoot()).toContainText('4'); expect(this.dataset.getRoot()).toContainText('5'); expect(this.dataset.getRoot()).toContainText('6'); }); it('should render empty when no suggestions are available', function() { this.dataset = new Dataset({ source: this.source, templates: { empty: '

empty

' } }); this.source.and.callFake(fakeGetWithSyncEmptyResults); this.dataset.update('woah'); expect(this.dataset.getRoot()).toContainText('empty'); }); it('should throw an error if suggestions is not an array', function() { this.source.and.callFake(fakeGetWithSyncNonArrayResults); expect(this.dataset.update.bind(this.dataset, 'woah')) .toThrowError(TypeError, 'suggestions must be an array'); }); it('should set the aa-without class when no suggestions are available', function() { var $menu = $('
'); this.dataset = new Dataset({ $menu: $menu, source: this.source, templates: { empty: '

empty

' } }); this.source.and.callFake(fakeGetWithSyncEmptyResults); this.dataset.update('woah'); expect($menu).toHaveClass('aa-without-1'); expect($menu).not.toHaveClass('aa-with-1'); }); it('should set the aa-with class when suggestions are available', function() { var $menu = $('
'); this.dataset = new Dataset({ $menu: $menu, name: 'fake', source: this.source, templates: { empty: '

empty

' } }); this.source.and.callFake(fakeGetWithSyncResults); this.dataset.update('woah'); expect($menu).not.toHaveClass('aa-without-fake'); expect($menu).toHaveClass('aa-with-fake'); }); it('should allow dataset name=0 and use the provided div', function() { var $menu = $('
'); this.dataset = new Dataset({ $menu: $menu, name: 0, source: this.source }); expect(this.dataset.$el).toHaveClass('predefined'); }); it('should render isEmpty with extra params', function() { var spy = jasmine.createSpy('empty with extra params'); this.dataset = new Dataset({ source: this.source, templates: { empty: spy } }); this.source.and.callFake(fakeGetWithSyncEmptyResultsAndExtraParams); this.dataset.update('woah'); expect(spy).toHaveBeenCalled(); expect(spy.calls.argsFor(0).length).toEqual(4); expect(spy.calls.argsFor(0)[0]).toEqual({query: 'woah', isEmpty: true}); expect(spy.calls.argsFor(0)[1]).toEqual(42); expect(spy.calls.argsFor(0)[2]).toEqual(true); expect(spy.calls.argsFor(0)[3]).toEqual(false); }); it('should render with extra params', function() { var headerSpy = jasmine.createSpy('header with extra params'); var footerSpy = jasmine.createSpy('footer with extra params'); var suggestionSpy = jasmine.createSpy('suggestion with extra params'); this.dataset = new Dataset({ source: this.source, templates: { header: headerSpy, footer: footerSpy, suggestion: suggestionSpy } }); var suggestions; fakeGetWithSyncResultsAndExtraParams('woah', function(all) { suggestions = all; }); this.source.and.callFake(fakeGetWithSyncResultsAndExtraParams); this.dataset.update('woah'); expect(headerSpy).toHaveBeenCalled(); expect(headerSpy.calls.argsFor(0).length).toEqual(4); expect(headerSpy.calls.argsFor(0)[0]).toEqual({query: 'woah', isEmpty: false}); expect(headerSpy.calls.argsFor(0)[1]).toEqual(42); expect(headerSpy.calls.argsFor(0)[2]).toEqual(true); expect(headerSpy.calls.argsFor(0)[3]).toEqual(false); expect(footerSpy).toHaveBeenCalled(); expect(footerSpy.calls.argsFor(0).length).toEqual(4); expect(footerSpy.calls.argsFor(0)[0]).toEqual({query: 'woah', isEmpty: false}); expect(footerSpy.calls.argsFor(0)[1]).toEqual(42); expect(footerSpy.calls.argsFor(0)[2]).toEqual(true); expect(footerSpy.calls.argsFor(0)[3]).toEqual(false); expect(suggestionSpy).toHaveBeenCalled(); for (var i = 0; i < 2; ++i) { expect(suggestionSpy.calls.argsFor(i).length).toEqual(4); expect(suggestionSpy.calls.argsFor(i)[0]).toEqual(suggestions[i]); expect(suggestionSpy.calls.argsFor(i)[1]).toEqual(42); expect(suggestionSpy.calls.argsFor(i)[2]).toEqual(true); expect(suggestionSpy.calls.argsFor(i)[3]).toEqual(false); } }); it('should render header', function() { this.dataset = new Dataset({ source: this.source, templates: { header: '

header

' } }); this.source.and.callFake(fakeGetWithSyncResults); this.dataset.update('woah'); expect(this.dataset.getRoot()).toContainText('header'); }); it('should render footer', function() { this.dataset = new Dataset({ source: this.source, templates: { footer: function(c) { return '

' + c.query + '

'; } } }); this.source.and.callFake(fakeGetWithSyncResults); this.dataset.update('woah'); expect(this.dataset.getRoot()).toContainText('woah'); }); it('should not render header/footer if there is no content', function() { this.dataset = new Dataset({ source: this.source, templates: { header: '

header

', footer: '

footer

' } }); this.source.and.callFake(fakeGetWithSyncEmptyResults); this.dataset.update('woah'); expect(this.dataset.getRoot()).not.toContainText('header'); expect(this.dataset.getRoot()).not.toContainText('footer'); }); it('should not render stale suggestions', function(done) { this.source.and.callFake(fakeGetWithAsyncResults); this.dataset.update('woah'); this.source.and.callFake(fakeGetWithSyncResults); this.dataset.update('nelly'); var that = this; setTimeout(function() { expect(that.dataset.getRoot()).toContainText('one'); expect(that.dataset.getRoot()).toContainText('two'); expect(that.dataset.getRoot()).toContainText('three'); expect(that.dataset.getRoot()).not.toContainText('four'); expect(that.dataset.getRoot()).not.toContainText('five'); done(); }, 100); }); it('should not render suggestions if update was canceled', function(done) { this.source.and.callFake(fakeGetWithAsyncResults); this.dataset.update('woah'); this.dataset.cancel(); var that = this; setTimeout(function() { expect(that.dataset.getRoot()).toBeEmpty(); done(); }, 100); }); it('should trigger rendered after suggestions are rendered', function(done) { var spy; this.dataset.onSync('rendered', spy = jasmine.createSpy()); this.source.and.callFake(fakeGetWithSyncResults); this.dataset.update('woah'); setTimeout(function() { expect(spy.calls.count()).toBe(1); done(); }, 100); }); it('should cache latest query, suggestions and extra render arguments', function() { this.source.and.callFake(fakeGetWithSyncResultsAndExtraParams); this.dataset.update('woah'); expect(this.dataset.cachedQuery).toEqual('woah'); expect(this.dataset.cachedSuggestions).toEqual([ {value: 'one', raw: {value: 'one'}}, {value: 'two', raw: {value: 'two'}}, {value: 'three', raw: {value: 'three'}} ]); expect(this.dataset.cachedRenderExtraArgs).toEqual([42, true, false]); }); it('should retrieve cached results for subsequent identical queries', function() { this.source.and.callFake(fakeGetWithSyncResults); this.dataset.update('woah'); expect(this.source.calls.count()).toBe(1); expect(this.dataset.getRoot()).toContainText('one'); expect(this.dataset.getRoot()).toContainText('two'); expect(this.dataset.getRoot()).toContainText('three'); this.dataset.clear(); this.dataset.update('woah'); expect(this.source.calls.count()).toBe(1); expect(this.dataset.getRoot()).toContainText('one'); expect(this.dataset.getRoot()).toContainText('two'); expect(this.dataset.getRoot()).toContainText('three'); }); it('should not retrieve cached results for subsequent identical queries if cache is disabled', function() { this.dataset = new Dataset({ name: 'test', source: this.source = jasmine.createSpy('source'), cache: false, }); this.source.and.callFake(fakeGetWithSyncResultsAndExtraParams); this.dataset.update('woah'); expect(this.source.calls.count()).toBe(1); expect(this.dataset.getRoot()).toContainText('one'); expect(this.dataset.getRoot()).toContainText('two'); expect(this.dataset.getRoot()).toContainText('three'); this.dataset.clear(); this.dataset.update('woah'); expect(this.source.calls.count()).toBe(2); expect(this.dataset.getRoot()).toContainText('one'); expect(this.dataset.getRoot()).toContainText('two'); expect(this.dataset.getRoot()).toContainText('three'); }); it('should reuse render function extra params for subsequent identical queries', function() { var spy = spyOn(this.dataset, '_render'); this.source.and.callFake(fakeGetWithSyncResultsAndExtraParams); this.dataset.update('woah'); expect(this.source.calls.count()).toBe(1); expect(spy.calls.argsFor(0)).toEqual([ 'woah', [ {value: 'one', raw: {value: 'one'}}, {value: 'two', raw: {value: 'two'}}, {value: 'three', raw: {value: 'three'}} ], 42, true, false]); this.dataset.clear(); this.dataset.update('woah'); expect(this.source.calls.count()).toBe(1); expect(spy.calls.argsFor(1)).toEqual([ 'woah', [ {value: 'one', raw: {value: 'one'}}, {value: 'two', raw: {value: 'two'}}, {value: 'three', raw: {value: 'three'}} ], 42, true, false]); }); it('should not retrieved cached results for subsequent different queries', function() { this.source.and.callFake(fakeGetWithSyncResultsAndExtraParams); this.dataset.update('woah'); expect(this.source.calls.count()).toBe(1); this.dataset.clear(); this.dataset.update('woah 2'); expect(this.source.calls.count()).toBe(2); }); it('should wait before calling the source if debounce is provided', function(done) { var that = this; this.dataset = new Dataset({ source: this.source, debounce: 250 }); this.source.and.callFake(fakeGetWithSyncResultsAndExtraParams); this.dataset.update('woah'); expect(this.source.calls.count()).toBe(0); this.dataset.update('woah 2'); expect(this.source.calls.count()).toBe(0); setTimeout(function() { expect(that.source.calls.count()).toBe(1); done(); }, 500); }); it('should not call the source if update was canceled', function(done) { var that = this; this.dataset = new Dataset({ source: this.source, debounce: 250 }); this.source.and.callFake(fakeGetWithSyncResultsAndExtraParams); this.dataset.update('woah'); expect(this.source.calls.count()).toBe(0); this.dataset.update('woah 2'); expect(this.source.calls.count()).toBe(0); this.dataset.clear(); expect(this.source.calls.count()).toBe(0); setTimeout(function() { expect(that.source.calls.count()).toBe(0); done(); }, 500); }); }); describe('#cacheSuggestions', function() { it('should assign cachedQuery, cachedSuggestions and cachedRenderArgs properties', function() { this.dataset.cacheSuggestions('woah', ['one', 'two'], 42); expect(this.dataset.cachedQuery).toEqual('woah'); expect(this.dataset.cachedSuggestions).toEqual(['one', 'two']); expect(this.dataset.cachedRenderExtraArgs).toEqual(42); }); }); describe('#clearCachedSuggestions', function() { it('should delete cachedQuery and cachedSuggestions properties', function() { this.dataset.cachedQuery = 'one'; this.dataset.cachedSuggestions = ['one', 'two']; this.dataset.cachedRenderExtraArgs = 42; this.dataset.clearCachedSuggestions(); expect(this.dataset.cachedQuery).toBeUndefined(); expect(this.dataset.cachedSuggestions).toBeUndefined(); expect(this.dataset.cachedRenderExtraArgs).toBeUndefined(); }); }); describe('#clear', function() { it('should clear suggestions', function() { this.source.and.callFake(fakeGetWithSyncResults); this.dataset.update('woah'); this.dataset.clear(); expect(this.dataset.getRoot()).toBeEmpty(); }); it('should cancel pending updates', function() { var spy = spyOn(this.dataset, 'cancel'); this.source.and.callFake(fakeGetWithSyncResults); this.dataset.update('woah'); expect(this.dataset.canceled).toBe(false); 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.and.callFake(fakeGetWithSyncResults); this.dataset.update('woah'); expect(this.dataset.isEmpty()).toBe(false); }); }); describe('#destroy', function() { it('should null out the reference to the dataset element', function() { this.dataset.destroy(); expect(this.dataset.$el).toBeNull(); }); it('should clear suggestion cache', function() { var spy = spyOn(this.dataset, 'clearCachedSuggestions'); this.dataset.destroy(); expect(spy).toHaveBeenCalled(); }); }); // helper functions // ---------------- function fakeGetWithSyncResults(query, cb) { cb([ {value: 'one', raw: {value: 'one'}}, {value: 'two', raw: {value: 'two'}}, {value: 'three', raw: {value: 'three'}} ]); } function fakeGetForDisplayFn(query, cb) { cb([{display: '4'}, {display: '5'}, {display: '6'}]); } function fakeGetWithSyncNonArrayResults(query, cb) { cb({}); } function fakeGetWithSyncEmptyResults(query, cb) { cb(); } function fakeGetWithSyncEmptyResultsAndExtraParams(query, cb) { cb([], 42, true, false); } function fakeGetWithSyncResultsAndExtraParams(query, cb) { cb([ {value: 'one', raw: {value: 'one'}}, {value: 'two', raw: {value: 'two'}}, {value: 'three', raw: {value: 'three'}} ], 42, true, false); } function fakeGetWithAsyncResults(query, cb) { setTimeout(function() { cb([ {value: 'four', raw: {value: 'four'}}, {value: 'five', raw: {value: 'five'}} ]); }, 0); } });