diff --git a/angularFiles.js b/angularFiles.js index 696bd8e78ec8..7b8ac77b9b08 100755 --- a/angularFiles.js +++ b/angularFiles.js @@ -40,6 +40,7 @@ var angularFiles = { 'src/ng/timeout.js', 'src/ng/urlUtils.js', 'src/ng/window.js', + 'src/ng/cookieReader.js', 'src/ng/filter.js', 'src/ng/filter/filter.js', @@ -89,7 +90,9 @@ var angularFiles = { 'src/ngAnimate/animate.js' ], 'ngCookies': [ - 'src/ngCookies/cookies.js' + 'src/ngCookies/cookies.js', + 'src/ngCookies/cookieStore.js', + 'src/ngCookies/cookieWriter.js' ], 'ngMessages': [ 'src/ngMessages/messages.js' @@ -162,7 +165,7 @@ var angularFiles = { 'src/publishExternalApis.js', '@angularSrcModules', '@angularScenario', - '@angularTest', + '@angularTest' ], 'karmaExclude': [ @@ -197,7 +200,7 @@ var angularFiles = { 'src/publishExternalApis.js', '@angularSrcModules', '@angularScenario', - '@angularTest', + '@angularTest' ], 'karmaJqueryExclude': [ diff --git a/src/AngularPublic.js b/src/AngularPublic.js index b81257b9fff7..31e8483ee6bf 100644 --- a/src/AngularPublic.js +++ b/src/AngularPublic.js @@ -84,7 +84,8 @@ $$RAFProvider, $$AsyncCallbackProvider, $WindowProvider, - $$jqLiteProvider + $$jqLiteProvider, + $$CookieReaderProvider */ @@ -238,7 +239,8 @@ function publishExternalAPI(angular) { $window: $WindowProvider, $$rAF: $$RAFProvider, $$asyncCallback: $$AsyncCallbackProvider, - $$jqLite: $$jqLiteProvider + $$jqLite: $$jqLiteProvider, + $$cookieReader: $$CookieReaderProvider }); } ]); diff --git a/src/ng/browser.js b/src/ng/browser.js index af2dd70e76a9..028536c340d6 100644 --- a/src/ng/browser.js +++ b/src/ng/browser.js @@ -73,11 +73,6 @@ function Browser(window, document, $log, $sniffer) { * @param {function()} callback Function that will be called when no outstanding request */ self.notifyWhenNoOutstandingRequests = function(callback) { - // force browser to execute all pollFns - this is needed so that cookies and other pollers fire - // at some deterministic time in respect to the test runner's actions. Leaving things up to the - // regular poller would result in flaky tests. - forEach(pollFns, function(pollFn) { pollFn(); }); - if (outstandingRequestCount === 0) { callback(); } else { @@ -85,44 +80,6 @@ function Browser(window, document, $log, $sniffer) { } }; - ////////////////////////////////////////////////////////////// - // Poll Watcher API - ////////////////////////////////////////////////////////////// - var pollFns = [], - pollTimeout; - - /** - * @name $browser#addPollFn - * - * @param {function()} fn Poll function to add - * - * @description - * Adds a function to the list of functions that poller periodically executes, - * and starts polling if not started yet. - * - * @returns {function()} the added function - */ - self.addPollFn = function(fn) { - if (isUndefined(pollTimeout)) startPoller(100, setTimeout); - pollFns.push(fn); - return fn; - }; - - /** - * @param {number} interval How often should browser call poll functions (ms) - * @param {function()} setTimeout Reference to a real or fake `setTimeout` function. - * - * @description - * Configures the poller to run in the specified intervals, using the specified - * setTimeout fn and kicks it off. - */ - function startPoller(interval, setTimeout) { - (function check() { - forEach(pollFns, function(pollFn) { pollFn(); }); - pollTimeout = setTimeout(check, interval); - })(); - } - ////////////////////////////////////////////////////////////// // URL API ////////////////////////////////////////////////////////////// @@ -324,89 +281,6 @@ function Browser(window, document, $log, $sniffer) { return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; }; - ////////////////////////////////////////////////////////////// - // Cookies API - ////////////////////////////////////////////////////////////// - var lastCookies = {}; - var lastCookieString = ''; - var cookiePath = self.baseHref(); - - function safeDecodeURIComponent(str) { - try { - return decodeURIComponent(str); - } catch (e) { - return str; - } - } - - /** - * @name $browser#cookies - * - * @param {string=} name Cookie name - * @param {string=} value Cookie value - * - * @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. - * - * The return values vary depending on the arguments that the method was called with as follows: - * - * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify - * it - * - cookies(name, value) -> set name to value, if value is undefined delete the cookie - * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that - * way) - * - * @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 = encodeURIComponent(name) + "=;path=" + cookiePath + - ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; - } else { - if (isString(value)) { - cookieLength = (rawDocument.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(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 - name = safeDecodeURIComponent(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] = safeDecodeURIComponent(cookie.substring(index + 1)); - } - } - } - } - return lastCookies; - } - }; - - /** * @name $browser#defer * @param {function()} fn A function, who's execution should be deferred. diff --git a/src/ng/cookieReader.js b/src/ng/cookieReader.js new file mode 100644 index 000000000000..53bc279f4269 --- /dev/null +++ b/src/ng/cookieReader.js @@ -0,0 +1,55 @@ +'use strict'; + +/** + * @name $$cookieReader + * @requires $document + * + * @description + * This is a private service for reading cookies used by $http and ngCookies + * + * @return {Object} a key/value map of the current cookies + */ +function $$CookieReader($document) { + var rawDocument = $document[0]; + var lastCookies = {}; + var lastCookieString = ''; + + function safeDecodeURIComponent(str) { + try { + return decodeURIComponent(str); + } catch (e) { + return str; + } + } + + return function() { + var cookieArray, cookie, i, index, name; + + 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 + name = safeDecodeURIComponent(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] = safeDecodeURIComponent(cookie.substring(index + 1)); + } + } + } + } + return lastCookies; + }; +} + +$$CookieReader.$inject = ['$document']; + +function $$CookieReaderProvider() { + this.$get = $$CookieReader; +} diff --git a/src/ng/http.js b/src/ng/http.js index dc37c1625a09..95a33ae7a1fb 100644 --- a/src/ng/http.js +++ b/src/ng/http.js @@ -220,8 +220,8 @@ function $HttpProvider() { **/ var interceptorFactories = this.interceptors = []; - this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', - function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { + this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', + function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) { var defaultCache = $cacheFactory('$http'); @@ -1066,7 +1066,7 @@ function $HttpProvider() { // send the request to the backend if (isUndefined(cachedResp)) { var xsrfValue = urlIsSameOrigin(config.url) - ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName] + ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName] : undefined; if (xsrfValue) { reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; diff --git a/src/ngCookies/cookieStore.js b/src/ngCookies/cookieStore.js new file mode 100644 index 000000000000..76a141114303 --- /dev/null +++ b/src/ngCookies/cookieStore.js @@ -0,0 +1,81 @@ +'use strict'; + +angular.module('ngCookies'). +/** + * @ngdoc service + * @name $cookieStore + * @deprecated + * @requires $cookies + * + * @description + * 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. + * + * Requires the {@link ngCookies `ngCookies`} module to be installed. + * + *
+ * **Note:** The $cookieStore service is deprecated. + * Please use the {@link ngCookies.$cookies `$cookies`} service instead. + *
+ * + * @example + * + * ```js + * angular.module('cookieStoreExample', ['ngCookies']) + * .controller('ExampleController', ['$cookieStore', function($cookieStore) { + * // Put cookie + * $cookieStore.put('myFavorite','oatmeal'); + * // Get cookie + * var favoriteCookie = $cookieStore.get('myFavorite'); + * // Removing a cookie + * $cookieStore.remove('myFavorite'); + * }]); + * ``` + */ + factory('$cookieStore', ['$cookies', function($cookies) { + + return { + /** + * @ngdoc method + * @name $cookieStore#get + * + * @description + * Returns the value of given cookie key + * + * @param {string} key Id to use for lookup. + * @returns {Object} Deserialized cookie value, undefined if the cookie does not exist. + */ + get: function(key) { + return $cookies.getObject(key); + }, + + /** + * @ngdoc method + * @name $cookieStore#put + * + * @description + * Sets a value for given cookie key + * + * @param {string} key Id for the `value`. + * @param {Object} value Value to be stored. + */ + put: function(key, value) { + $cookies.putObject(key, value); + }, + + /** + * @ngdoc method + * @name $cookieStore#remove + * + * @description + * Remove given cookie + * + * @param {string} key Id of the key-value pair to delete. + */ + remove: function(key) { + $cookies.remove(key); + } + }; + + }]); diff --git a/src/ngCookies/cookieWriter.js b/src/ngCookies/cookieWriter.js new file mode 100644 index 000000000000..b4f496f825bb --- /dev/null +++ b/src/ngCookies/cookieWriter.js @@ -0,0 +1,60 @@ +'use strict'; + +/** + * @name $$cookieWriter + * @requires $document + * + * @description + * This is a private service for writing cookies + * + * @param {string} name Cookie name + * @param {string=} value Cookie value (if undefined, cookie will be deleted) + * @param {Object=} options Object with options that need to be stored for the cookie. + */ +function $$CookieWriter($document, $log, $browser) { + var cookiePath = $browser.baseHref(); + var rawDocument = $document[0]; + + function buildCookieString(name, value, options) { + var path, expires; + options = options || {}; + expires = options.expires; + path = angular.isDefined(options.path) ? options.path : cookiePath; + if (value === undefined) { + expires = 'Thu, 01 Jan 1970 00:00:00 GMT'; + value = ''; + } + if (angular.isString(expires)) { + expires = new Date(expires); + } + + var str = encodeURIComponent(name) + '=' + encodeURIComponent(value); + str += path ? ';path=' + path : ''; + str += options.domain ? ';domain=' + options.domain : ''; + str += expires ? ';expires=' + expires.toUTCString() : ''; + str += options.secure ? ';secure' : ''; + + // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: + // - 300 cookies + // - 20 cookies per unique domain + // - 4096 bytes per cookie + var cookieLength = str.length + 1; + if (cookieLength > 4096) { + $log.warn("Cookie '" + name + + "' possibly not set or overflowed because it was too large (" + + cookieLength + " > 4096 bytes)!"); + } + + return str; + } + + return function(name, value, options) { + rawDocument.cookie = buildCookieString(name, value, options); + }; +} + +$$CookieWriter.$inject = ['$document', '$log', '$browser']; + +angular.module('ngCookies').provider('$$cookieWriter', function $$CookieWriterProvider() { + this.$get = $$CookieWriter; +}); diff --git a/src/ngCookies/cookies.js b/src/ngCookies/cookies.js index fcb66e62d16a..bde93ce407e4 100644 --- a/src/ngCookies/cookies.js +++ b/src/ngCookies/cookies.js @@ -19,180 +19,155 @@ angular.module('ngCookies', ['ng']). /** - * @ngdoc service - * @name $cookies - * + * @ngdoc provider + * @name $cookiesProvider * @description - * Provides read/write access to browser's cookies. - * - * 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 object's properties can only be strings. - * - * Requires the {@link ngCookies `ngCookies`} module to be installed. - * - * @example - * - * ```js - * angular.module('cookiesExample', ['ngCookies']) - * .controller('ExampleController', ['$cookies', function($cookies) { - * // Retrieving a cookie - * var favoriteCookie = $cookies.myFavorite; - * // Setting a cookie - * $cookies.myFavorite = 'oatmeal'; - * }]); - * ``` - */ - factory('$cookies', ['$rootScope', '$browser', function($rootScope, $browser) { - var cookies = {}, - lastCookies = {}, - lastBrowserCookies, - runEval = false, - copy = angular.copy, - isUndefined = angular.isUndefined; - - //creates a poller fn that copies all cookies from the $browser to service & inits the service - $browser.addPollFn(function() { - var currentCookies = $browser.cookies(); - if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl - lastBrowserCookies = currentCookies; - copy(currentCookies, lastCookies); - copy(currentCookies, cookies); - if (runEval) $rootScope.$apply(); - } - })(); - - runEval = true; - - //at the end of each eval, push cookies - //TODO: this should happen before the "delayed" watches fire, because if some cookies are not - // strings or browser refuses to store some cookies, we update the model in the push fn. - $rootScope.$watch(push); - - return cookies; - - - /** - * Pushes all the cookies from the service to the browser and verifies if all cookies were - * stored. - */ - function push() { - var name, - value, - browserCookies, - updated; - - //delete any cookies deleted in $cookies - for (name in lastCookies) { - if (isUndefined(cookies[name])) { - $browser.cookies(name, undefined); - } - } - - //update all cookies updated in $cookies - for (name in cookies) { - value = cookies[name]; - if (!angular.isString(value)) { - value = '' + value; - cookies[name] = value; - } - if (value !== lastCookies[name]) { - $browser.cookies(name, value); - updated = true; - } - } - - //verify what was actually stored - if (updated) { - updated = false; - browserCookies = $browser.cookies(); - - for (name in cookies) { - if (cookies[name] !== browserCookies[name]) { - //delete or reset all cookies that the browser dropped from $cookies - if (isUndefined(browserCookies[name])) { - delete cookies[name]; - } else { - cookies[name] = browserCookies[name]; - } - updated = true; - } - } - } - } - }]). - - - /** - * @ngdoc service - * @name $cookieStore - * @requires $cookies - * - * @description - * 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. - * - * Requires the {@link ngCookies `ngCookies`} module to be installed. - * - * @example - * - * ```js - * angular.module('cookieStoreExample', ['ngCookies']) - * .controller('ExampleController', ['$cookieStore', function($cookieStore) { - * // Put cookie - * $cookieStore.put('myFavorite','oatmeal'); - * // Get cookie - * var favoriteCookie = $cookieStore.get('myFavorite'); - * // Removing a cookie - * $cookieStore.remove('myFavorite'); - * }]); - * ``` - */ - factory('$cookieStore', ['$cookies', function($cookies) { - + * Use `$cookiesProvider` to change the default behavior of the {@link ngCookies.$cookies $cookies} service. + * */ + provider('$cookies', [function $CookiesProvider() { + /** + * @ngdoc property + * @name $cookiesProvider#defaults + * @description + * + * Object containing default options to pass when setting cookies. + * + * The object may have following properties: + * + * - **path** - `{string}` - The cookie will be available only for this path and its + * sub-paths. By default, this would be the URL that appears in your base tag. + * - **domain** - `{string}` - The cookie will be available only for this domain and + * its sub-domains. For obvious security reasons the user agent will not accept the + * cookie if the current domain is not a sub domain or equals to the requested domain. + * - **expires** - `{string|Date}` - String of the form "Wdy, DD Mon YYYY HH:MM:SS GMT" + * or a Date object indicating the exact date/time this cookie will expire. + * - **secure** - `{boolean}` - The cookie will be available only in secured connection. + * + * Note: by default the address that appears in your tag will be used as path. + * This is import so that cookies will be visible for all routes in case html5mode is enabled + * + **/ + var defaults = this.defaults = {}; + + function calcOptions(options) { + return options ? angular.extend({}, defaults, options) : defaults; + } + + /** + * @ngdoc service + * @name $cookies + * + * @description + * Provides read/write access to browser's cookies. + * + * BREAKING CHANGE: `$cookies` no longer exposes properties that represent the + * current browser cookie values. Now you must use the get/put/remove/etc. methods + * as described below. + * + * Requires the {@link ngCookies `ngCookies`} module to be installed. + * + * @example + * + * ```js + * angular.module('cookiesExample', ['ngCookies']) + * .controller('ExampleController', ['$cookies', function($cookies) { + * // Retrieving a cookie + * var favoriteCookie = $cookies.get('myFavorite'); + * // Setting a cookie + * $cookies.put('myFavorite', 'oatmeal'); + * }]); + * ``` + */ + this.$get = ['$$cookieReader', '$$cookieWriter', function($$cookieReader, $$cookieWriter) { return { /** * @ngdoc method - * @name $cookieStore#get + * @name $cookies#get * * @description * Returns the value of given cookie key * * @param {string} key Id to use for lookup. - * @returns {Object} Deserialized cookie value, undefined if the cookie does not exist. + * @returns {string} Raw cookie value. */ get: function(key) { - var value = $cookies[key]; + return $$cookieReader()[key]; + }, + + /** + * @ngdoc method + * @name $cookies#getObject + * + * @description + * Returns the deserialized value of given cookie key + * + * @param {string} key Id to use for lookup. + * @returns {Object} Deserialized cookie value. + */ + getObject: function(key) { + var value = this.get(key); return value ? angular.fromJson(value) : value; }, /** * @ngdoc method - * @name $cookieStore#put + * @name $cookies#getAll + * + * @description + * Returns a key value object with all the cookies + * + * @returns {Object} All cookies + */ + getAll: function() { + return $$cookieReader(); + }, + + /** + * @ngdoc method + * @name $cookies#put * * @description * Sets a value for given cookie key * * @param {string} key Id for the `value`. + * @param {string} value Raw value to be stored. + * @param {Object=} options Options object. + * See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults} + */ + put: function(key, value, options) { + $$cookieWriter(key, value, calcOptions(options)); + }, + + /** + * @ngdoc method + * @name $cookies#putObject + * + * @description + * Serializes and sets a value for given cookie key + * + * @param {string} key Id for the `value`. * @param {Object} value Value to be stored. + * @param {Object=} options Options object. + * See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults} */ - put: function(key, value) { - $cookies[key] = angular.toJson(value); + putObject: function(key, value, options) { + this.put(key, angular.toJson(value), options); }, /** * @ngdoc method - * @name $cookieStore#remove + * @name $cookies#remove * * @description * Remove given cookie * * @param {string} key Id of the key-value pair to delete. + * @param {Object=} options Options object. + * See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults} */ - remove: function(key) { - delete $cookies[key]; + remove: function(key, options) { + $$cookieWriter(key, undefined, calcOptions(options)); } }; - - }]); + }]; + }]); diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index dcefe12008ca..1b8be7d77b96 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -59,8 +59,6 @@ angular.mock.$Browser = function() { self.$$checkUrlChange = angular.noop; - self.cookieHash = {}; - self.lastCookieHash = {}; self.deferredFns = []; self.deferredNextId = 0; @@ -140,11 +138,6 @@ angular.mock.$Browser.prototype = { }); }, - addPollFn: function(pollFn) { - this.pollFns.push(pollFn); - return pollFn; - }, - url: function(url, replace, state) { if (angular.isUndefined(state)) { state = null; @@ -163,25 +156,6 @@ angular.mock.$Browser.prototype = { return this.$$state; }, - cookies: function(name, value) { - if (name) { - if (angular.isUndefined(value)) { - delete this.cookieHash[name]; - } else { - if (angular.isString(value) && //strings only - value.length <= 4096) { //strict cookie storage limits - this.cookieHash[name] = value; - } - } - } else { - if (!angular.equals(this.cookieHash, this.lastCookieHash)) { - this.lastCookieHash = angular.copy(this.cookieHash); - this.cookieHash = angular.copy(this.cookieHash); - } - return this.cookieHash; - } - }, - notifyWhenNoOutstandingRequests: function(fn) { fn(); } @@ -2169,7 +2143,6 @@ if (window.jasmine || window.mocha) { if (injector) { injector.get('$rootElement').off(); - injector.get('$browser').pollFns.length = 0; } // clean up jquery's fragment cache diff --git a/test/ng/browserSpecs.js b/test/ng/browserSpecs.js index 65321c24a499..3166a08a3599 100755 --- a/test/ng/browserSpecs.js +++ b/test/ng/browserSpecs.js @@ -189,10 +189,6 @@ describe('browser', function() { } }); - 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'); @@ -253,248 +249,6 @@ describe('browser', function() { }); - 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 encode 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 += 'x'; - } - - 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 decode cookie values that were encoded 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 decode special characters in cookie values', function() { - document.cookie = 'cookie_name=cookie_value_%E2%82%AC'; - expect(browser.cookies()['cookie_name']).toEqual('cookie_value_€'); - }); - - it('should not decode cookie values that do not appear to be encoded', function() { - // see #9211 - sometimes cookies contain a value that causes decodeURIComponent to throw - document.cookie = 'cookie_name=cookie_value_%XX'; - expect(browser.cookies()['cookie_name']).toEqual('cookie_value_%XX'); - }); - }); - - - 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; @@ -938,7 +692,6 @@ describe('browser', function() { fakeWindow.location.href = newUrl; }); $provide.value('$browser', browser); - browser.pollFns = []; sniffer.history = options.history; $provide.value('$sniffer', sniffer); @@ -1061,7 +814,6 @@ describe('browser', function() { beforeEach(module(function($provide, $locationProvider) { $provide.value('$browser', browser); - browser.pollFns = []; })); it('should not interfere with legacy browser url replace behavior', function() { diff --git a/test/ng/cookieReaderSpec.js b/test/ng/cookieReaderSpec.js new file mode 100644 index 000000000000..29a672953d26 --- /dev/null +++ b/test/ng/cookieReaderSpec.js @@ -0,0 +1,104 @@ +'use strict'; + +describe('$$cookieReader', function() { + var $$cookieReader; + + 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(''); + + inject(function(_$$cookieReader_) { + $$cookieReader = _$$cookieReader_; + }); + }); + + + afterEach(function() { + deleteAllCookies(); + expect(document.cookie).toEqual(''); + }); + + + describe('get via $$cookieReader()[cookieName]', function() { + + it('should return undefined for nonexistent cookie', function() { + expect($$cookieReader().nonexistent).not.toBeDefined(); + }); + + + it('should return a value for an existing cookie', function() { + document.cookie = "foo=bar=baz;path=/"; + expect($$cookieReader().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. $$cookieReader() + // should provide that value for the cookie. + document.cookie = 'foo="first"; foo="second"'; + expect($$cookieReader()['foo']).toBe('"first"'); + }); + + it('should decode cookie values that were encoded by puts', function() { + document.cookie = "cookie2%3Dbar%3Bbaz=val%3Due;path=/"; + expect($$cookieReader()['cookie2=bar;baz']).toEqual('val=ue'); + }); + + + it('should preserve leading & trailing spaces in names and values', function() { + document.cookie = '%20cookie%20name%20=%20cookie%20value%20'; + expect($$cookieReader()[' cookie name ']).toEqual(' cookie value '); + expect($$cookieReader()['cookie name']).not.toBeDefined(); + }); + + it('should decode special characters in cookie values', function() { + document.cookie = 'cookie_name=cookie_value_%E2%82%AC'; + expect($$cookieReader()['cookie_name']).toEqual('cookie_value_€'); + }); + + it('should not decode cookie values that do not appear to be encoded', function() { + // see #9211 - sometimes cookies contain a value that causes decodeURIComponent to throw + document.cookie = 'cookie_name=cookie_value_%XX'; + expect($$cookieReader()['cookie_name']).toEqual('cookie_value_%XX'); + }); + }); + + + describe('getAll via $$cookieReader()', function() { + + it('should return cookies as hash', function() { + document.cookie = "foo1=bar1;path=/"; + document.cookie = "foo2=bar2;path=/"; + expect($$cookieReader()).toEqual({'foo1':'bar1', 'foo2':'bar2'}); + }); + + + it('should return empty hash if no cookies exist', function() { + expect($$cookieReader()).toEqual({}); + }); + }); + + + it('should initialize cookie cache with existing cookies', function() { + document.cookie = "existingCookie=existingValue;path=/"; + expect($$cookieReader()).toEqual({'existingCookie':'existingValue'}); + }); + +}); + diff --git a/test/ng/httpSpec.js b/test/ng/httpSpec.js index 1a394f3e3fbf..dbb6290323ae 100644 --- a/test/ng/httpSpec.js +++ b/test/ng/httpSpec.js @@ -2,10 +2,16 @@ describe('$http', function() { - var callback; + var callback, mockedCookies; beforeEach(function() { callback = jasmine.createSpy('done'); + mockedCookies = {}; + module({ + $$cookieReader: function() { + return mockedCookies; + } + }); }); beforeEach(module(function($exceptionHandlerProvider) { @@ -691,7 +697,7 @@ describe('$http', function() { }); it('should not set XSRF cookie for cross-domain requests', inject(function($browser) { - $browser.cookies('XSRF-TOKEN', 'secret'); + mockedCookies['XSRF-TOKEN'] = 'secret'; $browser.url('http://host.com/base'); $httpBackend.expect('GET', 'http://www.test.com/url', undefined, function(headers) { return headers['X-XSRF-TOKEN'] === undefined; @@ -733,15 +739,15 @@ describe('$http', function() { $httpBackend.flush(); }); - it('should set the XSRF cookie into a XSRF header', inject(function($browser) { + it('should set the XSRF cookie into a XSRF header', inject(function() { function checkXSRF(secret, header) { return function(headers) { return headers[header || 'X-XSRF-TOKEN'] == secret; }; } - $browser.cookies('XSRF-TOKEN', 'secret'); - $browser.cookies('aCookie', 'secret2'); + mockedCookies['XSRF-TOKEN'] = 'secret'; + mockedCookies['aCookie'] = 'secret2'; $httpBackend.expect('GET', '/url', undefined, checkXSRF('secret')).respond(''); $httpBackend.expect('POST', '/url', undefined, checkXSRF('secret')).respond(''); $httpBackend.expect('PUT', '/url', undefined, checkXSRF('secret')).respond(''); @@ -809,23 +815,18 @@ describe('$http', function() { expect(config.foo).toBeUndefined(); }); - it('should check the cache before checking the XSRF cookie', inject(function($browser, $cacheFactory) { - var testCache = $cacheFactory('testCache'), - executionOrder = []; + it('should check the cache before checking the XSRF cookie', inject(function($cacheFactory) { + var testCache = $cacheFactory('testCache'); - spyOn($browser, 'cookies').andCallFake(function() { - executionOrder.push('cookies'); - return {'XSRF-TOKEN':'foo'}; - }); spyOn(testCache, 'get').andCallFake(function() { - executionOrder.push('cache'); + mockedCookies['XSRF-TOKEN'] = 'foo'; }); - $httpBackend.expect('GET', '/url', undefined).respond(''); + $httpBackend.expect('GET', '/url', undefined, function(headers) { + return headers['X-XSRF-TOKEN'] === 'foo'; + }).respond(''); $http({url: '/url', method: 'GET', cache: testCache}); $httpBackend.flush(); - - expect(executionOrder).toEqual(['cache', 'cookies']); })); }); diff --git a/test/ng/locationSpec.js b/test/ng/locationSpec.js index de0561d0c16f..bbeef29f88e6 100644 --- a/test/ng/locationSpec.js +++ b/test/ng/locationSpec.js @@ -99,7 +99,6 @@ describe('$location', function() { /* global Browser: false */ var b = new Browser($window, $document, fakeLog, sniffer); - b.pollFns = []; return b; }; }); diff --git a/test/ngCookies/cookieStoreSpec.js b/test/ngCookies/cookieStoreSpec.js new file mode 100644 index 000000000000..20436761b9f3 --- /dev/null +++ b/test/ngCookies/cookieStoreSpec.js @@ -0,0 +1,27 @@ +'use strict'; + +describe('$cookieStore', function() { + + beforeEach(module('ngCookies', { + $cookies: jasmine.createSpyObj('$cookies', ['getObject', 'putObject', 'remove']) + })); + + + it('should get cookie', inject(function($cookieStore, $cookies) { + $cookies.getObject.andReturn('value'); + expect($cookieStore.get('name')).toBe('value'); + expect($cookies.getObject).toHaveBeenCalledWith('name'); + })); + + + it('should put cookie', inject(function($cookieStore, $cookies) { + $cookieStore.put('name', 'value'); + expect($cookies.putObject).toHaveBeenCalledWith('name', 'value'); + })); + + + it('should remove cookie', inject(function($cookieStore, $cookies) { + $cookieStore.remove('name'); + expect($cookies.remove).toHaveBeenCalledWith('name'); + })); + }); diff --git a/test/ngCookies/cookieWriterSpec.js b/test/ngCookies/cookieWriterSpec.js new file mode 100644 index 000000000000..e94f2b16e7c7 --- /dev/null +++ b/test/ngCookies/cookieWriterSpec.js @@ -0,0 +1,198 @@ +'use strict'; + +describe('$$cookieWriter', function() { + var $$cookieWriter; + + 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(''); + + module('ngCookies'); + inject(function(_$$cookieWriter_) { + $$cookieWriter = _$$cookieWriter_; + }); + }); + + + afterEach(function() { + deleteAllCookies(); + expect(document.cookie).toEqual(''); + }); + + + describe('remove via $$cookieWriter(cookieName, undefined)', function() { + + it('should remove a cookie when it is present', function() { + document.cookie = 'foo=bar;path=/'; + + $$cookieWriter('foo', undefined); + + expect(document.cookie).toEqual(''); + }); + + + it('should do nothing when an nonexisting cookie is being removed', function() { + $$cookieWriter('doesntexist', undefined); + expect(document.cookie).toEqual(''); + }); + }); + + + describe('put via $$cookieWriter(cookieName, string)', function() { + + it('should create and store a cookie', function() { + $$cookieWriter('cookieName', 'cookie=Value'); + expect(document.cookie).toMatch(/cookieName=cookie%3DValue;? ?/); + }); + + + it('should overwrite an existing unsynced cookie', function() { + document.cookie = "cookie=new;path=/"; + + var oldVal = $$cookieWriter('cookie', 'newer'); + + expect(document.cookie).toEqual('cookie=newer'); + expect(oldVal).not.toBeDefined(); + }); + + it('should encode both name and value', function() { + $$cookieWriter('cookie1=', 'val;ue'); + $$cookieWriter('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', inject(function($log) { + var i, longVal = '', cookieStr; + + for (i = 0; i < 4083; i++) { + longVal += 'x'; + } + + cookieStr = document.cookie; + $$cookieWriter('x', longVal); //total size 4093-4096, so it should go through + expect(document.cookie).not.toEqual(cookieStr); + expect(document.cookie).toEqual('x=' + longVal); + expect($log.warn.logs).toEqual([]); + + $$cookieWriter('x', longVal + 'xxxx'); //total size 4097-4099, a warning should be logged + expect($log.warn.logs).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 + $$cookieWriter('x', 'shortVal'); + expect(document.cookie).toEqual('x=shortVal'); //needed to prime the cache + cookieStr = document.cookie; + $$cookieWriter('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(document.cookie).toEqual('x=shortVal'); + $log.reset(); + })); + }); + + describe('put via $$cookieWriter(cookieName, string), if no ', function() { + beforeEach(inject(function($browser) { + $browser.$$baseHref = undefined; + })); + + it('should default path in cookie to "" (empty string)', function() { + $$cookieWriter('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('cookie options', function() { + var fakeDocument, $$cookieWriter; + + function getLastCookieAssignment(key) { + return fakeDocument[0].cookie + .split(';') + .reduce(function(prev, value) { + var pair = value.split('=', 2); + if (pair[0] === key) { + if (prev === undefined) { + return pair[1] === undefined ? true : pair[1]; + } else { + throw 'duplicate key in cookie string'; + } + } else { + return prev; + } + }, undefined); + } + + beforeEach(function() { + fakeDocument = [{cookie: ''}]; + module('ngCookies', {$document: fakeDocument}); + inject(function($browser) { + $browser.$$baseHref = '/a/b'; + }); + inject(function(_$$cookieWriter_) { + $$cookieWriter = _$$cookieWriter_; + }); + }); + + it('should use baseHref as default path', function() { + $$cookieWriter('name', 'value'); + expect(getLastCookieAssignment('path')).toBe('/a/b'); + }); + + it('should accept path option', function() { + $$cookieWriter('name', 'value', {path: '/c/d'}); + expect(getLastCookieAssignment('path')).toBe('/c/d'); + }); + + it('should accept domain option', function() { + $$cookieWriter('name', 'value', {domain: '.example.com'}); + expect(getLastCookieAssignment('domain')).toBe('.example.com'); + }); + + it('should accept secure option', function() { + $$cookieWriter('name', 'value', {secure: true}); + expect(getLastCookieAssignment('secure')).toBe(true); + }); + + it('should accept expires option on set', function() { + $$cookieWriter('name', 'value', {expires: 'Fri, 19 Dec 2014 00:00:00 GMT'}); + expect(getLastCookieAssignment('expires')).toMatch(/^Fri, 19 Dec 2014 00:00:00 (UTC|GMT)$/); + }); + + it('should always use epoch time as expire time on remove', function() { + $$cookieWriter('name', undefined, {expires: 'Fri, 19 Dec 2014 00:00:00 GMT'}); + expect(getLastCookieAssignment('expires')).toMatch(/^Thu, 0?1 Jan 1970 00:00:00 (UTC|GMT)$/); + }); + + it('should accept date object as expires option', function() { + $$cookieWriter('name', 'value', {expires: new Date(Date.UTC(1981, 11, 27))}); + expect(getLastCookieAssignment('expires')).toMatch(/^Sun, 27 Dec 1981 00:00:00 (UTC|GMT)$/); + }); + +}); diff --git a/test/ngCookies/cookiesSpec.js b/test/ngCookies/cookiesSpec.js index 0a36c92f2aef..0fff36178d8b 100644 --- a/test/ngCookies/cookiesSpec.js +++ b/test/ngCookies/cookiesSpec.js @@ -1,149 +1,142 @@ 'use strict'; describe('$cookies', function() { - beforeEach(module('ngCookies', function($provide) { - $provide.factory('$browser', function() { - return angular.extend(new angular.mock.$Browser(), {cookieHash: {preexisting:'oldCookie'}}); + var mockedCookies; + + beforeEach(function() { + mockedCookies = {}; + module('ngCookies', { + $$cookieWriter: jasmine.createSpy('$$cookieWriter').andCallFake(function(name, value) { + mockedCookies[name] = value; + }), + $$cookieReader: function() { + return mockedCookies; + } }); - })); + }); - 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'}); + it('should serialize objects to json', inject(function($cookies) { + $cookies.putObject('objectCookie', {id: 123, name: 'blah'}); + expect($cookies.get('objectCookie')).toEqual('{"id":123,"name":"blah"}'); + })); - // 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'}); + it('should deserialize json to object', inject(function($cookies) { + $cookies.put('objectCookie', '{"id":123,"name":"blah"}'); + expect($cookies.getObject('objectCookie')).toEqual({id: 123, name: 'blah'}); + })); - $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 delete objects from the store when remove is called', inject(function($cookies) { + $cookies.putObject('gonner', { "I'll":"Be Back"}); + expect($cookies.get('gonner')).toEqual('{"I\'ll":"Be Back"}'); + $cookies.remove('gonner'); + expect($cookies.get('gonner')).toEqual(undefined); })); - 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'}); + it('should handle empty string value cookies', inject(function($cookies) { + $cookies.putObject("emptyCookie",''); + expect($cookies.get('emptyCookie')).toEqual('""'); + expect($cookies.getObject("emptyCookie")).toEqual(''); + mockedCookies['blankCookie'] = ''; + expect($cookies.getObject("blankCookie")).toEqual(''); + })); - $cookies.oatmealCookie = 'gone'; - $rootScope.$digest(); - expect($browser.cookies()). - toEqual({'preexisting': 'oldCookie', 'oatmealCookie': 'gone'}); + it('should put cookie value without serializing', inject(function($cookies) { + $cookies.put('name', 'value'); + $cookies.put('name2', '"value2"'); + expect($cookies.get('name')).toEqual('value'); + expect($cookies.getObject('name2')).toEqual('value2'); })); - it('should convert non-string values to string', - inject(function($cookies, $browser, $rootScope) { - $cookies.nonString = [1, 2, 3]; - $cookies.nullVal = null; - $cookies.undefVal = undefined; - var preexisting = $cookies.preexisting = function() {}; - $rootScope.$digest(); - expect($browser.cookies()).toEqual({ - 'preexisting': '' + preexisting, - 'nonString': '1,2,3', - 'nullVal': 'null', - 'undefVal': 'undefined' - }); - expect($cookies).toEqual({ - 'preexisting': '' + preexisting, - 'nonString': '1,2,3', - 'nullVal': 'null', - 'undefVal': 'undefined' - }); + it('should get cookie value without deserializing', inject(function($cookies) { + $cookies.put('name', 'value'); + $cookies.putObject('name2', 'value2'); + expect($cookies.get('name')).toEqual('value'); + expect($cookies.get('name2')).toEqual('"value2"'); })); + it('should get all the cookies', inject(function($cookies) { + $cookies.put('name', 'value'); + $cookies.putObject('name2', 'value2'); + expect($cookies.getAll()).toEqual({name: 'value', name2: '"value2"'}); + })); - 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 pass options on put', inject(function($cookies, $$cookieWriter) { + $cookies.put('name', 'value', {path: '/a/b'}); + expect($$cookieWriter).toHaveBeenCalledWith('name', 'value', {path: '/a/b'}); })); - it('should drop or reset cookies that browser refused to store', - inject(function($cookies, $browser, $rootScope) { - var i, longVal; + it('should pass options on putObject', inject(function($cookies, $$cookieWriter) { + $cookies.putObject('name', 'value', {path: '/a/b'}); + expect($$cookieWriter).toHaveBeenCalledWith('name', '"value"', {path: '/a/b'}); + })); - for (i = 0; i < 5000; i++) { - longVal += '*'; - } - //drop if no previous value - $cookies.longCookie = longVal; - $rootScope.$digest(); - expect($cookies).toEqual({'preexisting': 'oldCookie'}); + it('should pass options on remove', inject(function($cookies, $$cookieWriter) { + $cookies.remove('name', {path: '/a/b'}); + expect($$cookieWriter).toHaveBeenCalledWith('name', undefined, {path: '/a/b'}); + })); - //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'}); - })); -}); + it('should pass default options on put', function() { + module(function($cookiesProvider) { + $cookiesProvider.defaults.secure = true; + }); + inject(function($cookies, $$cookieWriter) { + $cookies.put('name', 'value', {path: '/a/b'}); + expect($$cookieWriter).toHaveBeenCalledWith('name', 'value', {path: '/a/b', secure: true}); + }); + }); -describe('$cookieStore', function() { + it('should pass default options on putObject', function() { + module(function($cookiesProvider) { + $cookiesProvider.defaults.secure = true; + }); + inject(function($cookies, $$cookieWriter) { + $cookies.putObject('name', 'value', {path: '/a/b'}); + expect($$cookieWriter).toHaveBeenCalledWith('name', '"value"', {path: '/a/b', secure: true}); + }); + }); - beforeEach(module('ngCookies')); - it('should serialize objects to json', inject(function($cookieStore, $browser, $rootScope) { - $cookieStore.put('objectCookie', {id: 123, name: 'blah'}); - $rootScope.$digest(); - expect($browser.cookies()).toEqual({'objectCookie': '{"id":123,"name":"blah"}'}); - })); + it('should pass default options on remove', function() { + module(function($cookiesProvider) { + $cookiesProvider.defaults.secure = true; + }); + inject(function($cookies, $$cookieWriter) { + $cookies.remove('name', {path: '/a/b'}); + expect($$cookieWriter).toHaveBeenCalledWith('name', undefined, {path: '/a/b', secure: true}); + }); + }); - it('should deserialize json to object', inject(function($cookieStore, $browser) { - $browser.cookies('objectCookie', '{"id":123,"name":"blah"}'); - $browser.poll(); - expect($cookieStore.get('objectCookie')).toEqual({id: 123, name: 'blah'}); - })); + it('should let passed options override default options', function() { + module(function($cookiesProvider) { + $cookiesProvider.defaults.secure = true; + }); + inject(function($cookies, $$cookieWriter) { + $cookies.put('name', 'value', {secure: false}); + expect($$cookieWriter).toHaveBeenCalledWith('name', 'value', {secure: false}); + }); + }); - it('should delete objects from the store when remove is called', inject(function($cookieStore, $browser, $rootScope) { - $cookieStore.put('gonner', { "I'll":"Be Back"}); - $rootScope.$digest(); //force eval in test - $browser.poll(); - expect($browser.cookies()).toEqual({'gonner': '{"I\'ll":"Be Back"}'}); + it('should pass default options if no options are passed', function() { + module(function($cookiesProvider) { + $cookiesProvider.defaults.secure = true; + }); + inject(function($cookies, $$cookieWriter) { + $cookies.put('name', 'value'); + expect($$cookieWriter).toHaveBeenCalledWith('name', 'value', {secure: true}); + }); + }); - $cookieStore.remove('gonner'); - $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(''); - })); -}); + });