// Copyright 2013 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. goog.provide('goog.ui.ac.CachingMatcherTest'); goog.setTestOnly('goog.ui.ac.CachingMatcherTest'); goog.require('goog.testing.MockControl'); goog.require('goog.testing.jsunit'); goog.require('goog.testing.mockmatchers'); goog.require('goog.ui.ac.CachingMatcher'); ignoreArgument = goog.testing.mockmatchers.ignoreArgument; /** * Fake version of Throttle which only fires when we call permitOne(). * @constructor * @suppress {missingProvide} */ goog.async.Throttle = function(fn, time, self) { this.fn = fn; this.time = time; this.self = self; this.numFires = 0; }; /** @suppress {missingProvide} */ goog.async.Throttle.prototype.fire = function() { this.numFires++; }; /** @suppress {missingProvide} */ goog.async.Throttle.prototype.permitOne = function() { if (this.numFires == 0) { return; } this.fn.call(this.self); this.numFires = 0; }; // Actual tests. var mockControl; var mockMatcher; var mockHandler; var matcher; function setUp() { mockControl = new goog.testing.MockControl(); mockMatcher = { requestMatchingRows: mockControl.createFunctionMock('requestMatchingRows') }; mockHandler = mockControl.createFunctionMock('matchHandler'); matcher = new goog.ui.ac.CachingMatcher(mockMatcher); } function tearDown() { mockControl.$tearDown(); } function testLocalThenRemoteMatch() { // We immediately get the local match. mockHandler('foo', []); mockControl.$replayAll(); matcher.requestMatchingRows('foo', 12, mockHandler); mockControl.$verifyAll(); mockControl.$resetAll(); // Now we run the remote match. mockHandler('foo', ['foo1', 'foo2'], ignoreArgument); mockMatcher.requestMatchingRows('foo', 100, ignoreArgument) .$does(function(token, maxResults, matchHandler) { matchHandler('foo', ['foo1', 'foo2', 'bar3']); }); mockControl.$replayAll(); matcher.throttledTriggerBaseMatch_.permitOne(); mockControl.$verifyAll(); mockControl.$resetAll(); } function testCacheSize() { matcher.setMaxCacheSize(4); // First we populate, but not overflow the cache. mockHandler('foo', []); mockHandler('foo', ['foo111', 'foo222'], ignoreArgument); mockMatcher.requestMatchingRows('foo', 100, ignoreArgument) .$does(function(token, maxResults, matchHandler) { matchHandler('foo', ['foo111', 'foo222', 'bar333']); }); mockControl.$replayAll(); matcher.requestMatchingRows('foo', 12, mockHandler); matcher.throttledTriggerBaseMatch_.permitOne(); mockControl.$verifyAll(); mockControl.$resetAll(); // Now we verify the cache is populated. mockHandler('foo1', ['foo111']); mockControl.$replayAll(); matcher.requestMatchingRows('foo1', 12, mockHandler); mockControl.$verifyAll(); mockControl.$resetAll(); // Now we overflow the cache. Check that the remote results show the first // time we get them back, even though they overflow the cache. mockHandler('foo11', ['foo111']); mockHandler( 'foo11', ['foo111', 'foo112', 'foo113', 'foo114'], ignoreArgument); mockMatcher.requestMatchingRows('foo11', 100, ignoreArgument) .$does(function(token, maxResults, matchHandler) { matchHandler('foo11', ['foo111', 'foo112', 'foo113', 'foo114']); }); mockControl.$replayAll(); matcher.requestMatchingRows('foo11', 12, mockHandler); matcher.throttledTriggerBaseMatch_.permitOne(); mockControl.$verifyAll(); mockControl.$resetAll(); // Now check that the cache is empty. mockHandler('foo11', []); mockControl.$replayAll(); matcher.requestMatchingRows('foo11', 12, mockHandler); mockControl.$verifyAll(); mockControl.$resetAll(); } function testClearCache() { // First we populate the cache. mockHandler('foo', []); mockHandler('foo', ['foo111', 'foo222'], ignoreArgument); mockMatcher.requestMatchingRows('foo', 100, ignoreArgument) .$does(function(token, maxResults, matchHandler) { matchHandler('foo', ['foo111', 'foo222', 'bar333']); }); mockControl.$replayAll(); matcher.requestMatchingRows('foo', 12, mockHandler); matcher.throttledTriggerBaseMatch_.permitOne(); mockControl.$verifyAll(); mockControl.$resetAll(); // Now we verify the cache is populated. mockHandler('foo1', ['foo111']); mockControl.$replayAll(); matcher.requestMatchingRows('foo1', 12, mockHandler); mockControl.$verifyAll(); mockControl.$resetAll(); // Now we clear the cache. matcher.clearCache(); // Now check that the cache is empty. mockHandler('foo11', []); mockControl.$replayAll(); matcher.requestMatchingRows('foo11', 12, mockHandler); mockControl.$verifyAll(); mockControl.$resetAll(); } function testSimilarMatchingDoesntReorderResults() { // Populate the cache. We get two prefix matches. mockHandler('ba', []); mockHandler('ba', ['bar', 'baz', 'bam'], ignoreArgument); mockMatcher.requestMatchingRows('ba', 100, ignoreArgument) .$does(function(token, maxResults, matchHandler) { matchHandler('ba', ['bar', 'baz', 'bam']); }); mockControl.$replayAll(); matcher.requestMatchingRows('ba', 12, mockHandler); matcher.throttledTriggerBaseMatch_.permitOne(); mockControl.$verifyAll(); mockControl.$resetAll(); // The user types another character. The local match gives us two similar // matches, but no prefix matches. The remote match returns a prefix match, // which would normally be ranked above the similar matches, but gets ranked // below the similar matches because the user hasn't typed any more // characters. mockHandler('bad', ['bar', 'baz', 'bam']); mockHandler( 'bad', ['bar', 'baz', 'bam', 'bad', 'badder', 'baddest'], ignoreArgument); mockMatcher.requestMatchingRows('bad', 100, ignoreArgument) .$does(function(token, maxResults, matchHandler) { matchHandler('bad', ['bad', 'badder', 'baddest']); }); mockControl.$replayAll(); matcher.requestMatchingRows('bad', 12, mockHandler); matcher.throttledTriggerBaseMatch_.permitOne(); mockControl.$verifyAll(); mockControl.$resetAll(); // The user types yet another character, which allows the prefix matches to // jump to the top of the list of suggestions. mockHandler('badd', ['badder', 'baddest']); mockControl.$replayAll(); matcher.requestMatchingRows('badd', 12, mockHandler); mockControl.$verifyAll(); mockControl.$resetAll(); } function testSetThrottleTime() { assertEquals(150, matcher.throttledTriggerBaseMatch_.time); matcher.setThrottleTime(234); assertEquals(234, matcher.throttledTriggerBaseMatch_.time); } function testSetBaseMatcherMaxMatches() { mockHandler('foo', []); // Local match mockMatcher.requestMatchingRows('foo', 789, ignoreArgument); mockControl.$replayAll(); matcher.setBaseMatcherMaxMatches(); matcher.requestMatchingRows('foo', 12, mockHandler); } function testSetLocalMatcher() { // Use a local matcher which just sorts all the rows alphabetically. function sillyMatcher(token, maxMatches, rows) { rows = rows.concat([]); rows.sort(); return rows; } mockHandler('foo', []); mockHandler('foo', ['a', 'b', 'c'], ignoreArgument); mockMatcher.requestMatchingRows('foo', 100, ignoreArgument) .$does(function(token, maxResults, matchHandler) { matchHandler('foo', ['b', 'a', 'c']); }); mockControl.$replayAll(); matcher.setLocalMatcher(sillyMatcher); matcher.requestMatchingRows('foo', 12, mockHandler); matcher.throttledTriggerBaseMatch_.permitOne(); mockControl.$verifyAll(); mockControl.$resetAll(); }