diff --git a/karma-jqlite.conf.js b/karma-jqlite.conf.js index 6cb9811428cc..3eee66ccd552 100644 --- a/karma-jqlite.conf.js +++ b/karma-jqlite.conf.js @@ -7,6 +7,7 @@ autoWatch = true; logLevel = LOG_INFO; logColors = true; browsers = ['Chrome']; +urlRoot = "/karma/tests/" junitReporter = { outputFile: 'test_out/jqlite.xml', diff --git a/karma-jquery.conf.js b/karma-jquery.conf.js index 7b527a15cbb9..bafcd10c2832 100644 --- a/karma-jquery.conf.js +++ b/karma-jquery.conf.js @@ -7,6 +7,7 @@ autoWatch = true; logLevel = LOG_INFO; logColors = true; browsers = ['Chrome']; +urlRoot = "/karma/tests/" junitReporter = { outputFile: 'test_out/jquery.xml', diff --git a/karma-modules.conf.js b/karma-modules.conf.js index 9fea7d5853ef..a205a56464a2 100644 --- a/karma-modules.conf.js +++ b/karma-modules.conf.js @@ -7,6 +7,7 @@ autoWatch = true; logLevel = LOG_INFO; logColors = true; browsers = ['Chrome']; +urlRoot = "/karma/tests/" junitReporter = { outputFile: 'test_out/modules.xml', diff --git a/src/ng/browser.js b/src/ng/browser.js index 7a32993f8b2d..efef5886c295 100644 --- a/src/ng/browser.js +++ b/src/ng/browser.js @@ -245,7 +245,6 @@ function Browser(window, document, $log, $sniffer) { ////////////////////////////////////////////////////////////// var lastCookies = {}; var lastCookieString = ''; - var cookiePath = self.baseHref(); /** * @name ng.$browser#cookies @@ -253,7 +252,12 @@ function Browser(window, document, $log, $sniffer) { * * @param {string=} name Cookie name * @param {string=} value Cookie value - * + * @param {object} options Object allowing additional control on how a cookie is created + * - **expires** - `{date}` - date for cookie to expire. + * If not passed, the object is not a date or the date is in the past, the cookie expiration + * date will not be set, so that the cookie will expire at the end of the session. + * - **path** - `{string}` - the path to set the cookie on. + * Defaults to {@link #baseHref baseHref} * @description * The cookies method provides a 'private' low level access to browser cookies. * It is not meant to be used directly, use the $cookie service instead. @@ -262,55 +266,115 @@ function Browser(window, document, $log, $sniffer) { * * * @returns {Object} Hash of all cookies (if called without any parameter) - */ - self.cookies = function(name, value) { - var cookieLength, cookieArray, cookie, i, index; - - if (name) { - if (value === undefined) { - rawDocument.cookie = escape(name) + "=;path=" + cookiePath + ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; - } else { - if (isString(value)) { - cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + ';path=' + cookiePath).length + 1; - - // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: - // - 300 cookies - // - 20 cookies per unique domain - // - 4096 bytes per cookie - if (cookieLength > 4096) { - $log.warn("Cookie '"+ name +"' possibly not set or overflowed because it was too large ("+ - cookieLength + " > 4096 bytes)!"); - } - } - } - } else { - if (rawDocument.cookie !== lastCookieString) { - lastCookieString = rawDocument.cookie; - cookieArray = lastCookieString.split("; "); - lastCookies = {}; - - for (i = 0; i < cookieArray.length; i++) { - cookie = cookieArray[i]; - index = cookie.indexOf('='); - if (index > 0) { //ignore nameless cookies - var name = unescape(cookie.substring(0, index)); - // the first value that is seen for a cookie is the most - // specific one. values for the same cookie name that - // follow are for less specific paths. - if (lastCookies[name] === undefined) { - lastCookies[name] = unescape(cookie.substring(index + 1)); - } - } - } - } - return lastCookies; - } - }; - + */ + self.cookies = (function() { + var cookies = function(name, value, options) { + if (!angular.isDefined(options) || options == null) options = {}; + if (name) { + if (value === undefined) { + deleteCookie(name, options); + } else { + if (isString(value)) { + setCookie(name, value, options); + } + } + } else { + return getAllCookies(); + } + } + + var defaultPath = self.baseHref; + + function deleteCookie(name, options) { + if (options.path) { + rawDocument.cookie = escape(name) + "=;path=" + options.path + ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; + } else { + rawDocument.cookie = escape(name) + "=;path=" + defaultPath() + ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; + var path = location.pathname; + //delete cookies under different paths + while (rawDocument.cookie.indexOf(name + "=") >= 0 && angular.isDefined(path)) { + rawDocument.cookie = escape(name) + "=;path=" + path + ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; + if (path == '') break; + path = path.replace(/\/$|[^\/]*[^\/]$/, ""); + } + } + } + function setCookie(name, value, options) { + var newCookie = escape(name) + '=' + escape(value) + + resolvePathString(name, options) + + resolveExpirationString(name, options); + + rawDocument.cookie = newCookie; + alertOnLength(name, newCookie); + } + function resolvePathString(name, options) { + var path = defaultPath(); + if (options.path) { + if (angular.isString(options.path) && location.pathname.indexOf(options.path) >= 0) { + path = options.path; + } else { + $log.warn("Cookie '" + name + "' was not set with requested path '" + options.path + + "' since path is not a String or not partial to window.location, which is " + location.pathname) + } + } + return ";path=" + path; + } + function resolveExpirationString(name, options) { + if (options.expires) { + if (angular.isDate(options.expires) && options.expires > new Date()) { + return ";expires=" + options.expires.toUTCString(); + } else { + $log.warn("Cookie '" + name + "' was not set with requested expiration '" + options.expires + + "' since date is in the past or object is not a date") + } + } + return ""; + } + function alertOnLength(name, cookieString) { + var cookieLength = (cookieString).length + 1; + + // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: + // - 300 cookies + // - 20 cookies per unique domain + // - 4096 bytes per cookie + if (cookieLength > 4096) { + $log.warn("Cookie '" + name + "' possibly not set or overflowed because it was too large (" + + cookieLength + " > 4096 bytes)!"); + } + } + function getAllCookies() { + if (rawDocument.cookie !== lastCookieString) { + lastCookieString = rawDocument.cookie; + var cookieArray = lastCookieString.split("; "); + lastCookies = {}; + var cookie, i, index; + for (i = 0; i < cookieArray.length; i++) { + cookie = cookieArray[i]; + index = cookie.indexOf('='); + if (index > 0) { + var name = unescape(cookie.substring(0, index)); + // the first value that is seen for a cookie is the most + // specific one. values for the same cookie name that + // follow are for less specific paths. + if (lastCookies[name] === undefined) { + lastCookies[name] = unescape(cookie.substring(index + 1)); + } + } else if (index <0) { + lastCookies[unescape(cookie)] = ''; // IE saves cookie= as cookie, which require special care + } //ignore nameless cookies where index ==0 + } + } + return lastCookies; + } + return cookies; + })(); /** * @name ng.$browser#defer diff --git a/src/ngCookies/cookies.js b/src/ngCookies/cookies.js index 683557077688..b7d64d9de819 100644 --- a/src/ngCookies/cookies.js +++ b/src/ngCookies/cookies.js @@ -3,8 +3,9 @@ /** * @ngdoc overview * @name ngCookies - */ - + */ + +var $cookieOptionsHash = {}; angular.module('ngCookies', ['ng']). /** @@ -13,26 +14,64 @@ angular.module('ngCookies', ['ng']). * @requires $browser * * @description - * Provides read/write access to browser's cookies. + * The '$cookies' service exposes the browser's cookies as a simple Object (I.E. dictionary/hashmap). + * Allowing read/write operations on cookies to work similar to changing an object's variables + * where each variable is a different cookie. * - * Only a simple Object is exposed and by adding or removing properties to/from - * this object, new cookies are created/deleted at the end of current $eval. + * The $cookies object acts as a cache for the actual cookies, which flushes at the end of the current $eval. + * That is, if you change a value of a cookie in $cookie, it will be written to the browser at the end of the $eval. * - * @example - - - - - + + + * # Cookie defaults + * when using the $cookie service, any cookies added/updated will be created with the following options: + * - if a base tag exists, then the path will be set to the base tag's href path. otherwise the path will be /. + * - no expiration date is passed, so all cookies set using $cookies will expire at end of session. * + * # Interoperability Considerations + * It is not advised to access or modify the browsers cookies directly when using the $cookies service + * on the same cookie, since the $cookie service caches changes and only flushes them at the end of the current $eval, + * interdependency is not predictable. + * Also note that changes made directly to the cookie will only appear in the $cookie cache after 100ms and not at + * the end of the current $eval (as opposed to changes made through the $cookie service) */ - factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) { + factory('$cookies', ['$rootScope', '$browser', function($rootScope, $browser) { var cookies = {}, lastCookies = {}, lastBrowserCookies, @@ -73,7 +112,9 @@ angular.module('ngCookies', ['ng']). //delete any cookies deleted in $cookies for (name in lastCookies) { if (isUndefined(cookies[name])) { - $browser.cookies(name, undefined); + updated = true; + $browser.cookies(name, undefined,$cookieOptionsHash[name]); + delete $cookieOptionsHash[name]; } } @@ -84,10 +125,12 @@ angular.module('ngCookies', ['ng']). if (angular.isDefined(lastCookies[name])) { cookies[name] = lastCookies[name]; } else { + //delete cookie who value was put as undefined, $browser already updated delete cookies[name]; } - } else if (value !== lastCookies[name]) { - $browser.cookies(name, value); + } else if (value !== lastCookies[name] || $cookieOptionsHash[name]) { + $browser.cookies(name, value,$cookieOptionsHash[name]); + delete $cookieOptionsHash[name]; updated = true; } } @@ -108,6 +151,7 @@ angular.module('ngCookies', ['ng']). updated = true; } } + copy(browserCookies, lastCookies); } } }]). @@ -122,7 +166,49 @@ angular.module('ngCookies', ['ng']). * Provides a key-value (string-object) storage, that is backed by session cookies. * Objects put or retrieved from this storage are automatically serialized or * deserialized by angular's toJson/fromJson. - * @example + * + * The service uses the $cookies service internally, so anything that applies to that service, applies here + * unless otherwise mentioned. + * Unlike the $cookies service, the $cookieStore service allows custom Path and expirationDate settions for cookies. + * @example + + +
+ + + + + + + + + + +
User info
first name:
last name:
+ Click to reset cookie in : + milliseconds. +
+
+ + function CookieCtrl($scope,$cookieStore,$cookies) { + $scope.details = $cookieStore.get("details") || {firstName:"hugo",lastName:"gogo"}; + $scope.cookieTimeout = 1000; + $scope.$watch("details",function(newValue){ + $cookieStore.put("details",newValue); + },true); + $scope.setExpire = function() { + $cookieStore.put("details",$cookieStore.get("details"),{expires: calcDate($scope)}); + setTimeout(function(){ + $scope.details = $cookieStore.get("details") || {firstName:"hugo",lastName:"gogo"}; + $scope.$apply() + }, +$scope.cookieTimeout + 50); + } + } + function calcDate($scope) { + return new Date(new Date().getTime() + +$scope.cookieTimeout); + } + +
*/ factory('$cookieStore', ['$cookies', function($cookies) { @@ -153,8 +239,15 @@ angular.module('ngCookies', ['ng']). * * @param {string} key Id for the `value`. * @param {Object} value Value to be stored. + * @param {object} options Options for the cookie stored, if not passed uses default. + * - **expires** - `{date}` - date for cookie to expire. + * If not passed, the object is not a date or the date is in the past, the cookie expiration + * date will not be set, so that the cookie will expire at the end of the session. + * - **path** - `{string}` - the path to set the cookie on. + * Defaults to / (or base tag's href attribute, if base tag exists) */ - put: function(key, value) { + put: function(key, value,options) { + $cookieOptionsHash[key] = options; $cookies[key] = angular.toJson(value); }, @@ -167,8 +260,12 @@ angular.module('ngCookies', ['ng']). * Remove given cookie * * @param {string} key Id of the key-value pair to delete. + * @param {object} options Options for the cookie to be deleted. + * - **path** - `{string}` - the path to delete the cookie + * If not passed, all cookies that matche the key are deleted. */ - remove: function(key) { + remove: function(key,options) { + $cookieOptionsHash[key] = options; delete $cookies[key]; } }; diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index a35e0f2fd473..67ba3506b12e 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -63,6 +63,7 @@ angular.mock.$Browser = function() { }; self.cookieHash = {}; + self.cookieOptionHash = {}; self.lastCookieHash = {}; self.deferredFns = []; self.deferredNextId = 0; @@ -160,13 +161,15 @@ angular.mock.$Browser.prototype = { return this.$$url; }, - cookies: function(name, value) { + cookies: function(name, value,options) { if (name) { if (value == undefined) { + if (options) this.cookieOptionHash[name] = options; delete this.cookieHash[name]; } else { if (angular.isString(value) && //strings only value.length <= 4096) { //strict cookie storage limits + if (options) this.cookieOptionHash[name] = options; this.cookieHash[name] = value; } } diff --git a/test/ng/browserSpecs.js b/test/ng/browserSpecs.js old mode 100755 new mode 100644 index 3ec78e615341..8c980ede4542 --- a/test/ng/browserSpecs.js +++ b/test/ng/browserSpecs.js @@ -37,7 +37,8 @@ function MockWindow() { this.location = { href: 'http://server', - replace: noop + replace: noop, + pathname: window.location.pathname }; this.history = { @@ -49,7 +50,19 @@ function MockWindow() { function MockDocument() { var self = this; - this[0] = window.document + //IE8 allows defineProperty only on dom elements + var fakeDocument = document.createElement('br'); + Object.defineProperty(fakeDocument,"cookie",{ + set: function(value) { + self.lastCookieSet = value; + window.document.cookie = value; + }, + get: function() { + return window.document.cookie; + } + }); + + this[0] = fakeDocument this.basePath = '/'; this.find = function(name) { @@ -67,524 +80,662 @@ function MockDocument() { throw new Error(name); } } -} - -describe('browser', function() { - - var browser, fakeWindow, fakeDocument, logs, scripts, removedScripts, sniffer; - - beforeEach(function() { - scripts = []; - removedScripts = []; - sniffer = {history: true, hashchange: true}; - fakeWindow = new MockWindow(); - fakeDocument = new MockDocument(); - - var fakeBody = [{appendChild: function(node){scripts.push(node);}, - removeChild: function(node){removedScripts.push(node);}}]; - - logs = {log:[], warn:[], info:[], error:[]}; - - var fakeLog = {log: function() { logs.log.push(slice.call(arguments)); }, - warn: function() { logs.warn.push(slice.call(arguments)); }, - info: function() { logs.info.push(slice.call(arguments)); }, - error: function() { logs.error.push(slice.call(arguments)); }}; - - browser = new Browser(fakeWindow, fakeDocument, fakeLog, sniffer); - }); - - it('should contain cookie cruncher', function() { - expect(browser.cookies).toBeDefined(); - }); - - describe('outstading requests', function() { - it('should process callbacks immedietly with no outstanding requests', function() { - var callback = jasmine.createSpy('callback'); - browser.notifyWhenNoOutstandingRequests(callback); - expect(callback).toHaveBeenCalled(); - }); - }); - - - describe('defer', function() { - it('should execute fn asynchroniously via setTimeout', function() { - var callback = jasmine.createSpy('deferred'); - - browser.defer(callback); - expect(callback).not.toHaveBeenCalled(); - - fakeWindow.setTimeout.flush(); - expect(callback).toHaveBeenCalledOnce(); - }); - - - it('should update outstandingRequests counter', function() { - var callback = jasmine.createSpy('deferred'); - - browser.defer(callback); - expect(callback).not.toHaveBeenCalled(); - - fakeWindow.setTimeout.flush(); - expect(callback).toHaveBeenCalledOnce(); +} + +describe('browser', function() { + var browser, fakeWindow, fakeDocument, logs, scripts, removedScripts, sniffer; + + beforeEach(function() { + scripts = []; + removedScripts = []; + sniffer = { history: true, hashchange: true }; + fakeWindow = new MockWindow(); + fakeDocument = new MockDocument(); + + var fakeBody = [{ appendChild: function(node) { scripts.push(node); }, + removeChild: function(node) { removedScripts.push(node); } + }]; + + logs = { log: [], warn: [], info: [], error: [] }; + + var fakeLog = { log: function() { logs.log.push(slice.call(arguments)); }, + warn: function() { logs.warn.push(slice.call(arguments)); }, + info: function() { logs.info.push(slice.call(arguments)); }, + error: function() { logs.error.push(slice.call(arguments)); } + }; + + browser = new Browser(fakeWindow, fakeDocument, fakeLog, sniffer); + }); + + it('should contain cookie cruncher', function() { + expect(browser.cookies).toBeDefined(); + }); + + describe('outstading requests', function() { + it('should process callbacks immedietly with no outstanding requests', function() { + var callback = jasmine.createSpy('callback'); + browser.notifyWhenNoOutstandingRequests(callback); + expect(callback).toHaveBeenCalled(); + }); + }); + + + describe('defer', function() { + it('should execute fn asynchroniously via setTimeout', function() { + var callback = jasmine.createSpy('deferred'); + + browser.defer(callback); + expect(callback).not.toHaveBeenCalled(); + + fakeWindow.setTimeout.flush(); + expect(callback).toHaveBeenCalledOnce(); + }); + + + it('should update outstandingRequests counter', function() { + var callback = jasmine.createSpy('deferred'); + + browser.defer(callback); + expect(callback).not.toHaveBeenCalled(); + + fakeWindow.setTimeout.flush(); + expect(callback).toHaveBeenCalledOnce(); + }); + + + it('should return unique deferId', function() { + var deferId1 = browser.defer(noop), + deferId2 = browser.defer(noop); + + expect(deferId1).toBeDefined(); + expect(deferId2).toBeDefined(); + expect(deferId1).not.toEqual(deferId2); + }); + + + describe('cancel', function() { + it('should allow tasks to be canceled with returned deferId', function() { + var log = [], + deferId1 = browser.defer(function() { log.push('cancel me'); }), + deferId2 = browser.defer(function() { log.push('ok'); }), + deferId3 = browser.defer(function() { log.push('cancel me, now!'); }); + + expect(log).toEqual([]); + expect(browser.defer.cancel(deferId1)).toBe(true); + expect(browser.defer.cancel(deferId3)).toBe(true); + fakeWindow.setTimeout.flush(); + expect(log).toEqual(['ok']); + expect(browser.defer.cancel(deferId2)).toBe(false); + }); + }); + }); + + + describe('cookies', function() { + function deleteAllCookies() { + var cookies = document.cookie.split(";"); + for (var i = 0; i < cookies.length; i++) { + var cookie = cookies[i]; + var path = location.pathname; + var eqPos = cookie.indexOf("="); + var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie; + //delete all possible paths + while (path && path != '') { + document.cookie = name + "=;path=" + path + ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; + path = path.replace(/\/$|[^\/]*[^\/]$/, ""); + } + document.cookie = name + "=;path=;expires=Thu, 01 Jan 1970 00:00:00 GMT"; + } + } + + beforeEach(function() { + deleteAllCookies(); + expect(document.cookie).toEqual(''); + }); + + + afterEach(function() { + deleteAllCookies(); + expect(document.cookie).toEqual(''); + }); + + + describe('remove all via (null)', function() { + + it('should do nothing when no cookies are set', function() { + browser.cookies(null); + expect(document.cookie).toEqual(''); + expect(browser.cookies()).toEqual({}); + }); + + }); + + describe('remove via cookies(cookieName, undefined)', function() { + + it('should remove a cookie when it is present', function() { + document.cookie = 'foo=bar;path=/'; + + browser.cookies('foo', undefined); + + expect(document.cookie).toEqual(''); + expect(browser.cookies()).toEqual({}); + }); + + + it('should do nothing when an nonexisting cookie is being removed', function() { + browser.cookies('doesntexist', undefined); + expect(document.cookie).toEqual(''); + expect(browser.cookies()).toEqual({}); + }); + it('should remove cookie with file path', function() { + document.cookie = 'foo=bar;path=/karma'; + browser.cookies('foo', undefined); + expect(document.cookie).toEqual(''); + expect(browser.cookies()).toEqual({}); + }) + it('should remove cookie with directory path', function() { + document.cookie = 'foo=bar;path=/karma/'; + browser.cookies('foo', undefined); + + expect(document.cookie).toEqual(''); + expect(browser.cookies()).toEqual({}); + }) + it('should remove cookie with nested path', function() { + document.cookie = 'foo=bar;path=/karma/tests'; + browser.cookies('foo', undefined); + + expect(document.cookie).toEqual(''); + expect(browser.cookies()).toEqual({}); + }) + it('should remove all named cookies with different paths', function() { + document.cookie = 'foo=first;path=/'; + document.cookie = 'foo=second;path=/karma'; + browser.cookies('foo', undefined); + + expect(document.cookie).toEqual(''); + expect(browser.cookies()).toEqual({}); + }) + }); + describe('remove via cookies(cookieName, undefined,options)',function(){ + it('should work with null in options argument',function() { + document.cookie = 'foo=bar;path=/'; + + browser.cookies('foo', undefined,null); + + expect(document.cookie).toEqual(''); + expect(browser.cookies()).toEqual({}); + }) + it('should work with array in options argument',function() { + document.cookie = 'foo=bar;path=/'; + + browser.cookies('foo', undefined,[]); + + expect(document.cookie).toEqual(''); + expect(browser.cookies()).toEqual({}); + }) + it('should not delete path not requested',function() { + document.cookie = 'foo=bar;path=/'; + + browser.cookies('foo', undefined,{path:"/karma"}); + + expect(document.cookie).toEqual('foo=bar'); + expect(browser.cookies()).toEqual({'foo':'bar'}); + }) + it('should delete requested path',function() { + document.cookie = 'foo=bar;path=/karma'; + + browser.cookies('foo', undefined,{path:"/karma"}); + + expect(document.cookie).toEqual(''); + expect(browser.cookies()).toEqual({}); + }) + it('should delete only requested path',function() { + document.cookie = 'foo=first;path=/'; + document.cookie = 'foo=second;path=/karma'; + + browser.cookies('foo', undefined,{path:"/"}); + + expect(document.cookie).toEqual('foo=second'); + expect(browser.cookies()).toEqual({'foo':'second'}); + }) + }) + + describe('put via cookies(cookieName, string)', function() { + + it('should create and store a cookie', function() { + browser.cookies('cookieName', 'cookie=Value'); + expect(document.cookie).toMatch(/cookieName=cookie%3DValue;? ?/); + expect(browser.cookies()).toEqual({ 'cookieName': 'cookie=Value' }); + expect(fakeDocument.lastCookieSet).toEqual("cookieName=cookie%3DValue;path=/") + }); + + + it('should overwrite an existing unsynced cookie', function() { + document.cookie = "cookie=new;path=/"; + + var oldVal = browser.cookies('cookie', 'newer'); + + expect(document.cookie).toEqual('cookie=newer'); + expect(browser.cookies()).toEqual({ 'cookie': 'newer' }); + expect(oldVal).not.toBeDefined(); + }); + + it('should escape both name and value', function() { + browser.cookies('cookie1=', 'val;ue'); + browser.cookies('cookie2=bar;baz', 'val=ue'); + + var rawCookies = document.cookie.split("; "); //order is not guaranteed, so we need to parse + expect(rawCookies.length).toEqual(2); + expect(rawCookies).toContain('cookie1%3D=val%3Bue'); + expect(rawCookies).toContain('cookie2%3Dbar%3Bbaz=val%3Due'); + }); + + it('should log warnings when 4kb per cookie storage limit is reached', function() { + var i, longVal = '', cookieStr; + + for (i = 0; i < 4083; i++) { + longVal += '+'; + } + + cookieStr = document.cookie; + browser.cookies('x', longVal); //total size 4093-4096, so it should go through + expect(document.cookie).not.toEqual(cookieStr); + expect(browser.cookies()['x']).toEqual(longVal); + expect(logs.warn).toEqual([]); + + browser.cookies('x', longVal + 'xxxx'); //total size 4097-4099, a warning should be logged + expect(logs.warn).toEqual( + [["Cookie 'x' possibly not set or overflowed because it was too large (4097 > 4096 " + + "bytes)!"]]); + + //force browser to dropped a cookie and make sure that the cache is not out of sync + browser.cookies('x', 'shortVal'); + expect(browser.cookies().x).toEqual('shortVal'); //needed to prime the cache + cookieStr = document.cookie; + browser.cookies('x', longVal + longVal + longVal); //should be too long for all browsers + + if (document.cookie !== cookieStr) { + fail("browser didn't drop long cookie when it was expected. make the cookie in this " + + "test longer"); + } + + expect(browser.cookies().x).toEqual('shortVal'); + }); + it('should allow empty values', function() { + browser.cookies('cookieName', ''); + expect(document.cookie).toMatch(/cookieName=?/) + expect(browser.cookies().cookieName).toEqual(''); + }) + }); + + describe('put via cookies(cookieName, string), if no ', function() { + beforeEach(function() { + fakeDocument.basePath = undefined; + }); + + it('should default path in cookie to "" (empty string)', function() { + browser.cookies('cookie', 'bender'); + expect(document.cookie).toEqual('cookie=bender'); + expect(fakeDocument.lastCookieSet).toEqual("cookie=bender;path=") + }); + }); + describe('put via cookies(cookieName, string), with complex ', function() { + beforeEach(function() { + fakeDocument.basePath = "http://location/karma/"; + }); + + it('should set path in cookie to uri path', function() { + browser.cookies('cookie', 'bender'); + //TODO: change test to run in URI /inner/path, so that the cookies will be saved + // and we can query them. + expect(document.cookie).toEqual('cookie=bender'); + expect(fakeDocument.lastCookieSet).toEqual("cookie=bender;path=/karma/") + }); + }); + describe('put via cookies(cookieName,string,options) with no options', function() { + it('should not throw exception when passing null', function() { + browser.cookies('cookie', 'bender', null); + expect(document.cookie).toEqual('cookie=bender'); + expect(fakeDocument.lastCookieSet).toEqual("cookie=bender;path=/"); + }) + it('should not throw exception when passing array', function() { + browser.cookies('cookie', 'bender', []); + expect(document.cookie).toEqual('cookie=bender'); + expect(fakeDocument.lastCookieSet).toEqual("cookie=bender;path=/"); + }) + }) + describe('put via cookies(cookieName, string,options) with different path', function() { + it('should set path in cookie to desired path', function() { + browser.cookies('cookie', 'bender', { path: "/karma/tests" }); + expect(document.cookie).toEqual('cookie=bender'); + expect(fakeDocument.lastCookieSet).toEqual("cookie=bender;path=/karma/tests") + }) + it('shuld ignore path if path is not part of location', function() { + browser.cookies('cookie', 'bender', { path: "/something" }); + expect(fakeDocument.lastCookieSet).toEqual("cookie=bender;path=/") + expect(document.cookie).toEqual('cookie=bender'); + expect(logs.warn).toEqual( + [["Cookie 'cookie' was not set with requested path '/something'" + + " since path is not a String or not partial to window.location, which is /karma/tests/context.html"]]); + }) + }) + describe('put via cookies(cookieName, string,options) with expiration', function() { + it('should set expiration if passed', function() { + browser.cookies('cookie', 'bender', { expires: new Date(2050, 1, 1) }); + expect(document.cookie).toEqual('cookie=bender'); + expect(fakeDocument.lastCookieSet).toEqual("cookie=bender;path=/;expires=" + new Date(2050, 1, 1).toUTCString()) + }) + it('should ignore if not a date object', function() { + browser.cookies('cookie', 'bender', { expires: [] }); + expect(document.cookie).toEqual('cookie=bender'); + expect(fakeDocument.lastCookieSet).toEqual("cookie=bender;path=/") + expect(logs.warn).toEqual( + [["Cookie 'cookie' was not set with requested expiration ''" + + " since date is in the past or object is not a date"]]); + }) + it('should ignore if date in the past', function() { + browser.cookies('cookie', 'bender', { expires: new Date(1999, 1, 1) }); + expect(document.cookie).toEqual('cookie=bender'); + expect(fakeDocument.lastCookieSet).toEqual("cookie=bender;path=/") + expect(logs.warn).toEqual( + [["Cookie 'cookie' was not set with requested expiration '" + (new Date(1999, 1, 1)) + + "' since date is in the past or object is not a date"]]); + }) + }) + describe('get via cookies()[cookieName]', function() { + + it('should return undefined for nonexistent cookie', function() { + expect(browser.cookies().nonexistent).not.toBeDefined(); + }); + + + it('should return a value for an existing cookie', function() { + document.cookie = "foo=bar=baz;path=/"; + expect(browser.cookies().foo).toEqual('bar=baz'); + }); + it('should return the the first value provided for a cookie', function() { + // For a cookie that has different values that differ by path, the + // value for the most specific path appears first. browser.cookies() + // should provide that value for the cookie. + document.cookie = 'foo="first"; foo="second"'; + expect(browser.cookies()['foo']).toBe('"first"'); + }); + it('should unescape cookie values that were escaped by puts', function() { + document.cookie = "cookie2%3Dbar%3Bbaz=val%3Due;path=/"; + expect(browser.cookies()['cookie2=bar;baz']).toEqual('val=ue'); + }); + + + it('should preserve leading & trailing spaces in names and values', function() { + browser.cookies(' cookie name ', ' cookie value '); + expect(browser.cookies()[' cookie name ']).toEqual(' cookie value '); + expect(browser.cookies()['cookie name']).not.toBeDefined(); + }); + it('should read empty values', function() { + document.cookie = "cookie=;path=/"; + expect(browser.cookies()['cookie']).toEqual(''); + }) + }); + + describe('getAll via cookies()', function() { + + it('should return cookies as hash', function() { + document.cookie = "foo1=bar1;path=/"; + document.cookie = "foo2=bar2;path=/"; + expect(browser.cookies()).toEqual({ 'foo1': 'bar1', 'foo2': 'bar2' }); + }); + + + it('should return empty hash if no cookies exist', function() { + expect(browser.cookies()).toEqual({}); + }); + }); + + + it('should pick up external changes made to browser cookies', function() { + browser.cookies('oatmealCookie', 'drool'); + expect(browser.cookies()).toEqual({ 'oatmealCookie': 'drool' }); + + document.cookie = 'oatmealCookie=changed;path=/'; + expect(browser.cookies().oatmealCookie).toEqual('changed'); + }); + + + it('should initialize cookie cache with existing cookies', function() { + document.cookie = "existingCookie=existingValue;path=/"; + expect(browser.cookies()).toEqual({ 'existingCookie': 'existingValue' }); + }); + + }); + + describe('poller', function() { + + it('should call functions in pollFns in regular intervals', function() { + var log = ''; + browser.addPollFn(function() { log += 'a'; }); + browser.addPollFn(function() { log += 'b'; }); + expect(log).toEqual(''); + fakeWindow.setTimeout.flush(); + expect(log).toEqual('ab'); + fakeWindow.setTimeout.flush(); + expect(log).toEqual('abab'); + }); + + it('should startPoller', function() { + expect(fakeWindow.timeouts.length).toEqual(0); + + browser.addPollFn(function() { }); + expect(fakeWindow.timeouts.length).toEqual(1); + + //should remain 1 as it is the check fn + browser.addPollFn(function() { }); + expect(fakeWindow.timeouts.length).toEqual(1); + }); + + it('should return fn that was passed into addPollFn', function() { + var fn = function() { return 1; }; + var returnedFn = browser.addPollFn(fn); + expect(returnedFn).toBe(fn); + }); + }); + + describe('url', function() { + var pushState, replaceState, locationReplace; + + beforeEach(function() { + pushState = spyOn(fakeWindow.history, 'pushState'); + replaceState = spyOn(fakeWindow.history, 'replaceState'); + locationReplace = spyOn(fakeWindow.location, 'replace'); + }); + + it('should return current location.href', function() { + fakeWindow.location.href = 'http://test.com'; + expect(browser.url()).toEqual('http://test.com'); + + fakeWindow.location.href = 'https://another.com'; + expect(browser.url()).toEqual('https://another.com'); + }); + + it('should use history.pushState when available', function() { + sniffer.history = true; + browser.url('http://new.org'); + + expect(pushState).toHaveBeenCalledOnce(); + expect(pushState.argsForCall[0][2]).toEqual('http://new.org'); + + expect(replaceState).not.toHaveBeenCalled(); + expect(locationReplace).not.toHaveBeenCalled(); + expect(fakeWindow.location.href).toEqual('http://server'); + }); + + it('should use history.replaceState when available', function() { + sniffer.history = true; + browser.url('http://new.org', true); + + expect(replaceState).toHaveBeenCalledOnce(); + expect(replaceState.argsForCall[0][2]).toEqual('http://new.org'); + + expect(pushState).not.toHaveBeenCalled(); + expect(locationReplace).not.toHaveBeenCalled(); + expect(fakeWindow.location.href).toEqual('http://server'); + }); + + it('should set location.href when pushState not available', function() { + sniffer.history = false; + browser.url('http://new.org'); + + expect(fakeWindow.location.href).toEqual('http://new.org'); + + expect(pushState).not.toHaveBeenCalled(); + expect(replaceState).not.toHaveBeenCalled(); + expect(locationReplace).not.toHaveBeenCalled(); + }); + + it('should use location.replace when history.replaceState not available', function() { + sniffer.history = false; + browser.url('http://new.org', true); + + expect(locationReplace).toHaveBeenCalledWith('http://new.org'); + + expect(pushState).not.toHaveBeenCalled(); + expect(replaceState).not.toHaveBeenCalled(); + expect(fakeWindow.location.href).toEqual('http://server'); + }); + + it('should return $browser to allow chaining', function() { + expect(browser.url('http://any.com')).toBe(browser); + }); + + + it('should decode single quotes to work around FF bug 407273', function() { + fakeWindow.location.href = "http://ff-bug/?single%27quote"; + expect(browser.url()).toBe("http://ff-bug/?single'quote"); + }); + + it('should not set URL when the URL is already set', function() { + var current = fakeWindow.location.href; + sniffer.history = false; + fakeWindow.location.href = 'dontchange'; + browser.url(current); + expect(fakeWindow.location.href).toBe('dontchange'); + }); + }); + + describe('urlChange', function() { + var callback; + + beforeEach(function() { + callback = jasmine.createSpy('onUrlChange'); + }); + + afterEach(function() { + if (!jQuery) jqLite(fakeWindow).dealoc(); + }); + + it('should return registered callback', function() { + expect(browser.onUrlChange(callback)).toBe(callback); + }); + + it('should forward popstate event with new url when history supported', function() { + sniffer.history = true; + browser.onUrlChange(callback); + fakeWindow.location.href = 'http://server/new'; + + fakeWindow.fire('popstate'); + expect(callback).toHaveBeenCalledWith('http://server/new'); + + fakeWindow.fire('hashchange'); + fakeWindow.setTimeout.flush(); + expect(callback).toHaveBeenCalledOnce(); + }); + + it('should forward only popstate event when both history and hashchange supported', function() { + sniffer.history = true; + sniffer.hashchange = true; + browser.onUrlChange(callback); + fakeWindow.location.href = 'http://server/new'; + + fakeWindow.fire('popstate'); + expect(callback).toHaveBeenCalledWith('http://server/new'); + + fakeWindow.fire('hashchange'); + fakeWindow.setTimeout.flush(); + expect(callback).toHaveBeenCalledOnce(); + }); + + it('should forward hashchange event with new url when only hashchange supported', function() { + sniffer.history = false; + sniffer.hashchange = true; + browser.onUrlChange(callback); + fakeWindow.location.href = 'http://server/new'; + + fakeWindow.fire('hashchange'); + expect(callback).toHaveBeenCalledWith('http://server/new'); + + fakeWindow.fire('popstate'); + fakeWindow.setTimeout.flush(); + expect(callback).toHaveBeenCalledOnce(); + }); + + it('should use polling when neither history nor hashchange supported', function() { + sniffer.history = false; + sniffer.hashchange = false; + browser.onUrlChange(callback); + + fakeWindow.location.href = 'http://server.new'; + fakeWindow.setTimeout.flush(); + expect(callback).toHaveBeenCalledWith('http://server.new'); + + fakeWindow.fire('popstate'); + fakeWindow.fire('hashchange'); + expect(callback).toHaveBeenCalledOnce(); + }); + + it('should not fire urlChange if changed by browser.url method (polling)', function() { + sniffer.history = false; + sniffer.hashchange = false; + browser.onUrlChange(callback); + browser.url('http://new.com'); + + fakeWindow.setTimeout.flush(); + expect(callback).not.toHaveBeenCalled(); + }); + + it('should not fire urlChange if changed by browser.url method (hashchange)', function() { + sniffer.history = false; + sniffer.hashchange = true; + browser.onUrlChange(callback); + browser.url('http://new.com'); + + fakeWindow.fire('hashchange'); + expect(callback).not.toHaveBeenCalled(); + }); + }); + + + describe('baseHref', function() { + var jqDocHead; + + beforeEach(function() { + jqDocHead = jqLite(document).find('head'); + }); + + it('should return value from ', function() { + fakeDocument.basePath = '/base/path/'; + expect(browser.baseHref()).toEqual('/base/path/'); + }); + + it('should return \'\' (empty string) if no ', function() { + fakeDocument.basePath = undefined; + expect(browser.baseHref()).toEqual(''); + }); + + it('should remove domain from ', function() { + fakeDocument.basePath = 'http://host.com/base/path/'; + expect(browser.baseHref()).toEqual('/base/path/'); + + fakeDocument.basePath = 'http://host.com/base/path/index.html'; + expect(browser.baseHref()).toEqual('/base/path/index.html'); + }); }); - - - it('should return unique deferId', function() { - var deferId1 = browser.defer(noop), - deferId2 = browser.defer(noop); - - expect(deferId1).toBeDefined(); - expect(deferId2).toBeDefined(); - expect(deferId1).not.toEqual(deferId2); - }); - - - describe('cancel', function() { - it('should allow tasks to be canceled with returned deferId', function() { - var log = [], - deferId1 = browser.defer(function() { log.push('cancel me'); }), - deferId2 = browser.defer(function() { log.push('ok'); }), - deferId3 = browser.defer(function() { log.push('cancel me, now!'); }); - - expect(log).toEqual([]); - expect(browser.defer.cancel(deferId1)).toBe(true); - expect(browser.defer.cancel(deferId3)).toBe(true); - fakeWindow.setTimeout.flush(); - expect(log).toEqual(['ok']); - expect(browser.defer.cancel(deferId2)).toBe(false); - }); - }); - }); - - - describe('cookies', function() { - - function deleteAllCookies() { - var cookies = document.cookie.split(";"); - var path = location.pathname; - - for (var i = 0; i < cookies.length; i++) { - var cookie = cookies[i]; - var eqPos = cookie.indexOf("="); - var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie; - var parts = path.split('/'); - while (parts.length) { - document.cookie = name + "=;path=" + (parts.join('/') || '/') + ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; - parts.pop(); - } - } - } - - beforeEach(function() { - deleteAllCookies(); - expect(document.cookie).toEqual(''); - }); - - - afterEach(function() { - deleteAllCookies(); - expect(document.cookie).toEqual(''); - }); - - - describe('remove all via (null)', function() { - - it('should do nothing when no cookies are set', function() { - browser.cookies(null); - expect(document.cookie).toEqual(''); - expect(browser.cookies()).toEqual({}); - }); - - }); - - describe('remove via cookies(cookieName, undefined)', function() { - - it('should remove a cookie when it is present', function() { - document.cookie = 'foo=bar;path=/'; - - browser.cookies('foo', undefined); - - expect(document.cookie).toEqual(''); - expect(browser.cookies()).toEqual({}); - }); - - - it('should do nothing when an nonexisting cookie is being removed', function() { - browser.cookies('doesntexist', undefined); - expect(document.cookie).toEqual(''); - expect(browser.cookies()).toEqual({}); - }); - }); - - - describe('put via cookies(cookieName, string)', function() { - - it('should create and store a cookie', function() { - browser.cookies('cookieName', 'cookie=Value'); - expect(document.cookie).toMatch(/cookieName=cookie%3DValue;? ?/); - expect(browser.cookies()).toEqual({'cookieName':'cookie=Value'}); - }); - - - it('should overwrite an existing unsynced cookie', function() { - document.cookie = "cookie=new;path=/"; - - var oldVal = browser.cookies('cookie', 'newer'); - - expect(document.cookie).toEqual('cookie=newer'); - expect(browser.cookies()).toEqual({'cookie':'newer'}); - expect(oldVal).not.toBeDefined(); - }); - - it('should escape both name and value', function() { - browser.cookies('cookie1=', 'val;ue'); - browser.cookies('cookie2=bar;baz', 'val=ue'); - - var rawCookies = document.cookie.split("; "); //order is not guaranteed, so we need to parse - expect(rawCookies.length).toEqual(2); - expect(rawCookies).toContain('cookie1%3D=val%3Bue'); - expect(rawCookies).toContain('cookie2%3Dbar%3Bbaz=val%3Due'); - }); - - it('should log warnings when 4kb per cookie storage limit is reached', function() { - var i, longVal = '', cookieStr; - - for(i=0; i<4083; i++) { - longVal += '+'; - } - - cookieStr = document.cookie; - browser.cookies('x', longVal); //total size 4093-4096, so it should go through - expect(document.cookie).not.toEqual(cookieStr); - expect(browser.cookies()['x']).toEqual(longVal); - expect(logs.warn).toEqual([]); - - browser.cookies('x', longVal + 'xxxx'); //total size 4097-4099, a warning should be logged - expect(logs.warn).toEqual( - [[ "Cookie 'x' possibly not set or overflowed because it was too large (4097 > 4096 " + - "bytes)!" ]]); - - //force browser to dropped a cookie and make sure that the cache is not out of sync - browser.cookies('x', 'shortVal'); - expect(browser.cookies().x).toEqual('shortVal'); //needed to prime the cache - cookieStr = document.cookie; - browser.cookies('x', longVal + longVal + longVal); //should be too long for all browsers - - if (document.cookie !== cookieStr) { - this.fail(new Error("browser didn't drop long cookie when it was expected. make the " + - "cookie in this test longer")); - } - - expect(browser.cookies().x).toEqual('shortVal'); - }); - }); - - describe('put via cookies(cookieName, string), if no ', function () { - beforeEach(function () { - fakeDocument.basePath = undefined; - }); - - it('should default path in cookie to "" (empty string)', function () { - browser.cookies('cookie', 'bender'); - // This only fails in Safari and IE when cookiePath returns undefined - // Where it now succeeds since baseHref return '' instead of undefined - expect(document.cookie).toEqual('cookie=bender'); - }); - }); - - describe('get via cookies()[cookieName]', function() { - - it('should return undefined for nonexistent cookie', function() { - expect(browser.cookies().nonexistent).not.toBeDefined(); - }); - - - it ('should return a value for an existing cookie', function() { - document.cookie = "foo=bar=baz;path=/"; - expect(browser.cookies().foo).toEqual('bar=baz'); - }); - - it('should return the the first value provided for a cookie', function() { - // For a cookie that has different values that differ by path, the - // value for the most specific path appears first. browser.cookies() - // should provide that value for the cookie. - document.cookie = 'foo="first"; foo="second"'; - expect(browser.cookies()['foo']).toBe('"first"'); - }); - - it ('should unescape cookie values that were escaped by puts', function() { - document.cookie = "cookie2%3Dbar%3Bbaz=val%3Due;path=/"; - expect(browser.cookies()['cookie2=bar;baz']).toEqual('val=ue'); - }); - - - it('should preserve leading & trailing spaces in names and values', function() { - browser.cookies(' cookie name ', ' cookie value '); - expect(browser.cookies()[' cookie name ']).toEqual(' cookie value '); - expect(browser.cookies()['cookie name']).not.toBeDefined(); - }); - }); - - - describe('getAll via cookies()', function() { - - it('should return cookies as hash', function() { - document.cookie = "foo1=bar1;path=/"; - document.cookie = "foo2=bar2;path=/"; - expect(browser.cookies()).toEqual({'foo1':'bar1', 'foo2':'bar2'}); - }); - - - it('should return empty hash if no cookies exist', function() { - expect(browser.cookies()).toEqual({}); - }); - }); - - - it('should pick up external changes made to browser cookies', function() { - browser.cookies('oatmealCookie', 'drool'); - expect(browser.cookies()).toEqual({'oatmealCookie':'drool'}); - - document.cookie = 'oatmealCookie=changed;path=/'; - expect(browser.cookies().oatmealCookie).toEqual('changed'); - }); - - - it('should initialize cookie cache with existing cookies', function() { - document.cookie = "existingCookie=existingValue;path=/"; - expect(browser.cookies()).toEqual({'existingCookie':'existingValue'}); - }); - - }); - - describe('poller', function() { - - it('should call functions in pollFns in regular intervals', function() { - var log = ''; - browser.addPollFn(function() {log+='a';}); - browser.addPollFn(function() {log+='b';}); - expect(log).toEqual(''); - fakeWindow.setTimeout.flush(); - expect(log).toEqual('ab'); - fakeWindow.setTimeout.flush(); - expect(log).toEqual('abab'); - }); - - it('should startPoller', function() { - expect(fakeWindow.timeouts.length).toEqual(0); - - browser.addPollFn(function() {}); - expect(fakeWindow.timeouts.length).toEqual(1); - - //should remain 1 as it is the check fn - browser.addPollFn(function() {}); - expect(fakeWindow.timeouts.length).toEqual(1); - }); - - it('should return fn that was passed into addPollFn', function() { - var fn = function() { return 1; }; - var returnedFn = browser.addPollFn(fn); - expect(returnedFn).toBe(fn); - }); - }); - - describe('url', function() { - var pushState, replaceState, locationReplace; - - beforeEach(function() { - pushState = spyOn(fakeWindow.history, 'pushState'); - replaceState = spyOn(fakeWindow.history, 'replaceState'); - locationReplace = spyOn(fakeWindow.location, 'replace'); - }); - - it('should return current location.href', function() { - fakeWindow.location.href = 'http://test.com'; - expect(browser.url()).toEqual('http://test.com'); - - fakeWindow.location.href = 'https://another.com'; - expect(browser.url()).toEqual('https://another.com'); - }); - - it('should use history.pushState when available', function() { - sniffer.history = true; - browser.url('http://new.org'); - - expect(pushState).toHaveBeenCalledOnce(); - expect(pushState.argsForCall[0][2]).toEqual('http://new.org'); - - expect(replaceState).not.toHaveBeenCalled(); - expect(locationReplace).not.toHaveBeenCalled(); - expect(fakeWindow.location.href).toEqual('http://server'); - }); - - it('should use history.replaceState when available', function() { - sniffer.history = true; - browser.url('http://new.org', true); - - expect(replaceState).toHaveBeenCalledOnce(); - expect(replaceState.argsForCall[0][2]).toEqual('http://new.org'); - - expect(pushState).not.toHaveBeenCalled(); - expect(locationReplace).not.toHaveBeenCalled(); - expect(fakeWindow.location.href).toEqual('http://server'); - }); - - it('should set location.href when pushState not available', function() { - sniffer.history = false; - browser.url('http://new.org'); - - expect(fakeWindow.location.href).toEqual('http://new.org'); - - expect(pushState).not.toHaveBeenCalled(); - expect(replaceState).not.toHaveBeenCalled(); - expect(locationReplace).not.toHaveBeenCalled(); - }); - - it('should use location.replace when history.replaceState not available', function() { - sniffer.history = false; - browser.url('http://new.org', true); - - expect(locationReplace).toHaveBeenCalledWith('http://new.org'); - - expect(pushState).not.toHaveBeenCalled(); - expect(replaceState).not.toHaveBeenCalled(); - expect(fakeWindow.location.href).toEqual('http://server'); - }); - - it('should return $browser to allow chaining', function() { - expect(browser.url('http://any.com')).toBe(browser); - }); - - - it('should decode single quotes to work around FF bug 407273', function() { - fakeWindow.location.href = "http://ff-bug/?single%27quote"; - expect(browser.url()).toBe("http://ff-bug/?single'quote"); - }); - - it('should not set URL when the URL is already set', function() { - var current = fakeWindow.location.href; - sniffer.history = false; - fakeWindow.location.href = 'dontchange'; - browser.url(current); - expect(fakeWindow.location.href).toBe('dontchange'); - }); - }); - - describe('urlChange', function() { - var callback; - - beforeEach(function() { - callback = jasmine.createSpy('onUrlChange'); - }); - - afterEach(function() { - if (!jQuery) jqLite(fakeWindow).dealoc(); - }); - - it('should return registered callback', function() { - expect(browser.onUrlChange(callback)).toBe(callback); - }); - - it('should forward popstate event with new url when history supported', function() { - sniffer.history = true; - browser.onUrlChange(callback); - fakeWindow.location.href = 'http://server/new'; - - fakeWindow.fire('popstate'); - expect(callback).toHaveBeenCalledWith('http://server/new'); - - fakeWindow.fire('hashchange'); - fakeWindow.setTimeout.flush(); - expect(callback).toHaveBeenCalledOnce(); - }); - - it('should forward only popstate event when both history and hashchange supported', function() { - sniffer.history = true; - sniffer.hashchange = true; - browser.onUrlChange(callback); - fakeWindow.location.href = 'http://server/new'; - - fakeWindow.fire('popstate'); - expect(callback).toHaveBeenCalledWith('http://server/new'); - - fakeWindow.fire('hashchange'); - fakeWindow.setTimeout.flush(); - expect(callback).toHaveBeenCalledOnce(); - }); - - it('should forward hashchange event with new url when only hashchange supported', function() { - sniffer.history = false; - sniffer.hashchange = true; - browser.onUrlChange(callback); - fakeWindow.location.href = 'http://server/new'; - - fakeWindow.fire('hashchange'); - expect(callback).toHaveBeenCalledWith('http://server/new'); - - fakeWindow.fire('popstate'); - fakeWindow.setTimeout.flush(); - expect(callback).toHaveBeenCalledOnce(); - }); - - it('should use polling when neither history nor hashchange supported', function() { - sniffer.history = false; - sniffer.hashchange = false; - browser.onUrlChange(callback); - - fakeWindow.location.href = 'http://server.new'; - fakeWindow.setTimeout.flush(); - expect(callback).toHaveBeenCalledWith('http://server.new'); - - fakeWindow.fire('popstate'); - fakeWindow.fire('hashchange'); - expect(callback).toHaveBeenCalledOnce(); - }); - - it('should not fire urlChange if changed by browser.url method (polling)', function() { - sniffer.history = false; - sniffer.hashchange = false; - browser.onUrlChange(callback); - browser.url('http://new.com'); - - fakeWindow.setTimeout.flush(); - expect(callback).not.toHaveBeenCalled(); - }); - - it('should not fire urlChange if changed by browser.url method (hashchange)', function() { - sniffer.history = false; - sniffer.hashchange = true; - browser.onUrlChange(callback); - browser.url('http://new.com'); - - fakeWindow.fire('hashchange'); - expect(callback).not.toHaveBeenCalled(); - }); - }); - - - describe('baseHref', function() { - var jqDocHead; - - beforeEach(function() { - jqDocHead = jqLite(document).find('head'); - }); - - it('should return value from ', function() { - fakeDocument.basePath = '/base/path/'; - expect(browser.baseHref()).toEqual('/base/path/'); - }); - - it('should return \'\' (empty string) if no ', function() { - fakeDocument.basePath = undefined; - expect(browser.baseHref()).toEqual(''); - }); - - it('should remove domain from ', function() { - fakeDocument.basePath = 'http://host.com/base/path/'; - expect(browser.baseHref()).toEqual('/base/path/'); - - fakeDocument.basePath = 'http://host.com/base/path/index.html'; - expect(browser.baseHref()).toEqual('/base/path/index.html'); - }); - }); }); diff --git a/test/ngCookies/cookiesSpec.js b/test/ngCookies/cookiesSpec.js index 674c27748f11..91343546f434 100644 --- a/test/ngCookies/cookiesSpec.js +++ b/test/ngCookies/cookiesSpec.js @@ -1,99 +1,109 @@ -'use strict'; - -describe('$cookies', function() { - beforeEach(module('ngCookies', function($provide) { - $provide.factory('$browser', function(){ - return angular.extend(new angular.mock.$Browser(), {cookieHash: {preexisting:'oldCookie'}}); - }); - })); - - - it('should provide access to existing cookies via object properties and keep them in sync', - inject(function($cookies, $browser, $rootScope) { - expect($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($cookies).toEqual({'preexisting': 'oldCookie', 'brandNew':'cookie'}); - - $browser.cookieHash['brandNew'] = 'cookie2'; - $browser.poll(); - expect($cookies).toEqual({'preexisting': 'oldCookie', 'brandNew':'cookie2'}); - - delete $browser.cookieHash['brandNew']; - $browser.poll(); - expect($cookies).toEqual({'preexisting': 'oldCookie'}); - })); - - - it('should create or update a cookie when a value is assigned to a property', - inject(function($cookies, $browser, $rootScope) { - $cookies.oatmealCookie = 'nom nom'; - $rootScope.$digest(); - - expect($browser.cookies()). - toEqual({'preexisting': 'oldCookie', 'oatmealCookie':'nom nom'}); - - $cookies.oatmealCookie = 'gone'; - $rootScope.$digest(); - - expect($browser.cookies()). - toEqual({'preexisting': 'oldCookie', 'oatmealCookie': 'gone'}); - })); - - - it('should drop or reset any cookie that was set to a non-string value', - inject(function($cookies, $browser, $rootScope) { - $cookies.nonString = [1, 2, 3]; - $cookies.nullVal = null; - $cookies.undefVal = undefined; - $cookies.preexisting = function() {}; - $rootScope.$digest(); - expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'}); - expect($cookies).toEqual({'preexisting': 'oldCookie'}); - })); - - - it('should remove a cookie when a $cookies property is deleted', - inject(function($cookies, $browser, $rootScope) { - $cookies.oatmealCookie = 'nom nom'; - $rootScope.$digest(); - $browser.poll(); - expect($browser.cookies()). - toEqual({'preexisting': 'oldCookie', 'oatmealCookie':'nom nom'}); - - delete $cookies.oatmealCookie; - $rootScope.$digest(); - - expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'}); - })); - - - it('should drop or reset cookies that browser refused to store', - inject(function($cookies, $browser, $rootScope) { - var i, longVal; - - for (i=0; i<5000; i++) { - longVal += '*'; - } - - //drop if no previous value - $cookies.longCookie = longVal; - $rootScope.$digest(); - expect($cookies).toEqual({'preexisting': 'oldCookie'}); - - - //reset if previous value existed - $cookies.longCookie = 'shortVal'; - $rootScope.$digest(); - expect($cookies).toEqual({'preexisting': 'oldCookie', 'longCookie': 'shortVal'}); - $cookies.longCookie = longVal; - $rootScope.$digest(); - expect($cookies).toEqual({'preexisting': 'oldCookie', 'longCookie': 'shortVal'}); - })); +'use strict'; + +describe('$cookies', function() { + beforeEach(module('ngCookies', function($provide) { + $provide.factory('$browser', function() { + return angular.extend(new angular.mock.$Browser(), { cookieHash: { preexisting: 'oldCookie'} }); + }); + })); + + + it('should provide access to existing cookies via object properties and keep them in sync', + inject(function($cookies, $browser, $rootScope) { + expect($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($cookies).toEqual({ 'preexisting': 'oldCookie', 'brandNew': 'cookie' }); + + $browser.cookieHash['brandNew'] = 'cookie2'; + $browser.poll(); + expect($cookies).toEqual({ 'preexisting': 'oldCookie', 'brandNew': 'cookie2' }); + + delete $browser.cookieHash['brandNew']; + $browser.poll(); + expect($cookies).toEqual({ 'preexisting': 'oldCookie' }); + })); + + + it('should create or update a cookie when a value is assigned to a property', + inject(function($cookies, $browser, $rootScope) { + $cookies.oatmealCookie = 'nom nom'; + $rootScope.$digest(); + + expect($browser.cookies()). + toEqual({ 'preexisting': 'oldCookie', 'oatmealCookie': 'nom nom' }); + + $cookies.oatmealCookie = 'gone'; + $rootScope.$digest(); + + expect($browser.cookies()). + toEqual({ 'preexisting': 'oldCookie', 'oatmealCookie': 'gone' }); + })); + + + it('should drop or reset any cookie that was set to a non-string value', + inject(function($cookies, $browser, $rootScope) { + $cookies.nonString = [1, 2, 3]; + $cookies.nullVal = null; + $cookies.undefVal = undefined; + $cookies.preexisting = function() { }; + $rootScope.$digest(); + expect($browser.cookies()).toEqual({ 'preexisting': 'oldCookie' }); + expect($cookies).toEqual({ 'preexisting': 'oldCookie' }); + })); + it('should handle empty string value cookies', inject(function($cookies, $browser, $rootScope) { + $cookies.emptyCookie = ''; + $rootScope.$digest(); + expect($browser.cookies()). + toEqual({ 'preexisting': 'oldCookie', 'emptyCookie': '' }); + + $browser.cookieHash['blankCookie'] = ''; + $browser.poll(); + expect($cookies).toEqual({ 'preexisting': 'oldCookie', 'emptyCookie': '', 'blankCookie': '' }); + + })) + + it('should remove a cookie when a $cookies property is deleted', + inject(function($cookies, $browser, $rootScope) { + $cookies.oatmealCookie = 'nom nom'; + $rootScope.$digest(); + $browser.poll(); + expect($browser.cookies()). + toEqual({ 'preexisting': 'oldCookie', 'oatmealCookie': 'nom nom' }); + + delete $cookies.oatmealCookie; + $rootScope.$digest(); + + expect($browser.cookies()).toEqual({ 'preexisting': 'oldCookie' }); + })); + + + it('should drop or reset cookies that browser refused to store', + inject(function($cookies, $browser, $rootScope) { + var i, longVal; + + for (i = 0; i < 5000; i++) { + longVal += '*'; + } + + //drop if no previous value + $cookies.longCookie = longVal; + $rootScope.$digest(); + expect($cookies).toEqual({ 'preexisting': 'oldCookie' }); + + + //reset if previous value existed + $cookies.longCookie = 'shortVal'; + $rootScope.$digest(); + expect($cookies).toEqual({ 'preexisting': 'oldCookie', 'longCookie': 'shortVal' }); + $cookies.longCookie = longVal; + $rootScope.$digest(); + expect($cookies).toEqual({ 'preexisting': 'oldCookie', 'longCookie': 'shortVal' }); + })); }); @@ -125,15 +135,32 @@ describe('$cookieStore', function() { $rootScope.$digest(); expect($browser.cookies()).toEqual({}); })); - it('should handle empty string value cookies', inject(function ($cookieStore, $browser, $rootScope) { - $cookieStore.put("emptyCookie",''); - $rootScope.$digest(); - expect($browser.cookies()). - toEqual({ 'emptyCookie': '""' }); - expect($cookieStore.get("emptyCookie")).toEqual(''); - - $browser.cookieHash['blankCookie'] = ''; - $browser.poll(); - expect($cookieStore.get("blankCookie")).toEqual(''); + it('should handle empty string value cookies', inject(function($cookieStore, $browser, $rootScope) { + $cookieStore.put("emptyCookie",''); + $rootScope.$digest(); + expect($browser.cookies()). + toEqual({ 'emptyCookie': '""' }); + expect($cookieStore.get("emptyCookie")).toEqual(''); + + $browser.cookieHash['blankCookie'] = ''; + $browser.poll(); + expect($cookieStore.get("blankCookie")).toEqual(''); + })) + it('should send options to cookies on put', + inject(function($cookieStore, $browser, $rootScope) { + $cookieStore.put("optionCookie","someVal","the option") + $rootScope.$digest(); + expect($browser.cookieOptionHash["optionCookie"]).toEqual("the option"); + expect($browser.cookies()).toEqual({'optionCookie': '"someVal"' }) + })) + it('should send options to cookies on delete', + inject(function($cookieStore, $browser, $rootScope) { + $cookieStore.put("optionCookie","someValue") + $rootScope.$digest(); + $browser.poll(); + $cookieStore.remove("optionCookie","the option"); + $rootScope.$digest(); + expect($browser.cookieOptionHash["optionCookie"]).toEqual("the option"); + expect($browser.cookies()).toEqual({}) })) });