angular.js/test/servicesSpec.js
Misko Hevery 47c454a315 change to keydown from keyup; add delayed $updateView
- There was a perceived lag when typing do to the fact that we were
   listening on the keyup event instead of keydown. The issue with
   keydown is that we can not read the value of the input field. To
   solve this we schedule a defer call and perform the model update
   then.

 - To prevent calling $eval on root scope too many times as well as to
   prevent drowning the browser with too many updates we now call the
   $eval only after 25ms and any additional requests get ignored. The
   new update service is called $updateView
2011-01-07 14:39:46 -08:00

833 lines
27 KiB
JavaScript

describe("service", function(){
var scope, $xhrError, $log, mockServices, $browser, $browserXhr, $xhrBulk, $xhr;
beforeEach(function(){
$xhrError = jasmine.createSpy('$xhr.error');
$log = {};
scope = createScope({}, angularService, {
'$xhr.error': $xhrError,
'$log': $log
});
$browser = scope.$service('$browser');
$browserXhr = $browser.xhr;
$xhrBulk = scope.$service('$xhr.bulk');
$xhr = scope.$service('$xhr');
});
afterEach(function(){
dealoc(scope);
});
it("should inject $window", function(){
expect(scope.$service('$window')).toEqual(window);
});
describe("$log", function(){
it('should use console if present', function(){
var logger = "";
function log(){ logger+= 'log;'; }
function warn(){ logger+= 'warn;'; }
function info(){ logger+= 'info;'; }
function error(){ logger+= 'error;'; }
var scope = createScope({}, angularService, {$window: {console:{log:log, warn:warn, info:info, error:error}}, $document:[{cookie:''}]}),
$log = scope.$service('$log');
$log.log();
$log.warn();
$log.info();
$log.error();
expect(logger).toEqual('log;warn;info;error;');
});
it('should use console.log if other not present', function(){
var logger = "";
function log(){ logger+= 'log;'; }
var scope = createScope({}, angularService, {$window: {console:{log:log}}, $document:[{cookie:''}]});
var $log = scope.$service('$log');
$log.log();
$log.warn();
$log.info();
$log.error();
expect(logger).toEqual('log;log;log;log;');
});
it('should use noop if no console', function(){
var scope = createScope({}, angularService, {$window: {}, $document:[{cookie:''}]}),
$log = scope.$service('$log');
$log.log();
$log.warn();
$log.info();
$log.error();
});
describe('Error', function(){
var e, $log, $console, errorArgs;
beforeEach(function(){
e = new Error('');
e.message = undefined;
e.sourceURL = undefined;
e.line = undefined;
e.stack = undefined;
$console = angular.service('$log')({console:{error:function(){
errorArgs = arguments;
}}});
});
it('should pass error if does not have trace', function(){
$console.error('abc', e);
expect(errorArgs).toEqual(['abc', e]);
});
it('should print stack', function(){
e.stack = 'stack';
$console.error('abc', e);
expect(errorArgs).toEqual(['abc', 'stack']);
});
it('should print line', function(){
e.message = 'message';
e.sourceURL = 'sourceURL';
e.line = '123';
$console.error('abc', e);
expect(errorArgs).toEqual(['abc', 'message\nsourceURL:123']);
});
});
});
describe("$exceptionHandler", function(){
it('should log errors', function(){
var error = '';
$log.error = function(m) { error += m; };
scope.$service('$exceptionHandler')('myError');
expect(error).toEqual('myError');
});
});
describe("$location", function(){
var $location;
beforeEach(function() {
$location = scope.$service('$location');
});
it("update should update location object immediately", function() {
var href = 'http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=';
$location.update(href);
expect($location.href).toEqual(href);
expect($location.protocol).toEqual("http");
expect($location.host).toEqual("host");
expect($location.port).toEqual("123");
expect($location.path).toEqual("/p/a/t/h.html");
expect($location.search).toEqual({query:'value'});
expect($location.hash).toEqual('path?key=value&flag&key2=');
expect($location.hashPath).toEqual('path');
expect($location.hashSearch).toEqual({key: 'value', flag: true, key2: ''});
});
it('should update location when browser url changed', function() {
var origUrl = $location.href;
expect(origUrl).toEqual($browser.getUrl());
var newUrl = 'http://somenew/url#foo';
$browser.setUrl(newUrl);
$browser.poll();
expect($location.href).toEqual(newUrl);
});
it('toString() should return actual representation', function() {
var href = 'http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=';
$location.update(href);
expect($location.toString()).toEqual(href);
scope.$eval();
$location.host = 'new';
$location.path = '';
expect($location.toString()).toEqual('http://new:123?query=value#path?key=value&flag&key2=');
});
it('toString() should not update browser', function() {
var url = $browser.getUrl();
$location.update('http://www.angularjs.org');
expect($location.toString()).toEqual('http://www.angularjs.org');
expect($browser.getUrl()).toEqual(url);
});
it('should update browser at the end of $eval', function() {
var url = $browser.getUrl();
$location.update('http://www.angularjs.org/');
$location.update({path: '/a/b'});
expect($location.toString()).toEqual('http://www.angularjs.org/a/b');
expect($browser.getUrl()).toEqual(url);
scope.$eval();
expect($browser.getUrl()).toEqual('http://www.angularjs.org/a/b');
});
it('should update hashPath and hashSearch on hash update', function(){
$location.update('http://server/#path?a=b');
scope.$eval();
$location.update({hash: ''});
expect($location.hashPath).toEqual('');
expect($location.hashSearch).toEqual({});
});
it('should update hash on hashPath or hashSearch update', function() {
$location.update('http://server/#path?a=b');
scope.$eval();
$location.update({hashPath: '', hashSearch: {}});
expect($location.hash).toEqual('');
});
it('should update hashPath and hashSearch on hash property change', function(){
$location.update('http://server/#path?a=b');
scope.$eval();
$location.hash = '';
expect($location.toString()).toEqual('http://server/');
expect($location.hashPath).toEqual('');
expect($location.hashSearch).toEqual({});
});
it('should update hash on hashPath or hashSearch property change', function() {
$location.update('http://server/#path?a=b');
scope.$eval();
$location.hashPath = '';
$location.hashSearch = {};
expect($location.toString()).toEqual('http://server/');
expect($location.hash).toEqual('');
});
it('should update hash before any processing', function(){
scope = compile('<div>');
scope.$location = scope.$service('$location');
var log = '';
scope.$watch('$location.hash', function(){
log += this.$location.hashPath + ';';
});
expect(log).toEqual(';');
log = '';
scope.$location.hash = '/abc';
scope.$eval();
expect(log).toEqual('/abc;');
});
it('udpate() should accept hash object and update only given properties', function() {
$location.update("http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=");
$location.update({host: 'new', port: 24});
expect($location.host).toEqual('new');
expect($location.port).toEqual(24);
expect($location.protocol).toEqual('http');
expect($location.href).toEqual("http://new:24/p/a/t/h.html?query=value#path?key=value&flag&key2=");
});
it('updateHash() should accept one string argument to update path', function() {
$location.updateHash('path');
expect($location.hash).toEqual('path');
expect($location.hashPath).toEqual('path');
});
it('updateHash() should accept one hash argument to update search', function() {
$location.updateHash({a: 'b'});
expect($location.hash).toEqual('?a=b');
expect($location.hashSearch).toEqual({a: 'b'});
});
it('updateHash() should accept path and search both', function() {
$location.updateHash('path', {a: 'b'});
expect($location.hash).toEqual('path?a=b');
expect($location.hashSearch).toEqual({a: 'b'});
expect($location.hashPath).toEqual('path');
});
it('should remove # if hash is empty', function() {
$location.update('http://www.angularjs.org/index.php#');
expect($location.href).toEqual('http://www.angularjs.org/index.php');
});
it('should not change browser\'s url with empty hash', function() {
$browser.setUrl('http://www.angularjs.org/index.php#');
spyOn($browser, 'setUrl');
$browser.poll();
expect($browser.setUrl).not.toHaveBeenCalled();
});
});
describe("$invalidWidgets", function(){
it("should count number of invalid widgets", function(){
scope = compile('<input name="price" ng:required ng:validate="number"></input>');
jqLite(document.body).append(scope.$element);
scope.$init();
var $invalidWidgets = scope.$service('$invalidWidgets');
expect($invalidWidgets.length).toEqual(1);
scope.price = 123;
scope.$eval();
expect($invalidWidgets.length).toEqual(0);
scope.$element.remove();
scope.price = 'abc';
scope.$eval();
expect($invalidWidgets.length).toEqual(0);
jqLite(document.body).append(scope.$element);
scope.price = 'abcd'; //force revalidation, maybe this should be done automatically?
scope.$eval();
expect($invalidWidgets.length).toEqual(1);
jqLite(document.body).html('');
scope.$eval();
expect($invalidWidgets.length).toEqual(0);
});
});
describe("$route", function(){
it('should route and fire change event', function(){
var log = '',
$location, $route;
function BookChapter() {
this.log = '<init>';
}
scope = compile('<div></div>').$init();
$location = scope.$service('$location');
$route = scope.$service('$route');
$route.when('/Book/:book/Chapter/:chapter', {controller: BookChapter, template:'Chapter.html'});
$route.when('/Blank');
$route.onChange(function(){
log += 'onChange();';
});
$location.update('http://server#/Book/Moby/Chapter/Intro?p=123');
scope.$eval();
expect(log).toEqual('onChange();');
expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', p:'123'});
expect($route.current.scope.log).toEqual('<init>');
var lastId = $route.current.scope.$id;
log = '';
$location.update('http://server#/Blank?ignore');
scope.$eval();
expect(log).toEqual('onChange();');
expect($route.current.params).toEqual({ignore:true});
expect($route.current.scope.$id).not.toEqual(lastId);
log = '';
$location.update('http://server#/NONE');
scope.$eval();
expect(log).toEqual('onChange();');
expect($route.current).toEqual(null);
$route.when('/NONE', {template:'instant update'});
scope.$eval();
expect($route.current.template).toEqual('instant update');
});
});
describe('$defer', function() {
var $defer, $exceptionHandler;
beforeEach(function(){
scope = createScope({}, angularService, {
'$exceptionHandler': jasmine.createSpy('$exceptionHandler')
});
$browser = scope.$service('$browser');
$defer = scope.$service('$defer');
$exceptionHandler = scope.$service('$exceptionHandler');
});
it('should delegate functions to $browser.defer', function() {
var counter = 0;
$defer(function() { counter++; });
expect(counter).toBe(0);
$browser.defer.flush();
expect(counter).toBe(1);
$browser.defer.flush(); //does nothing
expect(counter).toBe(1);
expect($exceptionHandler).not.toHaveBeenCalled();
});
it('should delegate exception to the $exceptionHandler service', function() {
$defer(function() {throw "Test Error";});
expect($exceptionHandler).not.toHaveBeenCalled();
$browser.defer.flush();
expect($exceptionHandler).toHaveBeenCalledWith("Test Error");
});
it('should call eval after each callback is executed', function() {
var eval = this.spyOn(scope, '$eval').andCallThrough();
$defer(function() {});
expect(eval).wasNotCalled();
$browser.defer.flush();
expect(eval).wasCalled();
eval.reset(); //reset the spy;
$defer(function() {});
$defer(function() {});
$browser.defer.flush();
expect(eval.callCount).toBe(2);
});
it('should call eval even if an exception is thrown in callback', function() {
var eval = this.spyOn(scope, '$eval').andCallThrough();
$defer(function() {throw "Test Error";});
expect(eval).wasNotCalled();
$browser.defer.flush();
expect(eval).wasCalled();
});
});
describe('$xhr', function(){
var log;
function callback(code, response) {
expect(code).toEqual(200);
log = log + toJson(response) + ';';
}
beforeEach(function(){
log = '';
});
it('should forward the request to $browser and decode JSON', function(){
$browserXhr.expectGET('/reqGET').respond('first');
$browserXhr.expectGET('/reqGETjson').respond('["second"]');
$browserXhr.expectPOST('/reqPOST', {post:'data'}).respond('third');
$xhr('GET', '/reqGET', null, callback);
$xhr('GET', '/reqGETjson', null, callback);
$xhr('POST', '/reqPOST', {post:'data'}, callback);
$browserXhr.flush();
expect(log).toEqual('"third";["second"];"first";');
});
it('should handle non 200 status codes by forwarding to error handler', function(){
$browserXhr.expectPOST('/req', 'MyData').respond(500, 'MyError');
$xhr('POST', '/req', 'MyData', callback);
$browserXhr.flush();
var cb = $xhrError.mostRecentCall.args[0].callback;
expect(typeof cb).toEqual($function);
expect($xhrError).wasCalledWith(
{url:'/req', method:'POST', data:'MyData', callback:cb},
{status:500, body:'MyError'});
});
it('should handle exceptions in callback', function(){
$log.error = jasmine.createSpy('$log.error');
$browserXhr.expectGET('/reqGET').respond('first');
$xhr('GET', '/reqGET', null, function(){ throw "MyException"; });
$browserXhr.flush();
expect($log.error).wasCalledWith("MyException");
});
describe('bulk', function(){
it('should collect requests', function(){
$xhrBulk.urls["/"] = {match:/.*/};
$xhrBulk('GET', '/req1', null, callback);
$xhrBulk('POST', '/req2', {post:'data'}, callback);
$browserXhr.expectPOST('/', {
requests:[{method:'GET', url:'/req1', data: null},
{method:'POST', url:'/req2', data:{post:'data'} }]
}).respond([
{status:200, response:'first'},
{status:200, response:'second'}
]);
$xhrBulk.flush(function(){ log += 'DONE';});
$browserXhr.flush();
expect(log).toEqual('"first";"second";DONE');
});
it('should handle non 200 status code by forwarding to error handler', function(){
$xhrBulk.urls['/'] = {match:/.*/};
$xhrBulk('GET', '/req1', null, callback);
$xhrBulk('POST', '/req2', {post:'data'}, callback);
$browserXhr.expectPOST('/', {
requests:[{method:'GET', url:'/req1', data: null},
{method:'POST', url:'/req2', data:{post:'data'} }]
}).respond([
{status:404, response:'NotFound'},
{status:200, response:'second'}
]);
$xhrBulk.flush(function(){ log += 'DONE';});
$browserXhr.flush();
expect($xhrError).wasCalled();
var cb = $xhrError.mostRecentCall.args[0].callback;
expect(typeof cb).toEqual($function);
expect($xhrError).wasCalledWith(
{url:'/req1', method:'GET', data:null, callback:cb},
{status:404, response:'NotFound'});
expect(log).toEqual('"second";DONE');
});
});
describe('cache', function(){
var cache;
beforeEach(function(){ cache = scope.$service('$xhr.cache'); });
it('should cache requests', function(){
$browserXhr.expectGET('/url').respond('first');
cache('GET', '/url', null, callback);
$browserXhr.flush();
$browserXhr.expectGET('/url').respond('ERROR');
cache('GET', '/url', null, callback);
$browser.defer.flush();
$browserXhr.flush();
expect(log).toEqual('"first";"first";');
cache('GET', '/url', null, callback, false);
$browser.defer.flush();
expect(log).toEqual('"first";"first";"first";');
});
it('should first return cache request, then return server request', function(){
$browserXhr.expectGET('/url').respond('first');
cache('GET', '/url', null, callback, true);
$browserXhr.flush();
$browserXhr.expectGET('/url').respond('ERROR');
cache('GET', '/url', null, callback, true);
$browser.defer.flush();
expect(log).toEqual('"first";"first";');
$browserXhr.flush();
expect(log).toEqual('"first";"first";"ERROR";');
});
it('should serve requests from cache', function(){
cache.data.url = {value:'123'};
cache('GET', 'url', null, callback);
$browser.defer.flush();
expect(log).toEqual('"123";');
cache('GET', 'url', null, callback, false);
$browser.defer.flush();
expect(log).toEqual('"123";"123";');
});
it('should keep track of in flight requests and request only once', function(){
scope.$service('$xhr.bulk').urls['/bulk'] = {
match:function(url){
return url == '/url';
}
};
$browserXhr.expectPOST('/bulk', {
requests:[{method:'GET', url:'/url', data: null}]
}).respond([
{status:200, response:'123'}
]);
cache('GET', '/url', null, callback);
cache('GET', '/url', null, callback);
cache.delegate.flush();
$browserXhr.flush();
expect(log).toEqual('"123";"123";');
});
it('should clear cache on non GET', function(){
$browserXhr.expectPOST('abc', {}).respond({});
cache.data.url = {value:123};
cache('POST', 'abc', {});
expect(cache.data.url).toBeUndefined();
});
it('should call callback asynchronously for both cache hit and cache miss', function() {
$browserXhr.expectGET('/url').respond('+');
cache('GET', '/url', null, callback);
expect(log).toEqual(''); //callback hasn't executed
$browserXhr.flush();
expect(log).toEqual('"+";'); //callback has executed
cache('GET', '/url', null, callback);
expect(log).toEqual('"+";'); //callback hasn't executed
$browser.defer.flush();
expect(log).toEqual('"+";"+";'); //callback has executed
});
it('should call eval after callbacks for both cache hit and cache miss execute', function() {
var eval = this.spyOn(scope, '$eval').andCallThrough();
$browserXhr.expectGET('/url').respond('+');
cache('GET', '/url', null, callback);
expect(eval).wasNotCalled();
$browserXhr.flush();
expect(eval).wasCalled();
eval.reset(); //reset the spy
cache('GET', '/url', null, callback);
expect(eval).wasNotCalled();
$browser.defer.flush();
expect(eval).wasCalled();
});
});
});
describe('$cookies', function() {
var scope, $browser;
beforeEach(function() {
$browser = new MockBrowser();
$browser.cookieHash['preexisting'] = 'oldCookie';
scope = createScope(null, angularService, {$browser: $browser});
scope.$cookies = scope.$service('$cookies');
});
it('should provide access to existing cookies via object properties and keep them in sync',
function(){
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'});
// access internal cookie storage of the browser mock directly to simulate behavior of
// document.cookie
$browser.cookieHash['brandNew'] = 'cookie';
$browser.poll();
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie', 'brandNew':'cookie'});
$browser.cookieHash['brandNew'] = 'cookie2';
$browser.poll();
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie', 'brandNew':'cookie2'});
delete $browser.cookieHash['brandNew'];
$browser.poll();
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'});
});
it('should create or update a cookie when a value is assigned to a property', function() {
scope.$cookies.oatmealCookie = 'nom nom';
scope.$eval();
expect($browser.cookies()).
toEqual({'preexisting': 'oldCookie', 'oatmealCookie':'nom nom'});
scope.$cookies.oatmealCookie = 'gone';
scope.$eval();
expect($browser.cookies()).
toEqual({'preexisting': 'oldCookie', 'oatmealCookie': 'gone'});
});
it('should ignore non-string values when asked to create a cookie', function() {
scope.$cookies.nonString = [1, 2, 3];
scope.$eval();
expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'});
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'});
});
it('should drop any null or undefined properties', function() {
scope.$cookies.nullVal = null;
scope.$cookies.undefVal = undefined;
scope.$eval();
expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'});
});
it('should remove a cookie when a $cookies property is deleted', function() {
scope.$cookies.oatmealCookie = 'nom nom';
scope.$eval();
$browser.poll();
expect($browser.cookies()).
toEqual({'preexisting': 'oldCookie', 'oatmealCookie':'nom nom'});
delete scope.$cookies.oatmealCookie;
scope.$eval();
expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'});
});
it('should drop or reset cookies that browser refused to store', function() {
var i, longVal;
for (i=0; i<5000; i++) {
longVal += '*';
}
//drop if no previous value
scope.$cookies.longCookie = longVal;
scope.$eval();
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'});
//reset if previous value existed
scope.$cookies.longCookie = 'shortVal';
scope.$eval();
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie', 'longCookie': 'shortVal'});
scope.$cookies.longCookie = longVal;
scope.$eval();
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie', 'longCookie': 'shortVal'});
});
});
describe('$cookieStore', function() {
it('should serialize objects to json', function() {
scope.$service('$cookieStore').put('objectCookie', {id: 123, name: 'blah'});
scope.$eval(); //force eval in test
expect($browser.cookies()).toEqual({'objectCookie': '{"id":123,"name":"blah"}'});
});
it('should deserialize json to object', function() {
$browser.cookies('objectCookie', '{"id":123,"name":"blah"}');
$browser.poll();
expect(scope.$service('$cookieStore').get('objectCookie')).toEqual({id: 123, name: 'blah'});
});
it('should delete objects from the store when remove is called', function() {
scope.$service('$cookieStore').put('gonner', { "I'll":"Be Back"});
scope.$eval(); //force eval in test
expect($browser.cookies()).toEqual({'gonner': '{"I\'ll":"Be Back"}'});
});
});
describe('URL_MATCH', function() {
it('should parse basic url', function() {
var match = URL_MATCH.exec('http://www.angularjs.org/path?search#hash?x=x');
expect(match[1]).toEqual('http');
expect(match[3]).toEqual('www.angularjs.org');
expect(match[6]).toEqual('/path');
expect(match[8]).toEqual('search');
expect(match[10]).toEqual('hash?x=x');
});
it('should parse file://', function(){
var match = URL_MATCH.exec('file:///Users/Shared/misko/work/angular.js/scenario/widgets.html');
expect(match[1]).toEqual('file');
expect(match[3]).toEqual('');
expect(match[5]).toBeFalsy();
expect(match[6]).toEqual('/Users/Shared/misko/work/angular.js/scenario/widgets.html');
expect(match[8]).toBeFalsy();
});
it('should parse url with "-" in host', function(){
var match = URL_MATCH.exec('http://a-b1.c-d.09/path');
expect(match[1]).toEqual('http');
expect(match[3]).toEqual('a-b1.c-d.09');
expect(match[5]).toBeFalsy();
expect(match[6]).toEqual('/path');
expect(match[8]).toBeFalsy();
});
it('should parse host without "/" at the end', function() {
var match = URL_MATCH.exec('http://host.org');
expect(match[3]).toEqual('host.org');
match = URL_MATCH.exec('http://host.org#');
expect(match[3]).toEqual('host.org');
match = URL_MATCH.exec('http://host.org?');
expect(match[3]).toEqual('host.org');
});
it('should match with just "/" path', function() {
var match = URL_MATCH.exec('http://server/#?book=moby');
expect(match[10]).toEqual('?book=moby');
});
});
describe('$updateView', function(){
var scope, browser, evalCount, $updateView;
beforeEach(function(){
browser = new MockBrowser();
// Pretend that you are real Browser so that we see the delays
browser.isMock = false;
browser.defer = jasmine.createSpy('defer');
scope = angular.scope(null, null, {$browser:browser});
$updateView = scope.$service('$updateView');
scope.$onEval(function(){ evalCount++; });
evalCount = 0;
});
it('should eval root scope after a delay', function(){
$updateView();
expect(evalCount).toEqual(0);
expect(browser.defer).toHaveBeenCalled();
expect(browser.defer.mostRecentCall.args[1]).toEqual(25);
browser.defer.mostRecentCall.args[0]();
expect(evalCount).toEqual(1);
});
it('should allow changing of delay time', function(){
var oldValue = angular.service('$updateView').delay;
angular.service('$updateView').delay = 50;
$updateView();
expect(evalCount).toEqual(0);
expect(browser.defer).toHaveBeenCalled();
expect(browser.defer.mostRecentCall.args[1]).toEqual(50);
angular.service('$updateView').delay = oldValue;
});
it('should ignore multiple requests for update', function(){
$updateView();
$updateView();
expect(evalCount).toEqual(0);
expect(browser.defer).toHaveBeenCalled();
expect(browser.defer.callCount).toEqual(1);
browser.defer.mostRecentCall.args[0]();
expect(evalCount).toEqual(1);
});
it('should update immediatelly in test/mock mode', function(){
scope = angular.scope();
scope.$onEval(function(){ evalCount++; });
expect(evalCount).toEqual(0);
scope.$service('$updateView')();
expect(evalCount).toEqual(1);
});
});
});