Skip to content

Commit

Permalink
fix(source): only trigger requests when query is different
Browse files Browse the repository at this point in the history
Prevent source triggering via dropdown update on Up/Down key presses or anything that does not trigger a query change.

The effect is that at any moment the full results, suggestions is kept in memory.
  • Loading branch information
vvo authored Apr 24, 2017
2 parents e6cd4f0 + a1356fc commit 5c94325
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 11 deletions.
43 changes: 32 additions & 11 deletions src/autocomplete/dataset.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ function Dataset(o) {
);

this.$menu = o.$menu;
this.clearCachedSuggestions();
}

// static methods
Expand Down Expand Up @@ -185,23 +186,42 @@ _.mixin(Dataset.prototype, EventEmitter, {
},

update: function update(query) {
var that = this;

this.query = query;
this.canceled = false;
this.source(query, render);

function render(suggestions) {
function handleSuggestions(suggestions) {
// if the update has been canceled or if the query has changed
// do not render the suggestions as they've become outdated
if (!that.canceled && query === that.query) {
if (!this.canceled && query === this.query) {
// concat all the other arguments that could have been passed
// to the render function, and forward them to _render
var args = [].slice.call(arguments, 1);
args = [query, suggestions].concat(args);
that._render.apply(that, args);
var extraArgs = [].slice.call(arguments, 1);
this.cacheSuggestions(query, suggestions, extraArgs);
this._render.apply(this, [query, suggestions].concat(extraArgs));
}
}

this.query = query;
this.canceled = false;

if (this.shouldFetchFromCache(query)) {
handleSuggestions.apply(this, [this.cachedSuggestions, this.cachedRenderExtraArgs]);
} else {
this.source(query, handleSuggestions.bind(this));
}
},

cacheSuggestions: function cacheSuggestions(query, suggestions, extraArgs) {
this.cachedQuery = query;
this.cachedSuggestions = suggestions;
this.cachedRenderExtraArgs = extraArgs;
},

shouldFetchFromCache: function shouldFetchFromCache(query) {
return this.cachedQuery === query && this.cachedSuggestions && this.cachedSuggestions.length;
},

clearCachedSuggestions: function clearCachedSuggestions() {
delete this.cachedQuery;
delete this.cachedSuggestions;
delete this.cachedRenderExtraArgs;
},

cancel: function cancel() {
Expand All @@ -219,6 +239,7 @@ _.mixin(Dataset.prototype, EventEmitter, {
},

destroy: function destroy() {
this.clearCachedSuggestions();
this.$el = null;
}
});
Expand Down
92 changes: 92 additions & 0 deletions test/unit/dataset_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,92 @@ describe('Dataset', function() {
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 retrieved 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 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).toHaveBeenCalledWith('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).toHaveBeenCalledWith('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);
});
});

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() {
Expand Down Expand Up @@ -319,6 +405,12 @@ describe('Dataset', function() {

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
Expand Down

0 comments on commit 5c94325

Please sign in to comment.