diff --git a/.bowerrc b/.bowerrc deleted file mode 100644 index 1bbcb37..0000000 --- a/.bowerrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "directory": "bower_components", - "json": "bower.json" -} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0569579..ab1a12a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,4 @@ coverage/Phantom*/* coverage/Firefox*/* coverage/ -bower_components/ \ No newline at end of file +bower_components/ diff --git a/.jshintrc b/.jshintrc index 78d2072..5be378b 100644 --- a/.jshintrc +++ b/.jshintrc @@ -20,6 +20,7 @@ "trailing": true, "smarttabs": true, "predef": [ + "assert", "inject", "describe", "describe", @@ -37,4 +38,4 @@ "globals": { "angular": false } -} \ No newline at end of file +} diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..fac0c6d --- /dev/null +++ b/.npmignore @@ -0,0 +1,9 @@ +.* +bower.json +*.iml +.idea/ +test/ +coverage/ +bower_components/ +karma* +Gruntfile.js diff --git a/.travis.yml b/.travis.yml index 8b443b9..592057f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,4 +10,4 @@ before_script: - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start script: - - grunt cli + - grunt ci diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c0a462..94868ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +##### 3.0.0-beta.1 24 February 2014 + +###### Breaking API changes +- `maxAge` and `deleteOnExpire` are no longer overridable for individual items +- Renamed angular module to `angular-data.DSCacheFactory`. Angular-cache is now part of the `angular-data` namespace +- The `verifyIntegrity` option has been completely removed due to a cache being exclusively in-memory OR in web storage #96 +- Supported values for the `storageMode` option are now: `"memory"`, `"localStorage"` or `"sessionStorage"` with the default being `"memory"` +- `DSCache#put(key, value)` no longer accepts a third `options` argument +- `DSCache#removeExpired()` no longer accepts an `options` argument and thus no longer supports returning removed expired items as an array +- `DSCache#remove(key)` no longer accepts an `options` argument +- `DSCache#setOptions(options[, strict])` no longer accepts `storageMode` and `storageImpl` as part of the `options` argument +- `storageMode` is no longer dynamically configurable +- `storageImpl` is no longer dynamically configurable + +###### Backwards compatible API changes +- Added `DSCache#enable()` +- Added `DSCache#disable()` +- Added `DSCache#setCapacity(capacity)` +- Added `DSCache#setMaxAge(maxAge)` +- Added `DSCache#setCacheFlushInterval(cacheFlushInterval)` +- Added `DSCache#setRecycleFreq(recycleFreq)` +- Added `DSCache#setDeleteOnExpire(deleteOnExpire)` +- Added `DSCache#setOnExpire(onExpire)` +- Added option `storagePrefix` for customizing the prefix used in `localStorage`, etc. #98 +- Refactored to be in-memory OR webStorage, never both #96 + +###### Other +- I might have missed something... + ##### 2.3.3 - 24 February 2014 ###### Backwards compatible bug fixes diff --git a/Gruntfile.js b/Gruntfile.js index 5756d56..19cb9d1 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,247 +1,278 @@ /* * angular-cache - * http://github.com/jmdobry/angular-cache + * https://github.com/jmdobry/angular-cache * - * Copyright (c) 2013 Jason Dobry + * Copyright (c) 2014 Jason Dobry * Licensed under the MIT license. */ module.exports = function (grunt) { - 'use strict'; + 'use strict'; - require('load-grunt-tasks')(grunt); - require('time-grunt')(grunt); + require('load-grunt-tasks')(grunt); + require('time-grunt')(grunt); - // Project configuration. - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - // these folders will no longer be checked into development branches - clean: { - pre: ['dist/', 'coverage/'], - post: ['coverage/'] - }, - jshint: { - all: ['Gruntfile.js', 'src/**/*.js', 'test/*.js'], - jshintrc: '.jshintrc' - }, - copy: { - options: { - processContent: function (contents) { - contents = contents.replace(/<%= pkg.version %>/g, grunt.file.readJSON('package.json').version); - return contents; - } - }, - dist: { - src: ['src/angular-cache.js'], - dest: 'dist/angular-cache.js' - } - }, - uglify: { - main: { - options: { - banner: '/**\n' + - '* @author Jason Dobry \n' + - '* @file angular-cache.min.js\n' + - '* @version <%= pkg.version %> - Homepage \n' + - '* @copyright (c) 2013 Jason Dobry \n' + - '* @license MIT \n' + - '*\n' + - '* @overview angular-cache is a very useful replacement for Angular\'s $cacheFactory.\n' + - '*/\n' - }, - files: { - 'dist/angular-cache.min.js': ['dist/angular-cache.js'] - } - } - }, - karma: { - options: { - configFile: './karma.conf.js' - }, - dev: { - browsers: ['Chrome'], - autoWatch: true, - singleRun: false - }, - '1.0.4': { - options: { - files: [ - 'bower_components/angular-1.0.4/angular.js', - 'bower_components/angular-mocks-1.0.4/angular-mocks.js', - 'src/angular-cache.js', - 'test/karma.start.js', - 'test/*.js' - ] - } - }, - '1.0.5': { - options: { - files: [ - 'bower_components/angular-1.0.5/angular.js', - 'bower_components/angular-mocks-1.0.5/angular-mocks.js', - 'src/angular-cache.js', - 'test/karma.start.js', - 'test/*.js' - ] - } - }, - '1.0.6': { - options: { - files: [ - 'bower_components/angular-1.0.6/angular.js', - 'bower_components/angular-mocks-1.0.6/angular-mocks.js', - 'src/angular-cache.js', - 'test/karma.start.js', - 'test/*.js' - ] - } - }, - '1.0.7': { - options: { - files: [ - 'bower_components/angular-1.0.7/angular.js', - 'bower_components/angular-mocks-1.0.7/angular-mocks.js', - 'src/angular-cache.js', - 'test/karma.start.js', - 'test/*.js' - ] - } - }, - '1.0.8': { - options: { - files: [ - 'bower_components/angular-1.0.8/angular.js', - 'bower_components/angular-mocks-1.0.8/angular-mocks.js', - 'src/angular-cache.js', - 'test/karma.start.js', - 'test/*.js' - ] - } - }, - '1.1.4': { - options: { - files: [ - 'bower_components/angular-1.1.4/angular.js', - // hopefully this works. 1.1.4 isn't available on bower - 'bower_components/angular-mocks-1.1.5/angular-mocks.js', - 'src/angular-cache.js', - 'test/karma.start.js', - 'test/*.js' - ] - } - }, - '1.1.5': { - options: { - files: [ - 'bower_components/angular-1.1.5/angular.js', - 'bower_components/angular-mocks-1.1.5/angular-mocks.js', - 'src/angular-cache.js', - 'test/karma.start.js', - 'test/*.js' - ] - } - }, - '1.2.0': { - options: { - files: [ - 'bower_components/angular-1.2.0/angular.js', - 'bower_components/angular-mocks-1.2.0/angular-mocks.js', - 'src/angular-cache.js', - 'test/karma.start.js', - 'test/*.js' - ] - } - }, - '1.2.1': { - options: { - files: [ - 'bower_components/angular-1.2.1/angular.js', - 'bower_components/angular-mocks-1.2.1/angular-mocks.js', - 'src/angular-cache.js', - 'test/karma.start.js', - 'test/*.js' - ] - } - }, - '1.2.2': { - options: { - files: [ - 'bower_components/angular-1.2.2/angular.js', - 'bower_components/angular-mocks-1.2.2/angular-mocks.js', - 'src/angular-cache.js', - 'test/karma.start.js', - 'test/*.js' - ] - } - }, - '1.2.3': { - options: { - files: [ - 'bower_components/angular-1.2.3/angular.js', - 'bower_components/angular-mocks-1.2.3/angular-mocks.js', - 'src/angular-cache.js', - 'test/karma.start.js', - 'test/*.js' - ] - } - }, - '1.2.4': { - options: { - files: [ - 'bower_components/angular-1.2.4/angular.js', - 'bower_components/angular-mocks-1.2.4/angular-mocks.js', - 'src/angular-cache.js', - 'test/karma.start.js', - 'test/*.js' - ] - } - }, - '1.2.5': { - options: { - files: [ - 'bower_components/angular-1.2.5/angular.js', - 'bower_components/angular-mocks-1.2.5/angular-mocks.js', - 'src/angular-cache.js', - 'test/karma.start.js', - 'test/*.js' - ] - } - }, - '1.2.6': { - options: { - files: [ - 'bower_components/angular-1.2.6/angular.js', - 'bower_components/angular-mocks-1.2.6/angular-mocks.js', - 'src/angular-cache.js', - 'test/karma.start.js', - 'test/*.js' - ] - } - }, - '1.2.7': { - options: { - files: [ - 'bower_components/angular-1.2.7/angular.js', - 'bower_components/angular-mocks-1.2.7/angular-mocks.js', - 'src/angular-cache.js', - 'test/karma.start.js', - 'test/*.js' - ] - } - } - }, - coveralls: { - options: { - coverage_dir: 'coverage' - } - } - }); + var pkg = grunt.file.readJSON('package.json'); - grunt.registerTask('test', ['karma:1.1.5']); - grunt.registerTask('release', ['clean:pre', 'jshint', 'copy', 'uglify', 'test', 'clean:post']); - grunt.registerTask('build', ['release', 'clean:pre']); - grunt.registerTask('default', ['build']); + // Project configuration. + grunt.initConfig({ + pkg: pkg, + clean: { + dist: ['dist/'], + coverage: ['coverage/'] + }, + jshint: { + all: ['Gruntfile.js', 'src/**/*.js', 'test/unit/**/*.js'], + jshintrc: '.jshintrc' + }, + watch: { + files: ['src/**/*.js'], + tasks: ['build'] + }, + uglify: { + main: { + options: { + banner: '/**\n' + + '* @author Jason Dobry \n' + + '* @file angular-cache.min.js\n' + + '* @version <%= pkg.version %> - Homepage \n' + + '* @copyright (c) 2013 Jason Dobry \n' + + '* @license MIT \n' + + '*\n' + + '* @overview angular-cache is a very useful replacement for Angular\'s $cacheFactory.\n' + + '*/\n' + }, + files: { + 'dist/angular-cache.min.js': ['dist/angular-cache.js'] + } + } + }, + browserify: { + options: { + alias: ['src/defaults/index.js:Defaults'] + }, + dist: { + files: { + 'dist/angular-cache.js': ['src/index.js'] + } + } + }, + karma: { + options: { + configFile: './karma.conf.js' + }, + dev: { + browsers: ['Chrome'], + autoWatch: true, + singleRun: false + }, + '1.0.4': { + options: { + files: [ + 'bower_components/angular-1.0.4/angular.js', + 'bower_components/angular-mocks-1.0.4/angular-mocks.js', + 'dist/angular-cache.js', + './karma.start.js', + 'test/unit/**/*.js' + ] + } + }, + '1.0.5': { + options: { + files: [ + 'bower_components/angular-1.0.5/angular.js', + 'bower_components/angular-mocks-1.0.5/angular-mocks.js', + 'dist/angular-cache.js', + './karma.start.js', + 'test/unit/**/*.js' + ] + } + }, + '1.0.6': { + options: { + files: [ + 'bower_components/angular-1.0.6/angular.js', + 'bower_components/angular-mocks-1.0.6/angular-mocks.js', + 'dist/angular-cache.js', + './karma.start.js', + 'test/unit/**/*.js' + ] + } + }, + '1.0.7': { + options: { + files: [ + 'bower_components/angular-1.0.7/angular.js', + 'bower_components/angular-mocks-1.0.7/angular-mocks.js', + 'dist/angular-cache.js', + './karma.start.js', + 'test/unit/**/*.js' + ] + } + }, + '1.0.8': { + options: { + files: [ + 'bower_components/angular-1.0.8/angular.js', + 'bower_components/angular-mocks-1.0.8/angular-mocks.js', + 'dist/angular-cache.js', + './karma.start.js', + 'test/unit/**/*.js' + ] + } + }, + '1.1.4': { + options: { + files: [ + 'bower_components/angular-1.1.4/angular.js', + // hopefully this works. 1.1.4 isn't available on bower + 'bower_components/angular-mocks-1.1.5/angular-mocks.js', + 'dist/angular-cache.js', + './karma.start.js', + 'test/unit/**/*.js' + ] + } + }, + '1.1.5': { + options: { + files: [ + 'bower_components/angular-1.1.5/angular.js', + 'bower_components/angular-mocks-1.1.5/angular-mocks.js', + 'dist/angular-cache.js', + './karma.start.js', + 'test/unit/**/*.js' + ] + } + }, + '1.2.0': { + options: { + files: [ + 'bower_components/angular-1.2.0/angular.js', + 'bower_components/angular-mocks-1.2.0/angular-mocks.js', + 'dist/angular-cache.js', + './karma.start.js', + 'test/unit/**/*.js' + ] + } + }, + '1.2.1': { + options: { + files: [ + 'bower_components/angular-1.2.1/angular.js', + 'bower_components/angular-mocks-1.2.1/angular-mocks.js', + 'dist/angular-cache.js', + './karma.start.js', + 'test/unit/**/*.js' + ] + } + }, + '1.2.2': { + options: { + files: [ + 'bower_components/angular-1.2.2/angular.js', + 'bower_components/angular-mocks-1.2.2/angular-mocks.js', + 'dist/angular-cache.js', + './karma.start.js', + 'test/unit/**/*.js' + ] + } + }, + '1.2.3': { + options: { + files: [ + 'bower_components/angular-1.2.3/angular.js', + 'bower_components/angular-mocks-1.2.3/angular-mocks.js', + 'dist/angular-cache.js', + './karma.start.js', + 'test/unit/**/*.js' + ] + } + }, + '1.2.4': { + options: { + files: [ + 'bower_components/angular-1.2.4/angular.js', + 'bower_components/angular-mocks-1.2.4/angular-mocks.js', + 'dist/angular-cache.js', + './karma.start.js', + 'test/unit/**/*.js' + ] + } + }, + '1.2.5': { + options: { + files: [ + 'bower_components/angular-1.2.5/angular.js', + 'bower_components/angular-mocks-1.2.5/angular-mocks.js', + 'dist/angular-cache.js', + './karma.start.js', + 'test/unit/**/*.js' + ] + } + }, + '1.2.6': { + options: { + files: [ + 'bower_components/angular-1.2.6/angular.js', + 'bower_components/angular-mocks-1.2.6/angular-mocks.js', + 'dist/angular-cache.js', + './karma.start.js', + 'test/unit/**/*.js' + ] + } + }, + '1.2.7': { + options: { + files: [ + 'bower_components/angular-1.2.7/angular.js', + 'bower_components/angular-mocks-1.2.7/angular-mocks.js', + 'dist/angular-cache.js', + './karma.start.js', + 'test/unit/**/*.js' + ] + } + } + }, + coveralls: { + options: { + coverage_dir: 'coverage' + } + } + }); - // Used by the CLI build servers - grunt.registerTask('test-cli', ['karma:1.0.4', 'karma:1.0.5', 'karma:1.0.6', 'karma:1.0.7', 'karma:1.0.8', 'karma:1.1.4', 'karma:1.1.5', 'karma:1.2.1', 'karma:1.2.2', 'karma:1.2.3', 'karma:1.2.4', 'karma:1.2.5', 'karma:1.2.6', 'karma:1.2.7']); - grunt.registerTask('cli', ['clean', 'jshint', 'copy', 'uglify', 'test-cli', 'coveralls']); + grunt.registerTask('version', function (filePath) { + var file = grunt.file.read(filePath); + + file = file.replace(/<%= pkg\.version %>/gi, pkg.version); + + grunt.file.write(filePath, file); + }); + + grunt.registerTask('test', ['karma:1.2.7']); + grunt.registerTask('build', [ + 'clean', + 'jshint', + 'browserify', + 'version:dist/angular-cache.js', + 'uglify' + ]); + grunt.registerTask('default', ['build']); + + // Used by the CLI build servers + grunt.registerTask('test-ci', [ +// 'karma:1.0.4', +// 'karma:1.0.5', +// 'karma:1.0.6', +// 'karma:1.0.7', +// 'karma:1.0.8', +// 'karma:1.1.4', +// 'karma:1.1.5', + 'karma:1.2.1', + 'karma:1.2.2', + 'karma:1.2.3', + 'karma:1.2.4', + 'karma:1.2.5', + 'karma:1.2.6', + 'karma:1.2.7' + ]); + grunt.registerTask('ci', ['build', 'test-ci', 'coveralls']); }; diff --git a/README.md b/README.md index 993cb46..f12c074 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ ## angular-cache __A very useful replacement for Angular's $cacheFactory.__ -__Version:__ 2.3.1 +__Version:__ 3.0.0-beta.1 -All [Documentation](http://jmdobry.github.io/angular-cache) can be found at [jmdobry.github.io/angular-cache](http://jmdobry.github.io/angular-cache). +Documentation for 3.x.x can be found at [angular-data.codetrain.io](http://angular-data.codetrain.io). + +Documentation for 2.x.x can be found at [jmdobry.github.io/angular-cache](http://jmdobry.github.io/angular-cache). + +See [TRANSITION.md](https://github.com/jmdobry/angular-cache/blob/master/TRANSITION.md) for upgrading from 2.x.x to 3.x.x. ## License [MIT License](https://github.com/jmdobry/angular-cache/blob/master/LICENSE) diff --git a/bower.json b/bower.json index cfadea9..e2ba1e5 100644 --- a/bower.json +++ b/bower.json @@ -2,8 +2,8 @@ "author": "Jason Dobry", "name": "angular-cache", "description": "angular-cache is a very useful replacement for Angular's $cacheFactory.", - "version": "2.3.3", - "homepage": "http://jmdobry.github.io/angular-cache/", + "version": "3.0.0-beta.1", + "homepage": "https://github.com/jmdobry/angular-cache", "repository": { "type": "git", "url": "git://github.com/jmdobry/angular-cache.git" @@ -11,22 +11,19 @@ "main": "./dist/angular-cache.min.js", "ignore": [ ".idea/", - ".bowerrc", + ".*", "*.iml", "src/", + "bower_components/", "coverage/", - ".jshintrc", - "demo/", - ".jslintrc", "Gruntfile.js", + "package.json", "CONTRIBUTING.md", - "TRANSITION.md", "node_modules/", "test/", "karma.conf.js", - "libpeerconnection.log", - ".gitignore", - ".travis.yml" + "karma.start.js", + "libpeerconnection.log" ], "devDependencies": { "angular-1.0.4": "angular#1.0.4", @@ -44,6 +41,7 @@ "angular-1.2.5": "angular#1.2.5", "angular-1.2.6": "angular#1.2.6", "angular-1.2.7": "angular#1.2.7", + "angular-1.2.13": "angular#1.2.13", "angular-mocks-1.0.4": "angular-mocks#1.0.4", "angular-mocks-1.0.5": "angular-mocks#1.0.5", "angular-mocks-1.0.6": "angular-mocks#1.0.6", @@ -57,7 +55,8 @@ "angular-mocks-1.2.4": "angular-mocks#1.2.4", "angular-mocks-1.2.5": "angular-mocks#1.2.5", "angular-mocks-1.2.6": "angular-mocks#1.2.6", - "angular-mocks-1.2.7": "angular-mocks#1.2.7" + "angular-mocks-1.2.7": "angular-mocks#1.2.7", + "angular-mocks-1.2.13": "angular-mocks#1.2.13" }, "resolutions": { "angular": "1.0.8" diff --git a/dist/angular-cache.js b/dist/angular-cache.js index 27b3937..bdbf8fa 100644 --- a/dist/angular-cache.js +++ b/dist/angular-cache.js @@ -1,1194 +1,2317 @@ /** * @author Jason Dobry - * @file angular-cache-2.3.2.js - * @version 2.3.2 - Homepage - * @copyright (c) 2013 Jason Dobry + * @file angular-cache.js + * @version 3.0.0-beta.1 - Homepage + * @copyright (c) 2013 Jason Dobry * @license MIT * * @overview angular-cache is a very useful replacement for Angular's $cacheFactory. */ -(function (window, angular, undefined) { - 'use strict'; - - angular.module('jmdobry.binary-heap', []); - - /** - * @desc Provider for the BinaryHeap. - */ - function BinaryHeapProvider() { - this.$get = function () { - /** - * @method bubbleUp - * @param {array} heap The heap. - * @param {function} weightFunc The weight function. - * @param {number} n The index of the element to bubble up. - */ - function bubbleUp(heap, weightFunc, n) { - var element = heap[n], - weight = weightFunc(element); - // When at 0, an element can not go up any further. - while (n > 0) { - // Compute the parent element's index, and fetch it. - var parentN = Math.floor((n + 1) / 2) - 1, - parent = heap[parentN]; - // If the parent has a lesser weight, things are in order and we - // are done. - if (weight >= weightFunc(parent)) { - break; - } else { - heap[parentN] = element; - heap[n] = parent; - n = parentN; - } +require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) { + // Compute the parent element's index, and fetch it. + var parentN = Math.floor((n + 1) / 2) - 1, + parent = heap[parentN]; + // If the parent has a lesser weight, things are in order and we + // are done. + if (weight >= weightFunc(parent)) { + break; + } else { + heap[parentN] = element; + heap[n] = parent; + n = parentN; } } + } - /** - * @method bubbleDown - * @param {array} heap The heap. - * @param {function} weightFunc The weight function. - * @param {number} n The index of the element to sink down. - */ - function bubbleDown(heap, weightFunc, n) { - var length = heap.length, - node = heap[n], - nodeWeight = weightFunc(node); - - while (true) { - var child2N = (n + 1) * 2, - child1N = child2N - 1; - var swap = null; - if (child1N < length) { - var child1 = heap[child1N], - child1Weight = weightFunc(child1); - // If the score is less than our node's, we need to swap. - if (child1Weight < nodeWeight) { - swap = child1N; - } + /** + * @method bubbleDown + * @param {array} heap The heap. + * @param {function} weightFunc The weight function. + * @param {number} n The index of the element to sink down. + */ + function bubbleDown(heap, weightFunc, n) { + var length = heap.length, + node = heap[n], + nodeWeight = weightFunc(node); + + while (true) { + var child2N = (n + 1) * 2, + child1N = child2N - 1; + var swap = null; + if (child1N < length) { + var child1 = heap[child1N], + child1Weight = weightFunc(child1); + // If the score is less than our node's, we need to swap. + if (child1Weight < nodeWeight) { + swap = child1N; } - // Do the same checks for the other child. - if (child2N < length) { - var child2 = heap[child2N], - child2Weight = weightFunc(child2); - if (child2Weight < (swap === null ? nodeWeight : weightFunc(heap[child1N]))) { - swap = child2N; - } + } + // Do the same checks for the other child. + if (child2N < length) { + var child2 = heap[child2N], + child2Weight = weightFunc(child2); + if (child2Weight < (swap === null ? nodeWeight : weightFunc(heap[child1N]))) { + swap = child2N; } + } - if (swap === null) { - break; - } else { - heap[n] = heap[swap]; - heap[swap] = node; - n = swap; - } + if (swap === null) { + break; + } else { + heap[n] = heap[swap]; + heap[swap] = node; + n = swap; } } + } - /** - * @class BinaryHeap - * @desc BinaryHeap implementation of a priority queue. - * @param {function} weightFunc Function that returns the value that should be used for node value comparison. - * @example - * angular.module('app').controller(function (BinaryHeap) { - * var bHeap = new BinaryHeap(function (x) { + /** + * @class DSBinaryHeap + * @desc DSBinaryHeap implementation of a priority queue. + * @param {function} weightFunc Function that returns the value that should be used for node value comparison. + * @example + * angular.module('app').controller(function (DSBinaryHeap) { + * var bHeap = new DSBinaryHeap(function (x) { * return x.value; * }); * ); */ - function BinaryHeap(weightFunc) { - if (weightFunc && !angular.isFunction(weightFunc)) { - throw new Error('BinaryHeap(weightFunc): weightFunc: must be a function!'); - } - weightFunc = weightFunc || function (x) { - return x; - }; - this.weightFunc = weightFunc; - this.heap = []; + function DSBinaryHeap(weightFunc) { + if (weightFunc && !angular.isFunction(weightFunc)) { + throw new Error('DSBinaryHeap(weightFunc): weightFunc: must be a function!'); } - - /** - * @method BinaryHeap.push - * @desc Push an element into the binary heap. - * @param {*} node The element to push into the binary heap. - */ - BinaryHeap.prototype.push = function (node) { - this.heap.push(node); - bubbleUp(this.heap, this.weightFunc, this.heap.length - 1); - }; - - /** - * @method BinaryHeap.peek - * @desc Return, but do not remove, the minimum element in the binary heap. - * @returns {*} - */ - BinaryHeap.prototype.peek = function () { - return this.heap[0]; - }; - - /** - * @method BinaryHeap.pop - * @desc Remove and return the minimum element in the binary heap. - * @returns {*} - */ - BinaryHeap.prototype.pop = function () { - var front = this.heap[0], - end = this.heap.pop(); - if (this.heap.length > 0) { - this.heap[0] = end; - bubbleDown(this.heap, this.weightFunc, 0); - } - return front; - }; - - /** - * @method BinaryHeap.remove - * @desc Remove the first node in the priority queue that satisfies angular.equals comparison with - * the given node. - * @param {*} node The node to remove. - * @returns {*} The removed node. - */ - BinaryHeap.prototype.remove = function (node) { - var length = this.heap.length; - for (var i = 0; i < length; i++) { - if (angular.equals(this.heap[i], node)) { - var removed = this.heap[i], - end = this.heap.pop(); - if (i !== length - 1) { - this.heap[i] = end; - bubbleUp(this.heap, this.weightFunc, i); - bubbleDown(this.heap, this.weightFunc, i); - } - return removed; - } - } - return null; - }; - - /** - * @method BinaryHeap.removeAll - * @desc Remove all nodes from this BinaryHeap. - */ - BinaryHeap.prototype.removeAll = function () { - this.heap = []; - }; - - /** - * @method BinaryHeap.size - * @desc Return the size of the priority queue. - * @returns {number} The size of the priority queue. - */ - BinaryHeap.prototype.size = function () { - return this.heap.length; + weightFunc = weightFunc || function (x) { + return x; }; + this.weightFunc = weightFunc; + this.heap = []; + } - return BinaryHeap; + /** + * @method DSBinaryHeap.push + * @desc Push an element into the binary heap. + * @param {*} node The element to push into the binary heap. + */ + DSBinaryHeap.prototype.push = function (node) { + this.heap.push(node); + bubbleUp(this.heap, this.weightFunc, this.heap.length - 1); }; - } - - angular.module('jmdobry.binary-heap').provider('BinaryHeap', BinaryHeapProvider); - - /** - * @desc Provides an $AngularCacheFactoryProvider, which gives you the ability to use an - * $angularCacheFactory. The $angularCacheFactory produces AngularCache objects, which - * the same abilities as the cache objects that come with Angular, except with some added - * functionality. - */ - angular.module('jmdobry.angular-cache', ['ng', 'jmdobry.binary-heap']); - - /** - * @class $AngularCacheFactoryProvider - * @desc Provider for the $angularCacheFactory. - * @see {@link http://docs.angularjs.org/api/ng.$cacheFactory|ng.$cacheFactory} - */ - function $AngularCacheFactoryProvider() { - - var cacheDefaults, - DEFAULTS = function () { - return { - capacity: Number.MAX_VALUE, - maxAge: null, - deleteOnExpire: 'none', - onExpire: null, - cacheFlushInterval: null, - recycleFreq: 1000, - storageMode: 'none', - storageImpl: null, - verifyIntegrity: true, - disabled: false - }; - }; /** - * @method _validateNumberOption - * @desc Validates the given number option. - * @param {number} option The number option to check. - * @param {function} cb Callback function + * @method DSBinaryHeap.peek + * @desc Return, but do not remove, the minimum element in the binary heap. + * @returns {*} */ - function _validateNumberOption(option, cb) { - if (!angular.isNumber(option)) { - cb('must be a number!'); - } else if (option < 0) { - cb('must be greater than zero!'); - } else { - cb(null); - } - } + DSBinaryHeap.prototype.peek = function () { + return this.heap[0]; + }; /** - * @method $AngularCacheFactoryProvider.setCacheDefaults - * @desc Set the default configuration for all caches created by $angularCacheFactory. - * @param {object} options + * @method DSBinaryHeap.pop + * @desc Remove and return the minimum element in the binary heap. + * @returns {*} */ - this.setCacheDefaults = function (options) { - var errStr = '$angularCacheFactoryProvider.setCacheDefaults(options): '; - options = options || {}; - - if (!angular.isObject(options)) { - throw new Error(errStr + 'options: must be an object!'); - } - - if ('disabled' in options) { - options.disabled = options.disabled === true; - } - - if ('capacity' in options) { - _validateNumberOption(options.capacity, function (err) { - if (err) { - throw new Error(errStr + 'capacity: ' + err); - } - }); - } - - if ('deleteOnExpire' in options) { - if (!angular.isString(options.deleteOnExpire)) { - throw new Error(errStr + 'deleteOnExpire: must be a string!'); - } else if (options.deleteOnExpire !== 'none' && options.deleteOnExpire !== 'passive' && options.deleteOnExpire !== 'aggressive') { - throw new Error(errStr + 'deleteOnExpire: accepted values are "none", "passive" or "aggressive"!'); - } - } - - if ('maxAge' in options) { - _validateNumberOption(options.maxAge, function (err) { - if (err) { - throw new Error(errStr + 'maxAge: ' + err); - } - }); - } - - if ('recycleFreq' in options) { - _validateNumberOption(options.recycleFreq, function (err) { - if (err) { - throw new Error(errStr + 'recycleFreq: ' + err); - } - }); - } - - if ('cacheFlushInterval' in options) { - _validateNumberOption(options.cacheFlushInterval, function (err) { - if (err) { - throw new Error(errStr + 'cacheFlushInterval: ' + err); - } - }); + DSBinaryHeap.prototype.pop = function () { + var front = this.heap[0], + end = this.heap.pop(); + if (this.heap.length > 0) { + this.heap[0] = end; + bubbleDown(this.heap, this.weightFunc, 0); } + return front; + }; - if ('storageMode' in options) { - if (!angular.isString(options.storageMode)) { - throw new Error(errStr + 'storageMode: must be a string!'); - } else if (options.storageMode !== 'none' && options.storageMode !== 'localStorage' && options.storageMode !== 'sessionStorage') { - throw new Error(errStr + 'storageMode: accepted values are "none", "localStorage" or "sessionStorage"!'); - } - if ('storageImpl' in options) { - if (!angular.isObject(options.storageImpl)) { - throw new Error(errStr + 'storageImpl: must be an object!'); - } else if (!('setItem' in options.storageImpl) || typeof options.storageImpl.setItem !== 'function') { - throw new Error(errStr + 'storageImpl: must implement "setItem(key, value)"!'); - } else if (!('getItem' in options.storageImpl) || typeof options.storageImpl.getItem !== 'function') { - throw new Error(errStr + 'storageImpl: must implement "getItem(key)"!'); - } else if (!('removeItem' in options.storageImpl) || typeof options.storageImpl.removeItem !== 'function') { - throw new Error(errStr + 'storageImpl: must implement "removeItem(key)"!'); + /** + * @method DSBinaryHeap.remove + * @desc Remove the first node in the priority queue that satisfies angular.equals comparison with + * the given node. + * @param {*} node The node to remove. + * @returns {*} The removed node. + */ + DSBinaryHeap.prototype.remove = function (node) { + var length = this.heap.length; + for (var i = 0; i < length; i++) { + if (angular.equals(this.heap[i], node)) { + var removed = this.heap[i], + end = this.heap.pop(); + if (i !== length - 1) { + this.heap[i] = end; + bubbleUp(this.heap, this.weightFunc, i); + bubbleDown(this.heap, this.weightFunc, i); } + return removed; } } - - if ('onExpire' in options) { - if (typeof options.onExpire !== 'function') { - throw new Error(errStr + 'onExpire: must be a function!'); - } - } - - cacheDefaults = angular.extend({}, DEFAULTS(), options); + return null; }; - // Initialize cacheDefaults with the defaults - this.setCacheDefaults({}); - /** - * @ignore + * @method DSBinaryHeap.removeAll + * @desc Remove all nodes from this DSBinaryHeap. */ - this.$get = ['$window', 'BinaryHeap', function ($window, BinaryHeap) { - var caches = {}; - - /** - * Stringify a number. - * @param {number|*} number The number to be stringified. - * @returns {*} number or a string. - * @private - */ - function _stringifyNumber(number) { - if (number && angular.isNumber(number)) { - return number.toString(); - } - return number; - } + DSBinaryHeap.prototype.removeAll = function () { + this.heap = []; + }; - /** - * @method _keySet - * @desc Returns an object of the keys of the given collection. - * @param {object} collection The collection from which to get the set of keys. - * @returns {object} A hash of the keys of the given collection. - */ - function _keySet(collection) { - var keySet = {}, key; - for (key in collection) { - if (collection.hasOwnProperty(key)) { - keySet[key] = key; - } - } - return keySet; - } + /** + * @method DSBinaryHeap.size + * @desc Return the size of the priority queue. + * @returns {number} The size of the priority queue. + */ + DSBinaryHeap.prototype.size = function () { + return this.heap.length; + }; - /** - * @method _keys - * @desc Returns an array of the keys of the given collection. - * @param {object} collection The collection from which to get the keys. - * @returns {array} An array of the keys of the given collection. - */ - function _keys(collection) { - var keys = [], key; - for (key in collection) { - if (collection.hasOwnProperty(key)) { - keys.push(key); - } - } - return keys; - } + return DSBinaryHeap; + }; +} - /** - * @class AngularCache - * @desc Instantiated via $angularCacheFactory(cacheId[, options]) - * @param {string} cacheId The id of the new cache. - * @param {object} [options] See [Configuration Options]{@link https://github.com/jmdobry/angular-cache#configuration}. - */ - function AngularCache(cacheId, options) { - var config = angular.extend({}, { id: cacheId }), - data = {}, - expiresHeap = new BinaryHeap(function (x) { - return x.expires; - }), - lruHeap = new BinaryHeap(function (x) { - return x.accessed; - }), - prefix = 'angular-cache.caches.' + cacheId, - cacheDirty = false, - self = this, - storage = null; - - options = options || {}; - - /** - * @method _setCapacity - * @desc Set the capacity for this cache. - * @param {number} capacity The new capacity for this cache. - */ - function _setCapacity(capacity) { - _validateNumberOption(capacity, function (err) { - if (err) { - throw new Error('capacity: ' + err); - } else { - config.capacity = capacity; - while (lruHeap.size() > config.capacity) { - self.remove(lruHeap.peek().key, { verifyIntegrity: false }); - } - } - }); - } +module.exports = DSBinaryHeapProvider; - /** - * @method _setDeleteOnExpire - * @desc Set the deleteOnExpire setting for this cache. - * @param {string} deleteOnExpire The new deleteOnExpire for this cache. - */ - function _setDeleteOnExpire(deleteOnExpire) { - if (!angular.isString(deleteOnExpire)) { - throw new Error('deleteOnExpire: must be a string!'); - } else if (deleteOnExpire !== 'none' && deleteOnExpire !== 'passive' && deleteOnExpire !== 'aggressive') { - throw new Error('deleteOnExpire: accepted values are "none", "passive" or "aggressive"!'); - } else { - config.deleteOnExpire = deleteOnExpire; - } - } +},{}],2:[function(require,module,exports){ +/** + * @doc method + * @id DSCache.methods:destroy + * @name destroy + * @description + * Destroy this cache and all of its data. + * + * ## Signature: + * ```js + * DSCache#destroy() + * ``` + * + * ## Example: + * ```js + * var someCache = DSCacheFactory.get('someCache'); + * + * someCache.destroy(); + * + * DSCacheFactory.get('someCache'); // undefined + * someCache.put('1', 'apple'); // Error + * ``` + */ +module.exports = function destroy() { + clearInterval(this.$$cacheFlushIntervalId); + clearInterval(this.$$recycleFreqId); + this.removeAll(); + if (this.$$storage) { + this.$$storage.removeItem(this.$$prefix + '.keys'); + this.$$storage.removeItem(this.$$prefix); + } + this.$$storage = null; + this.$$data = null; + this.$$lruHeap = null; + this.$$expiresHeap = null; + this.$$prefix = null; +}; - /** - * @method _setMaxAge - * @desc Set the maxAge for this cache. - * @param {number} maxAge The new maxAge for this cache. - */ - function _setMaxAge(maxAge) { - var keys = _keys(data); - if (maxAge === null) { - if (config.maxAge) { - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - if (!('maxAge' in data[key])) { - delete data[key].expires; - expiresHeap.remove(data[key]); - } - } - } - config.maxAge = maxAge; - } else { - _validateNumberOption(maxAge, function (err) { - if (err) { - throw new Error('maxAge: ' + err); - } else { - if (maxAge !== config.maxAge) { - config.maxAge = maxAge; - var now = new Date().getTime(); - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - if (!('maxAge' in data[key])) { - expiresHeap.remove(data[key]); - data[key].expires = data[key].created + config.maxAge; - expiresHeap.push(data[key]); - if (data[key].expires < now) { - self.remove(key, { verifyIntegrity: false }); - } - } - } - } - } - }); - } - } +},{}],3:[function(require,module,exports){ +var utils = require('../utils'); - /** - * @method _flushExpired - * @desc Remove expired items from the expiresHeap. - * @private - */ - function _flushExpired() { - var now = new Date().getTime(), - item = expiresHeap.peek(); - - while (item && item.expires && item.expires < now) { - self.remove(item.key, { verifyIntegrity: false }); - if (config.onExpire) { - config.onExpire(item.key, item.value); - } - item = expiresHeap.peek(); - } - } +/** + * @doc method + * @id DSCache.methods:get + * @name get + * @description + * Retrieve the item with the given key. + * + * ## Signature: + * ```js + * DSCache#get(key) + * ``` + * + * ## Examples: + * ```js + * var cache = DSCacheFactory('cache'); + * + * cache.put('1', 'apple'); + * + * cache.get('1'); // "apple" + * cache.get('2'); // undefined + * ``` + * + * ```js + * var options = { + * deleteOnExpire: 'passive', + * maxAge: 1000 + * }, + * cache = DSCacheFactory('cache', options); + * + * cache.put('1', 'apple'); + * + * cache.get('1'); // "apple" + * + * setTimeout(function () { + * cache.get('1'); // undefined + * }, 1500); + * ``` + * + * ```js + * var options = { + * deleteOnExpire: 'passive', + * maxAge: 1000 + * }, + * cache = DSCacheFactory('cache', options); + * + * cache.put('1', 'apple'); + * + * cache.get('1', { + * onExpire: function (key, value) { + * console.log(key, value); + * } + * }); // "apple" + * + * setTimeout(function () { + * cache.get('1'); // undefined + * // "1" "apple" (printed to console) + * }, 1500); + * ``` + * + * ```js + * var options = { + * deleteOnExpire: 'passive', + * maxAge: 1000, + * onExpire: function (key, value, done) { + * console.log('global hit'); + * if (done) { + * done(key, value); + * } + * } + * }, + * cache = DSCacheFactory('cache', options); + * + * cache.put('1', 'apple'); + * + * cache.get('1', { + * onExpire: function (key, value) { + * console.log(key, value); + * } + * }); // "apple" + * + * setTimeout(function () { + * cache.get('1'); // undefined + * // "global hit" (printed to console) + * // "1" "apple" (printed to console) + * }, 1500); + * ``` + * + * @param {string} key The key of the item to retrieve. + * @param {object=} options Optional configuration. Properties: + * + * - `{function=}` - `onExpire` - Callback to be used if in passive `deleteOnExpire` mode and the requested item has + * expired. If a global `onExpire` callback exists for this cache, then it will be called with three arguments: `key`, + * `value`, and `done`, where `done` is the `onExpire` callback passed into the call to `DSCache#get(key[, options])`. + * (See the last example above.) + * + * @returns {*} The item with the given key. + */ +module.exports = function get(key, options) { + var _this = this; - /** - * @method _setRecycleFreq - * @desc Set the recycleFreq setting for this cache. - * @param {number} recycleFreq The new recycleFreq for this cache. - */ - function _setRecycleFreq(recycleFreq) { - if (recycleFreq === null) { - if (config.recycleFreqId) { - clearInterval(config.recycleFreqId); - delete config.recycleFreqId; - } - config.recycleFreq = cacheDefaults.recycleFreq; - config.recycleFreqId = setInterval(_flushExpired, config.recycleFreq); - } else { - _validateNumberOption(recycleFreq, function (err) { - if (err) { - throw new Error('recycleFreq: ' + err); - } else { - config.recycleFreq = recycleFreq; - if (config.recycleFreqId) { - clearInterval(config.recycleFreqId); - } - config.recycleFreqId = setInterval(_flushExpired, config.recycleFreq); - } - }); - } - } + if (angular.isArray(key)) { + var keys = key, + values = []; - /** - * @method _setCacheFlushInterval - * @desc Set the cacheFlushInterval for this cache. - * @param {number} cacheFlushInterval The new cacheFlushInterval for this cache. - */ - function _setCacheFlushInterval(cacheFlushInterval) { - if (cacheFlushInterval === null) { - if (config.cacheFlushIntervalId) { - clearInterval(config.cacheFlushIntervalId); - delete config.cacheFlushIntervalId; - } - config.cacheFlushInterval = cacheFlushInterval; - } else { - _validateNumberOption(cacheFlushInterval, function (err) { - if (err) { - throw new Error('cacheFlushInterval: ' + err); - } else { - if (cacheFlushInterval !== config.cacheFlushInterval) { - if (config.cacheFlushIntervalId) { - clearInterval(config.cacheFlushIntervalId); - } - config.cacheFlushInterval = cacheFlushInterval; - config.cacheFlushIntervalId = setInterval(self.removeAll, config.cacheFlushInterval); - } - } - }); - } - } + angular.forEach(keys, function (key) { + var value = _this.get(key, options); + if (value !== null && value !== undefined) { + values.push(value); + } + }); - /** - * @method _setStorageMode - * @desc Configure the cache to use localStorage. - * @param {string} storageMode "localStorage"|"sessionStorage"|null - * @param {object} storageImpl The storage polyfill/replacement to use. - */ - function _setStorageMode(storageMode, storageImpl) { - var keys, i; - if (!angular.isString(storageMode)) { - throw new Error('storageMode: must be a string!'); - } else if (storageMode !== 'none' && storageMode !== 'localStorage' && storageMode !== 'sessionStorage') { - throw new Error('storageMode: accepted values are "none", "localStorage" or "sessionStorage"!'); - } - if ((config.storageMode === 'localStorage' || config.storageMode === 'sessionStorage') && - (storageMode !== config.storageMode)) { - keys = _keys(data); - for (i = 0; i < keys.length; i++) { - storage.removeItem(prefix + '.data.' + keys[i]); - } - storage.removeItem(prefix + '.keys'); - } - config.storageMode = storageMode; - if (storageImpl) { - if (!angular.isObject(storageImpl)) { - throw new Error('storageImpl: must be an object!'); - } else if (!('setItem' in storageImpl) || typeof storageImpl.setItem !== 'function') { - throw new Error('storageImpl: must implement "setItem(key, value)"!'); - } else if (!('getItem' in storageImpl) || typeof storageImpl.getItem !== 'function') { - throw new Error('storageImpl: must implement "getItem(key)"!'); - } else if (!('removeItem' in storageImpl) || typeof storageImpl.removeItem !== 'function') { - throw new Error('storageImpl: must implement "removeItem(key)"!'); - } - storage = storageImpl; - } else if (config.storageMode === 'localStorage') { - storage = $window.localStorage; - } else if (config.storageMode === 'sessionStorage') { - storage = $window.sessionStorage; - } + return values; + } else { + key = utils.stringifyNumber(key); - if (config.storageMode !== 'none' && storage) { - if (!cacheDirty) { - _loadCacheConfig(); - } else { - keys = _keys(data); - for (i = 0; i < keys.length; i++) { - _syncToStorage(keys[i]); - } - } - } - } + if (this.$$disabled) { + return; + } + } - /** - * @method _setOptions - * @desc Configure this cache with the given options. - * @param {object} cacheOptions New configuration options for the cache. - * @param {boolean} [strict] If true then any existing configuration will be reset to default before - * applying the new options, otherwise only the options specified in the options hash will be altered. - * @param {object} [options] Configuration. - */ - function _setOptions(cacheOptions, strict, options) { - cacheOptions = cacheOptions || {}; - options = options || {}; - strict = !!strict; - if (!angular.isObject(cacheOptions)) { - throw new Error('AngularCache.setOptions(cacheOptions, strict, options): cacheOptions: must be an object!'); - } + options = options || {}; + if (!angular.isString(key)) { + throw angular.$$minErr('ng')('areq', 'Expected key to be a string! Found: {0}.', typeof key); + } else if (options && !angular.isObject(options)) { + throw angular.$$minErr('ng')('areq', 'Expected options to be an object! Found: {0}.', typeof options); + } else if (options.onExpire && !angular.isFunction(options.onExpire)) { + throw angular.$$minErr('ng')('areq', 'Expected options.onExpire to be a function! Found: {0}.', typeof options.onExpire); + } - _verifyIntegrity(options.verifyIntegrity); + var item; - if (strict) { - cacheOptions = angular.extend({}, cacheDefaults, cacheOptions); - } + if (this.$$storage) { + var itemJson = this.$$storage.getItem(this.$$prefix + '.data.' + key); - if ('disabled' in cacheOptions) { - config.disabled = cacheOptions.disabled === true; - } - if ('verifyIntegrity' in cacheOptions) { - config.verifyIntegrity = cacheOptions.verifyIntegrity === true; - } - if ('capacity' in cacheOptions) { - _setCapacity(cacheOptions.capacity); - } + if (itemJson) { + item = angular.fromJson(itemJson); + } else { + return; + } + } else { + if (!(key in this.$$data)) { + return; + } - if ('deleteOnExpire' in cacheOptions) { - _setDeleteOnExpire(cacheOptions.deleteOnExpire); - } + item = this.$$data[key]; + } - if ('maxAge' in cacheOptions) { - _setMaxAge(cacheOptions.maxAge); - } + var value = item.value, + now = new Date().getTime(); + + if (this.$$storage) { + this.$$lruHeap.remove({ + key: key, + accessed: item.accessed + }); + item.accessed = now; + this.$$lruHeap.push({ + key: key, + accessed: now + }); + } else { + this.$$lruHeap.remove(item); + item.accessed = now; + this.$$lruHeap.push(item); + } - if ('recycleFreq' in cacheOptions) { - _setRecycleFreq(cacheOptions.recycleFreq); - } + if (this.$$deleteOnExpire === 'passive' && 'expires' in item && item.expires < now) { + this.remove(key); - if ('cacheFlushInterval' in cacheOptions) { - _setCacheFlushInterval(cacheOptions.cacheFlushInterval); - } + if (this.$$onExpire) { + this.$$onExpire(key, item.value, options.onExpire); + } else if (options.onExpire) { + options.onExpire(key, item.value); + } + value = undefined; + } else if (this.$$storage) { + this.$$storage.setItem(this.$$prefix + '.data.' + key, angular.toJson(item)); + } - if ('storageMode' in cacheOptions) { - _setStorageMode(cacheOptions.storageMode, cacheOptions.storageImpl); - } + return value; +}; - if ('onExpire' in cacheOptions) { - if (cacheOptions.onExpire !== null && typeof cacheOptions.onExpire !== 'function') { - throw new Error('onExpire: must be a function!'); - } - config.onExpire = cacheOptions.onExpire; - } +},{"../utils":22}],4:[function(require,module,exports){ +var defaults = require('../defaults'); - cacheDirty = true; - } +/*! + * Configure the cache to use webStorage. + */ +function _setStorageMode(storageMode, storageImpl) { + var $window = angular.injector(['ng']).get('$window'); - /** - * @method _loadCacheConfig - * @desc If storageMode is set, attempt to load previous cache configuration from localStorage. - */ - function _loadCacheConfig() { - var keys = angular.fromJson(storage.getItem(prefix + '.keys')); - storage.removeItem(prefix + '.keys'); - if (keys && keys.length) { - for (var i = 0; i < keys.length; i++) { - var data = angular.fromJson(storage.getItem(prefix + '.data.' + keys[i])) || {}, - maxAge = data.maxAge || config.maxAge, - deleteOnExpire = data.deleteOnExpire || config.deleteOnExpire; - if (maxAge && ((new Date().getTime() - data.created) > maxAge) && deleteOnExpire === 'aggressive') { - storage.removeItem(prefix + '.data.' + keys[i]); - } else { - var options = { - created: data.created - }; - if (data.expires) { - options.expires = data.expires; - } - if (data.accessed) { - options.accessed = data.accessed; - } - if (data.maxAge) { - options.maxAge = data.maxAge; - } - if (data.deleteOnExpire) { - options.deleteOnExpire = data.deleteOnExpire; - } - self.put(keys[i], data.value, options); - } - } - _syncToStorage(null); - } - } + if (!angular.isString(storageMode)) { + throw angular.$$minErr('ng')('areq', 'Expected storageMode to be a string! Found: {0}.', typeof storageMode); + } else if (storageMode !== 'memory' && storageMode !== 'localStorage' && storageMode !== 'sessionStorage') { + throw angular.$$minErr('ng')('areq', 'Expected storageMode to be "memory", "localStorage" or "sessionStorage"! Found: {0}.', storageMode); + } - /** - * @method _syncToStorage - * @desc If storageMode is set, sync to localStorage. - * @param {string} key The identifier of the item to sync. - */ - function _syncToStorage(key) { - if (config.storageMode !== 'none' && storage) { - storage.setItem(prefix + '.keys', angular.toJson(_keys(data))); - if (key) { - storage.setItem(prefix + '.data.' + key, angular.toJson(data[key])); - } - } - } + this.$$storageMode = storageMode; + + if (storageImpl) { + if (!angular.isObject(storageImpl)) { + throw angular.$$minErr('ng')('areq', 'Expected storageImpl to be an object! Found: {0}.', typeof storageImpl); + } else if (!('setItem' in storageImpl) || typeof storageImpl.setItem !== 'function') { + throw angular.$$minErr('ng')('areq', 'Expected storageImpl to implement "setItem(key, value)"! Found: {0}.', typeof storageImpl.setItem); + } else if (!('getItem' in storageImpl) || typeof storageImpl.getItem !== 'function') { + throw angular.$$minErr('ng')('areq', 'Expected storageImpl to implement "getItem(key)"! Found: {0}.', typeof storageImpl.getItem); + } else if (!('removeItem' in storageImpl) || typeof storageImpl.removeItem !== 'function') { + throw angular.$$minErr('ng')('areq', 'Expected storageImpl to implement "removeItem(key)"! Found: {0}.', typeof storageImpl.removeItem); + } + this.$$storage = storageImpl; + } else if (this.$$storageMode === 'localStorage') { + if ($window.localStorage) { + this.$$storage = $window.localStorage; + } else { + delete this.$$storage; + } + } else if (this.$$storageMode === 'sessionStorage') { + if ($window.sessionStorage) { + this.$$storage = $window.sessionStorage; + } else { + delete this.$$storage; + } + } +} - function _verifyIntegrity(verifyIntegrity) { - if (verifyIntegrity || (verifyIntegrity !== false && config.verifyIntegrity)) { - if (config.storageMode !== 'none' && storage) { - var keys = _keys(data); - storage.setItem(prefix + '.keys', angular.toJson(keys)); - for (var i = 0; i < keys.length; i++) { - storage.setItem(prefix + '.data.' + keys[i], angular.toJson(data[keys[i]])); - } - } - } - } +/** + * @doc method + * @id DSCache.methods:setOptions + * @name setOptions + * @description + * Configure this cache with the given options. With this method you can configure all of this cache's settings at once. + * + * ## Signature: + * ```js + * DSCache#setOptions(cacheOptions[, strict]) + * ``` + * + * ## Example: + * ```js + * cache.setOptions({ + * maxAge: 60000, + * deleteOnExpire: 'aggressive', + * disabled: false + * }); + * ``` + * + * @param {object} cacheOptions New configuration options for the cache. Properties: + * + * - `{number=}` - `capacity` - Default: `Number.MAX_VALUE` + * - `{number=}` - `maxAge` - Default: `null` + * - `{number=}` - `deleteOnExpire` - Default: `none` + * - `{function=}` - `onExpire` - Default: `null` + * - `{number=}` - `cacheFlushInterval` - Default: `null` + * - `{number=}` - `recycleFreq` - Default: `1000` + * - `{boolean=}` - `disabled` - Default: `false` + * + * @param {boolean=} strict If true then any existing configuration will be reset to the defaults before + * applying the new options, otherwise only the options specified in the options hash will be altered. + */ +function _setOptions(cacheOptions, strict) { + cacheOptions = cacheOptions || {}; + strict = !!strict; + if (!angular.isObject(cacheOptions)) { + throw angular.$$minErr('ng')('areq', 'Expected cacheOptions to be an object! Found: {0}.', typeof cacheOptions); + } - function _readItemFromStorage(key, verifyIntegrity) { - if (verifyIntegrity || (verifyIntegrity !== false && config.verifyIntegrity)) { - if (config.storageMode !== 'none' && storage) { - var itemJson = storage.getItem(prefix + '.data.' + key); - - if (!itemJson && key in data) { - self.remove(key); - } else if (itemJson) { - var item = angular.fromJson(itemJson), - value = item ? item.value : null; - - if (value) { - self.put(key, value); - } - } - } - } - } + if ('disabled' in cacheOptions) { + this.$$disabled = !!cacheOptions.disabled; + } else if (strict) { + delete this.$$disabled; + } - function _saveKeysToStorage(keys) { - if (config.storageMode !== 'none' && storage) { - var keysToSave = keys || _keys(data); - storage.setItem(prefix + '.keys', angular.toJson(keysToSave)); - } - } + if ('capacity' in cacheOptions) { + this.setCapacity(cacheOptions.capacity); + } else if (strict) { + this.setCapacity(null); + } - function _saveItemToStorage(key) { - if (config.storageMode !== 'none' && storage && key in data) { - storage.setItem(prefix + '.data.' + key, angular.toJson(data[key])); - } - } + if ('deleteOnExpire' in cacheOptions) { + this.setDeleteOnExpire(cacheOptions.deleteOnExpire); + } else if (strict) { + this.setDeleteOnExpire(null); + } - function _removeAllFromStorage() { - if (config.storageMode !== 'none' && storage) { - var keys = _keys(data); - for (var i = 0; i < keys.length; i++) { - storage.removeItem(prefix + '.data.' + keys[i]); - } - storage.setItem(prefix + '.keys', angular.toJson([])); - } - } + if ('maxAge' in cacheOptions) { + this.setMaxAge(cacheOptions.maxAge); + } else if (strict) { + this.setMaxAge(null); + } - /** - * @method AngularCache.put - * @desc Add a key-value pair to the cache. - * @param {string} key The identifier for the item to add to the cache. - * @param {*} value The value of the item to add to the cache. - * @param {object} [options] {{ maxAge: {number}, deleteOnExpire: {string} }} - * @returns {*} value The value of the item added to the cache. - */ - this.put = function (key, value, options) { - if (config.disabled) { - return; - } - options = options || {}; - - key = _stringifyNumber(key); - - if (!angular.isString(key)) { - throw new Error('AngularCache.put(key, value, options): key: must be a string!'); - } else if (options && !angular.isObject(options)) { - throw new Error('AngularCache.put(key, value, options): options: must be an object!'); - } else if (options.maxAge && options.maxAge !== null) { - _validateNumberOption(options.maxAge, function (err) { - if (err) { - throw new Error('AngularCache.put(key, value, options): maxAge: ' + err); - } - }); - } else if (options.deleteOnExpire && !angular.isString(options.deleteOnExpire)) { - throw new Error('AngularCache.put(key, value, options): deleteOnExpire: must be a string!'); - } else if (options.deleteOnExpire && options.deleteOnExpire !== 'none' && options.deleteOnExpire !== 'passive' && options.deleteOnExpire !== 'aggressive') { - throw new Error('AngularCache.put(key, value, options): deleteOnExpire: accepted values are "none", "passive" or "aggressive"!'); - } else if (angular.isUndefined(value)) { - return; - } + if ('recycleFreq' in cacheOptions) { + this.setRecycleFreq(cacheOptions.recycleFreq); + } else if (strict) { + this.setRecycleFreq(null); + } - var now = new Date().getTime(), - deleteOnExpire, item; + if ('cacheFlushInterval' in cacheOptions) { + this.setCacheFlushInterval(cacheOptions.cacheFlushInterval); + } else if (strict) { + this.setCacheFlushInterval(null); + } - _verifyIntegrity(options.verifyIntegrity); + if ('onExpire' in cacheOptions) { + this.setOnExpire(cacheOptions.onExpire); + } else if (strict) { + this.setOnExpire(null); + } +} - if (data[key]) { - expiresHeap.remove(data[key]); - lruHeap.remove(data[key]); - } else { - data[key] = { key: key }; - } +/** + * @doc function + * @id DSCache + * @name DSCache + * @description + * Instantiated via `DSCacheFactory(cacheId[, options])`. + * + * @param {string} cacheId The id of the new cache. + * @param {object=} options Configuration options. + */ +function DSCache(cacheId, options) { - item = data[key]; - item.value = value; - item.created = (parseInt(options.created, 10)) || item.created || now; - item.accessed = (parseInt(options.accessed, 10)) || now; + this.$$data = {}; + this.$$id = cacheId; + this.$$storage = null; - if (options.deleteOnExpire) { - item.deleteOnExpire = options.deleteOnExpire; - } - if (options.maxAge) { - item.maxAge = options.maxAge; - } + this.$$expiresHeap = new DSBinaryHeap(function (x) { + return x.expires; + }); - if (item.maxAge || config.maxAge) { - item.expires = item.created + (item.maxAge || config.maxAge); - } + this.$$lruHeap = new DSBinaryHeap(function (x) { + return x.accessed; + }); - deleteOnExpire = item.deleteOnExpire || config.deleteOnExpire; + options = options || {}; - if (item.expires && deleteOnExpire === 'aggressive') { - expiresHeap.push(item); - } + if ('storageMode' in options) { + _setStorageMode.apply(this, [options.storageMode, options.storageImpl]); + } + if ('storagePrefix' in options) { + this.$$storagePrefix = options.storagePrefix; + } - // Sync with localStorage, etc. - _saveKeysToStorage(); - _saveItemToStorage(key); + this.$$prefix = this.$$storagePrefix + cacheId; - lruHeap.push(item); + // Initialize this cache with the default and given options + _setOptions.apply(this, [options, true]); +} - if (lruHeap.size() > config.capacity) { - this.remove(lruHeap.peek().key, { verifyIntegrity: false }); - } +for (var key in defaults.defaults) { + DSCache.prototype['$$' + key] = defaults.defaults[key]; +} - return value; - }; - - /** - * @method AngularCache.get - * @desc Retrieve the item from the cache with the specified key. - * @param {string|Array} key The key of the item to retrieve or an array of keys of items to retrieve. - * @param {object} [options] Configuration. - * @returns {*} The value of the item in the cache with the specified key. - */ - this.get = function (key, options) { - if (config.disabled) { - return; - } - if (angular.isArray(key)) { - var keys = key, - values = []; - - angular.forEach(keys, function (key) { - var value = self.get(key, options); - if (angular.isDefined(value)) { - values.push(value); - } - }); - - return values; - } else { - key = _stringifyNumber(key); - } +/** + * @doc method + * @id DSCache.methods:setOptions + * @name setOptions + * @methodOf DSCache + * @description + * See [DSCache.setOptions](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.setOptions = _setOptions; - options = options || {}; - if (!angular.isString(key)) { - throw new Error('AngularCache.get(key, options): key: must be a string!'); - } else if (options && !angular.isObject(options)) { - throw new Error('AngularCache.get(key, options): options: must be an object!'); - } else if (options.onExpire && !angular.isFunction(options.onExpire)) { - throw new Error('AngularCache.get(key, options): onExpire: must be a function!'); - } +/** + * @doc method + * @id DSCache.methods:setCapacity + * @name setCapacity + * @methodOf DSCache + * @description + * See [DSCache.setCapacity](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.setCapacity = require('./setCapacity'); - _readItemFromStorage(key, options.verifyIntegrity); +/** + * @doc method + * @id DSCache.methods:setDeleteOnExpire + * @name setDeleteOnExpire + * @methodOf DSCache + * @description + * See [DSCache.setDeleteOnExpire](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.setDeleteOnExpire = require('./setDeleteOnExpire'); - if (!(key in data)) { - return; - } +/** + * @doc method + * @id DSCache.methods:setMaxAge + * @name setMaxAge + * @methodOf DSCache + * @description + * See [DSCache.setMaxAge](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.setMaxAge = require('./setMaxAge'); - var item = data[key], - value = item.value, - now = new Date().getTime(), - deleteOnExpire = item.deleteOnExpire || config.deleteOnExpire; +/** + * @doc method + * @id DSCache.methods:setRecycleFreq + * @name setRecycleFreq + * @methodOf DSCache + * @description + * See [DSCache.setRecycleFreq](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.setRecycleFreq = require('./setRecycleFreq'); - lruHeap.remove(item); - item.accessed = now; - lruHeap.push(item); +/** + * @doc method + * @id DSCache.methods:setCacheFlushInterval + * @name setCacheFlushInterval + * @methodOf DSCache + * @description + * See [DSCache.setCacheFlushInterval](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.setCacheFlushInterval = require('./setCacheFlushInterval'); - if (deleteOnExpire === 'passive' && 'expires' in item && item.expires < now) { - this.remove(key, { verifyIntegrity: false }); +/** + * @doc method + * @id DSCache.methods:setOnExpire + * @name setOnExpire + * @methodOf DSCache + * @description + * See [DSCache.setOnExpire](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.setOnExpire = require('./setOnExpire'); - if (config.onExpire) { - config.onExpire(key, item.value, (options.onExpire)); - } else if (options.onExpire) { - options.onExpire(key, item.value); - } - value = undefined; - } +/** + * @doc method + * @id DSCache.methods:put + * @name put + * @methodOf DSCache + * @description + * See [DSCache.put](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.put = require('./put'); - _saveItemToStorage(key); - - return value; - }; - - /** - * @method AngularCache.remove - * @desc Remove the item with the specified key from the cache. - * @param {string} key The key of the item to remove. - * @param {object} [options] Configuration. - */ - this.remove = function (key, options) { - options = options || {}; - _verifyIntegrity(options.verifyIntegrity); - lruHeap.remove(data[key]); - expiresHeap.remove(data[key]); - if (config.storageMode !== 'none' && storage) { - storage.removeItem(prefix + '.data.' + key); - } - delete data[key]; - _saveKeysToStorage(); - }; - - /** - * @method AngularCache.removeAll - * @desc Clear the cache. - */ - this.removeAll = function () { - _removeAllFromStorage(); - lruHeap.removeAll(); - expiresHeap.removeAll(); - data = {}; - }; - - /** - * @method AngularCache.removeExpired - * @desc Remove all expired items from the cache. - * @param {object} [options] Configuration. - * @returns {object|array} Object or array of removed items. - */ - this.removeExpired = function (options) { - options = options || {}; - var now = new Date().getTime(), - keys = _keys(data), - expired = {}; - - for (var i = 0; i < keys.length; i++) { - if (data[keys[i]] && data[keys[i]].expires && data[keys[i]].expires < now) { - expired[keys[i]] = data[keys[i]].value; - } - } - for (var key in expired) { - self.remove(key); - } - _verifyIntegrity(options.verifyIntegrity); - if (options.asArray) { - var expiredArray = []; - for (key in expired) { - expiredArray.push(expired[key]); - } - return expiredArray; - } else { - return expired; - } - }; - - /** - * @method AngularCache.destroy - * @desc Completely destroy the cache. - */ - this.destroy = function () { - if (config.cacheFlushIntervalId) { - clearInterval(config.cacheFlushIntervalId); - } - if (config.recycleFreqId) { - clearInterval(config.recycleFreqId); - } - this.removeAll(); - if (config.storageMode !== 'none' && storage) { - storage.removeItem(prefix + '.keys'); - storage.removeItem(prefix); - } - storage = null; - data = null; - lruHeap = null; - expiresHeap = null; - config = null; - prefix = null; - self = null; - var methodKeys = _keys(this); - - // Prevent this cache from being used after it has been destroyed - for (var i = 0; i < methodKeys.length; i++) { - if (this.hasOwnProperty(methodKeys[i])) { - delete this[methodKeys[i]]; - } - } +/** + * @doc method + * @id DSCache.methods:get + * @name get + * @methodOf DSCache + * @description + * See [DSCache.get](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.get = require('./get'); - caches[cacheId] = null; - delete caches[cacheId]; - }; - - /** - * @method AngularCache.info - * @desc Return an object containing information about the cache. - * @param {string} [key] The key of the item about which to retrieve information. - * @returns {object} stats Object containing information about this cache or the item with the - * specified key. - */ - this.info = function (key) { - if (key) { - if (data[key]) { - var info = { - created: data[key].created, - accessed: data[key].accessed, - expires: data[key].expires, - maxAge: data[key].maxAge || config.maxAge, - deleteOnExpire: data[key].deleteOnExpire || config.deleteOnExpire, - isExpired: false - }; - if (info.maxAge) { - info.isExpired = (new Date().getTime() - info.created) > info.maxAge; - } - return info; - } else { - return data[key]; - } - } else { - return angular.extend({}, config, { size: lruHeap && lruHeap.size() || 0 }); - } - }; - - /** - * @method AngularCache.keySet - * @desc Return the set of the keys of all items in the cache. - * @returns {object} The set of the keys of all items currently in this cache. - */ - this.keySet = function () { - return _keySet(data); - }; - - /** - * @method AngularCache.keys - * @desc Return an array of the keys of all items in the cache. - * @returns {array} An array of the keys of all items in the cache. - */ - this.keys = function () { - return _keys(data); - }; - - /** - * @method AngularCache.setOptions - * @desc Configure this cache with the given options. - * @param {object} cacheOptions - * @param {boolean} [strict] If true then any existing configuration will be reset to defaults before - * applying the new options, otherwise only the options specified in the hash will be altered. - * @param {object} [options] Configuration. - */ - this.setOptions = _setOptions; - - // Initialize this cache with the default and given options - _setOptions(options, true, { verifyIntegrity: false }); - } +/** + * @doc method + * @id DSCache.methods:remove + * @name remove + * @methodOf DSCache + * @description + * See [DSCache.remove](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.remove = require('./remove'); - /** - * @class AngularCacheFactory - * @param {string} cacheId The id of the new cache. - * @param {object} [options] See [Configuration Options]{@link https://github.com/jmdobry/angular-cache#configuration}. - * @returns {AngularCache} - */ - function angularCacheFactory(cacheId, options) { - if (cacheId in caches) { - throw new Error('cacheId ' + cacheId + ' taken!'); - } else if (!angular.isString(cacheId)) { - throw new Error('cacheId must be a string!'); - } +/** + * @doc method + * @id DSCache.methods:removeAll + * @name removeAll + * @methodOf DSCache + * @description + * See [DSCache.removeAll](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.removeAll = require('./removeAll'); - caches[cacheId] = new AngularCache(cacheId, options); - return caches[cacheId]; - } +/** + * @doc method + * @id DSCache.methods:removeExpired + * @name removeExpired + * @methodOf DSCache + * @description + * See [DSCache.removeExpired](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.removeExpired = require('./removeExpired'); - /** - * @method AngularCacheFactory.info - * @desc Return an object containing information about all caches in $angularCacheFactory. - * @returns {object} An object containing information about all caches of this factory. - */ - angularCacheFactory.info = function () { - var keys = _keys(caches); - var info = { - size: keys.length, - caches: {} - }; - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - info.caches[key] = caches[key].info(); - } - info.cacheDefaults = angular.extend({}, cacheDefaults); - return info; - }; +/** + * @doc method + * @id DSCache.methods:destroy + * @name destroy + * @methodOf DSCache + * @description + * See [DSCache.destroy](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.destroy = require('./destroy'); - /** - * @method AngularCacheFactory.get - * @desc Return the cache with the specified cacheId. - * @param {string} cacheId The id of the desired cache. - * @returns {AngularCache} The cache with the specified cachedId. - */ - angularCacheFactory.get = function (cacheId) { - if (!angular.isString(cacheId)) { - throw new Error('$angularCacheFactory.get(cacheId): cacheId: must be a string!'); - } - return caches[cacheId]; - }; +/** + * @doc method + * @id DSCache.methods:info + * @name info + * @methodOf DSCache + * @description + * See [DSCache.info](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.info = require('./info'); - /** - * @method AngularCacheFactory.keySet - * @desc Return the set of keys of all current caches owned by angularCacheFactory. - * @returns {object} The set of keys associated with all current caches owned by this - * angularCacheFactory. - */ - angularCacheFactory.keySet = function () { - return _keySet(caches); - }; +/** + * @doc method + * @id DSCache.methods:keySet + * @name keySet + * @methodOf DSCache + * @description + * See [DSCache.keySet](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.keySet = require('./keySet'); + +/** + * @doc method + * @id DSCache.methods:keys + * @name keys + * @methodOf DSCache + * @description + * See [DSCache.keys](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.keys = require('./keys'); + +/** + * @doc method + * @id DSCache.methods:disable + * @name disable + * @description + * Disable this cache. Disabling a cache does not remove any data, it just turns DSCache#get and DSCache#put into noops. + * + * ## Signature: + * ```js + * DSCache#disable() + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory.get('cache'); + * + * cache.put('1', 'apple'); + * cache.get('1'); // "apple" + * cache.info().size; // 1 + * + * cache.disable(); + * cache.info().size; // 1 + * + * cache.get('1'); // undefined + * cache.put('2', 'banana'); // undefined + * cache.get('2'); // undefined + * cache.info().size; // 1 + * ``` + */ +DSCache.prototype.disable = function () { + this.$$disabled = true; +}; + +/** + * @doc method + * @id DSCache.methods:enable + * @name enable + * @description + * Enable this cache. + * + * ## Signature: + * ```js + * DSCache#enable() + * ``` + * + * ## Example: + * ```js + * var options = { + * disabled: true + * }; + * var cache = DSCacheFactory.get('cache', options); + * + * cache.put('1', 'apple'); + * cache.get('1'); // undefined + * + * cache.enable(); + * + * cache.put('1', 'apple'); + * cache.get('1'); // "apple" + * ``` + */ +DSCache.prototype.enable = function () { + delete this.$$disabled; +}; - /** - * @method AngularCacheFactory.keys - * @desc Return an array of the keys of all caches owned by angularCacheFactory. - * @returns {array} An array of the keys associated with all current caches owned by - * this angularCacheFactory. - */ - angularCacheFactory.keys = function () { - return _keys(caches); +module.exports = DSCache; + +},{"../defaults":"Gv0+ce","./destroy":2,"./get":3,"./info":5,"./keySet":6,"./keys":7,"./put":8,"./remove":9,"./removeAll":10,"./removeExpired":11,"./setCacheFlushInterval":12,"./setCapacity":13,"./setDeleteOnExpire":14,"./setMaxAge":15,"./setOnExpire":16,"./setRecycleFreq":17}],5:[function(require,module,exports){ +/** + * @doc method + * @id DSCache.methods:info + * @name info + * @description + * Return the status of this cache, or if `key` is provided return the status of the item with that key. + * + * ## Signature: + * ```js + * DSCache#info([key]) + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory('cache'); + * + * cache.put('1', 'apple'); + * cache.put('2', 'banana'); + * + * cache.info(); // { + * // id: 'cache', + * // capacity: Number.MAX_VALUE, + * // maxAge: Number.MAX_VALUE, + * // deleteOnExpire: 'none', + * // onExpire: null, + * // cacheFlushInterval: null, + * // recycleFreq: 1000, + * // storageMode: 'memory', + * // storageImpl: null, + * // disabled: false, + * // size: 2 + * // } + * + * cache.info('1'); // { + * // created: 1234567890, + * // accessed: 1234567890, + * // expires: Number.MAX_VALUE, + * // isExpired: false + * // } + * + * cache.info('3'); // undefined + * ``` + * + * @param {string=} key The key of the item whose status is to be retrieved. + * @returns {object} The status of this cache or of the item with the given key. + */ +module.exports = function info(key) { + if (key) { + if (key in this.$$data) { + var item = this.$$data[key]; + + return { + created: item.created, + accessed: item.accessed, + expires: item.expires, + isExpired: (new Date().getTime() - item.created) > this.$$maxAge }; + } else { + return undefined; + } + } else { + return { + id: this.$$id, + capacity: this.$$capacity, + maxAge: this.$$maxAge, + deleteOnExpire: this.$$deleteOnExpire, + onExpire: this.$$onExpire, + cacheFlushInterval: this.$$cacheFlushInterval, + recycleFreq: this.$$recycleFreq, + storageMode: this.$$storageMode, + storageImpl: this.$$storageImpl, + disabled: this.$$disabled, + size: this.$$lruHeap && this.$$lruHeap.size() || 0 + }; + } +}; + +},{}],6:[function(require,module,exports){ +var utils = require('../utils'); + +/** + * @doc method + * @id DSCache.methods:keySet + * @name keySet + * @description + * Return an object of the keys in this cache. + * + * ## Signature: + * ```js + * DSCache#keySet() + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory('cache'); + * + * cache.put('1', 'apple'); + * cache.put('2', 'banana'); + * + * cache.keys(); // { "1": "1", "2": "2" } + * ``` + * + * @returns {object} An object of the keys in this cache. + */ +module.exports = function keySet() { + if (this.$$storage) { + var keysJson = this.$$storage.getItem(this.$$prefix + '.keys'), + kSet = {}; + + if (keysJson) { + var keys = angular.fromJson(keysJson); + + for (var i = 0; i < keys.length; i++) { + kSet[keys[i]] = keys[i]; + } + } + return kSet; + } else { + return utils.keySet(this.$$data); + } +}; + +},{"../utils":22}],7:[function(require,module,exports){ +var utils = require('../utils'); + +/** + * @doc method + * @id DSCache.methods:keys + * @name keys + * @description + * Return an array of the keys in this cache. + * + * ## Signature: + * ```js + * DSCache#keys() + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory('cache'); + * + * cache.put('1', 'apple'); + * cache.put('2', 'banana'); + * + * cache.keys(); // [ "1", "2" ] + * ``` + * + * @returns {Array} An array of the keys in this cache. + */ +module.exports = function keys() { + if (this.$$storage) { + var keysJson = this.$$storage.getItem(this.$$prefix + '.keys'); + + if (keysJson) { + return angular.fromJson(keysJson); + } else { + return []; + } + } else { + return utils.keys(this.$$data); + } +}; + +},{"../utils":22}],8:[function(require,module,exports){ +var utils = require('../utils'); + +/** + * @doc method + * @id DSCache.methods:put + * @name put + * @description + * Insert a value into the cache under the given key. + * + * ## Signature: + * ```js + * DSCache#put(key, value) + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory('cache'); + * + * cache.put('1', 'apple'); + * cache.put('2', 3); + * cache.put('3', { stuff: 'more stuff' }); + * + * cache.get('1'); // "apple" + * cache.get('2'); // 3 + * cache.get('3'); // { stuff: 'more stuff' } + * cache.get('4'); // undefined + * ``` + * + * ## Throws: + * - `Error` - `key` must be a string. + * + * @param {string} key The key under which to store the given value. + * @param {*} value The value to store. + * @returns {*} The newly stored item. + */ +module.exports = function put(key, value) { + if (this.$$disabled || value === null || value === undefined) { + return; + } + + key = utils.stringifyNumber(key); + + if (!angular.isString(key)) { + throw angular.$$minErr('ng')('areq', 'Expected key to be a string! Found: {0}.', typeof key); + } + + var now = new Date().getTime(), + item = { + key: key, + value: value, + created: now, + accessed: now, + }; + + item.expires = item.created + this.$$maxAge; + + if (this.$$storage) { + var keysJson = this.$$storage.getItem(this.$$prefix + '.keys'), + keys = keysJson ? angular.fromJson(keysJson) : [], + itemJson = this.$$storage.getItem(this.$$prefix + '.data.' + key); + + // Remove existing + if (itemJson) { + this.remove(key); + } + // Add to expires heap + this.$$expiresHeap.push({ + key: key, + expires: item.expires + }); + // Add to lru heap + this.$$lruHeap.push({ + key: key, + accessed: item.accessed + }); + // Set item + this.$$storage.setItem(this.$$prefix + '.data.' + key, angular.toJson(item)); + keys.push(key); + this.$$storage.setItem(this.$$prefix + '.keys', angular.toJson(keys)); + } else { + // Remove existing + if (this.$$data[key]) { + this.remove(key); + } + // Add to expires heap + this.$$expiresHeap.push(item); + // Add to lru heap + this.$$lruHeap.push(item); + // Set item + this.$$data[key] = item; + } + + // Handle exceeded capacity + if (this.$$lruHeap.size() > this.$$capacity) { + this.remove(this.$$lruHeap.peek().key); + } + + return value; +}; + +},{"../utils":22}],9:[function(require,module,exports){ +/** + * @doc method + * @id DSCache.methods:remove + * @name remove + * @description + * Remove the item with the given key. + * + * ## Signature: + * ```js + * DSCache#remove(key) + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory('cache'); + * + * cache.put('1', 'apple'); + * + * cache.get('1'); // "apple" + * + * cache.remove('1'); // "apple" + * + * cache.get('1'); // undefined + * ``` + * + * @param {string} key The key of the item to remove. + * @returns {*} The removed item if an item was removed. + */ +module.exports = function remove(key) { + if (this.$$storage) { + var itemJson = this.$$storage.getItem(this.$$prefix + '.data.' + key); + + if (itemJson) { + var item = angular.fromJson(itemJson); + this.$$lruHeap.remove({ + key: key, + accessed: item.accessed + }); + this.$$expiresHeap.remove({ + key: key, + expires: item.expires + }); + this.$$storage.removeItem(this.$$prefix + '.data.' + key); + var keysJson = this.$$storage.getItem(this.$$prefix + '.keys'), + keys = keysJson ? angular.fromJson(keysJson) : [], + index = keys.indexOf(key); + + if (index >= 0) { + keys.splice(index, 1); + } + this.$$storage.setItem(this.$$prefix + '.keys', angular.toJson(keys)); + return item.value; + } + } else { + var value = this.$$data[key] ? this.$$data[key].value : undefined; + this.$$lruHeap.remove(this.$$data[key]); + this.$$expiresHeap.remove(this.$$data[key]); + this.$$data[key] = null; + delete this.$$data[key]; + return value; + } +}; + +},{}],10:[function(require,module,exports){ +/** + * @doc method + * @id DSCache.methods:removeAll + * @name removeAll + * @description + * Remove all items from this cache. + * + * ## Signature: + * ```js + * DSCache#removeAll() + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory('cache'); + * + * cache.put('1', 'apple'); + * cache.put('2', 'banana'); + * cache.info().size; // 2 + * + * cache.get('1'); // "apple" + * cache.get('2'); // "banana" + * + * cache.removeAll(); + * cache.info().size; // 0 + * + * cache.get('1'); // undefined + * cache.get('2'); // undefined + * ``` + */ +module.exports = function removeAll() { + if (this.$$storage) { + this.$$lruHeap.removeAll(); + this.$$expiresHeap.removeAll(); + var keysJson = this.$$storage.getItem(this.$$prefix + '.keys'); + + if (keysJson) { + var keys = angular.fromJson(keysJson); + + for (var i = 0; i < keys.length; i++) { + this.remove(keys[i]); + } + } + this.$$storage.setItem(this.$$prefix + '.keys', angular.toJson([])); + } else { + this.$$lruHeap.removeAll(); + this.$$expiresHeap.removeAll(); + for (var key in this.$$data) { + this.$$data[key] = null; + } + this.$$data = {}; + } +}; + +},{}],11:[function(require,module,exports){ +/** + * @doc method + * @id DSCache.methods:removeExpired + * @name removeExpired + * @description + * Remove and return all expired items from the cache. + * + * ## Signature: + * ```js + * DSCache#removeExpired() + * ``` + * + * ## Example: + * ```js + * var options = { + * maxAge: 1000 + * }, + * // deleteOnExpire defaults to "none" + * cache = DSCacheFactory('cache', options); + * + * cache.put('1', 'apple'); + * cache.put('2', 'banana'); + * + * setTimeout(function () { + * cache.put('3', 'orange'); + * + * cache.info().size; // 3 + * cache.info('1').isExpired; // true + * cache.info('2').isExpired; // true + * cache.info('3').isExpired; // false + * + * cache.removeExpired(); // { "1": "apple", "2": "banana" } + * + * cache.info().size; // 1 + * cache.get('1'); // undefined + * cache.get('2'); // undefined + * cache.info('3').isExpired; // false + * }, 1500); + * ``` + * + * @returns {object} The removed items, if any. + */ +module.exports = function removeExpired() { + var now = new Date().getTime(), + expired = {}, + key, + expiredItem; + + while ((expiredItem = this.$$expiresHeap.peek()) && expiredItem.expires < now) { + expired[expiredItem.key] = expiredItem.value ? expiredItem.value : null; + this.$$expiresHeap.pop(); + } + + if (this.$$storage) { + for (key in expired) { + var itemJson = this.$$storage.getItem(this.$$prefix + '.data.' + key); + if (itemJson) { + expired[key] = angular.fromJson(itemJson).value; + this.remove(key); + } + } + } else { + for (key in expired) { + this.remove(key); + } + } + + if (this.$$onExpire) { + for (key in expired) { + this.$$onExpire(key, expired[key]); + } + } + + return expired; +}; + +},{}],12:[function(require,module,exports){ +/** + * @doc method + * @id DSCache.methods:setCacheFlushInterval + * @name setCacheFlushInterval + * @description + * Set the `cacheFlushInterval` setting for this cache. If set, this setting will cause this cache to periodically + * clear itself. + * + * ## Signature: + * ```js + * DSCache#setCacheFlushInterval(cacheFlushInterval) + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory('cache'); + * + * cache.put('1', 'apple'); + * cache.put('2', 'banana'); + * + * cache.info().size; // 2 + * cache.setCacheFlushInterval(60000); + * + * setTimeout(function () { + * cache.info().size; // 0 + * }, 90000); + * ``` + * + * ## Throws: + * - `Error` - `cacheFlushInterval` must be `null` or a number greater than zero. + * + * @param {number|null} cacheFlushInterval The new cacheFlushInterval for this cache in milliseconds. If + * `cacheFlushInterval` is `null` then `cacheFlushInterval` for this cache will be reset to the default (`null`). + */ +module.exports = function setCacheFlushInterval(cacheFlushInterval) { + if (cacheFlushInterval === null) { + delete this.$$cacheFlushInterval; + } else if (!angular.isNumber(cacheFlushInterval)) { + throw angular.$$minErr('ng')('areq', 'Expected cacheFlushInterval to be a number! Found: {0}.', typeof cacheFlushInterval); + } else if (cacheFlushInterval < 0) { + throw angular.$$minErr('ng')('areq', 'Expected cacheFlushInterval to be greater than zero! Found: {0}.', cacheFlushInterval); + } else if (cacheFlushInterval !== this.$$cacheFlushInterval) { + this.$$cacheFlushInterval = cacheFlushInterval; + clearInterval(this.$$cacheFlushIntervalId); + (function (_this) { + _this.$$cacheFlushIntervalId = setInterval(function () { + _this.removeAll(); + }, _this.$$cacheFlushInterval); + })(this); + } +}; + +},{}],13:[function(require,module,exports){ +/** + * @doc method + * @id DSCache.methods:setCapacity + * @name setCapacity + * @description + * Set the capacity for this cache. + * + * ## Signature: + * ```js + * DSCache#setCapacity(capacity) + * ``` + * + * ## Example: + * ```js + * var smallCache = DSCacheFactory('smallCache', { capacity: 2 }); + * + * smallCache.info().size; // 0 + * + * smallCache.put('1', 'apple'); + * smallCache.put('2', 'banana'); + * + * smallCache.info().size; // 2 + * + * // Least-recently used items are removed + * // when the cache's new capacity exceeds + * // its size + * smallCache.setCapacity(1); + * + * smallCache.get('1'); // undefined + * smallCache.info().size; // 1 + * ``` + * + * ## Throws: + * - `Error` - `capacity` must be `null` or a number greater than zero. + * + * @param {number|null} capacity The new capacity for this cache. If `capacity` is `null` then the capacity for this cache + * will be reset to the default (`Number.MAX_VALUE`). + * @returns {object} Key-value pairs of any items removed because this cache's size exceeds the new capacity. + */ +module.exports = function setCapacity(capacity) { + if (capacity === null) { + delete this.$$capacity; + } else if (!angular.isNumber(capacity)) { + throw angular.$$minErr('ng')('areq', 'Expected capacity to be a number! Found: {0}.', typeof capacity); + } else if (capacity < 0) { + throw angular.$$minErr('ng')('areq', 'Expected capacity to be greater than zero! Found: {0}.', capacity); + } else { + this.$$capacity = capacity; + } + var removed = {}; + while (this.$$lruHeap.size() > this.$$capacity) { + removed[this.$$lruHeap.peek().key] = this.remove(this.$$lruHeap.peek().key); + } + return removed; +}; + +},{}],14:[function(require,module,exports){ +/** + * @doc method + * @id DSCache.methods:setDeleteOnExpire + * @name setDeleteOnExpire + * @description + * Set the behavior for this cache for when items expire. This setting determines what this cache will do when one of + * its items expires. + * + * ## Possible Values: + * - `"none"` - Do nothing when items expire. + * - `"passive"` - Do nothing when items expire, but if an expired item is requested, remove it from the cache and return `undefined`. + * - `"aggressive"` - Scan for expired items on the interval specified by the `recycleFreq` setting for this cache (defaults + * to `1000ms`) and actively remove any expired items. + * + * ## Signature: + * ```js + * DSCache#setDeleteOnExpire(deleteOnExpire) + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory('cache'); + * + * cache.put('1', 'apple'); + * + * // Wait a few seconds + * + * cache.get('1'); // "apple" + * + * cache.setDeleteOnExpire('aggressive'); + * + * // Wait a few seconds + * + * cache.get('1'); // undefined + * ``` + * + * ## Throws: + * - `Error` - `deleteOnExpire` must be `null`, `"none"`, `"passive"` or `"aggressive"`. + * + * @param {string|null} deleteOnExpire The new deleteOnExpire for this cache. If `deleteOnExpire` is `null` then + * `deleteOnExpire` for this cache will be reset to the default (`"none"`). + */ +module.exports = function setDeleteOnExpire(deleteOnExpire) { + if (deleteOnExpire === null) { + delete this.$$deleteOnExpire; + } else if (!angular.isString(deleteOnExpire)) { + throw angular.$$minErr('ng')('areq', 'Expected deleteOnExpire to be a string! Found: {0}.', typeof deleteOnExpire); + } else if (deleteOnExpire !== 'none' && deleteOnExpire !== 'passive' && deleteOnExpire !== 'aggressive') { + throw angular.$$minErr('ng')('areq', 'Expected deleteOnExpire to be "none", "passive" or "aggressive"! Found: {0}.', deleteOnExpire); + } else { + this.$$deleteOnExpire = deleteOnExpire; + } + this.setRecycleFreq(this.$$recycleFreq); +}; + +},{}],15:[function(require,module,exports){ +var utils = require('../utils'); - /** - * @method AngularCacheFactory.removeAll - * @desc Destroy all caches in $angularCacheFactory. - */ - angularCacheFactory.removeAll = function () { - var keys = _keys(caches); - for (var i = 0; i < keys.length; i++) { - caches[keys[i]].destroy(); +/** + * @doc method + * @id DSCache.methods:setMaxAge + * @name setMaxAge + * @description + * Set the `maxAge` setting for this cache. This setting specifies how long items can be in the cache before they expire. + * + * ## Signature: + * ```js + * DSCache#setMaxAge(maxAge) + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory('cache', { deleteOnExpire: 'aggressive' }); + * + * // This won't expire for a long time + * cache.put('1', 'apple'); + * + * setTimeout(function () { + * // 'apple' will be removed because it + * // has already been in the cache longer + * // than the new maxAge + * var removed = cache.setMaxAge(1000); + * + * removed; // { + * // '1': 'apple' + * // } + * }, 1500); + * ``` + * + * ## Throws: + * - `Error` - `maxAge must be `null` or a number greater than zero. + * + * @param {number} maxAge The new maxAge for this cache in milliseconds. If `maxAge` is `null` then `maxAge` for this + * cache will be reset to the default (`Number.MAX_VALUE`); + * @returns {object} Key-value pairs of any items aggressively removed because they are expired according to the new + * `maxAge`. Items are only removed if the `deleteOnExpire` setting for this cache is set to `"aggressive"`. + */ +module.exports = function setMaxAge(maxAge) { + if (maxAge === null) { + delete this.$$maxAge; + } else if (!angular.isNumber(maxAge)) { + throw angular.$$minErr('ng')('areq', 'Expected maxAge to be a number! Found: {0}.', typeof maxAge); + } else if (maxAge < 0) { + throw angular.$$minErr('ng')('areq', 'Expected maxAge to be greater than zero! Found: {0}.', maxAge); + } else { + this.$$maxAge = maxAge; + } + var i, keys, key; + + this.$$expiresHeap.removeAll(); + + if (this.$$storage) { + var keysJson = this.$$storage.getItem(this.$$prefix + '.keys'); + + keys = keysJson ? angular.fromJson(keysJson) : []; + + for (i = 0; i < keys.length; i++) { + key = keys[i]; + var itemJson = this.$$storage.getItem(this.$$prefix + '.data.' + key); + + if (itemJson) { + var item = angular.fromJson(itemJson); + if (this.$$maxAge === Number.MAX_VALUE) { + item.expires = Number.MAX_VALUE; + } else { + item.expires = item.created + this.$$maxAge; } - }; + this.$$expiresHeap.push({ + key: key, + expires: item.expires + }); + } + } + } else { + keys = utils.keys(this.$$data); + + for (i = 0; i < keys.length; i++) { + key = keys[i]; + if (this.$$maxAge === Number.MAX_VALUE) { + this.$$data[key].expires = Number.MAX_VALUE; + } else { + this.$$data[key].expires = this.$$data[key].created + this.$$maxAge; + } + this.$$expiresHeap.push(this.$$data[key]); + } + } + if (this.$$deleteOnExpire === 'aggressive') { + return this.removeExpired(); + } else { + return {}; + } +}; + +},{"../utils":22}],16:[function(require,module,exports){ +/** + * @doc method + * @id DSCache.methods:setOnExpire + * @name setOnExpire + * @description + * Set the global `onExpire` callback for this cache. + * + * ## Signature: + * ```js + * DSCache#setOnExpire(onExpire) + * ``` + * + * ## Examples: + * ```js + * var options = { + * onExpire: function (key, value) { + * window.lastExpiredItem = key; + * }, + * maxAge: 1000, + * deleteOnExpire: 'aggressive' + * }; + * var cache = DSCacheFactory('cache', options); + * + * cache.put('1', 'apple'); + * + * setTimeout(function () { + * window.lastExpiredItem; // '1' + * }, 1500); + * ``` + * + * ## Throws: + * - `Error` - `cacheFlushInterval` must be `null` or a number greater than zero. + * + * @param {function|null} onExpire The new onExpire callback for this cache. If `onExpire` is `null` then the onExpire + * callback for this cache will be removed. + */ +module.exports = function setOnExpire(onExpire) { + if (onExpire === null) { + delete this.$$onExpire; + } else if (!angular.isFunction(onExpire)) { + throw angular.$$minErr('ng')('areq', 'Expected onExpire to be a function! Found: {0}.', typeof onExpire); + } else { + this.$$onExpire = onExpire; + } +}; - /** - * @method AngularCacheFactory.clearAll - * @desc Clears the contents of every cache in $angularCacheFactory. - */ - angularCacheFactory.clearAll = function () { - var keys = _keys(caches); - for (var i = 0; i < keys.length; i++) { - caches[keys[i]].removeAll(); +},{}],17:[function(require,module,exports){ +/** + * @doc method + * @id DSCache.methods:setRecycleFreq + * @name setRecycleFreq + * @description + * Set the `recycleFreq` setting for this cache. This setting determines how often this cache will scan for expired + * items. The cache will only scan for expired items if the `deleteOnExpire` setting for this cache is set to + * `"aggressive"`. + * + * ## Signature: + * ```js + * DSCache#setRecycleFreq(recycleFreq) + * ``` + * + * ## Example: + * ```js + * var options = { + * deleteOnExpire: 'aggressive', + * maxAge: 1000 + * }; + * var cache = DSCacheFactory('cache', options); + * + * cache.put('1', 'apple'); + * + * setTimeout(function () { + * + * cache.get('1'); // undefined + * cache.setRecycleFreq(60000); + * + * // This expires after 1 second, but the cache + * // only checks every 60 seconds now + * cache.put('1', 'apple'); + * + * setTimeout(function () { + * // expired, but won't be removed + * // until the next check + * cache.get('1'); // "apple" + * cache.info('1').isExpired; // true + * }, 1500); + * }, 1500); + * ``` + * + * ## Throws: + * - `Error` - `recycleFreq` must be `null` or a number greater than zero. + * + * @param {number} recycleFreq The new recycleFreq for this cache in milliseconds. If `recycleFreq` is `null` then + * `recycleFreq` for this cache will be reset to the default (`1000` milliseconds). + */ +module.exports = function setRecycleFreq(recycleFreq) { + if (recycleFreq === null) { + delete this.$$recycleFreq; + } else if (!angular.isNumber(recycleFreq)) { + throw angular.$$minErr('ng')('areq', 'Expected recycleFreq to be a number! Found: {0}.', typeof recycleFreq); + } else if (recycleFreq < 0) { + throw angular.$$minErr('ng')('areq', 'Expected recycleFreq to be greater than zero! Found: {0}.', recycleFreq); + } else { + this.$$recycleFreq = recycleFreq; + } + clearInterval(this.$$recycleFreqId); + if (this.$$deleteOnExpire === 'aggressive') { + (function (_this) { + _this.$$recycleFreqId = setInterval(function () { + _this.removeExpired(); + }, _this.$$recycleFreq); + })(this); + } else { + delete this.$$recycleFreqId; + } +}; + +},{}],18:[function(require,module,exports){ +var defaults = require('../defaults'), + DSCache = require('../DSCache'), + version = '3.0.0-beta.1'; + +/** + * @doc function + * @id DSCacheFactoryProvider + * @name DSCacheFactoryProvider + */ +function DSCacheFactoryProvider() { + + var config = new defaults.Config(); + + this.version = version; + + /** + * @doc method + * @id DSCacheFactoryProvider.methods:setCacheDefaults + * @name setCacheDefaults + * @desc Set the default configuration for all caches created by $angularCacheFactory. + * @param {object} options Default configuration options for each new cache. + */ + this.setCacheDefaults = function (options) { + options = options || {}; + + if (!angular.isObject(options)) { + throw angular.$$minErr('ng')('areq', 'Expected options to be an object! Found: {0}.', typeof options); + } + + for (var key in defaults.defaults) { + if (key in options) { + config[key] = options[key]; + } + } + if ('disabled' in options) { + config.$$disabled = !!options.disabled; + } + }; + + this.$get = function () { + var caches = {}; + + /*! + * @method _keys + * @desc Returns an array of the keys of the given collection. + * @param {object} collection The collection from which to get the keys. + * @returns {array} An array of the keys of the given collection. + */ + function _keys(collection) { + var keys = [], key; + for (key in collection) { + if (collection.hasOwnProperty(key)) { + keys.push(key); } + } + return keys; + } + + /** + * @doc function + * @id DSCacheFactory + * @name DSCacheFactory + * @description + * Factory function that produces instances of `DSCache`. + * + * @param {string} cacheId The id of the new cache. + * @param {object} options Configuration options. Properties: + * + * - `{number=}` - `capacity` - Default: `Number.MAX_VALUE` + * - `{number=}` - `maxAge` - Default: `null` + * - `{number=}` - `deleteOnExpire` - Default: `none` + * - `{function=}` - `onExpire` - Default: `null` + * - `{number=}` - `cacheFlushInterval` - Default: `null` + * - `{number=}` - `recycleFreq` - Default: `1000` + * - `{number=}` - `deleteOnExpire` - Default: `null` + * - `{string=}` - `storageMode` - Default: `'none` + * - `{object=}` - `storageImpl` - Default: `null` + * - `{boolean=}` - `disabled` - Default: `false` + * - `{string=}` - `storagePrefix` - Default: `"angular-cache.caches."` + * + * @returns {DSCache} New instance of DSCache. + */ + function DSCacheFactory(cacheId, options) { + if (cacheId in caches) { + throw angular.$$minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); + } else if (!angular.isString(cacheId)) { + throw angular.$$minErr('ng')('areq', 'Expected cacheId to be a string! Found: {0}.', typeof cacheId); + } + + caches[cacheId] = new DSCache(cacheId, angular.extend({}, config, options)); + caches[cacheId].destroy = function () { + this.constructor.prototype.destroy.call(this); + delete caches[this.$$id]; }; + return caches[cacheId]; + } - /** - * @method AngularCacheFactory.enableAll - * @desc Enable any disabled caches. - */ - angularCacheFactory.enableAll = function () { - var keys = _keys(caches); - for (var i = 0; i < keys.length; i++) { - caches[keys[i]].setOptions({ disabled: false }); - } + DSCacheFactory.version = version; + + /** + * @doc method + * @id DSCacheFactory.methods:info + * @name info + * @description + * Return the status of `DSCacheFactory`. + * @returns {object} The status of `DSCacheFactory`. + */ + DSCacheFactory.info = function () { + var keys = _keys(caches); + var info = { + size: keys.length, + caches: {} }; + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + info.caches[key] = caches[key].info(); + } + var c = info.cacheDefaults = angular.extend({}, config); + for (var option in defaults.defaults) { + if (!(option in c)) { + c[option] = config['$$' + option]; + } + } + return info; + }; + + /** + * @doc method + * @id DSCacheFactory.methods:get + * @name get + * @description + * Return the cache with the given `cacheId`. + * @param {string} cacheId The id of the desired cache. + * @returns {DSCache} The cache with the specified `cacheId`. + */ + DSCacheFactory.get = function (cacheId) { + if (!angular.isString(cacheId)) { + throw angular.$$minErr('ng')('areq', 'Expected cacheId to be a string! Found: {0}.', typeof cacheId); + } + return caches[cacheId]; + }; - /** - * @method AngularCacheFactory.disableAll - * @desc Disable all caches. - */ - angularCacheFactory.disableAll = function () { - var keys = _keys(caches); - for (var i = 0; i < keys.length; i++) { - caches[keys[i]].setOptions({ disabled: true }); + /** + * @doc method + * @id DSCacheFactory.methods:keySet + * @name keySet + * @description + * Return an object containing the `cacheId` of each cache. + * @returns {object} An object containing the `cacheId` of each cache. + */ + DSCacheFactory.keySet = function () { + var cacheIds = {}, cacheId; + for (cacheId in caches) { + if (caches.hasOwnProperty(cacheId)) { + cacheIds[cacheId] = cacheId; } - }; + } + return cacheIds; + }; - return angularCacheFactory; - }]; - } + /** + * @doc method + * @id DSCacheFactory.methods:keys + * @name keys + * @description + * Return an array containing the `cacheId` of each cache. + * @returns {array} An array containing the `cacheId` of each cache. + */ + DSCacheFactory.keys = function () { + return _keys(caches); + }; + + /** + * @doc method + * @id DSCacheFactory.methods:destroyAll + * @name destroyAll + * @description + * Destroy all caches. + * + * ## Signature: + * ```js + * DSCacheFactory.destroyAll() + * ``` + * + * ## Example: + * ```js + * var newCache = DSCacheFactory('newCache'); + * var otherCache = DSCacheFactory('otherCache'); + * + * newCache.info().size; // 0 + * otherCache.info().size; // 0 + * + * newCache.put('1', 'apple'); + * newCache.put('2', 'banana'); + * otherCache.put('abcd', 'horse'); + * + * newCache.info().size; // 2 + * otherCache.info().size; // 1 + * + * DSCacheFactory.destroyAll(); + * + * newCache.info().size; // Error thrown + * otherCache.info().size; // Error thrown + * + * DSCacheFactory.get('newCache'); // undefined + * DSCacheFactory.get('otherCache'); // undefined + * ``` + */ + DSCacheFactory.destroyAll = function () { + for (var cacheId in caches) { + caches[cacheId].destroy(); + } + caches = {}; + }; + + /** + * @doc method + * @id DSCacheFactory.methods:clearAll + * @name clearAll + * @description + * Clear the contents of all caches. + * + * ## Signature: + * ```js + * DSCacheFactory.clearAll() + * ``` + * + * ## Example: + * ```js + * var newCache = DSCacheFactory('newCache'); + * var otherCache = DSCacheFactory('otherCache'); + * + * newCache.info().size; // 0 + * otherCache.info().size; // 0 + * + * newCache.put('1', 'apple'); + * newCache.put('2', 'banana'); + * otherCache.put('abcd', 'horse'); + * + * newCache.info().size; // 2 + * otherCache.info().size; // 1 + * + * DSCacheFactory.clearAll(); + * + * newCache.info().size; // 0 + * otherCache.info().size; // 0 + * ``` + */ + DSCacheFactory.clearAll = function () { + for (var cacheId in caches) { + caches[cacheId].removeAll(); + } + }; + + /** + * @doc method + * @id DSCacheFactory.methods:enableAll + * @name enableAll + * @description + * Enable any disabled caches. + * + * ## Signature: + * ```js + * DSCacheFactory.enableAll() + * ``` + * + * ## Example: + * ```js + * var newCache = DSCacheFactory('newCache', { disabled: true }); + * var otherCache = DSCacheFactory('otherCache', { disabled: true }); + * + * newCache.info().disabled; // true + * otherCache.info().disabled; // true + * + * DSCacheFactory.enableAll(); + * + * newCache.info().disabled; // false + * otherCache.info().disabled; // false + * ``` + */ + DSCacheFactory.enableAll = function () { + for (var cacheId in caches) { + caches[cacheId].$$disabled = false; + } + }; + + /** + * @doc method + * @id DSCacheFactory.methods:disableAll + * @name disableAll + * @description + * Disable all caches. + * + * ## Signature: + * ```js + * DSCacheFactory.disableAll() + * ``` + * + * ## Example: + * ```js + * var newCache = DSCacheFactory('newCache'); + * var otherCache = DSCacheFactory('otherCache'); + * + * newCache.info().disabled; // false + * otherCache.info().disabled; // false + * + * DSCacheFactory.disableAll(); + * + * newCache.info().disabled; // true + * otherCache.info().disabled; // true + * ``` + */ + DSCacheFactory.disableAll = function () { + for (var cacheId in caches) { + caches[cacheId].$$disabled = true; + } + }; + + return DSCacheFactory; + }; +} + +module.exports = DSCacheFactoryProvider; + +},{"../DSCache":4,"../defaults":"Gv0+ce"}],"Defaults":[function(require,module,exports){ +module.exports=require('Gv0+ce'); +},{}],"Gv0+ce":[function(require,module,exports){ +var defaults = { + /** + * @doc overview + * @id capacity + * @name capacity + * @description + * __Default:__ `Number.MAX_VALUE` + * + * This option limits the capacity of a cache. With a maximum capacity set, a cache operates as an LRU cache, + * deleting the least-recently-used item when the cache exceeds capacity. + * + * This option is dynamically configurable. Must be a number (milliseconds) greater than zero. + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + * - `DSCache.setCapacity(capacity)` + * - `DSCache.setOptions(options[, strict])` + */ + capacity: Number.MAX_VALUE, + + /** + * @doc overview + * @id maxAge + * @name maxAge + * @description + * __Default:__ `Number.MAX_VALUE` + * + * This option determines how long an item is in a cache before the item expires.. With `maxAge` set, items are + * marked as expired when their time in a cache exceeds `maxAge`. A cache's behavior when an item expires is + * determined by the [deleteOnExpire](/documentation/api/angular-cache/deleteOnExpire) option. + * + * This option is dynamically configurable. Must be a number (milliseconds) greater than zero. + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + * - `DSCache.setMaxAge(maxAge)` + * - `DSCache.setOptions(options[, strict])` + */ + maxAge: Number.MAX_VALUE, + + /** + * @doc overview + * @id deleteOnExpire + * @name deleteOnExpire + * @description + * __Default:__ `"none"` + * + * This option determines how long an item is in a cache before the item expires.. With `maxAge` set, items are + * marked as expired when their time in a cache exceeds `maxAge`. A cache's behavior when an item expires is + * determined by the [deleteOnExpire](/documentation/api/angular-cache/deleteOnExpire) option. + * + * This option is dynamically configurable. Must be `"none"`, `"passive"` or `"aggressive"`. + * + * #### "none" + * A cache will do nothing when its items expire. + * + * #### "passive" + * A cache will do nothing when its items expire. If an expired item is request it is removed from the cache and + * `undefined` is returned. + * + * #### "aggressive" + * A cache will periodically scan for expired items and actively remove them from the cache if any are found. The + * frequency of the scan is determined by the [recycleFreq](/documentation/api/angular-cache/recycleFreq) option. + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + * - `DSCache.setRecycleFreq(recycleFreq)` + * - `DSCache.setOptions(options[, strict])` + */ + deleteOnExpire: 'none', + + /** + * @doc overview + * @id onExpire + * @name onExpire + * @description + * __Default:__ `"none"` + * + * This option is a callback function which will be executed whenever an expired item is removed from a cache by + * either requesting an expired item while the cache is in `"passive"` `deleteOnExpire` mode, or when an expired + * item is actively removed when the cache is in `"aggressive"` `deleteOnExpire` mode. + * + * This option is dynamically configurable. Must be a function. Will be passed the `key` and `value` of the expired + * item. Will be passed a third `done` argument (if in `"passive"` `deleteOnExpire` mode) which is the `onExpire` + * argument passed to [DSCache#get(key[, options])](/documentation/api/angular-cache/DSCache.methods:get). + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + * - `DSCache.setOnExpire(onExpire)` + * - `DSCache.setOptions(options[, strict])` + */ + onExpire: null, + + /** + * @doc overview + * @id cacheFlushInterval + * @name cacheFlushInterval + * @description + * __Default:__ `null` + * + * This option, if set, will cause a cache to periodically clear itself of all data. + * + * This option is dynamically configurable. Must be a number (milliseconds) greater than zero. + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + * - `DSCache.setCacheFlushInterval(cacheFlushInterval)` + * - `DSCache.setOptions(options[, strict])` + */ + cacheFlushInterval: null, + + /** + * @doc overview + * @id recycleFreq + * @name recycleFreq + * @description + * __Default:__ `1000` + * + * This option determines how often a cache will scan for expired items when in `"aggressive"` `deleteOnExpire` + * mode. + * + * This option is dynamically configurable. Must be a number (milliseconds) greater than zero. + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + * - `DSCache.setRecycleFreq(recycleFreq)` + * - `DSCache.setOptions(options[, strict])` + */ + recycleFreq: 1000, + + /** + * @doc overview + * @id storageMode + * @name storageMode + * @description + * __Default:__ `"memory"` + * + * This option determines the storage mode for a cache. + * + * #### "memory" + * All data will be held in memory. + * + * #### "localStorage" + * Data will be held in `localStorage`, if available (or + * [storageImpl](/documentation/api/angular-cache/storageImpl) is provided). + * + * #### "sessionStorage" + * Data will be held in `sessionStorage`, if available (or + * [storageImpl](/documentation/api/angular-cache/storageImpl) is provided). + * + * This option is NOT dynamically configurable. Must be `"memory"`, `"localStorage"` or `"sessionStorage"`. + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + */ + storageMode: 'memory', + + /** + * @doc overview + * @id storageImpl + * @name storageImpl + * @description + * __Default:__ `null` + * + * This option is available if you want to provide a custom `localStorage` or `sessionStorage` implementation. + * + * This option is NOT dynamically configurable. Must be an object that implements `setItem(key, value)`, + * `getItem(key)` and `removeItem(key)`. + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + */ + storageImpl: null, + + /** + * @doc overview + * @id disabled + * @name disabled + * @description + * __Default:__ `false` + * + * This option disables or enables cache. + * + * This option is dynamically configurable. Must be `true` or `false`. + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + * - `DSCache.setOptions(options[, strict])` + * + * or just use [DSCache#disable()](/documentation/api/angular-cache/DSCache.methods:disable) or + * [DSCache#enable()](/documentation/api/angular-cache/DSCache.methods:enable). + */ + disabled: false, + + /** + * @doc overview + * @id storagePrefix + * @name storagePrefix + * @description + * __Default:__ `"angular-cache.caches."` + * + * This option determines the namespace for a cache when `storageMode` is `"localStorage"` or `"sessionStorage"`. + * Setting this value to something like `"ac."` will save space when using WebStorage. + * + * This option is NOT dynamically configurable. Must be a string. + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + */ + storagePrefix: 'angular-cache.caches.' +}; + +function Config() { +} + +for (var option in defaults) { + Config.prototype['$$' + option] = defaults[option]; +} + +module.exports = { + Config: Config, + defaults: defaults +}; + +},{}],21:[function(require,module,exports){ +(function (window, angular, undefined) { + 'use strict'; + + angular.$$minErr = angular.$$minErr || function (module) { + return function () { + var code = arguments[0], + prefix = '[' + (module ? module + ':' : '') + code + '] ', + template = arguments[1]; + + return new Error(prefix + template); + }; + }; + + angular.module('angular-data.DSBinaryHeap', []) + .provider('DSBinaryHeap', require('./DSBinaryHeap')); + + /** + * @doc overview + * @id angular-cache + * @name Overview + * @description + * __Version:__ 3.0.0-beta.1 + * + * ## Install + * + * #### Bower + * ```text + * bower install angular-cache + * ``` + * + * Load `dist/angular-cache.js` or `dist/angular-cache.min.js` onto your web page after Angular.js. + * + * #### Npm + * ```text + * npm install angular-cache + * ``` + * + * Load `dist/angular-cache.js` or `dist/angular-cache.min.js` onto your web page after Angular.js. Angular-cache is + * also consumable by Browserify and you should be able to `require('angular-cache')`. The `main` file is `src/index.js`. + * + * #### Manual download + * Download angular-cache.3.0.0-beta.1.js from the [Releases](https://github.com/jmdobry/angular-cache/releases) + * section of the angular-cache GitHub project. + * + * ## Load into Angular + * Your Angular app must depend on the module `"angular-data.DSCacheFactory"` in order to use angular-cache. Loading + * angular-cache into your app allows you to inject the following: + * + * - `DSCacheFactory` + * - `DSBinaryHeap` + * + * [DSCacheFactory](/documentation/api/api/DSCacheFactory) is a factory function that produces instances of + * [DSCache](/documentation/api/api/DSCache), which is API compatible with caches produced by Angular's + * [$cacheFactory](http://docs.angularjs.org/api/ng/service/$cacheFactory). + * + * [DSBinaryHeap](/documentation/api/api/DSBinaryHeap) is a priority queue implemented as a Binary Heap. + * + * Angular-cache is a dependency of [angular-data](/documentation/api/api/angular-data) and must be loaded before + * angular-data if you are using angular-data. + */ + angular.module('angular-data.DSCacheFactory', ['ng', 'angular-data.DSBinaryHeap']) + .provider('DSCacheFactory', require('./DSCacheFactory')); - // Register the new provider with Angular. - angular.module('jmdobry.angular-cache').provider('$angularCacheFactory', $AngularCacheFactoryProvider); })(window, window.angular); + +},{"./DSBinaryHeap":1,"./DSCacheFactory":18}],22:[function(require,module,exports){ +module.exports = { + /*! + * Stringify a number. + */ + stringifyNumber: function (number) { + if (number && angular.isNumber(number)) { + return number.toString(); + } + return number; + }, + + /*! + * Return a hash of the keys in the given collection. + */ + keySet: function (collection) { + var keySet = {}, key; + for (key in collection) { + if (collection.hasOwnProperty(key)) { + keySet[key] = key; + } + } + return keySet; + }, + + /*! + * Return an array of the keys in the given collection + */ + keys: function (collection) { + var keys = [], key; + for (key in collection) { + if (collection.hasOwnProperty(key)) { + keys.push(key); + } + } + return keys; + } +}; + +},{}]},{},[21]) diff --git a/dist/angular-cache.min.js b/dist/angular-cache.min.js index 8dfb4e6..a78436a 100644 --- a/dist/angular-cache.min.js +++ b/dist/angular-cache.min.js @@ -1,10 +1,10 @@ /** * @author Jason Dobry * @file angular-cache.min.js -* @version 2.3.3 - Homepage -* @copyright (c) 2013 Jason Dobry +* @version 3.0.0-beta.1 - Homepage +* @copyright (c) 2013 Jason Dobry * @license MIT * * @overview angular-cache is a very useful replacement for Angular's $cacheFactory. */ -!function(a,b,c){"use strict";function d(){this.$get=function(){function a(a,b,c){for(var d=a[c],e=b(d);c>0;){var f=Math.floor((c+1)/2)-1,g=a[f];if(e>=b(g))break;a[f]=d,a[c]=g,c=f}}function c(a,b,c){for(var d=a.length,e=a[c],f=b(e);;){var g=2*(c+1),h=g-1,i=null;if(d>h){var j=a[h],k=b(j);f>k&&(i=h)}if(d>g){var l=a[g],m=b(l);m<(null===i?f:b(a[h]))&&(i=g)}if(null===i)break;a[c]=a[i],a[i]=e,c=i}}function d(a){if(a&&!b.isFunction(a))throw new Error("BinaryHeap(weightFunc): weightFunc: must be a function!");a=a||function(a){return a},this.weightFunc=a,this.heap=[]}return d.prototype.push=function(b){this.heap.push(b),a(this.heap,this.weightFunc,this.heap.length-1)},d.prototype.peek=function(){return this.heap[0]},d.prototype.pop=function(){var a=this.heap[0],b=this.heap.pop();return this.heap.length>0&&(this.heap[0]=b,c(this.heap,this.weightFunc,0)),a},d.prototype.remove=function(d){for(var e=this.heap.length,f=0;e>f;f++)if(b.equals(this.heap[f],d)){var g=this.heap[f],h=this.heap.pop();return f!==e-1&&(this.heap[f]=h,a(this.heap,this.weightFunc,f),c(this.heap,this.weightFunc,f)),g}return null},d.prototype.removeAll=function(){this.heap=[]},d.prototype.size=function(){return this.heap.length},d}}function e(){function a(a,c){c(b.isNumber(a)?0>a?"must be greater than zero!":null:"must be a number!")}var d,e=function(){return{capacity:Number.MAX_VALUE,maxAge:null,deleteOnExpire:"none",onExpire:null,cacheFlushInterval:null,recycleFreq:1e3,storageMode:"none",storageImpl:null,verifyIntegrity:!0,disabled:!1}};this.setCacheDefaults=function(c){var f="$angularCacheFactoryProvider.setCacheDefaults(options): ";if(c=c||{},!b.isObject(c))throw new Error(f+"options: must be an object!");if("disabled"in c&&(c.disabled=c.disabled===!0),"capacity"in c&&a(c.capacity,function(a){if(a)throw new Error(f+"capacity: "+a)}),"deleteOnExpire"in c){if(!b.isString(c.deleteOnExpire))throw new Error(f+"deleteOnExpire: must be a string!");if("none"!==c.deleteOnExpire&&"passive"!==c.deleteOnExpire&&"aggressive"!==c.deleteOnExpire)throw new Error(f+'deleteOnExpire: accepted values are "none", "passive" or "aggressive"!')}if("maxAge"in c&&a(c.maxAge,function(a){if(a)throw new Error(f+"maxAge: "+a)}),"recycleFreq"in c&&a(c.recycleFreq,function(a){if(a)throw new Error(f+"recycleFreq: "+a)}),"cacheFlushInterval"in c&&a(c.cacheFlushInterval,function(a){if(a)throw new Error(f+"cacheFlushInterval: "+a)}),"storageMode"in c){if(!b.isString(c.storageMode))throw new Error(f+"storageMode: must be a string!");if("none"!==c.storageMode&&"localStorage"!==c.storageMode&&"sessionStorage"!==c.storageMode)throw new Error(f+'storageMode: accepted values are "none", "localStorage" or "sessionStorage"!');if("storageImpl"in c){if(!b.isObject(c.storageImpl))throw new Error(f+"storageImpl: must be an object!");if(!("setItem"in c.storageImpl&&"function"==typeof c.storageImpl.setItem))throw new Error(f+'storageImpl: must implement "setItem(key, value)"!');if(!("getItem"in c.storageImpl&&"function"==typeof c.storageImpl.getItem))throw new Error(f+'storageImpl: must implement "getItem(key)"!');if(!("removeItem"in c.storageImpl)||"function"!=typeof c.storageImpl.removeItem)throw new Error(f+'storageImpl: must implement "removeItem(key)"!')}}if("onExpire"in c&&"function"!=typeof c.onExpire)throw new Error(f+"onExpire: must be a function!");d=b.extend({},e(),c)},this.setCacheDefaults({}),this.$get=["$window","BinaryHeap",function(e,f){function g(a){return a&&b.isNumber(a)?a.toString():a}function h(a){var b,c={};for(b in a)a.hasOwnProperty(b)&&(c[b]=b);return c}function i(a){var b,c=[];for(b in a)a.hasOwnProperty(b)&&c.push(b);return c}function j(j,k){function m(b){a(b,function(a){if(a)throw new Error("capacity: "+a);for(B.capacity=b;E.size()>B.capacity;)H.remove(E.peek().key,{verifyIntegrity:!1})})}function n(a){if(!b.isString(a))throw new Error("deleteOnExpire: must be a string!");if("none"!==a&&"passive"!==a&&"aggressive"!==a)throw new Error('deleteOnExpire: accepted values are "none", "passive" or "aggressive"!');B.deleteOnExpire=a}function o(b){var c=i(C);if(null===b){if(B.maxAge)for(var d=0;de&&"aggressive"===f)I.removeItem(F+".data."+a[c]);else{var g={created:d.created};d.expires&&(g.expires=d.expires),d.accessed&&(g.accessed=d.accessed),d.maxAge&&(g.maxAge=d.maxAge),d.deleteOnExpire&&(g.deleteOnExpire=d.deleteOnExpire),H.put(a[c],d.value,g)}}v(null)}}function v(a){"none"!==B.storageMode&&I&&(I.setItem(F+".keys",b.toJson(i(C))),a&&I.setItem(F+".data."+a,b.toJson(C[a])))}function w(a){if((a||a!==!1&&B.verifyIntegrity)&&"none"!==B.storageMode&&I){var c=i(C);I.setItem(F+".keys",b.toJson(c));for(var d=0;dB.capacity&&this.remove(E.peek().key,{verifyIntegrity:!1}),d}},this.get=function(a,d){if(!B.disabled){if(b.isArray(a)){var e=a,f=[];return b.forEach(e,function(a){var c=H.get(a,d);b.isDefined(c)&&f.push(c)}),f}if(a=g(a),d=d||{},!b.isString(a))throw new Error("AngularCache.get(key, options): key: must be a string!");if(d&&!b.isObject(d))throw new Error("AngularCache.get(key, options): options: must be an object!");if(d.onExpire&&!b.isFunction(d.onExpire))throw new Error("AngularCache.get(key, options): onExpire: must be a function!");if(x(a,d.verifyIntegrity),a in C){var h=C[a],i=h.value,j=(new Date).getTime(),k=h.deleteOnExpire||B.deleteOnExpire;return E.remove(h),h.accessed=j,E.push(h),"passive"===k&&"expires"in h&&h.expiresc.maxAge),c}return C[a]}return b.extend({},B,{size:E&&E.size()||0})},this.keySet=function(){return h(C)},this.keys=function(){return i(C)},this.setOptions=t,t(k,!0,{verifyIntegrity:!1})}function k(a,c){if(a in l)throw new Error("cacheId "+a+" taken!");if(!b.isString(a))throw new Error("cacheId must be a string!");return l[a]=new j(a,c),l[a]}var l={};return k.info=function(){for(var a=i(l),c={size:a.length,caches:{}},e=0;e0;){var f=Math.floor((c+1)/2)-1,g=a[f];if(e>=b(g))break;a[f]=d,a[c]=g,c=f}}function b(a,b,c){for(var d=a.length,e=a[c],f=b(e);;){var g=2*(c+1),h=g-1,i=null;if(d>h){var j=a[h],k=b(j);f>k&&(i=h)}if(d>g){var l=a[g],m=b(l);m<(null===i?f:b(a[h]))&&(i=g)}if(null===i)break;a[c]=a[i],a[i]=e,c=i}}function c(a){if(a&&!angular.isFunction(a))throw new Error("DSBinaryHeap(weightFunc): weightFunc: must be a function!");a=a||function(a){return a},this.weightFunc=a,this.heap=[]}return c.prototype.push=function(b){this.heap.push(b),a(this.heap,this.weightFunc,this.heap.length-1)},c.prototype.peek=function(){return this.heap[0]},c.prototype.pop=function(){var a=this.heap[0],c=this.heap.pop();return this.heap.length>0&&(this.heap[0]=c,b(this.heap,this.weightFunc,0)),a},c.prototype.remove=function(c){for(var d=this.heap.length,e=0;d>e;e++)if(angular.equals(this.heap[e],c)){var f=this.heap[e],g=this.heap.pop();return e!==d-1&&(this.heap[e]=g,a(this.heap,this.weightFunc,e),b(this.heap,this.weightFunc,e)),f}return null},c.prototype.removeAll=function(){this.heap=[]},c.prototype.size=function(){return this.heap.length},c}}b.exports=c},{}],2:[function(a,b){b.exports=function(){clearInterval(this.$$cacheFlushIntervalId),clearInterval(this.$$recycleFreqId),this.removeAll(),this.$$storage&&(this.$$storage.removeItem(this.$$prefix+".keys"),this.$$storage.removeItem(this.$$prefix)),this.$$storage=null,this.$$data=null,this.$$lruHeap=null,this.$$expiresHeap=null,this.$$prefix=null}},{}],3:[function(a,b){var c=a("../utils");b.exports=function(a,b){var d=this;if(angular.isArray(a)){var e=a,f=[];return angular.forEach(e,function(a){var c=d.get(a,b);null!==c&&void 0!==c&&f.push(c)}),f}if(a=c.stringifyNumber(a),!this.$$disabled){if(b=b||{},!angular.isString(a))throw angular.$$minErr("ng")("areq","Expected key to be a string! Found: {0}.",typeof a);if(b&&!angular.isObject(b))throw angular.$$minErr("ng")("areq","Expected options to be an object! Found: {0}.",typeof b);if(b.onExpire&&!angular.isFunction(b.onExpire))throw angular.$$minErr("ng")("areq","Expected options.onExpire to be a function! Found: {0}.",typeof b.onExpire);var g;if(this.$$storage){var h=this.$$storage.getItem(this.$$prefix+".data."+a);if(!h)return;g=angular.fromJson(h)}else{if(!(a in this.$$data))return;g=this.$$data[a]}var i=g.value,j=(new Date).getTime();return this.$$storage?(this.$$lruHeap.remove({key:a,accessed:g.accessed}),g.accessed=j,this.$$lruHeap.push({key:a,accessed:j})):(this.$$lruHeap.remove(g),g.accessed=j,this.$$lruHeap.push(g)),"passive"===this.$$deleteOnExpire&&"expires"in g&&g.expiresthis.$$maxAge}}return void 0}return{id:this.$$id,capacity:this.$$capacity,maxAge:this.$$maxAge,deleteOnExpire:this.$$deleteOnExpire,onExpire:this.$$onExpire,cacheFlushInterval:this.$$cacheFlushInterval,recycleFreq:this.$$recycleFreq,storageMode:this.$$storageMode,storageImpl:this.$$storageImpl,disabled:this.$$disabled,size:this.$$lruHeap&&this.$$lruHeap.size()||0}}},{}],6:[function(a,b){var c=a("../utils");b.exports=function(){if(this.$$storage){var a=this.$$storage.getItem(this.$$prefix+".keys"),b={};if(a)for(var d=angular.fromJson(a),e=0;ethis.$$capacity&&this.remove(this.$$lruHeap.peek().key),b}}},{"../utils":22}],9:[function(a,b){b.exports=function(a){if(!this.$$storage){var b=this.$$data[a]?this.$$data[a].value:void 0;return this.$$lruHeap.remove(this.$$data[a]),this.$$expiresHeap.remove(this.$$data[a]),this.$$data[a]=null,delete this.$$data[a],b}var c=this.$$storage.getItem(this.$$prefix+".data."+a);if(c){var d=angular.fromJson(c);this.$$lruHeap.remove({key:a,accessed:d.accessed}),this.$$expiresHeap.remove({key:a,expires:d.expires}),this.$$storage.removeItem(this.$$prefix+".data."+a);var e=this.$$storage.getItem(this.$$prefix+".keys"),f=e?angular.fromJson(e):[],g=f.indexOf(a);return g>=0&&f.splice(g,1),this.$$storage.setItem(this.$$prefix+".keys",angular.toJson(f)),d.value}}},{}],10:[function(a,b){b.exports=function(){if(this.$$storage){this.$$lruHeap.removeAll(),this.$$expiresHeap.removeAll();var a=this.$$storage.getItem(this.$$prefix+".keys");if(a)for(var b=angular.fromJson(a),c=0;ca)throw angular.$$minErr("ng")("areq","Expected cacheFlushInterval to be greater than zero! Found: {0}.",a);a!==this.$$cacheFlushInterval&&(this.$$cacheFlushInterval=a,clearInterval(this.$$cacheFlushIntervalId),function(a){a.$$cacheFlushIntervalId=setInterval(function(){a.removeAll()},a.$$cacheFlushInterval)}(this))}}},{}],13:[function(a,b){b.exports=function(a){if(null===a)delete this.$$capacity;else{if(!angular.isNumber(a))throw angular.$$minErr("ng")("areq","Expected capacity to be a number! Found: {0}.",typeof a);if(0>a)throw angular.$$minErr("ng")("areq","Expected capacity to be greater than zero! Found: {0}.",a);this.$$capacity=a}for(var b={};this.$$lruHeap.size()>this.$$capacity;)b[this.$$lruHeap.peek().key]=this.remove(this.$$lruHeap.peek().key);return b}},{}],14:[function(a,b){b.exports=function(a){if(null===a)delete this.$$deleteOnExpire;else{if(!angular.isString(a))throw angular.$$minErr("ng")("areq","Expected deleteOnExpire to be a string! Found: {0}.",typeof a);if("none"!==a&&"passive"!==a&&"aggressive"!==a)throw angular.$$minErr("ng")("areq",'Expected deleteOnExpire to be "none", "passive" or "aggressive"! Found: {0}.',a);this.$$deleteOnExpire=a}this.setRecycleFreq(this.$$recycleFreq)}},{}],15:[function(a,b){var c=a("../utils");b.exports=function(a){if(null===a)delete this.$$maxAge;else{if(!angular.isNumber(a))throw angular.$$minErr("ng")("areq","Expected maxAge to be a number! Found: {0}.",typeof a);if(0>a)throw angular.$$minErr("ng")("areq","Expected maxAge to be greater than zero! Found: {0}.",a);this.$$maxAge=a}var b,d,e;if(this.$$expiresHeap.removeAll(),this.$$storage){var f=this.$$storage.getItem(this.$$prefix+".keys");for(d=f?angular.fromJson(f):[],b=0;ba)throw angular.$$minErr("ng")("areq","Expected recycleFreq to be greater than zero! Found: {0}.",a);this.$$recycleFreq=a}clearInterval(this.$$recycleFreqId),"aggressive"===this.$$deleteOnExpire?!function(a){a.$$recycleFreqId=setInterval(function(){a.removeExpired()},a.$$recycleFreq)}(this):delete this.$$recycleFreqId}},{}],18:[function(a,b){function c(){var a=new d.Config;this.version=f,this.setCacheDefaults=function(b){if(b=b||{},!angular.isObject(b))throw angular.$$minErr("ng")("areq","Expected options to be an object! Found: {0}.",typeof b);for(var c in d.defaults)c in b&&(a[c]=b[c]);"disabled"in b&&(a.$$disabled=!!b.disabled)},this.$get=function(){function b(a){var b,c=[];for(b in a)a.hasOwnProperty(b)&&c.push(b);return c}function c(b,c){if(b in g)throw angular.$$minErr("$cacheFactory")("iid","CacheId '{0}' is already taken!",b);if(!angular.isString(b))throw angular.$$minErr("ng")("areq","Expected cacheId to be a string! Found: {0}.",typeof b);return g[b]=new e(b,angular.extend({},a,c)),g[b].destroy=function(){this.constructor.prototype.destroy.call(this),delete g[this.$$id]},g[b]}var g={};return c.version=f,c.info=function(){for(var c=b(g),e={size:c.length,caches:{}},f=0;f 0) { + // Compute the parent element's index, and fetch it. + var parentN = Math.floor((n + 1) / 2) - 1, + parent = heap[parentN]; + // If the parent has a lesser weight, things are in order and we + // are done. + if (weight >= weightFunc(parent)) { + break; + } else { + heap[parentN] = element; + heap[n] = parent; + n = parentN; + } + } + } + + /** + * @method bubbleDown + * @param {array} heap The heap. + * @param {function} weightFunc The weight function. + * @param {number} n The index of the element to sink down. + */ + function bubbleDown(heap, weightFunc, n) { + var length = heap.length, + node = heap[n], + nodeWeight = weightFunc(node); + + while (true) { + var child2N = (n + 1) * 2, + child1N = child2N - 1; + var swap = null; + if (child1N < length) { + var child1 = heap[child1N], + child1Weight = weightFunc(child1); + // If the score is less than our node's, we need to swap. + if (child1Weight < nodeWeight) { + swap = child1N; + } + } + // Do the same checks for the other child. + if (child2N < length) { + var child2 = heap[child2N], + child2Weight = weightFunc(child2); + if (child2Weight < (swap === null ? nodeWeight : weightFunc(heap[child1N]))) { + swap = child2N; + } + } + + if (swap === null) { + break; + } else { + heap[n] = heap[swap]; + heap[swap] = node; + n = swap; + } + } + } + + /** + * @class DSBinaryHeap + * @desc DSBinaryHeap implementation of a priority queue. + * @param {function} weightFunc Function that returns the value that should be used for node value comparison. + * @example + * angular.module('app').controller(function (DSBinaryHeap) { + * var bHeap = new DSBinaryHeap(function (x) { + * return x.value; + * }); + * ); + */ + function DSBinaryHeap(weightFunc) { + if (weightFunc && !angular.isFunction(weightFunc)) { + throw new Error('DSBinaryHeap(weightFunc): weightFunc: must be a function!'); + } + weightFunc = weightFunc || function (x) { + return x; + }; + this.weightFunc = weightFunc; + this.heap = []; + } + + /** + * @method DSBinaryHeap.push + * @desc Push an element into the binary heap. + * @param {*} node The element to push into the binary heap. + */ + DSBinaryHeap.prototype.push = function (node) { + this.heap.push(node); + bubbleUp(this.heap, this.weightFunc, this.heap.length - 1); + }; + + /** + * @method DSBinaryHeap.peek + * @desc Return, but do not remove, the minimum element in the binary heap. + * @returns {*} + */ + DSBinaryHeap.prototype.peek = function () { + return this.heap[0]; + }; + + /** + * @method DSBinaryHeap.pop + * @desc Remove and return the minimum element in the binary heap. + * @returns {*} + */ + DSBinaryHeap.prototype.pop = function () { + var front = this.heap[0], + end = this.heap.pop(); + if (this.heap.length > 0) { + this.heap[0] = end; + bubbleDown(this.heap, this.weightFunc, 0); + } + return front; + }; + + /** + * @method DSBinaryHeap.remove + * @desc Remove the first node in the priority queue that satisfies angular.equals comparison with + * the given node. + * @param {*} node The node to remove. + * @returns {*} The removed node. + */ + DSBinaryHeap.prototype.remove = function (node) { + var length = this.heap.length; + for (var i = 0; i < length; i++) { + if (angular.equals(this.heap[i], node)) { + var removed = this.heap[i], + end = this.heap.pop(); + if (i !== length - 1) { + this.heap[i] = end; + bubbleUp(this.heap, this.weightFunc, i); + bubbleDown(this.heap, this.weightFunc, i); + } + return removed; + } + } + return null; + }; + + /** + * @method DSBinaryHeap.removeAll + * @desc Remove all nodes from this DSBinaryHeap. + */ + DSBinaryHeap.prototype.removeAll = function () { + this.heap = []; + }; + + /** + * @method DSBinaryHeap.size + * @desc Return the size of the priority queue. + * @returns {number} The size of the priority queue. + */ + DSBinaryHeap.prototype.size = function () { + return this.heap.length; + }; + + return DSBinaryHeap; + }; +} + +module.exports = DSBinaryHeapProvider; diff --git a/src/DSCache/destroy.js b/src/DSCache/destroy.js new file mode 100644 index 0000000..7ecbce1 --- /dev/null +++ b/src/DSCache/destroy.js @@ -0,0 +1,36 @@ +/** + * @doc method + * @id DSCache.methods:destroy + * @name destroy + * @description + * Destroy this cache and all of its data. + * + * ## Signature: + * ```js + * DSCache#destroy() + * ``` + * + * ## Example: + * ```js + * var someCache = DSCacheFactory.get('someCache'); + * + * someCache.destroy(); + * + * DSCacheFactory.get('someCache'); // undefined + * someCache.put('1', 'apple'); // Error + * ``` + */ +module.exports = function destroy() { + clearInterval(this.$$cacheFlushIntervalId); + clearInterval(this.$$recycleFreqId); + this.removeAll(); + if (this.$$storage) { + this.$$storage.removeItem(this.$$prefix + '.keys'); + this.$$storage.removeItem(this.$$prefix); + } + this.$$storage = null; + this.$$data = null; + this.$$lruHeap = null; + this.$$expiresHeap = null; + this.$$prefix = null; +}; diff --git a/src/DSCache/get.js b/src/DSCache/get.js new file mode 100644 index 0000000..6f773ea --- /dev/null +++ b/src/DSCache/get.js @@ -0,0 +1,183 @@ +var utils = require('../utils'); + +/** + * @doc method + * @id DSCache.methods:get + * @name get + * @description + * Retrieve the item with the given key. + * + * ## Signature: + * ```js + * DSCache#get(key) + * ``` + * + * ## Examples: + * ```js + * var cache = DSCacheFactory('cache'); + * + * cache.put('1', 'apple'); + * + * cache.get('1'); // "apple" + * cache.get('2'); // undefined + * ``` + * + * ```js + * var options = { + * deleteOnExpire: 'passive', + * maxAge: 1000 + * }, + * cache = DSCacheFactory('cache', options); + * + * cache.put('1', 'apple'); + * + * cache.get('1'); // "apple" + * + * setTimeout(function () { + * cache.get('1'); // undefined + * }, 1500); + * ``` + * + * ```js + * var options = { + * deleteOnExpire: 'passive', + * maxAge: 1000 + * }, + * cache = DSCacheFactory('cache', options); + * + * cache.put('1', 'apple'); + * + * cache.get('1', { + * onExpire: function (key, value) { + * console.log(key, value); + * } + * }); // "apple" + * + * setTimeout(function () { + * cache.get('1'); // undefined + * // "1" "apple" (printed to console) + * }, 1500); + * ``` + * + * ```js + * var options = { + * deleteOnExpire: 'passive', + * maxAge: 1000, + * onExpire: function (key, value, done) { + * console.log('global hit'); + * if (done) { + * done(key, value); + * } + * } + * }, + * cache = DSCacheFactory('cache', options); + * + * cache.put('1', 'apple'); + * + * cache.get('1', { + * onExpire: function (key, value) { + * console.log(key, value); + * } + * }); // "apple" + * + * setTimeout(function () { + * cache.get('1'); // undefined + * // "global hit" (printed to console) + * // "1" "apple" (printed to console) + * }, 1500); + * ``` + * + * @param {string} key The key of the item to retrieve. + * @param {object=} options Optional configuration. Properties: + * + * - `{function=}` - `onExpire` - Callback to be used if in passive `deleteOnExpire` mode and the requested item has + * expired. If a global `onExpire` callback exists for this cache, then it will be called with three arguments: `key`, + * `value`, and `done`, where `done` is the `onExpire` callback passed into the call to `DSCache#get(key[, options])`. + * (See the last example above.) + * + * @returns {*} The item with the given key. + */ +module.exports = function get(key, options) { + var _this = this; + + if (angular.isArray(key)) { + var keys = key, + values = []; + + angular.forEach(keys, function (key) { + var value = _this.get(key, options); + if (value !== null && value !== undefined) { + values.push(value); + } + }); + + return values; + } else { + key = utils.stringifyNumber(key); + + if (this.$$disabled) { + return; + } + } + + options = options || {}; + if (!angular.isString(key)) { + throw angular.$$minErr('ng')('areq', 'Expected key to be a string! Found: {0}.', typeof key); + } else if (options && !angular.isObject(options)) { + throw angular.$$minErr('ng')('areq', 'Expected options to be an object! Found: {0}.', typeof options); + } else if (options.onExpire && !angular.isFunction(options.onExpire)) { + throw angular.$$minErr('ng')('areq', 'Expected options.onExpire to be a function! Found: {0}.', typeof options.onExpire); + } + + var item; + + if (this.$$storage) { + var itemJson = this.$$storage.getItem(this.$$prefix + '.data.' + key); + + if (itemJson) { + item = angular.fromJson(itemJson); + } else { + return; + } + } else { + if (!(key in this.$$data)) { + return; + } + + item = this.$$data[key]; + } + + var value = item.value, + now = new Date().getTime(); + + if (this.$$storage) { + this.$$lruHeap.remove({ + key: key, + accessed: item.accessed + }); + item.accessed = now; + this.$$lruHeap.push({ + key: key, + accessed: now + }); + } else { + this.$$lruHeap.remove(item); + item.accessed = now; + this.$$lruHeap.push(item); + } + + if (this.$$deleteOnExpire === 'passive' && 'expires' in item && item.expires < now) { + this.remove(key); + + if (this.$$onExpire) { + this.$$onExpire(key, item.value, options.onExpire); + } else if (options.onExpire) { + options.onExpire(key, item.value); + } + value = undefined; + } else if (this.$$storage) { + this.$$storage.setItem(this.$$prefix + '.data.' + key, angular.toJson(item)); + } + + return value; +}; diff --git a/src/DSCache/index.js b/src/DSCache/index.js new file mode 100644 index 0000000..b2c9a52 --- /dev/null +++ b/src/DSCache/index.js @@ -0,0 +1,395 @@ +var defaults = require('../defaults'); + +/*! + * Configure the cache to use webStorage. + */ +function _setStorageMode(storageMode, storageImpl) { + var $window = angular.injector(['ng']).get('$window'); + + if (!angular.isString(storageMode)) { + throw angular.$$minErr('ng')('areq', 'Expected storageMode to be a string! Found: {0}.', typeof storageMode); + } else if (storageMode !== 'memory' && storageMode !== 'localStorage' && storageMode !== 'sessionStorage') { + throw angular.$$minErr('ng')('areq', 'Expected storageMode to be "memory", "localStorage" or "sessionStorage"! Found: {0}.', storageMode); + } + + this.$$storageMode = storageMode; + + if (storageImpl) { + if (!angular.isObject(storageImpl)) { + throw angular.$$minErr('ng')('areq', 'Expected storageImpl to be an object! Found: {0}.', typeof storageImpl); + } else if (!('setItem' in storageImpl) || typeof storageImpl.setItem !== 'function') { + throw angular.$$minErr('ng')('areq', 'Expected storageImpl to implement "setItem(key, value)"! Found: {0}.', typeof storageImpl.setItem); + } else if (!('getItem' in storageImpl) || typeof storageImpl.getItem !== 'function') { + throw angular.$$minErr('ng')('areq', 'Expected storageImpl to implement "getItem(key)"! Found: {0}.', typeof storageImpl.getItem); + } else if (!('removeItem' in storageImpl) || typeof storageImpl.removeItem !== 'function') { + throw angular.$$minErr('ng')('areq', 'Expected storageImpl to implement "removeItem(key)"! Found: {0}.', typeof storageImpl.removeItem); + } + this.$$storage = storageImpl; + } else if (this.$$storageMode === 'localStorage') { + if ($window.localStorage) { + this.$$storage = $window.localStorage; + } else { + delete this.$$storage; + } + } else if (this.$$storageMode === 'sessionStorage') { + if ($window.sessionStorage) { + this.$$storage = $window.sessionStorage; + } else { + delete this.$$storage; + } + } +} + +/** + * @doc method + * @id DSCache.methods:setOptions + * @name setOptions + * @description + * Configure this cache with the given options. With this method you can configure all of this cache's settings at once. + * + * ## Signature: + * ```js + * DSCache#setOptions(cacheOptions[, strict]) + * ``` + * + * ## Example: + * ```js + * cache.setOptions({ + * maxAge: 60000, + * deleteOnExpire: 'aggressive', + * disabled: false + * }); + * ``` + * + * @param {object} cacheOptions New configuration options for the cache. Properties: + * + * - `{number=}` - `capacity` - Default: `Number.MAX_VALUE` + * - `{number=}` - `maxAge` - Default: `null` + * - `{number=}` - `deleteOnExpire` - Default: `none` + * - `{function=}` - `onExpire` - Default: `null` + * - `{number=}` - `cacheFlushInterval` - Default: `null` + * - `{number=}` - `recycleFreq` - Default: `1000` + * - `{boolean=}` - `disabled` - Default: `false` + * + * @param {boolean=} strict If true then any existing configuration will be reset to the defaults before + * applying the new options, otherwise only the options specified in the options hash will be altered. + */ +function _setOptions(cacheOptions, strict) { + cacheOptions = cacheOptions || {}; + strict = !!strict; + if (!angular.isObject(cacheOptions)) { + throw angular.$$minErr('ng')('areq', 'Expected cacheOptions to be an object! Found: {0}.', typeof cacheOptions); + } + + if ('disabled' in cacheOptions) { + this.$$disabled = !!cacheOptions.disabled; + } else if (strict) { + delete this.$$disabled; + } + + if ('capacity' in cacheOptions) { + this.setCapacity(cacheOptions.capacity); + } else if (strict) { + this.setCapacity(null); + } + + if ('deleteOnExpire' in cacheOptions) { + this.setDeleteOnExpire(cacheOptions.deleteOnExpire); + } else if (strict) { + this.setDeleteOnExpire(null); + } + + if ('maxAge' in cacheOptions) { + this.setMaxAge(cacheOptions.maxAge); + } else if (strict) { + this.setMaxAge(null); + } + + if ('recycleFreq' in cacheOptions) { + this.setRecycleFreq(cacheOptions.recycleFreq); + } else if (strict) { + this.setRecycleFreq(null); + } + + if ('cacheFlushInterval' in cacheOptions) { + this.setCacheFlushInterval(cacheOptions.cacheFlushInterval); + } else if (strict) { + this.setCacheFlushInterval(null); + } + + if ('onExpire' in cacheOptions) { + this.setOnExpire(cacheOptions.onExpire); + } else if (strict) { + this.setOnExpire(null); + } +} + +/** + * @doc function + * @id DSCache + * @name DSCache + * @description + * Instantiated via `DSCacheFactory(cacheId[, options])`. + * + * @param {string} cacheId The id of the new cache. + * @param {object=} options Configuration options. + */ +function DSCache(cacheId, options) { + + this.$$data = {}; + this.$$id = cacheId; + this.$$storage = null; + + this.$$expiresHeap = new DSBinaryHeap(function (x) { + return x.expires; + }); + + this.$$lruHeap = new DSBinaryHeap(function (x) { + return x.accessed; + }); + + options = options || {}; + + if ('storageMode' in options) { + _setStorageMode.apply(this, [options.storageMode, options.storageImpl]); + } + if ('storagePrefix' in options) { + this.$$storagePrefix = options.storagePrefix; + } + + this.$$prefix = this.$$storagePrefix + cacheId; + + // Initialize this cache with the default and given options + _setOptions.apply(this, [options, true]); +} + +for (var key in defaults.defaults) { + DSCache.prototype['$$' + key] = defaults.defaults[key]; +} + +/** + * @doc method + * @id DSCache.methods:setOptions + * @name setOptions + * @methodOf DSCache + * @description + * See [DSCache.setOptions](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.setOptions = _setOptions; + +/** + * @doc method + * @id DSCache.methods:setCapacity + * @name setCapacity + * @methodOf DSCache + * @description + * See [DSCache.setCapacity](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.setCapacity = require('./setCapacity'); + +/** + * @doc method + * @id DSCache.methods:setDeleteOnExpire + * @name setDeleteOnExpire + * @methodOf DSCache + * @description + * See [DSCache.setDeleteOnExpire](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.setDeleteOnExpire = require('./setDeleteOnExpire'); + +/** + * @doc method + * @id DSCache.methods:setMaxAge + * @name setMaxAge + * @methodOf DSCache + * @description + * See [DSCache.setMaxAge](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.setMaxAge = require('./setMaxAge'); + +/** + * @doc method + * @id DSCache.methods:setRecycleFreq + * @name setRecycleFreq + * @methodOf DSCache + * @description + * See [DSCache.setRecycleFreq](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.setRecycleFreq = require('./setRecycleFreq'); + +/** + * @doc method + * @id DSCache.methods:setCacheFlushInterval + * @name setCacheFlushInterval + * @methodOf DSCache + * @description + * See [DSCache.setCacheFlushInterval](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.setCacheFlushInterval = require('./setCacheFlushInterval'); + +/** + * @doc method + * @id DSCache.methods:setOnExpire + * @name setOnExpire + * @methodOf DSCache + * @description + * See [DSCache.setOnExpire](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.setOnExpire = require('./setOnExpire'); + +/** + * @doc method + * @id DSCache.methods:put + * @name put + * @methodOf DSCache + * @description + * See [DSCache.put](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.put = require('./put'); + +/** + * @doc method + * @id DSCache.methods:get + * @name get + * @methodOf DSCache + * @description + * See [DSCache.get](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.get = require('./get'); + +/** + * @doc method + * @id DSCache.methods:remove + * @name remove + * @methodOf DSCache + * @description + * See [DSCache.remove](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.remove = require('./remove'); + +/** + * @doc method + * @id DSCache.methods:removeAll + * @name removeAll + * @methodOf DSCache + * @description + * See [DSCache.removeAll](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.removeAll = require('./removeAll'); + +/** + * @doc method + * @id DSCache.methods:removeExpired + * @name removeExpired + * @methodOf DSCache + * @description + * See [DSCache.removeExpired](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.removeExpired = require('./removeExpired'); + +/** + * @doc method + * @id DSCache.methods:destroy + * @name destroy + * @methodOf DSCache + * @description + * See [DSCache.destroy](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.destroy = require('./destroy'); + +/** + * @doc method + * @id DSCache.methods:info + * @name info + * @methodOf DSCache + * @description + * See [DSCache.info](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.info = require('./info'); + +/** + * @doc method + * @id DSCache.methods:keySet + * @name keySet + * @methodOf DSCache + * @description + * See [DSCache.keySet](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.keySet = require('./keySet'); + +/** + * @doc method + * @id DSCache.methods:keys + * @name keys + * @methodOf DSCache + * @description + * See [DSCache.keys](/documentation/api/angular-cache/DSCache.methods:create). + */ +DSCache.prototype.keys = require('./keys'); + +/** + * @doc method + * @id DSCache.methods:disable + * @name disable + * @description + * Disable this cache. Disabling a cache does not remove any data, it just turns DSCache#get and DSCache#put into noops. + * + * ## Signature: + * ```js + * DSCache#disable() + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory.get('cache'); + * + * cache.put('1', 'apple'); + * cache.get('1'); // "apple" + * cache.info().size; // 1 + * + * cache.disable(); + * cache.info().size; // 1 + * + * cache.get('1'); // undefined + * cache.put('2', 'banana'); // undefined + * cache.get('2'); // undefined + * cache.info().size; // 1 + * ``` + */ +DSCache.prototype.disable = function () { + this.$$disabled = true; +}; + +/** + * @doc method + * @id DSCache.methods:enable + * @name enable + * @description + * Enable this cache. + * + * ## Signature: + * ```js + * DSCache#enable() + * ``` + * + * ## Example: + * ```js + * var options = { + * disabled: true + * }; + * var cache = DSCacheFactory.get('cache', options); + * + * cache.put('1', 'apple'); + * cache.get('1'); // undefined + * + * cache.enable(); + * + * cache.put('1', 'apple'); + * cache.get('1'); // "apple" + * ``` + */ +DSCache.prototype.enable = function () { + delete this.$$disabled; +}; + +module.exports = DSCache; diff --git a/src/DSCache/info.js b/src/DSCache/info.js new file mode 100644 index 0000000..dc5dd90 --- /dev/null +++ b/src/DSCache/info.js @@ -0,0 +1,76 @@ +/** + * @doc method + * @id DSCache.methods:info + * @name info + * @description + * Return the status of this cache, or if `key` is provided return the status of the item with that key. + * + * ## Signature: + * ```js + * DSCache#info([key]) + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory('cache'); + * + * cache.put('1', 'apple'); + * cache.put('2', 'banana'); + * + * cache.info(); // { + * // id: 'cache', + * // capacity: Number.MAX_VALUE, + * // maxAge: Number.MAX_VALUE, + * // deleteOnExpire: 'none', + * // onExpire: null, + * // cacheFlushInterval: null, + * // recycleFreq: 1000, + * // storageMode: 'memory', + * // storageImpl: null, + * // disabled: false, + * // size: 2 + * // } + * + * cache.info('1'); // { + * // created: 1234567890, + * // accessed: 1234567890, + * // expires: Number.MAX_VALUE, + * // isExpired: false + * // } + * + * cache.info('3'); // undefined + * ``` + * + * @param {string=} key The key of the item whose status is to be retrieved. + * @returns {object} The status of this cache or of the item with the given key. + */ +module.exports = function info(key) { + if (key) { + if (key in this.$$data) { + var item = this.$$data[key]; + + return { + created: item.created, + accessed: item.accessed, + expires: item.expires, + isExpired: (new Date().getTime() - item.created) > this.$$maxAge + }; + } else { + return undefined; + } + } else { + return { + id: this.$$id, + capacity: this.$$capacity, + maxAge: this.$$maxAge, + deleteOnExpire: this.$$deleteOnExpire, + onExpire: this.$$onExpire, + cacheFlushInterval: this.$$cacheFlushInterval, + recycleFreq: this.$$recycleFreq, + storageMode: this.$$storageMode, + storageImpl: this.$$storageImpl, + disabled: this.$$disabled, + size: this.$$lruHeap && this.$$lruHeap.size() || 0 + }; + } +}; diff --git a/src/DSCache/keySet.js b/src/DSCache/keySet.js new file mode 100644 index 0000000..6580c0c --- /dev/null +++ b/src/DSCache/keySet.js @@ -0,0 +1,43 @@ +var utils = require('../utils'); + +/** + * @doc method + * @id DSCache.methods:keySet + * @name keySet + * @description + * Return an object of the keys in this cache. + * + * ## Signature: + * ```js + * DSCache#keySet() + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory('cache'); + * + * cache.put('1', 'apple'); + * cache.put('2', 'banana'); + * + * cache.keys(); // { "1": "1", "2": "2" } + * ``` + * + * @returns {object} An object of the keys in this cache. + */ +module.exports = function keySet() { + if (this.$$storage) { + var keysJson = this.$$storage.getItem(this.$$prefix + '.keys'), + kSet = {}; + + if (keysJson) { + var keys = angular.fromJson(keysJson); + + for (var i = 0; i < keys.length; i++) { + kSet[keys[i]] = keys[i]; + } + } + return kSet; + } else { + return utils.keySet(this.$$data); + } +}; diff --git a/src/DSCache/keys.js b/src/DSCache/keys.js new file mode 100644 index 0000000..176522e --- /dev/null +++ b/src/DSCache/keys.js @@ -0,0 +1,39 @@ +var utils = require('../utils'); + +/** + * @doc method + * @id DSCache.methods:keys + * @name keys + * @description + * Return an array of the keys in this cache. + * + * ## Signature: + * ```js + * DSCache#keys() + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory('cache'); + * + * cache.put('1', 'apple'); + * cache.put('2', 'banana'); + * + * cache.keys(); // [ "1", "2" ] + * ``` + * + * @returns {Array} An array of the keys in this cache. + */ +module.exports = function keys() { + if (this.$$storage) { + var keysJson = this.$$storage.getItem(this.$$prefix + '.keys'); + + if (keysJson) { + return angular.fromJson(keysJson); + } else { + return []; + } + } else { + return utils.keys(this.$$data); + } +}; diff --git a/src/DSCache/put.js b/src/DSCache/put.js new file mode 100644 index 0000000..46883e3 --- /dev/null +++ b/src/DSCache/put.js @@ -0,0 +1,99 @@ +var utils = require('../utils'); + +/** + * @doc method + * @id DSCache.methods:put + * @name put + * @description + * Insert a value into the cache under the given key. + * + * ## Signature: + * ```js + * DSCache#put(key, value) + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory('cache'); + * + * cache.put('1', 'apple'); + * cache.put('2', 3); + * cache.put('3', { stuff: 'more stuff' }); + * + * cache.get('1'); // "apple" + * cache.get('2'); // 3 + * cache.get('3'); // { stuff: 'more stuff' } + * cache.get('4'); // undefined + * ``` + * + * ## Throws: + * - `Error` - `key` must be a string. + * + * @param {string} key The key under which to store the given value. + * @param {*} value The value to store. + * @returns {*} The newly stored item. + */ +module.exports = function put(key, value) { + if (this.$$disabled || value === null || value === undefined) { + return; + } + + key = utils.stringifyNumber(key); + + if (!angular.isString(key)) { + throw angular.$$minErr('ng')('areq', 'Expected key to be a string! Found: {0}.', typeof key); + } + + var now = new Date().getTime(), + item = { + key: key, + value: value, + created: now, + accessed: now, + }; + + item.expires = item.created + this.$$maxAge; + + if (this.$$storage) { + var keysJson = this.$$storage.getItem(this.$$prefix + '.keys'), + keys = keysJson ? angular.fromJson(keysJson) : [], + itemJson = this.$$storage.getItem(this.$$prefix + '.data.' + key); + + // Remove existing + if (itemJson) { + this.remove(key); + } + // Add to expires heap + this.$$expiresHeap.push({ + key: key, + expires: item.expires + }); + // Add to lru heap + this.$$lruHeap.push({ + key: key, + accessed: item.accessed + }); + // Set item + this.$$storage.setItem(this.$$prefix + '.data.' + key, angular.toJson(item)); + keys.push(key); + this.$$storage.setItem(this.$$prefix + '.keys', angular.toJson(keys)); + } else { + // Remove existing + if (this.$$data[key]) { + this.remove(key); + } + // Add to expires heap + this.$$expiresHeap.push(item); + // Add to lru heap + this.$$lruHeap.push(item); + // Set item + this.$$data[key] = item; + } + + // Handle exceeded capacity + if (this.$$lruHeap.size() > this.$$capacity) { + this.remove(this.$$lruHeap.peek().key); + } + + return value; +}; diff --git a/src/DSCache/remove.js b/src/DSCache/remove.js new file mode 100644 index 0000000..ca819b0 --- /dev/null +++ b/src/DSCache/remove.js @@ -0,0 +1,62 @@ +/** + * @doc method + * @id DSCache.methods:remove + * @name remove + * @description + * Remove the item with the given key. + * + * ## Signature: + * ```js + * DSCache#remove(key) + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory('cache'); + * + * cache.put('1', 'apple'); + * + * cache.get('1'); // "apple" + * + * cache.remove('1'); // "apple" + * + * cache.get('1'); // undefined + * ``` + * + * @param {string} key The key of the item to remove. + * @returns {*} The removed item if an item was removed. + */ +module.exports = function remove(key) { + if (this.$$storage) { + var itemJson = this.$$storage.getItem(this.$$prefix + '.data.' + key); + + if (itemJson) { + var item = angular.fromJson(itemJson); + this.$$lruHeap.remove({ + key: key, + accessed: item.accessed + }); + this.$$expiresHeap.remove({ + key: key, + expires: item.expires + }); + this.$$storage.removeItem(this.$$prefix + '.data.' + key); + var keysJson = this.$$storage.getItem(this.$$prefix + '.keys'), + keys = keysJson ? angular.fromJson(keysJson) : [], + index = keys.indexOf(key); + + if (index >= 0) { + keys.splice(index, 1); + } + this.$$storage.setItem(this.$$prefix + '.keys', angular.toJson(keys)); + return item.value; + } + } else { + var value = this.$$data[key] ? this.$$data[key].value : undefined; + this.$$lruHeap.remove(this.$$data[key]); + this.$$expiresHeap.remove(this.$$data[key]); + this.$$data[key] = null; + delete this.$$data[key]; + return value; + } +}; diff --git a/src/DSCache/removeAll.js b/src/DSCache/removeAll.js new file mode 100644 index 0000000..105795e --- /dev/null +++ b/src/DSCache/removeAll.js @@ -0,0 +1,53 @@ +/** + * @doc method + * @id DSCache.methods:removeAll + * @name removeAll + * @description + * Remove all items from this cache. + * + * ## Signature: + * ```js + * DSCache#removeAll() + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory('cache'); + * + * cache.put('1', 'apple'); + * cache.put('2', 'banana'); + * cache.info().size; // 2 + * + * cache.get('1'); // "apple" + * cache.get('2'); // "banana" + * + * cache.removeAll(); + * cache.info().size; // 0 + * + * cache.get('1'); // undefined + * cache.get('2'); // undefined + * ``` + */ +module.exports = function removeAll() { + if (this.$$storage) { + this.$$lruHeap.removeAll(); + this.$$expiresHeap.removeAll(); + var keysJson = this.$$storage.getItem(this.$$prefix + '.keys'); + + if (keysJson) { + var keys = angular.fromJson(keysJson); + + for (var i = 0; i < keys.length; i++) { + this.remove(keys[i]); + } + } + this.$$storage.setItem(this.$$prefix + '.keys', angular.toJson([])); + } else { + this.$$lruHeap.removeAll(); + this.$$expiresHeap.removeAll(); + for (var key in this.$$data) { + this.$$data[key] = null; + } + this.$$data = {}; + } +}; diff --git a/src/DSCache/removeExpired.js b/src/DSCache/removeExpired.js new file mode 100644 index 0000000..4dcaab2 --- /dev/null +++ b/src/DSCache/removeExpired.js @@ -0,0 +1,75 @@ +/** + * @doc method + * @id DSCache.methods:removeExpired + * @name removeExpired + * @description + * Remove and return all expired items from the cache. + * + * ## Signature: + * ```js + * DSCache#removeExpired() + * ``` + * + * ## Example: + * ```js + * var options = { + * maxAge: 1000 + * }, + * // deleteOnExpire defaults to "none" + * cache = DSCacheFactory('cache', options); + * + * cache.put('1', 'apple'); + * cache.put('2', 'banana'); + * + * setTimeout(function () { + * cache.put('3', 'orange'); + * + * cache.info().size; // 3 + * cache.info('1').isExpired; // true + * cache.info('2').isExpired; // true + * cache.info('3').isExpired; // false + * + * cache.removeExpired(); // { "1": "apple", "2": "banana" } + * + * cache.info().size; // 1 + * cache.get('1'); // undefined + * cache.get('2'); // undefined + * cache.info('3').isExpired; // false + * }, 1500); + * ``` + * + * @returns {object} The removed items, if any. + */ +module.exports = function removeExpired() { + var now = new Date().getTime(), + expired = {}, + key, + expiredItem; + + while ((expiredItem = this.$$expiresHeap.peek()) && expiredItem.expires < now) { + expired[expiredItem.key] = expiredItem.value ? expiredItem.value : null; + this.$$expiresHeap.pop(); + } + + if (this.$$storage) { + for (key in expired) { + var itemJson = this.$$storage.getItem(this.$$prefix + '.data.' + key); + if (itemJson) { + expired[key] = angular.fromJson(itemJson).value; + this.remove(key); + } + } + } else { + for (key in expired) { + this.remove(key); + } + } + + if (this.$$onExpire) { + for (key in expired) { + this.$$onExpire(key, expired[key]); + } + } + + return expired; +}; diff --git a/src/DSCache/setCacheFlushInterval.js b/src/DSCache/setCacheFlushInterval.js new file mode 100644 index 0000000..2f99416 --- /dev/null +++ b/src/DSCache/setCacheFlushInterval.js @@ -0,0 +1,51 @@ +/** + * @doc method + * @id DSCache.methods:setCacheFlushInterval + * @name setCacheFlushInterval + * @description + * Set the `cacheFlushInterval` setting for this cache. If set, this setting will cause this cache to periodically + * clear itself. + * + * ## Signature: + * ```js + * DSCache#setCacheFlushInterval(cacheFlushInterval) + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory('cache'); + * + * cache.put('1', 'apple'); + * cache.put('2', 'banana'); + * + * cache.info().size; // 2 + * cache.setCacheFlushInterval(60000); + * + * setTimeout(function () { + * cache.info().size; // 0 + * }, 90000); + * ``` + * + * ## Throws: + * - `Error` - `cacheFlushInterval` must be `null` or a number greater than zero. + * + * @param {number|null} cacheFlushInterval The new cacheFlushInterval for this cache in milliseconds. If + * `cacheFlushInterval` is `null` then `cacheFlushInterval` for this cache will be reset to the default (`null`). + */ +module.exports = function setCacheFlushInterval(cacheFlushInterval) { + if (cacheFlushInterval === null) { + delete this.$$cacheFlushInterval; + } else if (!angular.isNumber(cacheFlushInterval)) { + throw angular.$$minErr('ng')('areq', 'Expected cacheFlushInterval to be a number! Found: {0}.', typeof cacheFlushInterval); + } else if (cacheFlushInterval < 0) { + throw angular.$$minErr('ng')('areq', 'Expected cacheFlushInterval to be greater than zero! Found: {0}.', cacheFlushInterval); + } else if (cacheFlushInterval !== this.$$cacheFlushInterval) { + this.$$cacheFlushInterval = cacheFlushInterval; + clearInterval(this.$$cacheFlushIntervalId); + (function (_this) { + _this.$$cacheFlushIntervalId = setInterval(function () { + _this.removeAll(); + }, _this.$$cacheFlushInterval); + })(this); + } +}; diff --git a/src/DSCache/setCapacity.js b/src/DSCache/setCapacity.js new file mode 100644 index 0000000..3c9c691 --- /dev/null +++ b/src/DSCache/setCapacity.js @@ -0,0 +1,55 @@ +/** + * @doc method + * @id DSCache.methods:setCapacity + * @name setCapacity + * @description + * Set the capacity for this cache. + * + * ## Signature: + * ```js + * DSCache#setCapacity(capacity) + * ``` + * + * ## Example: + * ```js + * var smallCache = DSCacheFactory('smallCache', { capacity: 2 }); + * + * smallCache.info().size; // 0 + * + * smallCache.put('1', 'apple'); + * smallCache.put('2', 'banana'); + * + * smallCache.info().size; // 2 + * + * // Least-recently used items are removed + * // when the cache's new capacity exceeds + * // its size + * smallCache.setCapacity(1); + * + * smallCache.get('1'); // undefined + * smallCache.info().size; // 1 + * ``` + * + * ## Throws: + * - `Error` - `capacity` must be `null` or a number greater than zero. + * + * @param {number|null} capacity The new capacity for this cache. If `capacity` is `null` then the capacity for this cache + * will be reset to the default (`Number.MAX_VALUE`). + * @returns {object} Key-value pairs of any items removed because this cache's size exceeds the new capacity. + */ +module.exports = function setCapacity(capacity) { + if (capacity === null) { + delete this.$$capacity; + } else if (!angular.isNumber(capacity)) { + throw angular.$$minErr('ng')('areq', 'Expected capacity to be a number! Found: {0}.', typeof capacity); + } else if (capacity < 0) { + throw angular.$$minErr('ng')('areq', 'Expected capacity to be greater than zero! Found: {0}.', capacity); + } else { + this.$$capacity = capacity; + } + var removed = {}; + while (this.$$lruHeap.size() > this.$$capacity) { + removed[this.$$lruHeap.peek().key] = this.remove(this.$$lruHeap.peek().key); + } + return removed; +}; diff --git a/src/DSCache/setDeleteOnExpire.js b/src/DSCache/setDeleteOnExpire.js new file mode 100644 index 0000000..63e6469 --- /dev/null +++ b/src/DSCache/setDeleteOnExpire.js @@ -0,0 +1,54 @@ +/** + * @doc method + * @id DSCache.methods:setDeleteOnExpire + * @name setDeleteOnExpire + * @description + * Set the behavior for this cache for when items expire. This setting determines what this cache will do when one of + * its items expires. + * + * ## Possible Values: + * - `"none"` - Do nothing when items expire. + * - `"passive"` - Do nothing when items expire, but if an expired item is requested, remove it from the cache and return `undefined`. + * - `"aggressive"` - Scan for expired items on the interval specified by the `recycleFreq` setting for this cache (defaults + * to `1000ms`) and actively remove any expired items. + * + * ## Signature: + * ```js + * DSCache#setDeleteOnExpire(deleteOnExpire) + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory('cache'); + * + * cache.put('1', 'apple'); + * + * // Wait a few seconds + * + * cache.get('1'); // "apple" + * + * cache.setDeleteOnExpire('aggressive'); + * + * // Wait a few seconds + * + * cache.get('1'); // undefined + * ``` + * + * ## Throws: + * - `Error` - `deleteOnExpire` must be `null`, `"none"`, `"passive"` or `"aggressive"`. + * + * @param {string|null} deleteOnExpire The new deleteOnExpire for this cache. If `deleteOnExpire` is `null` then + * `deleteOnExpire` for this cache will be reset to the default (`"none"`). + */ +module.exports = function setDeleteOnExpire(deleteOnExpire) { + if (deleteOnExpire === null) { + delete this.$$deleteOnExpire; + } else if (!angular.isString(deleteOnExpire)) { + throw angular.$$minErr('ng')('areq', 'Expected deleteOnExpire to be a string! Found: {0}.', typeof deleteOnExpire); + } else if (deleteOnExpire !== 'none' && deleteOnExpire !== 'passive' && deleteOnExpire !== 'aggressive') { + throw angular.$$minErr('ng')('areq', 'Expected deleteOnExpire to be "none", "passive" or "aggressive"! Found: {0}.', deleteOnExpire); + } else { + this.$$deleteOnExpire = deleteOnExpire; + } + this.setRecycleFreq(this.$$recycleFreq); +}; diff --git a/src/DSCache/setMaxAge.js b/src/DSCache/setMaxAge.js new file mode 100644 index 0000000..61daeae --- /dev/null +++ b/src/DSCache/setMaxAge.js @@ -0,0 +1,96 @@ +var utils = require('../utils'); + +/** + * @doc method + * @id DSCache.methods:setMaxAge + * @name setMaxAge + * @description + * Set the `maxAge` setting for this cache. This setting specifies how long items can be in the cache before they expire. + * + * ## Signature: + * ```js + * DSCache#setMaxAge(maxAge) + * ``` + * + * ## Example: + * ```js + * var cache = DSCacheFactory('cache', { deleteOnExpire: 'aggressive' }); + * + * // This won't expire for a long time + * cache.put('1', 'apple'); + * + * setTimeout(function () { + * // 'apple' will be removed because it + * // has already been in the cache longer + * // than the new maxAge + * var removed = cache.setMaxAge(1000); + * + * removed; // { + * // '1': 'apple' + * // } + * }, 1500); + * ``` + * + * ## Throws: + * - `Error` - `maxAge must be `null` or a number greater than zero. + * + * @param {number} maxAge The new maxAge for this cache in milliseconds. If `maxAge` is `null` then `maxAge` for this + * cache will be reset to the default (`Number.MAX_VALUE`); + * @returns {object} Key-value pairs of any items aggressively removed because they are expired according to the new + * `maxAge`. Items are only removed if the `deleteOnExpire` setting for this cache is set to `"aggressive"`. + */ +module.exports = function setMaxAge(maxAge) { + if (maxAge === null) { + delete this.$$maxAge; + } else if (!angular.isNumber(maxAge)) { + throw angular.$$minErr('ng')('areq', 'Expected maxAge to be a number! Found: {0}.', typeof maxAge); + } else if (maxAge < 0) { + throw angular.$$minErr('ng')('areq', 'Expected maxAge to be greater than zero! Found: {0}.', maxAge); + } else { + this.$$maxAge = maxAge; + } + var i, keys, key; + + this.$$expiresHeap.removeAll(); + + if (this.$$storage) { + var keysJson = this.$$storage.getItem(this.$$prefix + '.keys'); + + keys = keysJson ? angular.fromJson(keysJson) : []; + + for (i = 0; i < keys.length; i++) { + key = keys[i]; + var itemJson = this.$$storage.getItem(this.$$prefix + '.data.' + key); + + if (itemJson) { + var item = angular.fromJson(itemJson); + if (this.$$maxAge === Number.MAX_VALUE) { + item.expires = Number.MAX_VALUE; + } else { + item.expires = item.created + this.$$maxAge; + } + this.$$expiresHeap.push({ + key: key, + expires: item.expires + }); + } + } + } else { + keys = utils.keys(this.$$data); + + for (i = 0; i < keys.length; i++) { + key = keys[i]; + if (this.$$maxAge === Number.MAX_VALUE) { + this.$$data[key].expires = Number.MAX_VALUE; + } else { + this.$$data[key].expires = this.$$data[key].created + this.$$maxAge; + } + this.$$expiresHeap.push(this.$$data[key]); + } + } + if (this.$$deleteOnExpire === 'aggressive') { + return this.removeExpired(); + } else { + return {}; + } +}; diff --git a/src/DSCache/setOnExpire.js b/src/DSCache/setOnExpire.js new file mode 100644 index 0000000..e1fc350 --- /dev/null +++ b/src/DSCache/setOnExpire.js @@ -0,0 +1,45 @@ +/** + * @doc method + * @id DSCache.methods:setOnExpire + * @name setOnExpire + * @description + * Set the global `onExpire` callback for this cache. + * + * ## Signature: + * ```js + * DSCache#setOnExpire(onExpire) + * ``` + * + * ## Examples: + * ```js + * var options = { + * onExpire: function (key, value) { + * window.lastExpiredItem = key; + * }, + * maxAge: 1000, + * deleteOnExpire: 'aggressive' + * }; + * var cache = DSCacheFactory('cache', options); + * + * cache.put('1', 'apple'); + * + * setTimeout(function () { + * window.lastExpiredItem; // '1' + * }, 1500); + * ``` + * + * ## Throws: + * - `Error` - `cacheFlushInterval` must be `null` or a number greater than zero. + * + * @param {function|null} onExpire The new onExpire callback for this cache. If `onExpire` is `null` then the onExpire + * callback for this cache will be removed. + */ +module.exports = function setOnExpire(onExpire) { + if (onExpire === null) { + delete this.$$onExpire; + } else if (!angular.isFunction(onExpire)) { + throw angular.$$minErr('ng')('areq', 'Expected onExpire to be a function! Found: {0}.', typeof onExpire); + } else { + this.$$onExpire = onExpire; + } +}; diff --git a/src/DSCache/setRecycleFreq.js b/src/DSCache/setRecycleFreq.js new file mode 100644 index 0000000..9ef5a36 --- /dev/null +++ b/src/DSCache/setRecycleFreq.js @@ -0,0 +1,69 @@ +/** + * @doc method + * @id DSCache.methods:setRecycleFreq + * @name setRecycleFreq + * @description + * Set the `recycleFreq` setting for this cache. This setting determines how often this cache will scan for expired + * items. The cache will only scan for expired items if the `deleteOnExpire` setting for this cache is set to + * `"aggressive"`. + * + * ## Signature: + * ```js + * DSCache#setRecycleFreq(recycleFreq) + * ``` + * + * ## Example: + * ```js + * var options = { + * deleteOnExpire: 'aggressive', + * maxAge: 1000 + * }; + * var cache = DSCacheFactory('cache', options); + * + * cache.put('1', 'apple'); + * + * setTimeout(function () { + * + * cache.get('1'); // undefined + * cache.setRecycleFreq(60000); + * + * // This expires after 1 second, but the cache + * // only checks every 60 seconds now + * cache.put('1', 'apple'); + * + * setTimeout(function () { + * // expired, but won't be removed + * // until the next check + * cache.get('1'); // "apple" + * cache.info('1').isExpired; // true + * }, 1500); + * }, 1500); + * ``` + * + * ## Throws: + * - `Error` - `recycleFreq` must be `null` or a number greater than zero. + * + * @param {number} recycleFreq The new recycleFreq for this cache in milliseconds. If `recycleFreq` is `null` then + * `recycleFreq` for this cache will be reset to the default (`1000` milliseconds). + */ +module.exports = function setRecycleFreq(recycleFreq) { + if (recycleFreq === null) { + delete this.$$recycleFreq; + } else if (!angular.isNumber(recycleFreq)) { + throw angular.$$minErr('ng')('areq', 'Expected recycleFreq to be a number! Found: {0}.', typeof recycleFreq); + } else if (recycleFreq < 0) { + throw angular.$$minErr('ng')('areq', 'Expected recycleFreq to be greater than zero! Found: {0}.', recycleFreq); + } else { + this.$$recycleFreq = recycleFreq; + } + clearInterval(this.$$recycleFreqId); + if (this.$$deleteOnExpire === 'aggressive') { + (function (_this) { + _this.$$recycleFreqId = setInterval(function () { + _this.removeExpired(); + }, _this.$$recycleFreq); + })(this); + } else { + delete this.$$recycleFreqId; + } +}; diff --git a/src/DSCacheFactory/index.js b/src/DSCacheFactory/index.js new file mode 100644 index 0000000..bc832a2 --- /dev/null +++ b/src/DSCacheFactory/index.js @@ -0,0 +1,323 @@ +var defaults = require('../defaults'), + DSCache = require('../DSCache'), + version = '<%= pkg.version %>'; + +/** + * @doc function + * @id DSCacheFactoryProvider + * @name DSCacheFactoryProvider + */ +function DSCacheFactoryProvider() { + + var config = new defaults.Config(); + + this.version = version; + + /** + * @doc method + * @id DSCacheFactoryProvider.methods:setCacheDefaults + * @name setCacheDefaults + * @desc Set the default configuration for all caches created by $angularCacheFactory. + * @param {object} options Default configuration options for each new cache. + */ + this.setCacheDefaults = function (options) { + options = options || {}; + + if (!angular.isObject(options)) { + throw angular.$$minErr('ng')('areq', 'Expected options to be an object! Found: {0}.', typeof options); + } + + for (var key in defaults.defaults) { + if (key in options) { + config[key] = options[key]; + } + } + if ('disabled' in options) { + config.$$disabled = !!options.disabled; + } + }; + + this.$get = function () { + var caches = {}; + + /*! + * @method _keys + * @desc Returns an array of the keys of the given collection. + * @param {object} collection The collection from which to get the keys. + * @returns {array} An array of the keys of the given collection. + */ + function _keys(collection) { + var keys = [], key; + for (key in collection) { + if (collection.hasOwnProperty(key)) { + keys.push(key); + } + } + return keys; + } + + /** + * @doc function + * @id DSCacheFactory + * @name DSCacheFactory + * @description + * Factory function that produces instances of `DSCache`. + * + * @param {string} cacheId The id of the new cache. + * @param {object} options Configuration options. Properties: + * + * - `{number=}` - `capacity` - Default: `Number.MAX_VALUE` + * - `{number=}` - `maxAge` - Default: `null` + * - `{number=}` - `deleteOnExpire` - Default: `none` + * - `{function=}` - `onExpire` - Default: `null` + * - `{number=}` - `cacheFlushInterval` - Default: `null` + * - `{number=}` - `recycleFreq` - Default: `1000` + * - `{number=}` - `deleteOnExpire` - Default: `null` + * - `{string=}` - `storageMode` - Default: `'none` + * - `{object=}` - `storageImpl` - Default: `null` + * - `{boolean=}` - `disabled` - Default: `false` + * - `{string=}` - `storagePrefix` - Default: `"angular-cache.caches."` + * + * @returns {DSCache} New instance of DSCache. + */ + function DSCacheFactory(cacheId, options) { + if (cacheId in caches) { + throw angular.$$minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); + } else if (!angular.isString(cacheId)) { + throw angular.$$minErr('ng')('areq', 'Expected cacheId to be a string! Found: {0}.', typeof cacheId); + } + + caches[cacheId] = new DSCache(cacheId, angular.extend({}, config, options)); + caches[cacheId].destroy = function () { + this.constructor.prototype.destroy.call(this); + delete caches[this.$$id]; + }; + return caches[cacheId]; + } + + DSCacheFactory.version = version; + + /** + * @doc method + * @id DSCacheFactory.methods:info + * @name info + * @description + * Return the status of `DSCacheFactory`. + * @returns {object} The status of `DSCacheFactory`. + */ + DSCacheFactory.info = function () { + var keys = _keys(caches); + var info = { + size: keys.length, + caches: {} + }; + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + info.caches[key] = caches[key].info(); + } + var c = info.cacheDefaults = angular.extend({}, config); + for (var option in defaults.defaults) { + if (!(option in c)) { + c[option] = config['$$' + option]; + } + } + return info; + }; + + /** + * @doc method + * @id DSCacheFactory.methods:get + * @name get + * @description + * Return the cache with the given `cacheId`. + * @param {string} cacheId The id of the desired cache. + * @returns {DSCache} The cache with the specified `cacheId`. + */ + DSCacheFactory.get = function (cacheId) { + if (!angular.isString(cacheId)) { + throw angular.$$minErr('ng')('areq', 'Expected cacheId to be a string! Found: {0}.', typeof cacheId); + } + return caches[cacheId]; + }; + + /** + * @doc method + * @id DSCacheFactory.methods:keySet + * @name keySet + * @description + * Return an object containing the `cacheId` of each cache. + * @returns {object} An object containing the `cacheId` of each cache. + */ + DSCacheFactory.keySet = function () { + var cacheIds = {}, cacheId; + for (cacheId in caches) { + if (caches.hasOwnProperty(cacheId)) { + cacheIds[cacheId] = cacheId; + } + } + return cacheIds; + }; + + /** + * @doc method + * @id DSCacheFactory.methods:keys + * @name keys + * @description + * Return an array containing the `cacheId` of each cache. + * @returns {array} An array containing the `cacheId` of each cache. + */ + DSCacheFactory.keys = function () { + return _keys(caches); + }; + + /** + * @doc method + * @id DSCacheFactory.methods:destroyAll + * @name destroyAll + * @description + * Destroy all caches. + * + * ## Signature: + * ```js + * DSCacheFactory.destroyAll() + * ``` + * + * ## Example: + * ```js + * var newCache = DSCacheFactory('newCache'); + * var otherCache = DSCacheFactory('otherCache'); + * + * newCache.info().size; // 0 + * otherCache.info().size; // 0 + * + * newCache.put('1', 'apple'); + * newCache.put('2', 'banana'); + * otherCache.put('abcd', 'horse'); + * + * newCache.info().size; // 2 + * otherCache.info().size; // 1 + * + * DSCacheFactory.destroyAll(); + * + * newCache.info().size; // Error thrown + * otherCache.info().size; // Error thrown + * + * DSCacheFactory.get('newCache'); // undefined + * DSCacheFactory.get('otherCache'); // undefined + * ``` + */ + DSCacheFactory.destroyAll = function () { + for (var cacheId in caches) { + caches[cacheId].destroy(); + } + caches = {}; + }; + + /** + * @doc method + * @id DSCacheFactory.methods:clearAll + * @name clearAll + * @description + * Clear the contents of all caches. + * + * ## Signature: + * ```js + * DSCacheFactory.clearAll() + * ``` + * + * ## Example: + * ```js + * var newCache = DSCacheFactory('newCache'); + * var otherCache = DSCacheFactory('otherCache'); + * + * newCache.info().size; // 0 + * otherCache.info().size; // 0 + * + * newCache.put('1', 'apple'); + * newCache.put('2', 'banana'); + * otherCache.put('abcd', 'horse'); + * + * newCache.info().size; // 2 + * otherCache.info().size; // 1 + * + * DSCacheFactory.clearAll(); + * + * newCache.info().size; // 0 + * otherCache.info().size; // 0 + * ``` + */ + DSCacheFactory.clearAll = function () { + for (var cacheId in caches) { + caches[cacheId].removeAll(); + } + }; + + /** + * @doc method + * @id DSCacheFactory.methods:enableAll + * @name enableAll + * @description + * Enable any disabled caches. + * + * ## Signature: + * ```js + * DSCacheFactory.enableAll() + * ``` + * + * ## Example: + * ```js + * var newCache = DSCacheFactory('newCache', { disabled: true }); + * var otherCache = DSCacheFactory('otherCache', { disabled: true }); + * + * newCache.info().disabled; // true + * otherCache.info().disabled; // true + * + * DSCacheFactory.enableAll(); + * + * newCache.info().disabled; // false + * otherCache.info().disabled; // false + * ``` + */ + DSCacheFactory.enableAll = function () { + for (var cacheId in caches) { + caches[cacheId].$$disabled = false; + } + }; + + /** + * @doc method + * @id DSCacheFactory.methods:disableAll + * @name disableAll + * @description + * Disable all caches. + * + * ## Signature: + * ```js + * DSCacheFactory.disableAll() + * ``` + * + * ## Example: + * ```js + * var newCache = DSCacheFactory('newCache'); + * var otherCache = DSCacheFactory('otherCache'); + * + * newCache.info().disabled; // false + * otherCache.info().disabled; // false + * + * DSCacheFactory.disableAll(); + * + * newCache.info().disabled; // true + * otherCache.info().disabled; // true + * ``` + */ + DSCacheFactory.disableAll = function () { + for (var cacheId in caches) { + caches[cacheId].$$disabled = true; + } + }; + + return DSCacheFactory; + }; +} + +module.exports = DSCacheFactoryProvider; diff --git a/src/angular-cache.js b/src/angular-cache.js deleted file mode 100644 index 27b3937..0000000 --- a/src/angular-cache.js +++ /dev/null @@ -1,1194 +0,0 @@ -/** - * @author Jason Dobry - * @file angular-cache-2.3.2.js - * @version 2.3.2 - Homepage - * @copyright (c) 2013 Jason Dobry - * @license MIT - * - * @overview angular-cache is a very useful replacement for Angular's $cacheFactory. - */ -(function (window, angular, undefined) { - 'use strict'; - - angular.module('jmdobry.binary-heap', []); - - /** - * @desc Provider for the BinaryHeap. - */ - function BinaryHeapProvider() { - this.$get = function () { - /** - * @method bubbleUp - * @param {array} heap The heap. - * @param {function} weightFunc The weight function. - * @param {number} n The index of the element to bubble up. - */ - function bubbleUp(heap, weightFunc, n) { - var element = heap[n], - weight = weightFunc(element); - // When at 0, an element can not go up any further. - while (n > 0) { - // Compute the parent element's index, and fetch it. - var parentN = Math.floor((n + 1) / 2) - 1, - parent = heap[parentN]; - // If the parent has a lesser weight, things are in order and we - // are done. - if (weight >= weightFunc(parent)) { - break; - } else { - heap[parentN] = element; - heap[n] = parent; - n = parentN; - } - } - } - - /** - * @method bubbleDown - * @param {array} heap The heap. - * @param {function} weightFunc The weight function. - * @param {number} n The index of the element to sink down. - */ - function bubbleDown(heap, weightFunc, n) { - var length = heap.length, - node = heap[n], - nodeWeight = weightFunc(node); - - while (true) { - var child2N = (n + 1) * 2, - child1N = child2N - 1; - var swap = null; - if (child1N < length) { - var child1 = heap[child1N], - child1Weight = weightFunc(child1); - // If the score is less than our node's, we need to swap. - if (child1Weight < nodeWeight) { - swap = child1N; - } - } - // Do the same checks for the other child. - if (child2N < length) { - var child2 = heap[child2N], - child2Weight = weightFunc(child2); - if (child2Weight < (swap === null ? nodeWeight : weightFunc(heap[child1N]))) { - swap = child2N; - } - } - - if (swap === null) { - break; - } else { - heap[n] = heap[swap]; - heap[swap] = node; - n = swap; - } - } - } - - /** - * @class BinaryHeap - * @desc BinaryHeap implementation of a priority queue. - * @param {function} weightFunc Function that returns the value that should be used for node value comparison. - * @example - * angular.module('app').controller(function (BinaryHeap) { - * var bHeap = new BinaryHeap(function (x) { - * return x.value; - * }); - * ); - */ - function BinaryHeap(weightFunc) { - if (weightFunc && !angular.isFunction(weightFunc)) { - throw new Error('BinaryHeap(weightFunc): weightFunc: must be a function!'); - } - weightFunc = weightFunc || function (x) { - return x; - }; - this.weightFunc = weightFunc; - this.heap = []; - } - - /** - * @method BinaryHeap.push - * @desc Push an element into the binary heap. - * @param {*} node The element to push into the binary heap. - */ - BinaryHeap.prototype.push = function (node) { - this.heap.push(node); - bubbleUp(this.heap, this.weightFunc, this.heap.length - 1); - }; - - /** - * @method BinaryHeap.peek - * @desc Return, but do not remove, the minimum element in the binary heap. - * @returns {*} - */ - BinaryHeap.prototype.peek = function () { - return this.heap[0]; - }; - - /** - * @method BinaryHeap.pop - * @desc Remove and return the minimum element in the binary heap. - * @returns {*} - */ - BinaryHeap.prototype.pop = function () { - var front = this.heap[0], - end = this.heap.pop(); - if (this.heap.length > 0) { - this.heap[0] = end; - bubbleDown(this.heap, this.weightFunc, 0); - } - return front; - }; - - /** - * @method BinaryHeap.remove - * @desc Remove the first node in the priority queue that satisfies angular.equals comparison with - * the given node. - * @param {*} node The node to remove. - * @returns {*} The removed node. - */ - BinaryHeap.prototype.remove = function (node) { - var length = this.heap.length; - for (var i = 0; i < length; i++) { - if (angular.equals(this.heap[i], node)) { - var removed = this.heap[i], - end = this.heap.pop(); - if (i !== length - 1) { - this.heap[i] = end; - bubbleUp(this.heap, this.weightFunc, i); - bubbleDown(this.heap, this.weightFunc, i); - } - return removed; - } - } - return null; - }; - - /** - * @method BinaryHeap.removeAll - * @desc Remove all nodes from this BinaryHeap. - */ - BinaryHeap.prototype.removeAll = function () { - this.heap = []; - }; - - /** - * @method BinaryHeap.size - * @desc Return the size of the priority queue. - * @returns {number} The size of the priority queue. - */ - BinaryHeap.prototype.size = function () { - return this.heap.length; - }; - - return BinaryHeap; - }; - } - - angular.module('jmdobry.binary-heap').provider('BinaryHeap', BinaryHeapProvider); - - /** - * @desc Provides an $AngularCacheFactoryProvider, which gives you the ability to use an - * $angularCacheFactory. The $angularCacheFactory produces AngularCache objects, which - * the same abilities as the cache objects that come with Angular, except with some added - * functionality. - */ - angular.module('jmdobry.angular-cache', ['ng', 'jmdobry.binary-heap']); - - /** - * @class $AngularCacheFactoryProvider - * @desc Provider for the $angularCacheFactory. - * @see {@link http://docs.angularjs.org/api/ng.$cacheFactory|ng.$cacheFactory} - */ - function $AngularCacheFactoryProvider() { - - var cacheDefaults, - DEFAULTS = function () { - return { - capacity: Number.MAX_VALUE, - maxAge: null, - deleteOnExpire: 'none', - onExpire: null, - cacheFlushInterval: null, - recycleFreq: 1000, - storageMode: 'none', - storageImpl: null, - verifyIntegrity: true, - disabled: false - }; - }; - - /** - * @method _validateNumberOption - * @desc Validates the given number option. - * @param {number} option The number option to check. - * @param {function} cb Callback function - */ - function _validateNumberOption(option, cb) { - if (!angular.isNumber(option)) { - cb('must be a number!'); - } else if (option < 0) { - cb('must be greater than zero!'); - } else { - cb(null); - } - } - - /** - * @method $AngularCacheFactoryProvider.setCacheDefaults - * @desc Set the default configuration for all caches created by $angularCacheFactory. - * @param {object} options - */ - this.setCacheDefaults = function (options) { - var errStr = '$angularCacheFactoryProvider.setCacheDefaults(options): '; - options = options || {}; - - if (!angular.isObject(options)) { - throw new Error(errStr + 'options: must be an object!'); - } - - if ('disabled' in options) { - options.disabled = options.disabled === true; - } - - if ('capacity' in options) { - _validateNumberOption(options.capacity, function (err) { - if (err) { - throw new Error(errStr + 'capacity: ' + err); - } - }); - } - - if ('deleteOnExpire' in options) { - if (!angular.isString(options.deleteOnExpire)) { - throw new Error(errStr + 'deleteOnExpire: must be a string!'); - } else if (options.deleteOnExpire !== 'none' && options.deleteOnExpire !== 'passive' && options.deleteOnExpire !== 'aggressive') { - throw new Error(errStr + 'deleteOnExpire: accepted values are "none", "passive" or "aggressive"!'); - } - } - - if ('maxAge' in options) { - _validateNumberOption(options.maxAge, function (err) { - if (err) { - throw new Error(errStr + 'maxAge: ' + err); - } - }); - } - - if ('recycleFreq' in options) { - _validateNumberOption(options.recycleFreq, function (err) { - if (err) { - throw new Error(errStr + 'recycleFreq: ' + err); - } - }); - } - - if ('cacheFlushInterval' in options) { - _validateNumberOption(options.cacheFlushInterval, function (err) { - if (err) { - throw new Error(errStr + 'cacheFlushInterval: ' + err); - } - }); - } - - if ('storageMode' in options) { - if (!angular.isString(options.storageMode)) { - throw new Error(errStr + 'storageMode: must be a string!'); - } else if (options.storageMode !== 'none' && options.storageMode !== 'localStorage' && options.storageMode !== 'sessionStorage') { - throw new Error(errStr + 'storageMode: accepted values are "none", "localStorage" or "sessionStorage"!'); - } - if ('storageImpl' in options) { - if (!angular.isObject(options.storageImpl)) { - throw new Error(errStr + 'storageImpl: must be an object!'); - } else if (!('setItem' in options.storageImpl) || typeof options.storageImpl.setItem !== 'function') { - throw new Error(errStr + 'storageImpl: must implement "setItem(key, value)"!'); - } else if (!('getItem' in options.storageImpl) || typeof options.storageImpl.getItem !== 'function') { - throw new Error(errStr + 'storageImpl: must implement "getItem(key)"!'); - } else if (!('removeItem' in options.storageImpl) || typeof options.storageImpl.removeItem !== 'function') { - throw new Error(errStr + 'storageImpl: must implement "removeItem(key)"!'); - } - } - } - - if ('onExpire' in options) { - if (typeof options.onExpire !== 'function') { - throw new Error(errStr + 'onExpire: must be a function!'); - } - } - - cacheDefaults = angular.extend({}, DEFAULTS(), options); - }; - - // Initialize cacheDefaults with the defaults - this.setCacheDefaults({}); - - /** - * @ignore - */ - this.$get = ['$window', 'BinaryHeap', function ($window, BinaryHeap) { - var caches = {}; - - /** - * Stringify a number. - * @param {number|*} number The number to be stringified. - * @returns {*} number or a string. - * @private - */ - function _stringifyNumber(number) { - if (number && angular.isNumber(number)) { - return number.toString(); - } - return number; - } - - /** - * @method _keySet - * @desc Returns an object of the keys of the given collection. - * @param {object} collection The collection from which to get the set of keys. - * @returns {object} A hash of the keys of the given collection. - */ - function _keySet(collection) { - var keySet = {}, key; - for (key in collection) { - if (collection.hasOwnProperty(key)) { - keySet[key] = key; - } - } - return keySet; - } - - /** - * @method _keys - * @desc Returns an array of the keys of the given collection. - * @param {object} collection The collection from which to get the keys. - * @returns {array} An array of the keys of the given collection. - */ - function _keys(collection) { - var keys = [], key; - for (key in collection) { - if (collection.hasOwnProperty(key)) { - keys.push(key); - } - } - return keys; - } - - /** - * @class AngularCache - * @desc Instantiated via $angularCacheFactory(cacheId[, options]) - * @param {string} cacheId The id of the new cache. - * @param {object} [options] See [Configuration Options]{@link https://github.com/jmdobry/angular-cache#configuration}. - */ - function AngularCache(cacheId, options) { - var config = angular.extend({}, { id: cacheId }), - data = {}, - expiresHeap = new BinaryHeap(function (x) { - return x.expires; - }), - lruHeap = new BinaryHeap(function (x) { - return x.accessed; - }), - prefix = 'angular-cache.caches.' + cacheId, - cacheDirty = false, - self = this, - storage = null; - - options = options || {}; - - /** - * @method _setCapacity - * @desc Set the capacity for this cache. - * @param {number} capacity The new capacity for this cache. - */ - function _setCapacity(capacity) { - _validateNumberOption(capacity, function (err) { - if (err) { - throw new Error('capacity: ' + err); - } else { - config.capacity = capacity; - while (lruHeap.size() > config.capacity) { - self.remove(lruHeap.peek().key, { verifyIntegrity: false }); - } - } - }); - } - - /** - * @method _setDeleteOnExpire - * @desc Set the deleteOnExpire setting for this cache. - * @param {string} deleteOnExpire The new deleteOnExpire for this cache. - */ - function _setDeleteOnExpire(deleteOnExpire) { - if (!angular.isString(deleteOnExpire)) { - throw new Error('deleteOnExpire: must be a string!'); - } else if (deleteOnExpire !== 'none' && deleteOnExpire !== 'passive' && deleteOnExpire !== 'aggressive') { - throw new Error('deleteOnExpire: accepted values are "none", "passive" or "aggressive"!'); - } else { - config.deleteOnExpire = deleteOnExpire; - } - } - - /** - * @method _setMaxAge - * @desc Set the maxAge for this cache. - * @param {number} maxAge The new maxAge for this cache. - */ - function _setMaxAge(maxAge) { - var keys = _keys(data); - if (maxAge === null) { - if (config.maxAge) { - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - if (!('maxAge' in data[key])) { - delete data[key].expires; - expiresHeap.remove(data[key]); - } - } - } - config.maxAge = maxAge; - } else { - _validateNumberOption(maxAge, function (err) { - if (err) { - throw new Error('maxAge: ' + err); - } else { - if (maxAge !== config.maxAge) { - config.maxAge = maxAge; - var now = new Date().getTime(); - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - if (!('maxAge' in data[key])) { - expiresHeap.remove(data[key]); - data[key].expires = data[key].created + config.maxAge; - expiresHeap.push(data[key]); - if (data[key].expires < now) { - self.remove(key, { verifyIntegrity: false }); - } - } - } - } - } - }); - } - } - - /** - * @method _flushExpired - * @desc Remove expired items from the expiresHeap. - * @private - */ - function _flushExpired() { - var now = new Date().getTime(), - item = expiresHeap.peek(); - - while (item && item.expires && item.expires < now) { - self.remove(item.key, { verifyIntegrity: false }); - if (config.onExpire) { - config.onExpire(item.key, item.value); - } - item = expiresHeap.peek(); - } - } - - /** - * @method _setRecycleFreq - * @desc Set the recycleFreq setting for this cache. - * @param {number} recycleFreq The new recycleFreq for this cache. - */ - function _setRecycleFreq(recycleFreq) { - if (recycleFreq === null) { - if (config.recycleFreqId) { - clearInterval(config.recycleFreqId); - delete config.recycleFreqId; - } - config.recycleFreq = cacheDefaults.recycleFreq; - config.recycleFreqId = setInterval(_flushExpired, config.recycleFreq); - } else { - _validateNumberOption(recycleFreq, function (err) { - if (err) { - throw new Error('recycleFreq: ' + err); - } else { - config.recycleFreq = recycleFreq; - if (config.recycleFreqId) { - clearInterval(config.recycleFreqId); - } - config.recycleFreqId = setInterval(_flushExpired, config.recycleFreq); - } - }); - } - } - - /** - * @method _setCacheFlushInterval - * @desc Set the cacheFlushInterval for this cache. - * @param {number} cacheFlushInterval The new cacheFlushInterval for this cache. - */ - function _setCacheFlushInterval(cacheFlushInterval) { - if (cacheFlushInterval === null) { - if (config.cacheFlushIntervalId) { - clearInterval(config.cacheFlushIntervalId); - delete config.cacheFlushIntervalId; - } - config.cacheFlushInterval = cacheFlushInterval; - } else { - _validateNumberOption(cacheFlushInterval, function (err) { - if (err) { - throw new Error('cacheFlushInterval: ' + err); - } else { - if (cacheFlushInterval !== config.cacheFlushInterval) { - if (config.cacheFlushIntervalId) { - clearInterval(config.cacheFlushIntervalId); - } - config.cacheFlushInterval = cacheFlushInterval; - config.cacheFlushIntervalId = setInterval(self.removeAll, config.cacheFlushInterval); - } - } - }); - } - } - - /** - * @method _setStorageMode - * @desc Configure the cache to use localStorage. - * @param {string} storageMode "localStorage"|"sessionStorage"|null - * @param {object} storageImpl The storage polyfill/replacement to use. - */ - function _setStorageMode(storageMode, storageImpl) { - var keys, i; - if (!angular.isString(storageMode)) { - throw new Error('storageMode: must be a string!'); - } else if (storageMode !== 'none' && storageMode !== 'localStorage' && storageMode !== 'sessionStorage') { - throw new Error('storageMode: accepted values are "none", "localStorage" or "sessionStorage"!'); - } - if ((config.storageMode === 'localStorage' || config.storageMode === 'sessionStorage') && - (storageMode !== config.storageMode)) { - keys = _keys(data); - for (i = 0; i < keys.length; i++) { - storage.removeItem(prefix + '.data.' + keys[i]); - } - storage.removeItem(prefix + '.keys'); - } - config.storageMode = storageMode; - if (storageImpl) { - if (!angular.isObject(storageImpl)) { - throw new Error('storageImpl: must be an object!'); - } else if (!('setItem' in storageImpl) || typeof storageImpl.setItem !== 'function') { - throw new Error('storageImpl: must implement "setItem(key, value)"!'); - } else if (!('getItem' in storageImpl) || typeof storageImpl.getItem !== 'function') { - throw new Error('storageImpl: must implement "getItem(key)"!'); - } else if (!('removeItem' in storageImpl) || typeof storageImpl.removeItem !== 'function') { - throw new Error('storageImpl: must implement "removeItem(key)"!'); - } - storage = storageImpl; - } else if (config.storageMode === 'localStorage') { - storage = $window.localStorage; - } else if (config.storageMode === 'sessionStorage') { - storage = $window.sessionStorage; - } - - if (config.storageMode !== 'none' && storage) { - if (!cacheDirty) { - _loadCacheConfig(); - } else { - keys = _keys(data); - for (i = 0; i < keys.length; i++) { - _syncToStorage(keys[i]); - } - } - } - } - - /** - * @method _setOptions - * @desc Configure this cache with the given options. - * @param {object} cacheOptions New configuration options for the cache. - * @param {boolean} [strict] If true then any existing configuration will be reset to default before - * applying the new options, otherwise only the options specified in the options hash will be altered. - * @param {object} [options] Configuration. - */ - function _setOptions(cacheOptions, strict, options) { - cacheOptions = cacheOptions || {}; - options = options || {}; - strict = !!strict; - if (!angular.isObject(cacheOptions)) { - throw new Error('AngularCache.setOptions(cacheOptions, strict, options): cacheOptions: must be an object!'); - } - - _verifyIntegrity(options.verifyIntegrity); - - if (strict) { - cacheOptions = angular.extend({}, cacheDefaults, cacheOptions); - } - - if ('disabled' in cacheOptions) { - config.disabled = cacheOptions.disabled === true; - } - if ('verifyIntegrity' in cacheOptions) { - config.verifyIntegrity = cacheOptions.verifyIntegrity === true; - } - if ('capacity' in cacheOptions) { - _setCapacity(cacheOptions.capacity); - } - - if ('deleteOnExpire' in cacheOptions) { - _setDeleteOnExpire(cacheOptions.deleteOnExpire); - } - - if ('maxAge' in cacheOptions) { - _setMaxAge(cacheOptions.maxAge); - } - - if ('recycleFreq' in cacheOptions) { - _setRecycleFreq(cacheOptions.recycleFreq); - } - - if ('cacheFlushInterval' in cacheOptions) { - _setCacheFlushInterval(cacheOptions.cacheFlushInterval); - } - - if ('storageMode' in cacheOptions) { - _setStorageMode(cacheOptions.storageMode, cacheOptions.storageImpl); - } - - if ('onExpire' in cacheOptions) { - if (cacheOptions.onExpire !== null && typeof cacheOptions.onExpire !== 'function') { - throw new Error('onExpire: must be a function!'); - } - config.onExpire = cacheOptions.onExpire; - } - - cacheDirty = true; - } - - /** - * @method _loadCacheConfig - * @desc If storageMode is set, attempt to load previous cache configuration from localStorage. - */ - function _loadCacheConfig() { - var keys = angular.fromJson(storage.getItem(prefix + '.keys')); - storage.removeItem(prefix + '.keys'); - if (keys && keys.length) { - for (var i = 0; i < keys.length; i++) { - var data = angular.fromJson(storage.getItem(prefix + '.data.' + keys[i])) || {}, - maxAge = data.maxAge || config.maxAge, - deleteOnExpire = data.deleteOnExpire || config.deleteOnExpire; - if (maxAge && ((new Date().getTime() - data.created) > maxAge) && deleteOnExpire === 'aggressive') { - storage.removeItem(prefix + '.data.' + keys[i]); - } else { - var options = { - created: data.created - }; - if (data.expires) { - options.expires = data.expires; - } - if (data.accessed) { - options.accessed = data.accessed; - } - if (data.maxAge) { - options.maxAge = data.maxAge; - } - if (data.deleteOnExpire) { - options.deleteOnExpire = data.deleteOnExpire; - } - self.put(keys[i], data.value, options); - } - } - _syncToStorage(null); - } - } - - /** - * @method _syncToStorage - * @desc If storageMode is set, sync to localStorage. - * @param {string} key The identifier of the item to sync. - */ - function _syncToStorage(key) { - if (config.storageMode !== 'none' && storage) { - storage.setItem(prefix + '.keys', angular.toJson(_keys(data))); - if (key) { - storage.setItem(prefix + '.data.' + key, angular.toJson(data[key])); - } - } - } - - function _verifyIntegrity(verifyIntegrity) { - if (verifyIntegrity || (verifyIntegrity !== false && config.verifyIntegrity)) { - if (config.storageMode !== 'none' && storage) { - var keys = _keys(data); - storage.setItem(prefix + '.keys', angular.toJson(keys)); - for (var i = 0; i < keys.length; i++) { - storage.setItem(prefix + '.data.' + keys[i], angular.toJson(data[keys[i]])); - } - } - } - } - - function _readItemFromStorage(key, verifyIntegrity) { - if (verifyIntegrity || (verifyIntegrity !== false && config.verifyIntegrity)) { - if (config.storageMode !== 'none' && storage) { - var itemJson = storage.getItem(prefix + '.data.' + key); - - if (!itemJson && key in data) { - self.remove(key); - } else if (itemJson) { - var item = angular.fromJson(itemJson), - value = item ? item.value : null; - - if (value) { - self.put(key, value); - } - } - } - } - } - - function _saveKeysToStorage(keys) { - if (config.storageMode !== 'none' && storage) { - var keysToSave = keys || _keys(data); - storage.setItem(prefix + '.keys', angular.toJson(keysToSave)); - } - } - - function _saveItemToStorage(key) { - if (config.storageMode !== 'none' && storage && key in data) { - storage.setItem(prefix + '.data.' + key, angular.toJson(data[key])); - } - } - - function _removeAllFromStorage() { - if (config.storageMode !== 'none' && storage) { - var keys = _keys(data); - for (var i = 0; i < keys.length; i++) { - storage.removeItem(prefix + '.data.' + keys[i]); - } - storage.setItem(prefix + '.keys', angular.toJson([])); - } - } - - /** - * @method AngularCache.put - * @desc Add a key-value pair to the cache. - * @param {string} key The identifier for the item to add to the cache. - * @param {*} value The value of the item to add to the cache. - * @param {object} [options] {{ maxAge: {number}, deleteOnExpire: {string} }} - * @returns {*} value The value of the item added to the cache. - */ - this.put = function (key, value, options) { - if (config.disabled) { - return; - } - options = options || {}; - - key = _stringifyNumber(key); - - if (!angular.isString(key)) { - throw new Error('AngularCache.put(key, value, options): key: must be a string!'); - } else if (options && !angular.isObject(options)) { - throw new Error('AngularCache.put(key, value, options): options: must be an object!'); - } else if (options.maxAge && options.maxAge !== null) { - _validateNumberOption(options.maxAge, function (err) { - if (err) { - throw new Error('AngularCache.put(key, value, options): maxAge: ' + err); - } - }); - } else if (options.deleteOnExpire && !angular.isString(options.deleteOnExpire)) { - throw new Error('AngularCache.put(key, value, options): deleteOnExpire: must be a string!'); - } else if (options.deleteOnExpire && options.deleteOnExpire !== 'none' && options.deleteOnExpire !== 'passive' && options.deleteOnExpire !== 'aggressive') { - throw new Error('AngularCache.put(key, value, options): deleteOnExpire: accepted values are "none", "passive" or "aggressive"!'); - } else if (angular.isUndefined(value)) { - return; - } - - var now = new Date().getTime(), - deleteOnExpire, item; - - _verifyIntegrity(options.verifyIntegrity); - - if (data[key]) { - expiresHeap.remove(data[key]); - lruHeap.remove(data[key]); - } else { - data[key] = { key: key }; - } - - item = data[key]; - item.value = value; - item.created = (parseInt(options.created, 10)) || item.created || now; - item.accessed = (parseInt(options.accessed, 10)) || now; - - if (options.deleteOnExpire) { - item.deleteOnExpire = options.deleteOnExpire; - } - if (options.maxAge) { - item.maxAge = options.maxAge; - } - - if (item.maxAge || config.maxAge) { - item.expires = item.created + (item.maxAge || config.maxAge); - } - - deleteOnExpire = item.deleteOnExpire || config.deleteOnExpire; - - if (item.expires && deleteOnExpire === 'aggressive') { - expiresHeap.push(item); - } - - // Sync with localStorage, etc. - _saveKeysToStorage(); - _saveItemToStorage(key); - - lruHeap.push(item); - - if (lruHeap.size() > config.capacity) { - this.remove(lruHeap.peek().key, { verifyIntegrity: false }); - } - - return value; - }; - - /** - * @method AngularCache.get - * @desc Retrieve the item from the cache with the specified key. - * @param {string|Array} key The key of the item to retrieve or an array of keys of items to retrieve. - * @param {object} [options] Configuration. - * @returns {*} The value of the item in the cache with the specified key. - */ - this.get = function (key, options) { - if (config.disabled) { - return; - } - if (angular.isArray(key)) { - var keys = key, - values = []; - - angular.forEach(keys, function (key) { - var value = self.get(key, options); - if (angular.isDefined(value)) { - values.push(value); - } - }); - - return values; - } else { - key = _stringifyNumber(key); - } - - options = options || {}; - if (!angular.isString(key)) { - throw new Error('AngularCache.get(key, options): key: must be a string!'); - } else if (options && !angular.isObject(options)) { - throw new Error('AngularCache.get(key, options): options: must be an object!'); - } else if (options.onExpire && !angular.isFunction(options.onExpire)) { - throw new Error('AngularCache.get(key, options): onExpire: must be a function!'); - } - - _readItemFromStorage(key, options.verifyIntegrity); - - if (!(key in data)) { - return; - } - - var item = data[key], - value = item.value, - now = new Date().getTime(), - deleteOnExpire = item.deleteOnExpire || config.deleteOnExpire; - - lruHeap.remove(item); - item.accessed = now; - lruHeap.push(item); - - if (deleteOnExpire === 'passive' && 'expires' in item && item.expires < now) { - this.remove(key, { verifyIntegrity: false }); - - if (config.onExpire) { - config.onExpire(key, item.value, (options.onExpire)); - } else if (options.onExpire) { - options.onExpire(key, item.value); - } - value = undefined; - } - - _saveItemToStorage(key); - - return value; - }; - - /** - * @method AngularCache.remove - * @desc Remove the item with the specified key from the cache. - * @param {string} key The key of the item to remove. - * @param {object} [options] Configuration. - */ - this.remove = function (key, options) { - options = options || {}; - _verifyIntegrity(options.verifyIntegrity); - lruHeap.remove(data[key]); - expiresHeap.remove(data[key]); - if (config.storageMode !== 'none' && storage) { - storage.removeItem(prefix + '.data.' + key); - } - delete data[key]; - _saveKeysToStorage(); - }; - - /** - * @method AngularCache.removeAll - * @desc Clear the cache. - */ - this.removeAll = function () { - _removeAllFromStorage(); - lruHeap.removeAll(); - expiresHeap.removeAll(); - data = {}; - }; - - /** - * @method AngularCache.removeExpired - * @desc Remove all expired items from the cache. - * @param {object} [options] Configuration. - * @returns {object|array} Object or array of removed items. - */ - this.removeExpired = function (options) { - options = options || {}; - var now = new Date().getTime(), - keys = _keys(data), - expired = {}; - - for (var i = 0; i < keys.length; i++) { - if (data[keys[i]] && data[keys[i]].expires && data[keys[i]].expires < now) { - expired[keys[i]] = data[keys[i]].value; - } - } - for (var key in expired) { - self.remove(key); - } - _verifyIntegrity(options.verifyIntegrity); - if (options.asArray) { - var expiredArray = []; - for (key in expired) { - expiredArray.push(expired[key]); - } - return expiredArray; - } else { - return expired; - } - }; - - /** - * @method AngularCache.destroy - * @desc Completely destroy the cache. - */ - this.destroy = function () { - if (config.cacheFlushIntervalId) { - clearInterval(config.cacheFlushIntervalId); - } - if (config.recycleFreqId) { - clearInterval(config.recycleFreqId); - } - this.removeAll(); - if (config.storageMode !== 'none' && storage) { - storage.removeItem(prefix + '.keys'); - storage.removeItem(prefix); - } - storage = null; - data = null; - lruHeap = null; - expiresHeap = null; - config = null; - prefix = null; - self = null; - var methodKeys = _keys(this); - - // Prevent this cache from being used after it has been destroyed - for (var i = 0; i < methodKeys.length; i++) { - if (this.hasOwnProperty(methodKeys[i])) { - delete this[methodKeys[i]]; - } - } - - caches[cacheId] = null; - delete caches[cacheId]; - }; - - /** - * @method AngularCache.info - * @desc Return an object containing information about the cache. - * @param {string} [key] The key of the item about which to retrieve information. - * @returns {object} stats Object containing information about this cache or the item with the - * specified key. - */ - this.info = function (key) { - if (key) { - if (data[key]) { - var info = { - created: data[key].created, - accessed: data[key].accessed, - expires: data[key].expires, - maxAge: data[key].maxAge || config.maxAge, - deleteOnExpire: data[key].deleteOnExpire || config.deleteOnExpire, - isExpired: false - }; - if (info.maxAge) { - info.isExpired = (new Date().getTime() - info.created) > info.maxAge; - } - return info; - } else { - return data[key]; - } - } else { - return angular.extend({}, config, { size: lruHeap && lruHeap.size() || 0 }); - } - }; - - /** - * @method AngularCache.keySet - * @desc Return the set of the keys of all items in the cache. - * @returns {object} The set of the keys of all items currently in this cache. - */ - this.keySet = function () { - return _keySet(data); - }; - - /** - * @method AngularCache.keys - * @desc Return an array of the keys of all items in the cache. - * @returns {array} An array of the keys of all items in the cache. - */ - this.keys = function () { - return _keys(data); - }; - - /** - * @method AngularCache.setOptions - * @desc Configure this cache with the given options. - * @param {object} cacheOptions - * @param {boolean} [strict] If true then any existing configuration will be reset to defaults before - * applying the new options, otherwise only the options specified in the hash will be altered. - * @param {object} [options] Configuration. - */ - this.setOptions = _setOptions; - - // Initialize this cache with the default and given options - _setOptions(options, true, { verifyIntegrity: false }); - } - - /** - * @class AngularCacheFactory - * @param {string} cacheId The id of the new cache. - * @param {object} [options] See [Configuration Options]{@link https://github.com/jmdobry/angular-cache#configuration}. - * @returns {AngularCache} - */ - function angularCacheFactory(cacheId, options) { - if (cacheId in caches) { - throw new Error('cacheId ' + cacheId + ' taken!'); - } else if (!angular.isString(cacheId)) { - throw new Error('cacheId must be a string!'); - } - - caches[cacheId] = new AngularCache(cacheId, options); - return caches[cacheId]; - } - - /** - * @method AngularCacheFactory.info - * @desc Return an object containing information about all caches in $angularCacheFactory. - * @returns {object} An object containing information about all caches of this factory. - */ - angularCacheFactory.info = function () { - var keys = _keys(caches); - var info = { - size: keys.length, - caches: {} - }; - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - info.caches[key] = caches[key].info(); - } - info.cacheDefaults = angular.extend({}, cacheDefaults); - return info; - }; - - /** - * @method AngularCacheFactory.get - * @desc Return the cache with the specified cacheId. - * @param {string} cacheId The id of the desired cache. - * @returns {AngularCache} The cache with the specified cachedId. - */ - angularCacheFactory.get = function (cacheId) { - if (!angular.isString(cacheId)) { - throw new Error('$angularCacheFactory.get(cacheId): cacheId: must be a string!'); - } - return caches[cacheId]; - }; - - /** - * @method AngularCacheFactory.keySet - * @desc Return the set of keys of all current caches owned by angularCacheFactory. - * @returns {object} The set of keys associated with all current caches owned by this - * angularCacheFactory. - */ - angularCacheFactory.keySet = function () { - return _keySet(caches); - }; - - /** - * @method AngularCacheFactory.keys - * @desc Return an array of the keys of all caches owned by angularCacheFactory. - * @returns {array} An array of the keys associated with all current caches owned by - * this angularCacheFactory. - */ - angularCacheFactory.keys = function () { - return _keys(caches); - }; - - /** - * @method AngularCacheFactory.removeAll - * @desc Destroy all caches in $angularCacheFactory. - */ - angularCacheFactory.removeAll = function () { - var keys = _keys(caches); - for (var i = 0; i < keys.length; i++) { - caches[keys[i]].destroy(); - } - }; - - /** - * @method AngularCacheFactory.clearAll - * @desc Clears the contents of every cache in $angularCacheFactory. - */ - angularCacheFactory.clearAll = function () { - var keys = _keys(caches); - for (var i = 0; i < keys.length; i++) { - caches[keys[i]].removeAll(); - } - }; - - /** - * @method AngularCacheFactory.enableAll - * @desc Enable any disabled caches. - */ - angularCacheFactory.enableAll = function () { - var keys = _keys(caches); - for (var i = 0; i < keys.length; i++) { - caches[keys[i]].setOptions({ disabled: false }); - } - }; - - /** - * @method AngularCacheFactory.disableAll - * @desc Disable all caches. - */ - angularCacheFactory.disableAll = function () { - var keys = _keys(caches); - for (var i = 0; i < keys.length; i++) { - caches[keys[i]].setOptions({ disabled: true }); - } - }; - - return angularCacheFactory; - }]; - } - - // Register the new provider with Angular. - angular.module('jmdobry.angular-cache').provider('$angularCacheFactory', $AngularCacheFactoryProvider); -})(window, window.angular); diff --git a/src/defaults/index.js b/src/defaults/index.js new file mode 100644 index 0000000..04a65d3 --- /dev/null +++ b/src/defaults/index.js @@ -0,0 +1,233 @@ +var defaults = { + /** + * @doc overview + * @id capacity + * @name capacity + * @description + * __Default:__ `Number.MAX_VALUE` + * + * This option limits the capacity of a cache. With a maximum capacity set, a cache operates as an LRU cache, + * deleting the least-recently-used item when the cache exceeds capacity. + * + * This option is dynamically configurable. Must be a number (milliseconds) greater than zero. + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + * - `DSCache.setCapacity(capacity)` + * - `DSCache.setOptions(options[, strict])` + */ + capacity: Number.MAX_VALUE, + + /** + * @doc overview + * @id maxAge + * @name maxAge + * @description + * __Default:__ `Number.MAX_VALUE` + * + * This option determines how long an item is in a cache before the item expires.. With `maxAge` set, items are + * marked as expired when their time in a cache exceeds `maxAge`. A cache's behavior when an item expires is + * determined by the [deleteOnExpire](/documentation/api/angular-cache/deleteOnExpire) option. + * + * This option is dynamically configurable. Must be a number (milliseconds) greater than zero. + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + * - `DSCache.setMaxAge(maxAge)` + * - `DSCache.setOptions(options[, strict])` + */ + maxAge: Number.MAX_VALUE, + + /** + * @doc overview + * @id deleteOnExpire + * @name deleteOnExpire + * @description + * __Default:__ `"none"` + * + * This option determines how long an item is in a cache before the item expires.. With `maxAge` set, items are + * marked as expired when their time in a cache exceeds `maxAge`. A cache's behavior when an item expires is + * determined by the [deleteOnExpire](/documentation/api/angular-cache/deleteOnExpire) option. + * + * This option is dynamically configurable. Must be `"none"`, `"passive"` or `"aggressive"`. + * + * #### "none" + * A cache will do nothing when its items expire. + * + * #### "passive" + * A cache will do nothing when its items expire. If an expired item is request it is removed from the cache and + * `undefined` is returned. + * + * #### "aggressive" + * A cache will periodically scan for expired items and actively remove them from the cache if any are found. The + * frequency of the scan is determined by the [recycleFreq](/documentation/api/angular-cache/recycleFreq) option. + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + * - `DSCache.setRecycleFreq(recycleFreq)` + * - `DSCache.setOptions(options[, strict])` + */ + deleteOnExpire: 'none', + + /** + * @doc overview + * @id onExpire + * @name onExpire + * @description + * __Default:__ `"none"` + * + * This option is a callback function which will be executed whenever an expired item is removed from a cache by + * either requesting an expired item while the cache is in `"passive"` `deleteOnExpire` mode, or when an expired + * item is actively removed when the cache is in `"aggressive"` `deleteOnExpire` mode. + * + * This option is dynamically configurable. Must be a function. Will be passed the `key` and `value` of the expired + * item. Will be passed a third `done` argument (if in `"passive"` `deleteOnExpire` mode) which is the `onExpire` + * argument passed to [DSCache#get(key[, options])](/documentation/api/angular-cache/DSCache.methods:get). + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + * - `DSCache.setOnExpire(onExpire)` + * - `DSCache.setOptions(options[, strict])` + */ + onExpire: null, + + /** + * @doc overview + * @id cacheFlushInterval + * @name cacheFlushInterval + * @description + * __Default:__ `null` + * + * This option, if set, will cause a cache to periodically clear itself of all data. + * + * This option is dynamically configurable. Must be a number (milliseconds) greater than zero. + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + * - `DSCache.setCacheFlushInterval(cacheFlushInterval)` + * - `DSCache.setOptions(options[, strict])` + */ + cacheFlushInterval: null, + + /** + * @doc overview + * @id recycleFreq + * @name recycleFreq + * @description + * __Default:__ `1000` + * + * This option determines how often a cache will scan for expired items when in `"aggressive"` `deleteOnExpire` + * mode. + * + * This option is dynamically configurable. Must be a number (milliseconds) greater than zero. + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + * - `DSCache.setRecycleFreq(recycleFreq)` + * - `DSCache.setOptions(options[, strict])` + */ + recycleFreq: 1000, + + /** + * @doc overview + * @id storageMode + * @name storageMode + * @description + * __Default:__ `"memory"` + * + * This option determines the storage mode for a cache. + * + * #### "memory" + * All data will be held in memory. + * + * #### "localStorage" + * Data will be held in `localStorage`, if available (or + * [storageImpl](/documentation/api/angular-cache/storageImpl) is provided). + * + * #### "sessionStorage" + * Data will be held in `sessionStorage`, if available (or + * [storageImpl](/documentation/api/angular-cache/storageImpl) is provided). + * + * This option is NOT dynamically configurable. Must be `"memory"`, `"localStorage"` or `"sessionStorage"`. + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + */ + storageMode: 'memory', + + /** + * @doc overview + * @id storageImpl + * @name storageImpl + * @description + * __Default:__ `null` + * + * This option is available if you want to provide a custom `localStorage` or `sessionStorage` implementation. + * + * This option is NOT dynamically configurable. Must be an object that implements `setItem(key, value)`, + * `getItem(key)` and `removeItem(key)`. + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + */ + storageImpl: null, + + /** + * @doc overview + * @id disabled + * @name disabled + * @description + * __Default:__ `false` + * + * This option disables or enables cache. + * + * This option is dynamically configurable. Must be `true` or `false`. + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + * - `DSCache.setOptions(options[, strict])` + * + * or just use [DSCache#disable()](/documentation/api/angular-cache/DSCache.methods:disable) or + * [DSCache#enable()](/documentation/api/angular-cache/DSCache.methods:enable). + */ + disabled: false, + + /** + * @doc overview + * @id storagePrefix + * @name storagePrefix + * @description + * __Default:__ `"angular-cache.caches."` + * + * This option determines the namespace for a cache when `storageMode` is `"localStorage"` or `"sessionStorage"`. + * Setting this value to something like `"ac."` will save space when using WebStorage. + * + * This option is NOT dynamically configurable. Must be a string. + * + * ### Where can it be used? + * - `DSCacheFactoryProvider.setCacheDefaults(options)` + * - `DSCacheFactory(cacheId[, options])` + */ + storagePrefix: 'angular-cache.caches.' +}; + +function Config() { +} + +for (var option in defaults) { + Config.prototype['$$' + option] = defaults[option]; +} + +module.exports = { + Config: Config, + defaults: defaults +}; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..a063dc4 --- /dev/null +++ b/src/index.js @@ -0,0 +1,64 @@ +(function (window, angular, undefined) { + 'use strict'; + + angular.$$minErr = angular.$$minErr || function (module) { + return function () { + var code = arguments[0], + prefix = '[' + (module ? module + ':' : '') + code + '] ', + template = arguments[1]; + + return new Error(prefix + template); + }; + }; + + angular.module('angular-data.DSBinaryHeap', []) + .provider('DSBinaryHeap', require('./DSBinaryHeap')); + + /** + * @doc overview + * @id angular-cache + * @name Overview + * @description + * __Version:__ <%= pkg.version %> + * + * ## Install + * + * #### Bower + * ```text + * bower install angular-cache + * ``` + * + * Load `dist/angular-cache.js` or `dist/angular-cache.min.js` onto your web page after Angular.js. + * + * #### Npm + * ```text + * npm install angular-cache + * ``` + * + * Load `dist/angular-cache.js` or `dist/angular-cache.min.js` onto your web page after Angular.js. Angular-cache is + * also consumable by Browserify and you should be able to `require('angular-cache')`. The `main` file is `src/index.js`. + * + * #### Manual download + * Download angular-cache.<%= pkg.version %>.js from the [Releases](https://github.com/jmdobry/angular-cache/releases) + * section of the angular-cache GitHub project. + * + * ## Load into Angular + * Your Angular app must depend on the module `"angular-data.DSCacheFactory"` in order to use angular-cache. Loading + * angular-cache into your app allows you to inject the following: + * + * - `DSCacheFactory` + * - `DSBinaryHeap` + * + * [DSCacheFactory](/documentation/api/api/DSCacheFactory) is a factory function that produces instances of + * [DSCache](/documentation/api/api/DSCache), which is API compatible with caches produced by Angular's + * [$cacheFactory](http://docs.angularjs.org/api/ng/service/$cacheFactory). + * + * [DSBinaryHeap](/documentation/api/api/DSBinaryHeap) is a priority queue implemented as a Binary Heap. + * + * Angular-cache is a dependency of [angular-data](/documentation/api/api/angular-data) and must be loaded before + * angular-data if you are using angular-data. + */ + angular.module('angular-data.DSCacheFactory', ['ng', 'angular-data.DSBinaryHeap']) + .provider('DSCacheFactory', require('./DSCacheFactory')); + +})(window, window.angular); diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000..e5c529c --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,37 @@ +module.exports = { + /*! + * Stringify a number. + */ + stringifyNumber: function (number) { + if (number && angular.isNumber(number)) { + return number.toString(); + } + return number; + }, + + /*! + * Return a hash of the keys in the given collection. + */ + keySet: function (collection) { + var keySet = {}, key; + for (key in collection) { + if (collection.hasOwnProperty(key)) { + keySet[key] = key; + } + } + return keySet; + }, + + /*! + * Return an array of the keys in the given collection + */ + keys: function (collection) { + var keys = [], key; + for (key in collection) { + if (collection.hasOwnProperty(key)) { + keys.push(key); + } + } + return keys; + } +}; diff --git a/test/angularCache.destroy-test.js b/test/angularCache.destroy-test.js deleted file mode 100644 index fa65b6d..0000000 --- a/test/angularCache.destroy-test.js +++ /dev/null @@ -1,47 +0,0 @@ -describe('AngularCache.destroy()', function () { - it('should destroy the cache and remove all traces of its existence.', function () { - var cache = $angularCacheFactory('cache'); - cache.destroy(); - try { - expect(cache.info()).toEqual({ size: 0 }); - fail('should not be able to use a cache after destroying it'); - } catch (err) { - - } - expect($angularCacheFactory.get('cache')).toEqual(undefined); - }); - it('should remove items from localStorage when storageMode is used.', function () { - var localStorageCache = $angularCacheFactory('localStorageCache', { storageMode: 'localStorage' }), - sessionStorageCache = $angularCacheFactory('sessionStorageCache', { storageMode: 'sessionStorage' }); - - localStorageCache.put('item1', 'value1'); - sessionStorageCache.put('item1', 'value1'); - localStorageCache.put('item2', 'value2'); - sessionStorageCache.put('item2', 'value2'); - - if (localStorage) { - expect(angular.fromJson(localStorage.getItem('angular-cache.caches.localStorageCache.data.item1')).value).toEqual('value1'); - expect(angular.fromJson(localStorage.getItem('angular-cache.caches.localStorageCache.data.item2')).value).toEqual('value2'); - expect(localStorage.getItem('angular-cache.caches.localStorageCache.keys')).toEqual('["item1","item2"]'); - } - if (sessionStorage) { - expect(angular.fromJson(sessionStorage.getItem('angular-cache.caches.sessionStorageCache.data.item1')).value).toEqual('value1'); - expect(angular.fromJson(sessionStorage.getItem('angular-cache.caches.sessionStorageCache.data.item2')).value).toEqual('value2'); - expect(sessionStorage.getItem('angular-cache.caches.sessionStorageCache.keys')).toEqual('["item1","item2"]'); - } - - localStorageCache.destroy(); - sessionStorageCache.destroy(); - - if (localStorage) { - expect(angular.fromJson(localStorage.getItem('angular-cache.caches.localStorageCache.data.item1'))).toEqual(null); - expect(angular.fromJson(localStorage.getItem('angular-cache.caches.localStorageCache.data.item2'))).toEqual(null); - expect(localStorage.getItem('angular-cache.caches.localStorageCache.keys')).toEqual(null); - } - if (sessionStorage) { - expect(angular.fromJson(sessionStorage.getItem('angular-cache.caches.sessionStorageCache.data.item1'))).toEqual(null); - expect(angular.fromJson(sessionStorage.getItem('angular-cache.caches.sessionStorageCache.data.item2'))).toEqual(null); - expect(sessionStorage.getItem('angular-cache.caches.sessionStorageCache.keys')).toEqual(null); - } - }); -}); \ No newline at end of file diff --git a/test/angularCache.info-test.js b/test/angularCache.info-test.js deleted file mode 100644 index bb4a38f..0000000 --- a/test/angularCache.info-test.js +++ /dev/null @@ -1,81 +0,0 @@ -describe('AngularCache.info()', function () { - it('should return the correct values.', function () { - var onExpire = function () { - }; - var cache = $angularCacheFactory('cache'), - cache2 = $angularCacheFactory('cache2', { maxAge: 1000 }), - cache3 = $angularCacheFactory('cache3', { cacheFlushInterval: 1000 }), - cache4 = $angularCacheFactory('cache4', { capacity: 1000 }), - cache5 = $angularCacheFactory('cache5', { storageMode: 'localStorage' }), - cache6 = $angularCacheFactory('cache6', { storageMode: 'sessionStorage' }); - cache7 = $angularCacheFactory('cache7', { maxAge: 100, onExpire: onExpire }); - var cacheInfo = cache.info(); - expect(cacheInfo.id).toEqual('cache'); - expect(cacheInfo.capacity).toEqual(Number.MAX_VALUE); - expect(cacheInfo.size).toEqual(0); - expect(cacheInfo.recycleFreq).toEqual(1000); - expect(cacheInfo.maxAge).toEqual(null); - expect(cacheInfo.cacheFlushInterval).toEqual(null); - expect(typeof cacheInfo.recycleFreqId).toEqual('number'); - expect(cacheInfo.deleteOnExpire).toEqual('none'); - expect(cacheInfo.storageMode).toEqual('none'); - expect(cacheInfo.onExpire).toEqual(null); - cache.put('item', 'value'); - cache.put('item2', 'value2', { maxAge: 200, deleteOnExpire: 'aggressive' }); - - // AngularCache#info(key) - expect(cache.info('non-existent item')).not.toBeDefined(); - expect(typeof cache.info('item').created).toEqual('number'); - expect(typeof cache.info('item').expires).toEqual('undefined'); - expect(typeof cache.info('item').accessed).toEqual('number'); - expect(cache.info('item').maxAge).toEqual(null); - expect(cache.info('item').deleteOnExpire).toEqual('none'); - expect(typeof cache.info('item2').created).toEqual('number'); - expect(typeof cache.info('item2').expires).toEqual('number'); - expect(typeof cache.info('item2').accessed).toEqual('number'); - expect(cache.info('item2').maxAge).toEqual(200); - expect(cache.info('item2').deleteOnExpire).toEqual('aggressive'); - - expect(cache.info().size).toEqual(2); - - var cacheInfo2 = cache2.info(); - expect(cacheInfo2.id).toEqual('cache2'); - expect(cacheInfo2.capacity).toEqual(Number.MAX_VALUE); - expect(cacheInfo2.size).toEqual(0); - expect(cacheInfo2.recycleFreq).toEqual(1000); - expect(cacheInfo2.maxAge).toEqual(1000); - expect(cacheInfo2.cacheFlushInterval).toEqual(null); - expect(typeof cacheInfo2.recycleFreqId).toEqual('number'); - expect(cacheInfo2.deleteOnExpire).toEqual('none'); - expect(cacheInfo2.storageMode).toEqual('none'); - expect(cacheInfo2.onExpire).toEqual(null); - - expect(cache3.info().id).toEqual('cache3'); - expect(cache3.info().capacity).toEqual(Number.MAX_VALUE); - expect(cache3.info().cacheFlushInterval).toEqual(1000); - expect(cache3.info().size).toEqual(0); - - var cacheInfo4 = cache4.info(); - expect(cacheInfo4.id).toEqual('cache4'); - expect(cacheInfo4.capacity).toEqual(1000); - expect(cacheInfo4.size).toEqual(0); - expect(cacheInfo4.recycleFreq).toEqual(1000); - expect(cacheInfo4.maxAge).toEqual(null); - expect(cacheInfo4.cacheFlushInterval).toEqual(null); - expect(typeof cacheInfo4.recycleFreqId).toEqual('number'); - expect(cacheInfo4.deleteOnExpire).toEqual('none'); - expect(cacheInfo4.storageMode).toEqual('none'); - expect(cacheInfo4.onExpire).toEqual(null); - if (localStorage) { - expect(cache5.info().storageMode).toEqual('localStorage'); - } else { - expect(cache5.info().storageMode).toEqual(null); - } - if (sessionStorage) { - expect(cache6.info().storageMode).toEqual('sessionStorage'); - } else { - expect(cache6.info().storageMode).toEqual(null); - } - expect(cache7.info().onExpire).toEqual(onExpire); - }); -}); \ No newline at end of file diff --git a/test/angularCache.keySet-test.js b/test/angularCache.keySet-test.js deleted file mode 100644 index b9f90b8..0000000 --- a/test/angularCache.keySet-test.js +++ /dev/null @@ -1,31 +0,0 @@ -describe('AngularCache.keySet()', function () { - it('should return the set of keys of all items in the cache.', function () { - var itemKeys = ['item1', 'item2', 'item3']; - - var cache = $angularCacheFactory('cache'); - - cache.put(itemKeys[0], itemKeys[0]); - cache.put(itemKeys[1], itemKeys[1]); - cache.put(itemKeys[2], itemKeys[2]); - - var keySet = cache.keySet(); - - expect(keySet.hasOwnProperty(itemKeys[0])).toEqual(true); - expect(keySet.hasOwnProperty(itemKeys[1])).toEqual(true); - expect(keySet.hasOwnProperty(itemKeys[2])).toEqual(true); - - expect(keySet[itemKeys[0]]).toEqual(itemKeys[0]); - expect(keySet[itemKeys[1]]).toEqual(itemKeys[1]); - expect(keySet[itemKeys[2]]).toEqual(itemKeys[2]); - - cache.remove(itemKeys[0]); - cache.remove(itemKeys[1]); - cache.remove(itemKeys[2]); - - keySet = cache.keySet(); - - expect(keySet.hasOwnProperty(itemKeys[0])).toEqual(false); - expect(keySet.hasOwnProperty(itemKeys[1])).toEqual(false); - expect(keySet.hasOwnProperty(itemKeys[2])).toEqual(false); - }); -}); \ No newline at end of file diff --git a/test/angularCache.keys-test.js b/test/angularCache.keys-test.js deleted file mode 100644 index a03403b..0000000 --- a/test/angularCache.keys-test.js +++ /dev/null @@ -1,25 +0,0 @@ -describe('AngularCache.keys()', function () { - it('should return the array of keys of all items in the cache.', function () { - var itemKeys = ['item1', 'item2', 'item3']; - - var cache = $angularCacheFactory('cache'); - - cache.put(itemKeys[0], itemKeys[0]); - cache.put(itemKeys[1], itemKeys[1]); - cache.put(itemKeys[2], itemKeys[2]); - - var keys = cache.keys(); - - expect(keys[0]).toEqual(itemKeys[0]); - expect(keys[1]).toEqual(itemKeys[1]); - expect(keys[2]).toEqual(itemKeys[2]); - - cache.remove(itemKeys[0]); - cache.remove(itemKeys[1]); - cache.remove(itemKeys[2]); - - keys = cache.keys(); - - expect(keys.length).toEqual(0); - }); -}); \ No newline at end of file diff --git a/test/angularCache.put-test.js b/test/angularCache.put-test.js deleted file mode 100644 index d1b9f85..0000000 --- a/test/angularCache.put-test.js +++ /dev/null @@ -1,228 +0,0 @@ -describe('AngularCache.put(key, value, options)', function () { - it('should do nothing if the cache is disabled.', function () { - var cache = $angularCacheFactory('cache', { disabled: true }); - - expect(cache.info().size).toEqual(0); - expect(cache.put('1', 'item')).toBeUndefined(); - expect(cache.info().size).toEqual(0); - }); - it('should throw an error if "key" is not a string.', function () { - var cache = $angularCacheFactory('cache'); - for (var i = 0; i < TYPES_EXCEPT_STRING_OR_NUMBER.length; i++) { - try { - cache.put(TYPES_EXCEPT_STRING_OR_NUMBER[i], 'value'); - fail(TYPES_EXCEPT_STRING_OR_NUMBER[i]); - } catch (err) { - expect(err.message).toEqual('AngularCache.put(key, value, options): key: must be a string!'); - continue; - } - fail(TYPES_EXCEPT_STRING_OR_NUMBER[i]); - } - }); - it('should throw an error if "options" is not an object.', function () { - var cache = $angularCacheFactory('cache'); - for (var i = 0; i < TYPES_EXCEPT_OBJECT.length; i++) { - try { - cache.put('item', 'value', TYPES_EXCEPT_OBJECT[i]); - if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { - fail(TYPES_EXCEPT_OBJECT[i]); - } - } catch (err) { - expect(err.message).toEqual('AngularCache.put(key, value, options): options: must be an object!'); - continue; - } - if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { - fail(TYPES_EXCEPT_OBJECT[i]); - } - } - }); - it('should throw an error if "maxAge" is not valid.', function () { - var cache = $angularCacheFactory('cache'); - var maxAge = Math.floor((Math.random() * 100000) + 1) * -1; - try { - cache.put('item', 'value', { maxAge: maxAge }); - fail(); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('AngularCache.put(key, value, options): maxAge: must be greater than zero!'); - for (var i = 0; i < TYPES_EXCEPT_NUMBER.length; i++) { - try { - cache.put('item', 'value', { maxAge: TYPES_EXCEPT_NUMBER[i] }); - if (TYPES_EXCEPT_NUMBER[i] !== null && TYPES_EXCEPT_NUMBER[i] !== undefined && TYPES_EXCEPT_NUMBER[i] !== false) { - fail(TYPES_EXCEPT_NUMBER[i]); - } - } catch (err) { - expect(err.message).toEqual('AngularCache.put(key, value, options): maxAge: must be a number!'); - continue; - } - if (TYPES_EXCEPT_NUMBER[i] !== null && TYPES_EXCEPT_NUMBER[i] !== undefined && TYPES_EXCEPT_NUMBER[i] !== false) { - fail(TYPES_EXCEPT_NUMBER[i]); - } - } - }); - it('should throw an error if "deleteOnExpire" is not valid.', function () { - var cache = $angularCacheFactory('cache'); - var deleteOnExpire = 'fail'; - try { - cache.put('item', 'value', { deleteOnExpire: deleteOnExpire }); - fail('should not reach this!'); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('AngularCache.put(key, value, options): deleteOnExpire: accepted values are "none", "passive" or "aggressive"!'); - for (var i = 0; i < TYPES_EXCEPT_STRING.length; i++) { - try { - cache.put('item', 'value', { deleteOnExpire: TYPES_EXCEPT_STRING[i] }); - if (TYPES_EXCEPT_STRING[i] !== null && TYPES_EXCEPT_STRING[i] !== undefined && TYPES_EXCEPT_STRING[i] !== false) { - fail(TYPES_EXCEPT_STRING[i]); - } - } catch (err) { - expect(err.message).toEqual('AngularCache.put(key, value, options): deleteOnExpire: must be a string!'); - continue; - } - if (TYPES_EXCEPT_STRING[i] !== null && TYPES_EXCEPT_STRING[i] !== undefined && TYPES_EXCEPT_STRING[i] !== false) { - fail(TYPES_EXCEPT_STRING[i]); - } - } - }); - it('should not add values that are not defined.', function () { - var cache = $angularCacheFactory('cache'); - cache.put('item', null); - expect(cache.get('item')).toEqual(undefined); - cache.put('item', undefined); - expect(cache.get('item')).toEqual(undefined); - }); - it('should increase the size of the cache by one.', function () { - var cache = $angularCacheFactory('cache'); - expect(cache.info().size).toEqual(0); - cache.put('item', 'value1'); - expect(cache.info().size).toEqual(1); - cache.put('item2', 'value2'); - expect(cache.info().size).toEqual(2); - }); - it('should overwrite an item if it is re-added to the cache.', function () { - var cache = $angularCacheFactory('cache'); - expect(cache.info().size).toEqual(0); - cache.put('item', 'value1'); - expect(cache.info().size).toEqual(1); - cache.put('item', 'value2'); - expect(cache.info().size).toEqual(1); - expect(cache.get('item')).toEqual('value2'); - }); - it('should remove the least recently used item if the capacity has been reached.', function () { - var cache = $angularCacheFactory('cache', { capacity: 2 }); - expect(cache.info().size).toEqual(0); - cache.put('item1', 'value1'); - expect(cache.info().size).toEqual(1); - cache.put('item2', 'value2'); - expect(cache.info().size).toEqual(2); - cache.put('item3', 'value3'); - expect(cache.info().size).toEqual(2); - expect(cache.get('item1')).toEqual(undefined); - expect(cache.get('item2')).toEqual('value2'); - expect(cache.get('item3')).toEqual('value3'); - cache.get('item2'); - cache.put('item1', 'value1'); - expect(cache.get('item3')).toEqual(undefined); - expect(cache.get('item1')).toEqual('value1'); - expect(cache.get('item2')).toEqual('value2'); - }); - it('should not delete items if maxAge is specified and deleteOnExpire is set to "none".', function () { - var cache = $angularCacheFactory('cache', { maxAge: 10, deleteOnExpire: 'none', recycleFreq: 20 }); - cache.put('item1', 'value1'); - expect(cache.get('item1')).toEqual('value1'); - waits(100); - runs(function () { - expect(cache.get('item1')).toEqual('value1'); - expect(cache.info('item1').isExpired).toEqual(true); - }); - }); - it('should remove items if maxAge is specified and deleteOnExpire is set to "aggressive".', function () { - var cache = $angularCacheFactory('cache', { maxAge: 10, deleteOnExpire: 'aggressive', recycleFreq: 20 }); - cache.put('item1', 'value1'); - expect(cache.get('item1')).toEqual('value1'); - waits(100); - runs(function () { - expect(cache.info('item1')).not.toBeDefined(); - expect(cache.get('item1')).not.toBeDefined(); - }); - }); - it('should should lazy delete an item when maxAge is specified and deleteOnExpire is set to "passive".', function () { - var cache = $angularCacheFactory('cache', { maxAge: 10, deleteOnExpire: 'passive' }); - cache.put('item1', 'value1'); - expect(cache.get('item1')).toEqual('value1'); - waits(100); - runs(function () { - expect(cache.info('item1').isExpired).toEqual(true); - expect(cache.get('item1')).not.toBeDefined(); - }); - }); - it('should not delete items if maxAge is specified and deleteOnExpire is set to "none" for an item.', function () { - var cache = $angularCacheFactory('cache'); - cache.put('item1', 'value1', { maxAge: 10, deleteOnExpire: 'none' }); - expect(cache.get('item1')).toEqual('value1'); - waits(100); - runs(function () { - expect(cache.get('item1')).toEqual('value1'); - expect(cache.info('item1').isExpired).toEqual(true); - }); - }); - it('should remove an item if maxAge for item is specified and deleteOnExpire is set to "aggressive".', function () { - var cache = $angularCacheFactory('cache', { recycleFreq: 10 }); - cache.put('item1', 'value1', { maxAge: 10, deleteOnExpire: 'aggressive' }); - expect(cache.get('item1')).toEqual('value1'); - waits(100); - runs(function () { - expect(cache.info('item1')).not.toBeDefined(); - expect(cache.get('item1')).not.toBeDefined(); - }); - }); - it('should passively expire an item if maxAge for the item is specified and deleteOnExpire is set to "passive".', function () { - var cache = $angularCacheFactory('cache'); - cache.put('item1', 'value1', { maxAge: 10, deleteOnExpire: 'passive' }); - expect(cache.get('item1')).toEqual('value1'); - waits(100); - runs(function () { - expect(cache.info('item1').isExpired).toEqual(true); - expect(cache.get('item1')).toEqual(undefined); - }); - }); - it('maxAge for a specific item should override maxAge for the cache.', function () { - var cache = $angularCacheFactory('cache', { maxAge: 1000, deleteOnExpire: 'aggressive', recycleFreq: 20 }); - cache.put('item1', 'value1', { maxAge: 5 }); - expect(cache.info('item1').maxAge).toEqual(5); - expect(cache.get('item1')).toEqual('value1'); - waits(100); - runs(function () { - expect(cache.get('item1')).toEqual(undefined); - }); - }); - it('deleteOnExpire set to "passive" for a specific item should override deleteOnExpire set to "aggressive" for the cache.', function () { - var cache = $angularCacheFactory('cache', { maxAge: 10, deleteOnExpire: 'aggressive' }); - cache.put('item1', 'value1', { maxAge: 10, deleteOnExpire: 'passive' }); - expect(cache.get('item1')).toEqual('value1'); - expect(cache.info('item1').deleteOnExpire).toEqual("passive"); - waits(100); - runs(function () { - expect(cache.info('item1').isExpired).toEqual(true); - expect(cache.get('item1')).toEqual(undefined); - }); - }); - it('should save data to localStorage when storageMode is used.', function () { - var localStorageCache = $angularCacheFactory('localStorageCache', { storageMode: 'localStorage' }), - sessionStorageCache = $angularCacheFactory('sessionStorageCache', { storageMode: 'sessionStorage' }); - - localStorageCache.put('item1', 'value1'); - sessionStorageCache.put('item1', 'value1'); - - if (localStorage) { - expect(angular.fromJson(localStorage.getItem('angular-cache.caches.localStorageCache.data.item1')).value).toEqual('value1'); - expect(localStorage.getItem('angular-cache.caches.localStorageCache.keys')).toEqual('["item1"]'); - } - if (sessionStorage) { - expect(angular.fromJson(sessionStorage.getItem('angular-cache.caches.sessionStorageCache.data.item1')).value).toEqual('value1'); - expect(sessionStorage.getItem('angular-cache.caches.sessionStorageCache.keys')).toEqual('["item1"]'); - } - }); -}); diff --git a/test/angularCache.remove-test.js b/test/angularCache.remove-test.js deleted file mode 100644 index f8470ab..0000000 --- a/test/angularCache.remove-test.js +++ /dev/null @@ -1,62 +0,0 @@ -describe('AngularCache.remove(key)', function () { - it('should remove the item with the specified key.', function () { - var cache = $angularCacheFactory('cache'); - var value1 = 'value1', - value2 = 2, - value3 = { - value3: 'stuff' - }; - cache.put('item1', value1); - cache.put('item2', value2); - cache.put('item3', value3); - cache.remove('item1'); - expect(cache.get('item1')).toEqual(undefined); - cache.remove('item2'); - expect(cache.get('item2')).toEqual(undefined); - cache.remove('item3'); - expect(cache.get('item3')).toEqual(undefined); - }); - it('should reduce the size of the cache by one if the size is greater than zero.', function () { - var cache = $angularCacheFactory('cache'); - cache.put('item1', 'value1'); - expect(cache.info().size).toEqual(1); - cache.put('item2', 'value2'); - expect(cache.info().size).toEqual(2); - cache.remove('item1'); - expect(cache.info().size).toEqual(1); - cache.remove('item2'); - expect(cache.info().size).toEqual(0); - cache.remove('item1'); - expect(cache.info().size).toEqual(0); - cache.remove('item2'); - expect(cache.info().size).toEqual(0); - }); - it('should remove items from localStorage when storageMode is used.', function () { - var localStorageCache = $angularCacheFactory('localStorageCache', { storageMode: 'localStorage' }), - sessionStorageCache = $angularCacheFactory('sessionStorageCache', { storageMode: 'sessionStorage' }); - - localStorageCache.put('item1', 'value1'); - sessionStorageCache.put('item1', 'value1'); - - if (localStorage) { - expect(angular.fromJson(localStorage.getItem('angular-cache.caches.localStorageCache.data.item1')).value).toEqual('value1'); - expect(localStorage.getItem('angular-cache.caches.localStorageCache.keys')).toEqual('["item1"]'); - } - if (sessionStorage) { - expect(angular.fromJson(sessionStorage.getItem('angular-cache.caches.sessionStorageCache.data.item1')).value).toEqual('value1'); - expect(sessionStorage.getItem('angular-cache.caches.sessionStorageCache.keys')).toEqual('["item1"]'); - } - - localStorageCache.remove('item1'); - sessionStorageCache.remove('item1'); - - if (localStorage) { - expect(angular.fromJson(localStorage.getItem('angular-cache.caches.localStorageCache.data.item1'))).toEqual(null); - expect(localStorage.getItem('angular-cache.caches.localStorageCache.keys')).toEqual('[]'); - } - if (sessionStorage) { - expect(angular.fromJson(sessionStorage.getItem('angular-cache.caches.sessionStorageCache.data.item1'))).toEqual(null); - expect(sessionStorage.getItem('angular-cache.caches.sessionStorageCache.keys')).toEqual('[]'); - } - }); -}); \ No newline at end of file diff --git a/test/angularCache.removeAll-test.js b/test/angularCache.removeAll-test.js deleted file mode 100644 index fd3b872..0000000 --- a/test/angularCache.removeAll-test.js +++ /dev/null @@ -1,51 +0,0 @@ -describe('AngularCache.removeAll()', function () { - it('should remove all items in the cache.', function () { - var cache = $angularCacheFactory('cache'); - var value1 = 'value1', - value2 = 2, - value3 = { - value3: 'stuff' - }; - cache.put('item1', value1); - cache.put('item2', value2); - cache.put('item3', value3); - cache.removeAll(); - expect(cache.get('item1')).toEqual(undefined); - expect(cache.get('item2')).toEqual(undefined); - expect(cache.get('item3')).toEqual(undefined); - }); - it('should remove items from localStorage when storageMode is used.', function () { - var localStorageCache = $angularCacheFactory('localStorageCache', { storageMode: 'localStorage' }), - sessionStorageCache = $angularCacheFactory('sessionStorageCache', { storageMode: 'sessionStorage' }); - - localStorageCache.put('item1', 'value1'); - sessionStorageCache.put('item1', 'value1'); - localStorageCache.put('item2', 'value2'); - sessionStorageCache.put('item2', 'value2'); - - if (localStorage) { - expect(angular.fromJson(localStorage.getItem('angular-cache.caches.localStorageCache.data.item1')).value).toEqual('value1'); - expect(angular.fromJson(localStorage.getItem('angular-cache.caches.localStorageCache.data.item2')).value).toEqual('value2'); - expect(localStorage.getItem('angular-cache.caches.localStorageCache.keys')).toEqual('["item1","item2"]'); - } - if (sessionStorage) { - expect(angular.fromJson(sessionStorage.getItem('angular-cache.caches.sessionStorageCache.data.item1')).value).toEqual('value1'); - expect(angular.fromJson(sessionStorage.getItem('angular-cache.caches.sessionStorageCache.data.item2')).value).toEqual('value2'); - expect(sessionStorage.getItem('angular-cache.caches.sessionStorageCache.keys')).toEqual('["item1","item2"]'); - } - - localStorageCache.removeAll(); - sessionStorageCache.removeAll(); - - if (localStorage) { - expect(angular.fromJson(localStorage.getItem('angular-cache.caches.localStorageCache.data.item1'))).toEqual(null); - expect(angular.fromJson(localStorage.getItem('angular-cache.caches.localStorageCache.data.item2'))).toEqual(null); - expect(localStorage.getItem('angular-cache.caches.localStorageCache.keys')).toEqual('[]'); - } - if (sessionStorage) { - expect(angular.fromJson(sessionStorage.getItem('angular-cache.caches.sessionStorageCache.data.item1'))).toEqual(null); - expect(angular.fromJson(sessionStorage.getItem('angular-cache.caches.sessionStorageCache.data.item2'))).toEqual(null); - expect(sessionStorage.getItem('angular-cache.caches.sessionStorageCache.keys')).toEqual('[]'); - } - }); -}); \ No newline at end of file diff --git a/test/angularCache.removeExpired-test.js b/test/angularCache.removeExpired-test.js deleted file mode 100644 index 28ab1d8..0000000 --- a/test/angularCache.removeExpired-test.js +++ /dev/null @@ -1,28 +0,0 @@ -describe('AngularCache.removeExpired()', function () { - it('should remove all expired items.', function () { - var cache = $angularCacheFactory('cache', { - deleteOnExpire: 'none', - maxAge: 10, - recycleFreq: 20 - }); - var value1 = 'value1', - value2 = 2, - value3 = { - value3: 'stuff' - }; - cache.put('item1', value1, { - deleteOnExpire: 'passive' - }); - cache.put('item2', value2, { - deleteOnExpire: 'aggressive' - }); - cache.put('item3', value3); - waits(100); - runs(function () { - cache.removeExpired(); - expect(cache.info().size).toEqual(0); - cache.put('item3', value3); - expect(cache.info().size).toEqual(1); - }); - }); -}); diff --git a/test/angularCache.setOptions-test.js b/test/angularCache.setOptions-test.js deleted file mode 100644 index 2d5e586..0000000 --- a/test/angularCache.setOptions-test.js +++ /dev/null @@ -1,242 +0,0 @@ -describe('AngularCache.setOptions()', function () { - it('should throw an error if "options" is not an object.', function () { - var cache = $angularCacheFactory('cache'); - for (var i = 0; i < TYPES_EXCEPT_OBJECT.length; i++) { - try { - cache.setOptions(TYPES_EXCEPT_OBJECT[i]); - if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { - fail(TYPES_EXCEPT_OBJECT[i]); - } - } catch (err) { - expect(err.message).toEqual('AngularCache.setOptions(cacheOptions, strict, options): cacheOptions: must be an object!'); - continue; - } - if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { - fail(TYPES_EXCEPT_OBJECT[i]); - } - } - }); - it('should correctly reset to defaults if strict mode is true', function () { - var onExpire = function () { - }; - var cache = $angularCacheFactory('cache', { - maxAge: 100, - cacheFlushInterval: 200, - onExpire: onExpire, - storageMode: 'localStorage', - disabled: true - }); - expect(cache.info().maxAge).toEqual(100); - expect(cache.info().cacheFlushInterval).toEqual(200); - expect(cache.info().onExpire).toEqual(onExpire); - expect(cache.info().storageMode).toEqual('localStorage'); - expect(cache.info().disabled).toEqual(true); - cache.setOptions({ }, true); - expect(cache.info().maxAge).toEqual(null); - expect(cache.info().cacheFlushInterval).toEqual(null); - expect(cache.info().onExpire).toEqual(null); - expect(cache.info().storageMode).toEqual('none'); - expect(cache.info().disabled).toEqual(false); - }); - it('should correctly modify the capacity of a cache', function () { - var cache = $angularCacheFactory('cache'); - expect(cache.info().capacity).toEqual(Number.MAX_VALUE); - cache.setOptions({ capacity: 5 }, false); - expect(cache.info().capacity).toEqual(5); - cache.put('item1', 1); - cache.put('item2', 2); - cache.put('item3', 3); - cache.put('item4', 4); - cache.put('item5', 5); - cache.put('item6', 6); - expect(cache.get('item1')).not.toBeDefined(); - waits(50); - runs(function () { - cache.get('item2'); - cache.get('item3'); - cache.get('item6'); - cache.setOptions({ capacity: 3 }, false); - // Least-recently used items over the new capacity should have been removed. - expect(cache.get('item4')).not.toBeDefined(); - expect(cache.get('item5')).not.toBeDefined(); - expect(cache.info().size).toEqual(3); - }); - }); - it('should correctly modify the maxAge of a cache', function () { - var cache = $angularCacheFactory('cache'); - expect(cache.info().maxAge).toEqual(null); - cache.setOptions({ maxAge: 10, deleteOnExpire: 'aggressive', recycleFreq: 20 }, false); - expect(cache.info().maxAge).toEqual(10); - cache.put('item1', 1); - cache.put('item2', 2); - waits(100); - runs(function () { - expect(cache.get('item1')).not.toBeDefined(); - expect(cache.get('item2')).not.toBeDefined(); - cache.setOptions({ maxAge: 10, deleteOnExpire: 'aggressive', recycleFreq: 20 }, false); - expect(cache.info().maxAge).toEqual(10); - cache.put('item1', 1); - cache.put('item2', 2); - waits(100); - // The new items should be removed after 500 ms (the new maxAge) - runs(function () { - expect(cache.get('item1')).not.toBeDefined(); - expect(cache.get('item2')).not.toBeDefined(); - cache.put('item1', 1); - cache.put('item2', 2, { maxAge: 25, deleteOnExpire: 'aggressive' }); - cache.setOptions({ maxAge: null, deleteOnExpire: 'none', recycleFreq: 20 }, false); - expect(cache.info().maxAge).toEqual(null); - waits(100); - // The new items should be removed after 500 ms (the new maxAge) - runs(function () { - expect(cache.get('item1')).toEqual(1); - expect(cache.info('item1').expires).not.toBeDefined(); - expect(cache.get('item2')).not.toBeDefined(); - expect(cache.info('item2')).not.toBeDefined(); - - cache.setOptions({ maxAge: 1000, deleteOnExpire: 'aggressive', recycleFreq: 20 }, false); - cache.put('item1', 1); - cache.put('item2', 2); - waits(100); - // The new items should be removed after 500 ms (the new maxAge) - runs(function () { - cache.setOptions({ maxAge: 10, deleteOnExpire: 'aggressive' }, false); - expect(cache.get('item1')).not.toBeDefined(); - expect(cache.get('item2')).not.toBeDefined(); - }); - }); - }); - }); - }); - it('should correctly modify the cacheFlushInterval of a cache', function () { - var cache = $angularCacheFactory('cache'); - expect(cache.info().cacheFlushInterval).toEqual(null); - cache.setOptions({ cacheFlushInterval: 10 }, false); - expect(cache.info().cacheFlushInterval).toEqual(10); - cache.put('item1', 1); - cache.put('item2', 2); - waits(100); - // The first items should be removed after 2000 ms - runs(function () { - expect(cache.get('item1')).not.toBeDefined(); - expect(cache.get('item2')).not.toBeDefined(); - cache.setOptions({ cacheFlushInterval: 20 }, false); - expect(cache.info().cacheFlushInterval).toEqual(20); - cache.put('item1', 1); - cache.put('item2', 2); - waits(100); - // The new items should be removed after 500 ms (the new maxAge) - runs(function () { - expect(cache.get('item1')).not.toBeDefined(); - expect(cache.get('item2')).not.toBeDefined(); - cache.setOptions({ cacheFlushInterval: 20 }, false); - expect(cache.info().cacheFlushInterval).toEqual(20); - cache.put('item1', 1); - cache.put('item2', 2); - waits(100); - // The new items should be removed after 500 ms (the new maxAge) - runs(function () { - expect(cache.get('item1')).not.toBeDefined(); - expect(cache.get('item2')).not.toBeDefined(); - }); - }); - }); - }); - it('should correctly modify the deleteOnExpire of a cache', function () { - var cache = $angularCacheFactory('cache', { maxAge: 10 }); - expect(cache.info().deleteOnExpire).toEqual('none'); - cache.setOptions({ deleteOnExpire: 'passive' }, false); - expect(cache.info().deleteOnExpire).toEqual('passive'); - cache.put('item1', 1); - cache.put('item2', 2); - waits(100); - // The first items should be removed after 2000 ms - runs(function () { - expect(cache.get('item1')).not.toBeDefined(); - expect(cache.get('item2')).not.toBeDefined(); - cache.setOptions({ maxAge: 10, deleteOnExpire: 'aggressive', recycleFreq: 20 }, false); - expect(cache.info().deleteOnExpire).toEqual('aggressive'); - cache.put('item1', 1); - cache.put('item2', 2); - waits(100); - // The new items should be removed after 500 ms (the new maxAge) - runs(function () { - expect(cache.get('item1')).not.toBeDefined(); - expect(cache.get('item2')).not.toBeDefined(); - }); - }); - }); - it('should correctly set configuration to default when "strict" is true', function () { - var cache = $angularCacheFactory('cache', { - capacity: 10, - maxAge: 1000, - cacheFlushInterval: 1000, - deleteOnExpire: 'aggressive', - storageMode: 'none' - }); - cache.setOptions({}, true); - var cacheInfo = cache.info(); - expect(cacheInfo.id).toEqual('cache'); - expect(cacheInfo.capacity).toEqual(Number.MAX_VALUE); - expect(cacheInfo.size).toEqual(0); - expect(cacheInfo.recycleFreq).toEqual(1000); - expect(cacheInfo.maxAge).toEqual(null); - expect(cacheInfo.cacheFlushInterval).toEqual(null); - expect(typeof cacheInfo.recycleFreqId).toEqual('number'); - expect(cacheInfo.deleteOnExpire).toEqual('none'); - expect(cacheInfo.storageMode).toEqual('none'); - expect(cacheInfo.onExpire).toEqual(null); - - cache.setOptions({ recycleFreq: null }); - expect(cache.info().recycleFreq).toEqual(1000); - }); - it('should correctly switch to using local/session storage when storageMode is activated', function () { - var cache = $angularCacheFactory('cache'), - cache2 = $angularCacheFactory('cache2'); - cache.put('item', 'value'); - cache2.put('item', 'value'); - cache.setOptions({ maxAge: 10, deleteOnExpire: 'aggressive', storageMode: 'localStorage', recycleFreq: 20 }); - cache2.setOptions({ maxAge: 10, deleteOnExpire: 'aggressive', storageMode: 'sessionStorage', recycleFreq: 20 }); - - if (localStorage) { - expect(angular.fromJson(localStorage.getItem('angular-cache.caches.cache.data.item')).value).toEqual('value'); - } - if (sessionStorage) { - expect(angular.fromJson(sessionStorage.getItem('angular-cache.caches.cache2.data.item')).value).toEqual('value'); - } - waits(100); - runs(function () { - expect(cache.get('item')).not.toBeDefined(); - expect(cache2.get('item')).not.toBeDefined(); - if (localStorage) { - expect(localStorage.getItem('angular-cache.caches.cache.data.item')).toEqual(null); - } - if (sessionStorage) { - expect(sessionStorage.getItem('angular-cache.caches.cache2.data.item')).toEqual(null); - } - }); - }); - it('should correctly stop using local/session storage when storageMode is deactivated', function () { - var cache = $angularCacheFactory('cache', { storageMode: 'localStorage' }), - cache2 = $angularCacheFactory('cache2', { storageMode: 'sessionStorage' }); - cache.put('item', 'value'); - cache2.put('item', 'value'); - - if (localStorage) { - expect(angular.fromJson(localStorage.getItem('angular-cache.caches.cache.data.item')).value).toEqual('value'); - } - if (sessionStorage) { - expect(angular.fromJson(sessionStorage.getItem('angular-cache.caches.cache2.data.item')).value).toEqual('value'); - } - - cache.setOptions({ storageMode: 'none' }, true); - cache2.setOptions({ storageMode: 'none' }, true); - - if (localStorage) { - expect(localStorage.getItem('angular-cache.caches.cache.data.item')).toEqual(null); - } - if (sessionStorage) { - expect(sessionStorage.getItem('angular-cache.caches.cache2.data.item')).toEqual(null); - } - }); -}); diff --git a/test/angularCacheFactory-test.js b/test/angularCacheFactory-test.js deleted file mode 100644 index e29868a..0000000 --- a/test/angularCacheFactory-test.js +++ /dev/null @@ -1,338 +0,0 @@ -describe('$angularCacheFactory(cacheId, options)', function () { - it('should be able to create a default cache.', function () { - var cache = $angularCacheFactory('cache'); - expect(cache).toBeDefined(); - expect(cache.info().id).toEqual('cache'); - expect(cache.info().capacity).toEqual(CACHE_DEFAULTS.capacity); - expect(cache.info().maxAge).toEqual(CACHE_DEFAULTS.maxAge); - expect(cache.info().cacheFlushInterval).toEqual(CACHE_DEFAULTS.cacheFlushInterval); - expect(cache.info().deleteOnExpire).toEqual(CACHE_DEFAULTS.deleteOnExpire); - expect(cache.info().onExpire).toEqual(CACHE_DEFAULTS.onExpire); - expect(cache.info().recycleFreq).toEqual(CACHE_DEFAULTS.recycleFreq); - expect(cache.info().storageMode).toEqual(CACHE_DEFAULTS.storageMode); - expect(cache.info().storageImpl).toEqual(CACHE_DEFAULTS.storageImpl); - expect(cache.info().verifyIntegrity).toEqual(CACHE_DEFAULTS.verifyIntegrity); - cache.destroy(); - }); - it('should be able to create a cache with options.', function () { - var options = { - capacity: Math.floor((Math.random() * 100000) + 1), - maxAge: Math.floor((Math.random() * 100000) + 1), - cacheFlushInterval: Math.floor((Math.random() * 100000) + 1), - deleteOnExpire: 'aggressive', - storageMode: 'localStorage', - localStorageImpl: { - setItem: function () { - }, - getItem: function () { - }, - removeItem: function () { - } - }, - verifyIntegrity: false, - recycleFreq: 2000, - onExpire: function () { - }, - readOnGet: false - }; - var cache = $angularCacheFactory('cache', options); - expect(cache).toBeDefined(); - expect(cache.info().id).toEqual('cache'); - expect(cache.info().capacity).toEqual(options.capacity); - expect(cache.info().maxAge).toEqual(options.maxAge); - expect(cache.info().cacheFlushInterval).toEqual(options.cacheFlushInterval); - expect(cache.info().deleteOnExpire).toEqual(options.deleteOnExpire); - expect(cache.info().storageMode).toEqual(options.storageMode); - expect(cache.info().localStorageImpl).not.toBeDefined(); // We don't expose this to the user - expect(cache.info().onExpire).toEqual(options.onExpire); - cache.destroy(); - expect($angularCacheFactory.get('cache')).not.toBeDefined(); - }); - it('should throw an exception if "capacity" is not a number or is less than zero.', function () { - try { - $angularCacheFactory('capacityCache99', { capacity: Math.floor((Math.random() * 100000) + 1) * -1 }); - fail(); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('capacity: must be greater than zero!'); - for (var i = 0; i < TYPES_EXCEPT_NUMBER.length; i++) { - try { - $angularCacheFactory('capacityCache' + i, { capacity: TYPES_EXCEPT_NUMBER[i] }); - fail(); - } catch (err) { - expect(err.message).toEqual('capacity: must be a number!'); - continue; - } - fail(); - } - }); - it('should validate maxAge.', function () { - var maxAge = Math.floor((Math.random() * 100000) + 1) * -1; - try { - $angularCacheFactory('maxAgeCache99', { maxAge: maxAge }); - fail(); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('maxAge: must be greater than zero!'); - for (var i = 0; i < TYPES_EXCEPT_NUMBER.length; i++) { - try { - $angularCacheFactory('maxAgeCache' + i, { maxAge: TYPES_EXCEPT_NUMBER[i] }); - if (TYPES_EXCEPT_NUMBER[i] !== null) { - fail(TYPES_EXCEPT_NUMBER[i]); - } - } catch (err) { - expect(err.message).toEqual('maxAge: must be a number!'); - continue; - } - if (TYPES_EXCEPT_NUMBER[i] !== null) { - fail(TYPES_EXCEPT_NUMBER[i]); - } - } - }); - it('should validate cacheFlushInterval.', function () { - var cacheFlushInterval = Math.floor((Math.random() * 100000) + 1) * -1; - try { - $angularCacheFactory('cacheFlushIntervalCache99', { cacheFlushInterval: cacheFlushInterval }); - fail(); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('cacheFlushInterval: must be greater than zero!'); - for (var i = 0; i < TYPES_EXCEPT_NUMBER.length; i++) { - try { - $angularCacheFactory('cacheFlushIntervalCache' + i, { cacheFlushInterval: TYPES_EXCEPT_NUMBER[i] }); - if (TYPES_EXCEPT_NUMBER[i] !== null) { - fail(); - } - } catch (err) { - expect(err.message).toEqual('cacheFlushInterval: must be a number!'); - continue; - } - if (TYPES_EXCEPT_NUMBER[i] !== null) { - fail(); - } - } - }); - it('should validate recycleFreq.', function () { - var recycleFreq = Math.floor((Math.random() * 100000) + 1) * -1; - try { - $angularCacheFactory('recycleFreqCache99', { recycleFreq: recycleFreq }); - fail(); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('recycleFreq: must be greater than zero!'); - for (var i = 0; i < TYPES_EXCEPT_NUMBER.length; i++) { - try { - $angularCacheFactory('recycleFreqCache' + i, { recycleFreq: TYPES_EXCEPT_NUMBER[i] }); - if (TYPES_EXCEPT_NUMBER[i] !== null) { - fail(); - } - } catch (err) { - expect(err.message).toEqual('recycleFreq: must be a number!'); - continue; - } - if (TYPES_EXCEPT_NUMBER[i] !== null) { - fail(); - } - } - }); - it('should validate onExpire.', function () { - var onExpire = 234; - try { - $angularCacheFactory('onExpireCache99', { onExpire: onExpire }); - expect('should not reach this!').toEqual(false); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('onExpire: must be a function!'); - for (var i = 0; i < TYPES_EXCEPT_FUNCTION.length; i++) { - try { - $angularCacheFactory('onExpireCache' + i, { onExpire: TYPES_EXCEPT_FUNCTION[i] }); - if (TYPES_EXCEPT_FUNCTION[i] !== null) { - fail(); - } - } catch (err) { - expect(err.message).toEqual('onExpire: must be a function!'); - continue; - } - if (TYPES_EXCEPT_FUNCTION[i] !== null) { - fail(); - } - } - }); - it('should validate deleteOnExpire.', function () { - var deleteOnExpire = 'fail'; - try { - $angularCacheFactory('cache', { deleteOnExpire: deleteOnExpire }); - fail('should not reach this!'); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('deleteOnExpire: accepted values are "none", "passive" or "aggressive"!'); - for (var i = 0; i < TYPES_EXCEPT_STRING.length; i++) { - try { - $angularCacheFactory('deleteOnExpireCache' + i, { deleteOnExpire: TYPES_EXCEPT_STRING[i] }); - fail(TYPES_EXCEPT_STRING[i]); - } catch (err) { - expect(err.message).toEqual('deleteOnExpire: must be a string!'); - continue; - } - fail(TYPES_EXCEPT_STRING[i]); - } - }); - it('should validate storageMode.', function () { - var storageMode = 'fail'; - try { - $angularCacheFactory('cache', { storageMode: storageMode }); - expect('should not reach this!').toEqual(false); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('storageMode: accepted values are "none", "localStorage" or "sessionStorage"!'); - for (var i = 0; i < TYPES_EXCEPT_STRING.length; i++) { - try { - $angularCacheFactory('storageModeCache' + i, { storageMode: TYPES_EXCEPT_STRING[i] }); - fail(TYPES_EXCEPT_STRING[i]); - } catch (err) { - expect(err.message).toEqual('storageMode: must be a string!'); - continue; - } - fail(TYPES_EXCEPT_STRING[i]); - } - }); - it('should validate storageImpl.', function () { - var storageImpl = 'fail'; - try { - $angularCacheFactory('cache', { storageMode: 'localStorage', storageImpl: storageImpl }); - expect('should not reach this!').toEqual(false); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('storageImpl: must be an object!'); - for (var i = 0; i < TYPES_EXCEPT_OBJECT.length; i++) { - try { - $angularCacheFactory('storageImplCache' + i, { storageMode: 'localStorage', storageImpl: TYPES_EXCEPT_OBJECT[i] }); - if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { - fail(TYPES_EXCEPT_OBJECT[i]); - } - } catch (err) { - expect(err.message.length).not.toEqual(0); - continue; - } - if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { - fail(TYPES_EXCEPT_OBJECT[i]); - } - } - try { - $angularCacheFactory('storageImplCache-noSetItem', { - storageMode: 'localStorage', - storageImpl: { - getItem: function () { - }, - removeItem: function () { - } - } - }); - fail(); - } catch (err) { - expect(err.message.length).not.toEqual(0); - } - try { - $angularCacheFactory('storageImplCache-noGetItem', { - storageMode: 'localStorage', - storageImpl: { - setItem: function () { - }, - removeItem: function () { - } - } - }); - fail(); - } catch (err) { - expect(err.message.length).not.toEqual(0); - } - try { - $angularCacheFactory('storageImplCache-noRemoveItem', { - storageMode: 'localStorage', - storageImpl: { - getItem: function () { - }, - setItem: function () { - } - } - }); - fail(); - } catch (err) { - expect(err.message.length).not.toEqual(0); - } - try { - $angularCacheFactory('storageImplCache-stringGetItem', { - storageMode: 'localStorage', - storageImpl: { - getItem: 'should not be a string', - setItem: function () { - }, - removeItem: function () { - } - } - }); - fail(); - } catch (err) { - expect(err.message.length).not.toEqual(0); - } - try { - $angularCacheFactory('storageImplCache-stringSetItem', { - storageMode: 'localStorage', - storageImpl: { - getItem: function () { - }, - setItem: 'should not be a string', - removeItem: function () { - } - } - }); - fail(); - } catch (err) { - expect(err.message.length).not.toEqual(0); - } - try { - $angularCacheFactory('storageImplCache-stringRemoveItem', { - storageMode: 'localStorage', - storageImpl: { - setItem: function () { - }, - getItem: function () { - }, - removeItem: 'should not be a string' - } - }); - fail(); - } catch (err) { - expect(err.message.length).not.toEqual(0); - } - }); - it('should prevent a cache from being duplicated.', function () { - try { - $angularCacheFactory('cache'); - $angularCacheFactory('cache'); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('cacheId cache taken!'); - }); - it('should require cacheId to be a string.', function () { - for (var i = 0; i < TYPES_EXCEPT_STRING.length; i++) { - try { - $angularCacheFactory(TYPES_EXCEPT_STRING[i]); - fail(TYPES_EXCEPT_STRING[i]); - } catch (err) { - expect(err.message.length).not.toEqual(0); - continue; - } - fail(TYPES_EXCEPT_STRING[i]); - } - }); -}); \ No newline at end of file diff --git a/test/angularCacheFactory.clearAll-test.js b/test/angularCacheFactory.clearAll-test.js deleted file mode 100644 index ef333fd..0000000 --- a/test/angularCacheFactory.clearAll-test.js +++ /dev/null @@ -1,52 +0,0 @@ -describe('$angularCacheFactory.clearAll()', function () { - it('should call "removeAll()" on all caches in $angularCacheFactory.', function () { - var cacheKeys = ['cache', 'cache1', 'cache2'], - caches = []; - - caches.push($angularCacheFactory(cacheKeys[0])); - caches[0].put('item', 'value'); - caches[0].put('item2', 'value2'); - caches.push($angularCacheFactory(cacheKeys[1])); - caches[1].put('item', 'value'); - caches[1].put('item2', 'value2'); - caches.push($angularCacheFactory(cacheKeys[2])); - caches[2].put('item', 'value'); - caches[2].put('item2', 'value2'); - - spyOn(caches[0], 'removeAll'); - spyOn(caches[1], 'removeAll'); - spyOn(caches[2], 'removeAll'); - - $angularCacheFactory.clearAll(); - - expect(caches[0].removeAll.callCount).toEqual(1); - expect(caches[1].removeAll.callCount).toEqual(1); - expect(caches[2].removeAll.callCount).toEqual(1); - }); - it('should result in each cache being cleared.', function () { - var cacheKeys = ['cache', 'cache1', 'cache2'], - caches = []; - - caches.push($angularCacheFactory(cacheKeys[0])); - caches[0].put('item', 'value'); - caches[0].put('item2', 'value2'); - caches.push($angularCacheFactory(cacheKeys[1])); - caches[1].put('item', 'value'); - caches[1].put('item2', 'value2'); - caches.push($angularCacheFactory(cacheKeys[2])); - caches[2].put('item', 'value'); - caches[2].put('item2', 'value2'); - - $angularCacheFactory.clearAll(); - - expect(caches[0].get('item')).not.toBeDefined(); - expect(caches[1].get('item')).not.toBeDefined(); - expect(caches[2].get('item')).not.toBeDefined(); - expect(caches[0].get('item2')).not.toBeDefined(); - expect(caches[1].get('item2')).not.toBeDefined(); - expect(caches[2].get('item2')).not.toBeDefined(); - expect(caches[0].info().size).toEqual(0); - expect(caches[1].info().size).toEqual(0); - expect(caches[2].info().size).toEqual(0); - }); -}); diff --git a/test/angularCacheFactory.disableAll-test.js b/test/angularCacheFactory.disableAll-test.js deleted file mode 100644 index da7d899..0000000 --- a/test/angularCacheFactory.disableAll-test.js +++ /dev/null @@ -1,19 +0,0 @@ -describe('$angularCacheFactory.disableAll()', function () { - it('should disable all caches in $angularCacheFactory.', function () { - var cacheKeys = ['cache', 'cache1', 'cache2']; - - $angularCacheFactory(cacheKeys[0]); - $angularCacheFactory(cacheKeys[1], { disabled: true }); - $angularCacheFactory(cacheKeys[2]); - - expect($angularCacheFactory.get(cacheKeys[0]).info().disabled).toEqual(false); - expect($angularCacheFactory.get(cacheKeys[1]).info().disabled).toEqual(true); - expect($angularCacheFactory.get(cacheKeys[2]).info().disabled).toEqual(false); - - $angularCacheFactory.disableAll(); - - expect($angularCacheFactory.get(cacheKeys[0]).info().disabled).toEqual(true); - expect($angularCacheFactory.get(cacheKeys[1]).info().disabled).toEqual(true); - expect($angularCacheFactory.get(cacheKeys[2]).info().disabled).toEqual(true); - }); -}); diff --git a/test/angularCacheFactory.enableAll-test.js b/test/angularCacheFactory.enableAll-test.js deleted file mode 100644 index 833e3c0..0000000 --- a/test/angularCacheFactory.enableAll-test.js +++ /dev/null @@ -1,19 +0,0 @@ -describe('$angularCacheFactory.enableAll()', function () { - it('should enable all caches in $angularCacheFactory.', function () { - var cacheKeys = ['cache', 'cache1', 'cache2']; - - $angularCacheFactory(cacheKeys[0], { disabled: true }); - $angularCacheFactory(cacheKeys[1]); - $angularCacheFactory(cacheKeys[2], { disabled: true }); - - expect($angularCacheFactory.get(cacheKeys[0]).info().disabled).toEqual(true); - expect($angularCacheFactory.get(cacheKeys[1]).info().disabled).toEqual(false); - expect($angularCacheFactory.get(cacheKeys[2]).info().disabled).toEqual(true); - - $angularCacheFactory.enableAll(); - - expect($angularCacheFactory.get(cacheKeys[0]).info().disabled).toEqual(false); - expect($angularCacheFactory.get(cacheKeys[1]).info().disabled).toEqual(false); - expect($angularCacheFactory.get(cacheKeys[2]).info().disabled).toEqual(false); - }); -}); diff --git a/test/angularCacheFactory.get-test.js b/test/angularCacheFactory.get-test.js deleted file mode 100644 index b89d0ad..0000000 --- a/test/angularCacheFactory.get-test.js +++ /dev/null @@ -1,24 +0,0 @@ -describe('$angularCacheFactory.get(cacheId)', function () { - it('should throw an exception if "cacheId" is not a string.', function () { - for (var i = 0; i < TYPES_EXCEPT_STRING.length; i++) { - try { - $angularCacheFactory.get(TYPES_EXCEPT_STRING[i]); - fail(); - } catch (err) { - expect(err.message).toEqual('$angularCacheFactory.get(cacheId): cacheId: must be a string!'); - continue; - } - fail(); - } - }); - it('should return "undefined" if the cache does not exist.', function () { - expect($angularCacheFactory.get('someNonExistentCache')).toEqual(undefined); - }); - it('should return the correct cache with the specified cacheId.', function () { - var cache = $angularCacheFactory('cache'), - cache2 = $angularCacheFactory('cache2'); - expect($angularCacheFactory.get('cache')).toEqual(cache); - expect($angularCacheFactory.get('cache2')).toEqual(cache2); - expect(cache).not.toEqual(cache2); - }); -}); \ No newline at end of file diff --git a/test/angularCacheFactory.info-test.js b/test/angularCacheFactory.info-test.js deleted file mode 100644 index 9042f64..0000000 --- a/test/angularCacheFactory.info-test.js +++ /dev/null @@ -1,45 +0,0 @@ -describe('$angularCacheFactory.info()', function () { - it('should return the correct info for $angularCacheFactory.', function () { - var options = { - capacity: Math.floor((Math.random() * 100000) + 1), - maxAge: Math.floor((Math.random() * 100000) + 1), - cacheFlushInterval: Math.floor((Math.random() * 100000) + 1) - }, - caches = []; - - caches.push($angularCacheFactory('cache')); - caches.push($angularCacheFactory('cache2', { - maxAge: options.maxAge - })); - caches.push($angularCacheFactory('cache3', { - capacity: options.capacity, - cacheFlushInterval: options.cacheFlushInterval - })); - var info = $angularCacheFactory.info(); - expect(info.size).toEqual(3); - - expect(info.cacheDefaults.capacity).toEqual(CACHE_DEFAULTS.capacity); - expect(info.cacheDefaults.maxAge).toEqual(CACHE_DEFAULTS.maxAge); - expect(info.cacheDefaults.cacheFlushInterval).toEqual(CACHE_DEFAULTS.cacheFlushInterval); - expect(info.cacheDefaults.deleteOnExpire).toEqual(CACHE_DEFAULTS.deleteOnExpire); - expect(info.cacheDefaults.onExpire).toEqual(CACHE_DEFAULTS.onExpire); - expect(info.cacheDefaults.recycleFreq).toEqual(CACHE_DEFAULTS.recycleFreq); - expect(info.cacheDefaults.verifyIntegrity).toEqual(CACHE_DEFAULTS.verifyIntegrity); - expect(info.cacheDefaults.storageMode).toEqual(CACHE_DEFAULTS.storageMode); - expect(info.cacheDefaults.storageImpl).toEqual(CACHE_DEFAULTS.storageImpl); - - expect(info.caches.cache.id).toEqual(caches[0].info().id); - expect(info.caches.cache.capacity).toEqual(caches[0].info().capacity); - expect(info.caches.cache.size).toEqual(caches[0].info().size); - - expect(info.caches.cache2.id).toEqual(caches[1].info().id); - expect(info.caches.cache2.capacity).toEqual(caches[1].info().capacity); - expect(info.caches.cache2.size).toEqual(caches[1].info().size); - expect(info.caches.cache2.maxAge).toEqual(caches[1].info().maxAge); - - expect(info.caches.cache3.id).toEqual(caches[2].info().id); - expect(info.caches.cache3.capacity).toEqual(caches[2].info().capacity); - expect(info.caches.cache3.size).toEqual(caches[2].info().size); - expect(info.caches.cache3.cacheFlushInterval).toEqual(caches[2].info().cacheFlushInterval); - }); -}); diff --git a/test/angularCacheFactory.keySet-test.js b/test/angularCacheFactory.keySet-test.js deleted file mode 100644 index 5317f31..0000000 --- a/test/angularCacheFactory.keySet-test.js +++ /dev/null @@ -1,48 +0,0 @@ -describe('$angularCacheFactory.keySet()', function () { - it('should return the set of keys of all caches in $angularCacheFactory.', function () { - var cacheKeys = ['cache', 'cache1', 'cache2']; - - $angularCacheFactory(cacheKeys[0]); - $angularCacheFactory(cacheKeys[1]); - $angularCacheFactory(cacheKeys[2]); - - var keySet = $angularCacheFactory.keySet(); - - expect(keySet.hasOwnProperty(cacheKeys[0])).toEqual(true); - expect(keySet.hasOwnProperty(cacheKeys[1])).toEqual(true); - expect(keySet.hasOwnProperty(cacheKeys[2])).toEqual(true); - - expect(keySet[cacheKeys[0]]).toEqual(cacheKeys[0]); - expect(keySet[cacheKeys[1]]).toEqual(cacheKeys[1]); - expect(keySet[cacheKeys[2]]).toEqual(cacheKeys[2]); - - $angularCacheFactory.get(cacheKeys[0]).destroy(); - keySet = $angularCacheFactory.keySet(); - expect(keySet.hasOwnProperty(cacheKeys[0])).toEqual(false); - expect(keySet.hasOwnProperty(cacheKeys[1])).toEqual(true); - expect(keySet.hasOwnProperty(cacheKeys[2])).toEqual(true); - expect(keySet[cacheKeys[0]]).not.toBeDefined(); - expect(keySet[cacheKeys[1]]).toEqual(cacheKeys[1]); - expect(keySet[cacheKeys[2]]).toEqual(cacheKeys[2]); - - $angularCacheFactory.get(cacheKeys[1]).destroy(); - keySet = $angularCacheFactory.keySet(); - expect(keySet.hasOwnProperty(cacheKeys[0])).toEqual(false); - expect(keySet.hasOwnProperty(cacheKeys[1])).toEqual(false); - expect(keySet.hasOwnProperty(cacheKeys[2])).toEqual(true); - expect(keySet[cacheKeys[0]]).not.toBeDefined(); - expect(keySet[cacheKeys[1]]).not.toBeDefined(); - expect(keySet[cacheKeys[2]]).toEqual(cacheKeys[2]); - - $angularCacheFactory.get(cacheKeys[2]).destroy(); - - keySet = $angularCacheFactory.keySet(); - - expect(keySet.hasOwnProperty(cacheKeys[0])).toEqual(false); - expect(keySet.hasOwnProperty(cacheKeys[1])).toEqual(false); - expect(keySet.hasOwnProperty(cacheKeys[2])).toEqual(false); - expect(keySet[cacheKeys[0]]).not.toBeDefined(); - expect(keySet[cacheKeys[1]]).not.toBeDefined(); - expect(keySet[cacheKeys[2]]).not.toBeDefined(); - }); -}); diff --git a/test/angularCacheFactory.keys-test.js b/test/angularCacheFactory.keys-test.js deleted file mode 100644 index 007b3a9..0000000 --- a/test/angularCacheFactory.keys-test.js +++ /dev/null @@ -1,32 +0,0 @@ -describe('$angularCacheFactory.keys()', function () { - it('should return the array of keys of all caches in $angularCacheFactory.', function () { - var cacheKeys = ['cache', 'cache1', 'cache2']; - - $angularCacheFactory(cacheKeys[0]); - $angularCacheFactory(cacheKeys[1]); - $angularCacheFactory(cacheKeys[2]); - - var keys = $angularCacheFactory.keys(); - expect(keys.length).toEqual(3); - expect(keys[0]).toEqual(cacheKeys[0]); - expect(keys[1]).toEqual(cacheKeys[1]); - expect(keys[2]).toEqual(cacheKeys[2]); - - $angularCacheFactory.get(cacheKeys[0]).destroy(); - keys = $angularCacheFactory.keys(); - expect(keys.length).toEqual(2); - expect(keys.indexOf(cacheKeys[1])).not.toEqual(-1); - expect(keys.indexOf(cacheKeys[2])).not.toEqual(-1); - - $angularCacheFactory.get(cacheKeys[1]).destroy(); - keys = $angularCacheFactory.keys(); - expect(keys.length).toEqual(1); - expect(keys.indexOf(cacheKeys[2])).not.toEqual(-1); - - $angularCacheFactory.get(cacheKeys[2]).destroy(); - - keys = $angularCacheFactory.keys(); - - expect(keys.length).toEqual(0); - }); -}); diff --git a/test/angularCacheFactory.removeAll-test.js b/test/angularCacheFactory.removeAll-test.js deleted file mode 100644 index 8e7e211..0000000 --- a/test/angularCacheFactory.removeAll-test.js +++ /dev/null @@ -1,33 +0,0 @@ -describe('$angularCacheFactory.removeAll()', function () { - it('should call "destroy()" on all caches currently owned by the factory.', function () { - var cacheKeys = ['cache', 'cache1', 'cache2'], - caches = []; - - caches.push($angularCacheFactory(cacheKeys[0])); - caches.push($angularCacheFactory(cacheKeys[1])); - caches.push($angularCacheFactory(cacheKeys[2])); - - spyOn(caches[0], 'destroy'); - spyOn(caches[1], 'destroy'); - spyOn(caches[2], 'destroy'); - $angularCacheFactory.removeAll(); - - expect(caches[0].destroy.callCount).toEqual(1); - expect(caches[1].destroy.callCount).toEqual(1); - expect(caches[2].destroy.callCount).toEqual(1); - }); - it('should result in all caches being removed from $angularCacheFactory.', function () { - var cacheKeys = ['cache', 'cache1', 'cache2'], - caches = []; - - caches.push($angularCacheFactory(cacheKeys[0])); - caches.push($angularCacheFactory(cacheKeys[1])); - caches.push($angularCacheFactory(cacheKeys[2])); - - $angularCacheFactory.removeAll(); - - expect($angularCacheFactory.get(cacheKeys[0])).not.toBeDefined(); - expect($angularCacheFactory.get(cacheKeys[1])).not.toBeDefined(); - expect($angularCacheFactory.get(cacheKeys[2])).not.toBeDefined(); - }); -}); diff --git a/test/angularCacheFactoryProvider.setCacheDefaults-test.js b/test/angularCacheFactoryProvider.setCacheDefaults-test.js deleted file mode 100644 index c9f5f6a..0000000 --- a/test/angularCacheFactoryProvider.setCacheDefaults-test.js +++ /dev/null @@ -1,350 +0,0 @@ -describe('$angularCacheFactoryProvider.setCacheDefaults(options)', function () { - it('should have the correct defaults.', function () { - expect($angularCacheFactory.info().cacheDefaults).toEqual({ - capacity: CACHE_DEFAULTS.capacity, - maxAge: CACHE_DEFAULTS.maxAge, - cacheFlushInterval: CACHE_DEFAULTS.cacheFlushInterval, - deleteOnExpire: CACHE_DEFAULTS.deleteOnExpire, - onExpire: CACHE_DEFAULTS.onExpire, - recycleFreq: CACHE_DEFAULTS.recycleFreq, - storageMode: CACHE_DEFAULTS.storageMode, - storageImpl: CACHE_DEFAULTS.storageImpl, - verifyIntegrity: CACHE_DEFAULTS.verifyIntegrity, - disabled: CACHE_DEFAULTS.disabled, - readOnGet: CACHE_DEFAULTS.readOnGet - }); - var cache = $angularCacheFactory('cache'); - expect(cache).toBeDefined(); - expect(cache.info().id).toEqual('cache'); - expect(cache.info().capacity).toEqual(CACHE_DEFAULTS.capacity); - expect(cache.info().maxAge).toEqual(CACHE_DEFAULTS.maxAge); - expect(cache.info().cacheFlushInterval).toEqual(CACHE_DEFAULTS.cacheFlushInterval); - expect(cache.info().deleteOnExpire).toEqual(CACHE_DEFAULTS.deleteOnExpire); - expect(cache.info().onExpire).toEqual(CACHE_DEFAULTS.onExpire); - expect(cache.info().recycleFreq).toEqual(CACHE_DEFAULTS.recycleFreq); - expect(cache.info().storageMode).toEqual(CACHE_DEFAULTS.storageMode); - expect(cache.info().storageImpl).toEqual(CACHE_DEFAULTS.storageImpl); - expect(cache.info().verifyIntegrity).toEqual(CACHE_DEFAULTS.verifyIntegrity); - expect(cache.info().readOnGet).toEqual(CACHE_DEFAULTS.readOnGet); - expect(cache.info().disabled).toEqual(CACHE_DEFAULTS.disabled); - cache.destroy(); - }); - it('should set the default options.', function () { - var options = { - capacity: Math.floor((Math.random() * 100000) + 1), - maxAge: Math.floor((Math.random() * 100000) + 1), - cacheFlushInterval: Math.floor((Math.random() * 100000) + 1), - deleteOnExpire: 'aggressive', - storageMode: 'localStorage', - localStorageImpl: { - setItem: function () { - }, - getItem: function () { - }, - removeItem: function () { - } - }, - verifyIntegrity: false, - recycleFreq: 2000, - disabled: true, - onExpire: function () { - } - }; - $angularCacheFactoryProvider.setCacheDefaults(options); - var cache = $angularCacheFactory('cache'); - expect(cache).toBeDefined(); - expect(cache.info().id).toEqual('cache'); - expect(cache.info().capacity).toEqual(options.capacity); - expect(cache.info().maxAge).toEqual(options.maxAge); - expect(cache.info().cacheFlushInterval).toEqual(options.cacheFlushInterval); - expect(cache.info().deleteOnExpire).toEqual(options.deleteOnExpire); - expect(cache.info().storageMode).toEqual(options.storageMode); - expect(cache.info().localStorageImpl).not.toBeDefined(); // We don't expose this to the user - expect(cache.info().onExpire).toEqual(options.onExpire); - expect(cache.info().disabled).toEqual(options.disabled); - cache.destroy(); - expect($angularCacheFactory.get('cache')).not.toBeDefined(); - }); - it('should throw an exception if "capacity" is not a number or is less than zero.', function () { - try { - $angularCacheFactoryProvider.setCacheDefaults({ capacity: Math.floor((Math.random() * 100000) + 1) * -1 }); - fail(); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('$angularCacheFactoryProvider.setCacheDefaults(options): capacity: must be greater than zero!'); - for (var i = 0; i < TYPES_EXCEPT_NUMBER.length; i++) { - try { - $angularCacheFactoryProvider.setCacheDefaults({ capacity: TYPES_EXCEPT_NUMBER[i] }); - fail(); - } catch (err) { - expect(err.message).toEqual('$angularCacheFactoryProvider.setCacheDefaults(options): capacity: must be a number!'); - continue; - } - fail(); - } - }); - it('should validate maxAge.', function () { - var maxAge = Math.floor((Math.random() * 100000) + 1) * -1; - try { - $angularCacheFactoryProvider.setCacheDefaults({ maxAge: maxAge }); - fail(); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('$angularCacheFactoryProvider.setCacheDefaults(options): maxAge: must be greater than zero!'); - for (var i = 0; i < TYPES_EXCEPT_NUMBER.length; i++) { - try { - $angularCacheFactoryProvider.setCacheDefaults({ maxAge: TYPES_EXCEPT_NUMBER[i] }); - if (TYPES_EXCEPT_NUMBER[i] !== null) { - fail(); - } - } catch (err) { - expect(err.message).toEqual('$angularCacheFactoryProvider.setCacheDefaults(options): maxAge: must be a number!'); - continue; - } - if (TYPES_EXCEPT_NUMBER[i] !== null) { - fail(); - } - } - }); - it('should validate cacheFlushInterval.', function () { - var cacheFlushInterval = Math.floor((Math.random() * 100000) + 1) * -1; - try { - $angularCacheFactoryProvider.setCacheDefaults({ cacheFlushInterval: cacheFlushInterval }); - fail(); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('$angularCacheFactoryProvider.setCacheDefaults(options): cacheFlushInterval: must be greater than zero!'); - for (var i = 0; i < TYPES_EXCEPT_NUMBER.length; i++) { - try { - $angularCacheFactoryProvider.setCacheDefaults({ cacheFlushInterval: TYPES_EXCEPT_NUMBER[i] }); - if (TYPES_EXCEPT_NUMBER[i] !== null) { - fail(); - } - } catch (err) { - expect(err.message).toEqual('$angularCacheFactoryProvider.setCacheDefaults(options): cacheFlushInterval: must be a number!'); - continue; - } - if (TYPES_EXCEPT_NUMBER[i] !== null) { - fail(); - } - } - }); - it('should validate recycleFreq.', function () { - var recycleFreq = Math.floor((Math.random() * 100000) + 1) * -1; - try { - $angularCacheFactoryProvider.setCacheDefaults({ recycleFreq: recycleFreq }); - fail(); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('$angularCacheFactoryProvider.setCacheDefaults(options): recycleFreq: must be greater than zero!'); - for (var i = 0; i < TYPES_EXCEPT_NUMBER.length; i++) { - try { - $angularCacheFactoryProvider.setCacheDefaults({ recycleFreq: TYPES_EXCEPT_NUMBER[i] }); - if (TYPES_EXCEPT_NUMBER[i] !== null) { - fail(); - } - } catch (err) { - expect(err.message).toEqual('$angularCacheFactoryProvider.setCacheDefaults(options): recycleFreq: must be a number!'); - continue; - } - if (TYPES_EXCEPT_NUMBER[i] !== null) { - fail(); - } - } - }); - it('should validate onExpire.', function () { - var onExpire = 234; - try { - $angularCacheFactoryProvider.setCacheDefaults({ onExpire: onExpire }); - expect('should not reach this!').toEqual(false); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('$angularCacheFactoryProvider.setCacheDefaults(options): onExpire: must be a function!'); - for (var i = 0; i < TYPES_EXCEPT_FUNCTION.length; i++) { - try { - $angularCacheFactoryProvider.setCacheDefaults({ onExpire: TYPES_EXCEPT_FUNCTION[i] }); - if (TYPES_EXCEPT_FUNCTION[i] !== null) { - fail(); - } - } catch (err) { - expect(err.message).toEqual('$angularCacheFactoryProvider.setCacheDefaults(options): onExpire: must be a function!'); - continue; - } - if (TYPES_EXCEPT_FUNCTION[i] !== null) { - fail(); - } - } - }); - it('should validate deleteOnExpire.', function () { - var deleteOnExpire = 'fail'; - try { - $angularCacheFactoryProvider.setCacheDefaults({ deleteOnExpire: deleteOnExpire }); - expect('should not reach this!').toEqual(false); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('$angularCacheFactoryProvider.setCacheDefaults(options): deleteOnExpire: accepted values are "none", "passive" or "aggressive"!'); - for (var i = 0; i < TYPES_EXCEPT_STRING.length; i++) { - try { - $angularCacheFactoryProvider.setCacheDefaults({ deleteOnExpire: TYPES_EXCEPT_STRING[i] }); - fail(); - } catch (err) { - expect(err.message).toEqual('$angularCacheFactoryProvider.setCacheDefaults(options): deleteOnExpire: must be a string!'); - continue; - } - fail(); - } - }); - it('should validate storageMode.', function () { - var storageMode = 'fail'; - try { - $angularCacheFactoryProvider.setCacheDefaults({ storageMode: storageMode }); - expect('should not reach this!').toEqual(false); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('$angularCacheFactoryProvider.setCacheDefaults(options): storageMode: accepted values are "none", "localStorage" or "sessionStorage"!'); - for (var i = 0; i < TYPES_EXCEPT_STRING.length; i++) { - try { - $angularCacheFactoryProvider.setCacheDefaults({ storageMode: TYPES_EXCEPT_STRING[i] }); - fail(); - } catch (err) { - expect(err.message).toEqual('$angularCacheFactoryProvider.setCacheDefaults(options): storageMode: must be a string!'); - continue; - } - fail(); - } - }); - it('should validate storageImpl.', function () { - var storageImpl = 'fail'; - try { - $angularCacheFactoryProvider.setCacheDefaults({ storageMode: 'localStorage', storageImpl: storageImpl }); - expect('should not reach this!').toEqual(false); - } catch (err) { - var msg = err.message; - } - expect(msg).toEqual('$angularCacheFactoryProvider.setCacheDefaults(options): storageImpl: must be an object!'); - for (var i = 0; i < TYPES_EXCEPT_OBJECT.length; i++) { - try { - $angularCacheFactoryProvider.setCacheDefaults({ storageMode: 'localStorage', storageImpl: TYPES_EXCEPT_OBJECT[i] }); - if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { - fail(TYPES_EXCEPT_OBJECT[i]); - } - } catch (err) { - expect(err.message.length).not.toEqual(0); - continue; - } - if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { - fail(TYPES_EXCEPT_OBJECT[i]); - } - } - try { - $angularCacheFactoryProvider.setCacheDefaults({ - storageMode: 'localStorage', - storageImpl: { - getItem: function () { - }, - removeItem: function () { - } - } - }); - fail(); - } catch (err) { - expect(err.message.length).not.toEqual(0); - } - try { - $angularCacheFactoryProvider.setCacheDefaults({ - storageMode: 'localStorage', - storageImpl: { - setItem: function () { - }, - removeItem: function () { - } - } - }); - fail(); - } catch (err) { - expect(err.message.length).not.toEqual(0); - } - try { - $angularCacheFactoryProvider.setCacheDefaults({ - storageMode: 'localStorage', - storageImpl: { - getItem: function () { - }, - setItem: function () { - } - } - }); - fail(); - } catch (err) { - expect(err.message.length).not.toEqual(0); - } - try { - $angularCacheFactoryProvider.setCacheDefaults({ - storageMode: 'localStorage', - storageImpl: { - getItem: 'should not be a string', - setItem: function () { - }, - removeItem: function () { - } - } - }); - fail(); - } catch (err) { - expect(err.message.length).not.toEqual(0); - } - try { - $angularCacheFactoryProvider.setCacheDefaults({ - storageMode: 'localStorage', - storageImpl: { - getItem: function () { - }, - setItem: 'should not be a string', - removeItem: function () { - } - } - }); - fail(); - } catch (err) { - expect(err.message.length).not.toEqual(0); - } - try { - $angularCacheFactoryProvider.setCacheDefaults({ - storageMode: 'localStorage', - storageImpl: { - setItem: function () { - }, - getItem: function () { - }, - removeItem: 'should not be a string' - } - }); - fail(); - } catch (err) { - expect(err.message.length).not.toEqual(0); - } - }); - it('should require options to be an object.', function () { - for (var i = 0; i < TYPES_EXCEPT_OBJECT.length; i++) { - try { - $angularCacheFactoryProvider.setCacheDefaults(TYPES_EXCEPT_OBJECT[i]); - if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { - fail(TYPES_EXCEPT_OBJECT[i]); - } - } catch (err) { - expect(err.message.length).not.toEqual(0); - continue; - } - if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { - fail(TYPES_EXCEPT_OBJECT[i]); - } - } - }); -}); diff --git a/test/binaryHeap-test.js b/test/binaryHeap-test.js deleted file mode 100644 index cce9cf7..0000000 --- a/test/binaryHeap-test.js +++ /dev/null @@ -1,23 +0,0 @@ -describe('BinaryHeap(weightFunc)', function () { - it('should create an empty heap with size 0.', function () { - var heap = new BinaryHeap(); - expect(heap.size()).toEqual(0); - }); - it('should throw an error if "weightFunc" is not a function.', function () { - var heap; - for (var i = 0; i < TYPES_EXCEPT_FUNCTION.length; i++) { - try { - heap = new BinaryHeap(TYPES_EXCEPT_FUNCTION[i]); - if (TYPES_EXCEPT_FUNCTION[i]) { - fail(); - } - } catch (err) { - expect(err.message).toEqual('BinaryHeap(weightFunc): weightFunc: must be a function!'); - continue; - } - if (TYPES_EXCEPT_FUNCTION[i]) { - fail(); - } - } - }); -}); \ No newline at end of file diff --git a/test/binaryHeap.peek-test.js b/test/binaryHeap.peek-test.js deleted file mode 100644 index cd6bac3..0000000 --- a/test/binaryHeap.peek-test.js +++ /dev/null @@ -1,138 +0,0 @@ -describe('BinaryHeap.peek()', function () { - it('should show the item at the front of the BinaryHeap.', function () { - var heap = new BinaryHeap(); - var objHeap = new BinaryHeap(function (x) { - return x.value; - }); - var items = [20, 4, 33, 1, 0, 34, 22, 31, 32, 5, 6, 7], - objItems = []; - for (var i = 0; i < items.length; i++) { - objItems.push({ - value: items[i] - }); - } - - expect(heap.peek()).not.toBeDefined(); - expect(objHeap.peek()).not.toBeDefined(); - - heap.push(items[0]); - objHeap.push(objItems[0]); - expect(heap.peek()).toEqual(items[0]); - expect(objHeap.peek()).toEqual(objItems[0]); - - heap.push(items[1]); - objHeap.push(objItems[1]); - expect(heap.peek()).toEqual(items[1]); - expect(objHeap.peek()).toEqual(objItems[1]); - - heap.push(items[2]); - objHeap.push(objItems[2]); - expect(heap.peek()).toEqual(items[1]); - expect(objHeap.peek()).toEqual(objItems[1]); - - heap.push(items[3]); - objHeap.push(objItems[3]); - expect(heap.peek()).toEqual(items[3]); - expect(objHeap.peek()).toEqual(objItems[3]); - - heap.push(items[4]); - objHeap.push(objItems[4]); - expect(heap.peek()).toEqual(items[4]); - expect(objHeap.peek()).toEqual(objItems[4]); - - heap.push(items[5]); - objHeap.push(objItems[5]); - expect(heap.peek()).toEqual(items[4]); - expect(objHeap.peek()).toEqual(objItems[4]); - - heap.push(items[6]); - objHeap.push(objItems[6]); - expect(heap.peek()).toEqual(items[4]); - expect(objHeap.peek()).toEqual(objItems[4]); - - heap.push(items[7]); - objHeap.push(objItems[7]); - expect(heap.peek()).toEqual(items[4]); - expect(objHeap.peek()).toEqual(objItems[4]); - - heap.push(items[8]); - objHeap.push(objItems[8]); - expect(heap.peek()).toEqual(items[4]); - expect(objHeap.peek()).toEqual(objItems[4]); - - heap.push(items[9]); - objHeap.push(objItems[9]); - expect(heap.peek()).toEqual(items[4]); - expect(objHeap.peek()).toEqual(objItems[4]); - - heap.push(items[10]); - objHeap.push(objItems[10]); - expect(heap.peek()).toEqual(items[4]); - expect(objHeap.peek()).toEqual(objItems[4]); - - heap.push(items[11]); - objHeap.push(objItems[11]); - expect(heap.peek()).toEqual(items[4]); - expect(objHeap.peek()).toEqual(objItems[4]); - - expect(heap.pop()).toEqual(0); - expect(objHeap.pop()).toEqual({ value: 0 }); - expect(heap.peek()).toEqual(1); - expect(objHeap.peek()).toEqual({ value: 1 }); - - expect(heap.pop()).toEqual(1); - expect(objHeap.pop()).toEqual({ value: 1 }); - expect(heap.peek()).toEqual(4); - expect(objHeap.peek()).toEqual({ value: 4 }); - - expect(heap.pop()).toEqual(4); - expect(objHeap.pop()).toEqual({ value: 4 }); - expect(heap.peek()).toEqual(5); - expect(objHeap.peek()).toEqual({ value: 5 }); - - expect(heap.pop()).toEqual(5); - expect(objHeap.pop()).toEqual({ value: 5 }); - expect(heap.peek()).toEqual(6); - expect(objHeap.peek()).toEqual({ value: 6 }); - - expect(heap.pop()).toEqual(6); - expect(objHeap.pop()).toEqual({ value: 6 }); - expect(heap.peek()).toEqual(7); - expect(objHeap.peek()).toEqual({ value: 7 }); - - expect(heap.pop()).toEqual(7); - expect(objHeap.pop()).toEqual({ value: 7 }); - expect(heap.peek()).toEqual(20); - expect(objHeap.peek()).toEqual({ value: 20 }); - - expect(heap.pop()).toEqual(20); - expect(objHeap.pop()).toEqual({ value: 20 }); - expect(heap.peek()).toEqual(22); - expect(objHeap.peek()).toEqual({ value: 22 }); - - expect(heap.pop()).toEqual(22); - expect(objHeap.pop()).toEqual({ value: 22 }); - expect(heap.peek()).toEqual(31); - expect(objHeap.peek()).toEqual({ value: 31 }); - - expect(heap.pop()).toEqual(31); - expect(objHeap.pop()).toEqual({ value: 31 }); - expect(heap.peek()).toEqual(32); - expect(objHeap.peek()).toEqual({ value: 32 }); - - expect(heap.pop()).toEqual(32); - expect(objHeap.pop()).toEqual({ value: 32 }); - expect(heap.peek()).toEqual(33); - expect(objHeap.peek()).toEqual({ value: 33 }); - - expect(heap.pop()).toEqual(33); - expect(objHeap.pop()).toEqual({ value: 33 }); - expect(heap.peek()).toEqual(34); - expect(objHeap.peek()).toEqual({ value: 34 }); - - expect(heap.pop()).toEqual(34); - expect(objHeap.pop()).toEqual({ value: 34 }); - expect(heap.peek()).not.toBeDefined(); - expect(objHeap.peek()).not.toBeDefined(); - }); -}); \ No newline at end of file diff --git a/test/binaryHeap.pop-test.js b/test/binaryHeap.pop-test.js deleted file mode 100644 index 93342cd..0000000 --- a/test/binaryHeap.pop-test.js +++ /dev/null @@ -1,78 +0,0 @@ -describe('BinaryHeap.pop()', function () { - it('should pop the item off of the front of the BinaryHeap.', function () { - var heap = new BinaryHeap(); - var objHeap = new BinaryHeap(function (x) { - return x.value; - }); - var items = [20, 4, 33, 1, 0, 34, 22, 31, 32, 5, 6, 7]; - for (var i = 0; i < items.length; i++) { - heap.push(items[i]); - objHeap.push({ - value: items[i] - }); - } - - expect(heap.size()).toEqual(12); - expect(objHeap.size()).toEqual(12); - - expect(heap.pop()).toEqual(0); - expect(objHeap.pop()).toEqual({ value: 0 }); - expect(heap.size()).toEqual(11); - expect(objHeap.size()).toEqual(11); - - expect(heap.pop()).toEqual(1); - expect(objHeap.pop()).toEqual({ value: 1 }); - expect(heap.size()).toEqual(10); - expect(objHeap.size()).toEqual(10); - - expect(heap.pop()).toEqual(4); - expect(objHeap.pop()).toEqual({ value: 4 }); - expect(heap.size()).toEqual(9); - expect(objHeap.size()).toEqual(9); - - expect(heap.pop()).toEqual(5); - expect(objHeap.pop()).toEqual({ value: 5 }); - expect(heap.size()).toEqual(8); - expect(objHeap.size()).toEqual(8); - - expect(heap.pop()).toEqual(6); - expect(objHeap.pop()).toEqual({ value: 6 }); - expect(heap.size()).toEqual(7); - expect(objHeap.size()).toEqual(7); - - expect(heap.pop()).toEqual(7); - expect(objHeap.pop()).toEqual({ value: 7 }); - expect(heap.size()).toEqual(6); - expect(objHeap.size()).toEqual(6); - - expect(heap.pop()).toEqual(20); - expect(objHeap.pop()).toEqual({ value: 20 }); - expect(heap.size()).toEqual(5); - expect(objHeap.size()).toEqual(5); - - expect(heap.pop()).toEqual(22); - expect(objHeap.pop()).toEqual({ value: 22 }); - expect(heap.size()).toEqual(4); - expect(objHeap.size()).toEqual(4); - - expect(heap.pop()).toEqual(31); - expect(objHeap.pop()).toEqual({ value: 31 }); - expect(heap.size()).toEqual(3); - expect(objHeap.size()).toEqual(3); - - expect(heap.pop()).toEqual(32); - expect(objHeap.pop()).toEqual({ value: 32 }); - expect(heap.size()).toEqual(2); - expect(objHeap.size()).toEqual(2); - - expect(heap.pop()).toEqual(33); - expect(objHeap.pop()).toEqual({ value: 33 }); - expect(heap.size()).toEqual(1); - expect(objHeap.size()).toEqual(1); - - expect(heap.pop()).toEqual(34); - expect(objHeap.pop()).toEqual({ value: 34 }); - expect(heap.size()).toEqual(0); - expect(objHeap.size()).toEqual(0); - }); -}); \ No newline at end of file diff --git a/test/binaryHeap.push-test.js b/test/binaryHeap.push-test.js deleted file mode 100644 index 6fbe2da..0000000 --- a/test/binaryHeap.push-test.js +++ /dev/null @@ -1,99 +0,0 @@ -describe('BinaryHeap.push(node)', function () { - it('should push items to the front of the BinaryHeap.', function () { - var heap = new BinaryHeap(); - var objHeap = new BinaryHeap(function (x) { - return x.value; - }); - var items = [20, 4, 33, 1, 0, 34, 22, 31, 32, 5, 6, 7], - objItems = []; - for (var i = 0; i < items.length; i++) { - objItems.push({ - value: items[i] - }); - } - - heap.push(items[0]); - objHeap.push(objItems[0]); - expect(heap.size()).toEqual(1); - expect(objHeap.size()).toEqual(1); - expect(heap.peek()).toEqual(items[0]); - expect(objHeap.peek()).toEqual(objItems[0]); - - heap.push(items[1]); - objHeap.push(objItems[1]); - expect(heap.size()).toEqual(2); - expect(objHeap.size()).toEqual(2); - expect(heap.peek()).toEqual(items[1]); - expect(objHeap.peek()).toEqual(objItems[1]); - - heap.push(items[2]); - objHeap.push(objItems[2]); - expect(heap.size()).toEqual(3); - expect(objHeap.size()).toEqual(3); - expect(heap.peek()).toEqual(items[1]); - expect(objHeap.peek()).toEqual(objItems[1]); - - heap.push(items[3]); - objHeap.push(objItems[3]); - expect(heap.size()).toEqual(4); - expect(objHeap.size()).toEqual(4); - expect(heap.peek()).toEqual(items[3]); - expect(objHeap.peek()).toEqual(objItems[3]); - - heap.push(items[4]); - objHeap.push(objItems[4]); - expect(heap.size()).toEqual(5); - expect(objHeap.size()).toEqual(5); - expect(heap.peek()).toEqual(items[4]); - expect(objHeap.peek()).toEqual(objItems[4]); - - heap.push(items[5]); - objHeap.push(objItems[5]); - expect(heap.size()).toEqual(6); - expect(objHeap.size()).toEqual(6); - expect(heap.peek()).toEqual(items[4]); - expect(objHeap.peek()).toEqual(objItems[4]); - - heap.push(items[6]); - objHeap.push(objItems[6]); - expect(heap.size()).toEqual(7); - expect(objHeap.size()).toEqual(7); - expect(heap.peek()).toEqual(items[4]); - expect(objHeap.peek()).toEqual(objItems[4]); - - heap.push(items[7]); - objHeap.push(objItems[7]); - expect(heap.size()).toEqual(8); - expect(objHeap.size()).toEqual(8); - expect(heap.peek()).toEqual(items[4]); - expect(objHeap.peek()).toEqual(objItems[4]); - - heap.push(items[8]); - objHeap.push(objItems[8]); - expect(heap.size()).toEqual(9); - expect(objHeap.size()).toEqual(9); - expect(heap.peek()).toEqual(items[4]); - expect(objHeap.peek()).toEqual(objItems[4]); - - heap.push(items[9]); - objHeap.push(objItems[9]); - expect(heap.size()).toEqual(10); - expect(objHeap.size()).toEqual(10); - expect(heap.peek()).toEqual(items[4]); - expect(objHeap.peek()).toEqual(objItems[4]); - - heap.push(items[10]); - objHeap.push(objItems[10]); - expect(heap.size()).toEqual(11); - expect(objHeap.size()).toEqual(11); - expect(heap.peek()).toEqual(items[4]); - expect(objHeap.peek()).toEqual(objItems[4]); - - heap.push(items[11]); - objHeap.push(objItems[11]); - expect(heap.size()).toEqual(12); - expect(objHeap.size()).toEqual(12); - expect(heap.peek()).toEqual(items[4]); - expect(objHeap.peek()).toEqual(objItems[4]); - }); -}); \ No newline at end of file diff --git a/test/binaryHeap.remove-test.js b/test/binaryHeap.remove-test.js deleted file mode 100644 index b4a2dd7..0000000 --- a/test/binaryHeap.remove-test.js +++ /dev/null @@ -1,75 +0,0 @@ -describe('BinaryHeap.remove(node)', function () { - it('should remove the item from the heap.', function () { - var heap = new BinaryHeap(); - var objHeap = new BinaryHeap(function (x) { - return x.value; - }); - var items = [20, 4, 33, 1, 0, 34, 22, 31, 32, 5, 6, 7]; - for (var i = 0; i < items.length; i++) { - heap.push(items[i]); - objHeap.push({ - value: items[i] - }); - } - - expect(heap.remove(0)).toEqual(0); - expect(objHeap.remove({ value: 0 })).toEqual({ value: 0 }); - expect(heap.peek()).toEqual(1); - expect(objHeap.peek()).toEqual({ value: 1 }); - - expect(heap.remove(1)).toEqual(1); - expect(objHeap.remove({ value: 1 })).toEqual({ value: 1 }); - expect(heap.peek()).toEqual(4); - expect(objHeap.peek()).toEqual({ value: 4 }); - - expect(heap.remove(4)).toEqual(4); - expect(objHeap.remove({ value: 4 })).toEqual({ value: 4 }); - expect(heap.peek()).toEqual(5); - expect(objHeap.peek()).toEqual({ value: 5 }); - - expect(heap.remove(5)).toEqual(5); - expect(objHeap.remove({ value: 5 })).toEqual({ value: 5 }); - expect(heap.peek()).toEqual(6); - expect(objHeap.peek()).toEqual({ value: 6 }); - - expect(heap.remove(6)).toEqual(6); - expect(objHeap.remove({ value: 6 })).toEqual({ value: 6 }); - expect(heap.peek()).toEqual(7); - expect(objHeap.peek()).toEqual({ value: 7 }); - - expect(heap.remove(7)).toEqual(7); - expect(objHeap.remove({ value: 7 })).toEqual({ value: 7 }); - expect(heap.peek()).toEqual(20); - expect(objHeap.peek()).toEqual({ value: 20 }); - - expect(heap.remove(20)).toEqual(20); - expect(objHeap.remove({ value: 20 })).toEqual({ value: 20 }); - expect(heap.peek()).toEqual(22); - expect(objHeap.peek()).toEqual({ value: 22 }); - - expect(heap.remove(22)).toEqual(22); - expect(objHeap.remove({ value: 22 })).toEqual({ value: 22 }); - expect(heap.peek()).toEqual(31); - expect(objHeap.peek()).toEqual({ value: 31 }); - - expect(heap.remove(31)).toEqual(31); - expect(objHeap.remove({ value: 31 })).toEqual({ value: 31 }); - expect(heap.peek()).toEqual(32); - expect(objHeap.peek()).toEqual({ value: 32 }); - - expect(heap.remove(32)).toEqual(32); - expect(objHeap.remove({ value: 32 })).toEqual({ value: 32 }); - expect(heap.peek()).toEqual(33); - expect(objHeap.peek()).toEqual({ value: 33 }); - - expect(heap.remove(33)).toEqual(33); - expect(objHeap.remove({ value: 33 })).toEqual({ value: 33 }); - expect(heap.peek()).toEqual(34); - expect(objHeap.peek()).toEqual({ value: 34 }); - - expect(heap.remove(34)).toEqual(34); - expect(objHeap.remove({ value: 34 })).toEqual({ value: 34 }); - expect(heap.peek()).not.toBeDefined(); - expect(objHeap.peek()).not.toBeDefined(); - }); -}); \ No newline at end of file diff --git a/test/binaryHeap.removeAll-test.js b/test/binaryHeap.removeAll-test.js deleted file mode 100644 index 927a977..0000000 --- a/test/binaryHeap.removeAll-test.js +++ /dev/null @@ -1,27 +0,0 @@ -describe('BinaryHeap.removeAll()', function () { - it('should remove all items from the heap.', function () { - var heap = new BinaryHeap(); - var objHeap = new BinaryHeap(function (x) { - return x.value; - }); - var items = [20, 4, 33, 1, 0, 34, 22, 31, 32, 5, 6, 7]; - for (var i = 0; i < items.length; i++) { - heap.push(items[i]); - objHeap.push({ - value: items[i] - }); - } - - expect(heap.size()).toEqual(12); - expect(objHeap.size()).toEqual(12); - - heap.removeAll(); - objHeap.removeAll(); - - expect(heap.size()).toEqual(0); - expect(objHeap.size()).toEqual(0); - - expect(heap.peek()).not.toBeDefined(); - expect(objHeap.peek()).not.toBeDefined(); - }); -}); \ No newline at end of file diff --git a/test/unit/DSBinaryHeap/index.peek.test.js b/test/unit/DSBinaryHeap/index.peek.test.js new file mode 100644 index 0000000..3a5822d --- /dev/null +++ b/test/unit/DSBinaryHeap/index.peek.test.js @@ -0,0 +1,138 @@ +describe('DSBinaryHeap.peek()', function () { + it('should show the item at the front of the DSBinaryHeap.', function () { + var heap = new DSBinaryHeap(); + var objHeap = new DSBinaryHeap(function (x) { + return x.value; + }); + var items = [20, 4, 33, 1, 0, 34, 22, 31, 32, 5, 6, 7], + objItems = []; + for (var i = 0; i < items.length; i++) { + objItems.push({ + value: items[i] + }); + } + + assert.isUndefined(heap.peek()); + assert.isUndefined(objHeap.peek()); + + heap.push(items[0]); + objHeap.push(objItems[0]); + assert.deepEqual(heap.peek(), items[0]); + assert.deepEqual(objHeap.peek(), objItems[0]); + + heap.push(items[1]); + objHeap.push(objItems[1]); + assert.deepEqual(heap.peek(), items[1]); + assert.deepEqual(objHeap.peek(), objItems[1]); + + heap.push(items[2]); + objHeap.push(objItems[2]); + assert.deepEqual(heap.peek(), items[1]); + assert.deepEqual(objHeap.peek(), objItems[1]); + + heap.push(items[3]); + objHeap.push(objItems[3]); + assert.deepEqual(heap.peek(), items[3]); + assert.deepEqual(objHeap.peek(), objItems[3]); + + heap.push(items[4]); + objHeap.push(objItems[4]); + assert.deepEqual(heap.peek(), items[4]); + assert.deepEqual(objHeap.peek(), objItems[4]); + + heap.push(items[5]); + objHeap.push(objItems[5]); + assert.deepEqual(heap.peek(), items[4]); + assert.deepEqual(objHeap.peek(), objItems[4]); + + heap.push(items[6]); + objHeap.push(objItems[6]); + assert.deepEqual(heap.peek(), items[4]); + assert.deepEqual(objHeap.peek(), objItems[4]); + + heap.push(items[7]); + objHeap.push(objItems[7]); + assert.deepEqual(heap.peek(), items[4]); + assert.deepEqual(objHeap.peek(), objItems[4]); + + heap.push(items[8]); + objHeap.push(objItems[8]); + assert.deepEqual(heap.peek(), items[4]); + assert.deepEqual(objHeap.peek(), objItems[4]); + + heap.push(items[9]); + objHeap.push(objItems[9]); + assert.deepEqual(heap.peek(), items[4]); + assert.deepEqual(objHeap.peek(), objItems[4]); + + heap.push(items[10]); + objHeap.push(objItems[10]); + assert.deepEqual(heap.peek(), items[4]); + assert.deepEqual(objHeap.peek(), objItems[4]); + + heap.push(items[11]); + objHeap.push(objItems[11]); + assert.deepEqual(heap.peek(), items[4]); + assert.deepEqual(objHeap.peek(), objItems[4]); + + assert.deepEqual(heap.pop(), 0); + assert.deepEqual(objHeap.pop(), { value: 0 }); + assert.deepEqual(heap.peek(), 1); + assert.deepEqual(objHeap.peek(), { value: 1 }); + + assert.deepEqual(heap.pop(), 1); + assert.deepEqual(objHeap.pop(), { value: 1 }); + assert.deepEqual(heap.peek(), 4); + assert.deepEqual(objHeap.peek(), { value: 4 }); + + assert.deepEqual(heap.pop(), 4); + assert.deepEqual(objHeap.pop(), { value: 4 }); + assert.deepEqual(heap.peek(), 5); + assert.deepEqual(objHeap.peek(), { value: 5 }); + + assert.deepEqual(heap.pop(), 5); + assert.deepEqual(objHeap.pop(), { value: 5 }); + assert.deepEqual(heap.peek(), 6); + assert.deepEqual(objHeap.peek(), { value: 6 }); + + assert.deepEqual(heap.pop(), 6); + assert.deepEqual(objHeap.pop(), { value: 6 }); + assert.deepEqual(heap.peek(), 7); + assert.deepEqual(objHeap.peek(), { value: 7 }); + + assert.deepEqual(heap.pop(), 7); + assert.deepEqual(objHeap.pop(), { value: 7 }); + assert.deepEqual(heap.peek(), 20); + assert.deepEqual(objHeap.peek(), { value: 20 }); + + assert.deepEqual(heap.pop(), 20); + assert.deepEqual(objHeap.pop(), { value: 20 }); + assert.deepEqual(heap.peek(), 22); + assert.deepEqual(objHeap.peek(), { value: 22 }); + + assert.deepEqual(heap.pop(), 22); + assert.deepEqual(objHeap.pop(), { value: 22 }); + assert.deepEqual(heap.peek(), 31); + assert.deepEqual(objHeap.peek(), { value: 31 }); + + assert.deepEqual(heap.pop(), 31); + assert.deepEqual(objHeap.pop(), { value: 31 }); + assert.deepEqual(heap.peek(), 32); + assert.deepEqual(objHeap.peek(), { value: 32 }); + + assert.deepEqual(heap.pop(), 32); + assert.deepEqual(objHeap.pop(), { value: 32 }); + assert.deepEqual(heap.peek(), 33); + assert.deepEqual(objHeap.peek(), { value: 33 }); + + assert.deepEqual(heap.pop(), 33); + assert.deepEqual(objHeap.pop(), { value: 33 }); + assert.deepEqual(heap.peek(), 34); + assert.deepEqual(objHeap.peek(), { value: 34 }); + + assert.deepEqual(heap.pop(), 34); + assert.deepEqual(objHeap.pop(), { value: 34 }); + assert.isUndefined(heap.peek()); + assert.isUndefined(objHeap.peek()); + }); +}); diff --git a/test/unit/DSBinaryHeap/index.pop.test.js b/test/unit/DSBinaryHeap/index.pop.test.js new file mode 100644 index 0000000..a27e690 --- /dev/null +++ b/test/unit/DSBinaryHeap/index.pop.test.js @@ -0,0 +1,78 @@ +describe('DSBinaryHeap.pop()', function () { + it('should pop the item off of the front of the DSBinaryHeap.', function () { + var heap = new DSBinaryHeap(); + var objHeap = new DSBinaryHeap(function (x) { + return x.value; + }); + var items = [20, 4, 33, 1, 0, 34, 22, 31, 32, 5, 6, 7]; + for (var i = 0; i < items.length; i++) { + heap.push(items[i]); + objHeap.push({ + value: items[i] + }); + } + + assert.deepEqual(heap.size(), 12); + assert.deepEqual(objHeap.size(), 12); + + assert.deepEqual(heap.pop(), 0); + assert.deepEqual(objHeap.pop(), { value: 0 }); + assert.deepEqual(heap.size(), 11); + assert.deepEqual(objHeap.size(), 11); + + assert.deepEqual(heap.pop(), 1); + assert.deepEqual(objHeap.pop(), { value: 1 }); + assert.deepEqual(heap.size(), 10); + assert.deepEqual(objHeap.size(), 10); + + assert.deepEqual(heap.pop(), 4); + assert.deepEqual(objHeap.pop(), { value: 4 }); + assert.deepEqual(heap.size(), 9); + assert.deepEqual(objHeap.size(), 9); + + assert.deepEqual(heap.pop(), 5); + assert.deepEqual(objHeap.pop(), { value: 5 }); + assert.deepEqual(heap.size(), 8); + assert.deepEqual(objHeap.size(), 8); + + assert.deepEqual(heap.pop(), 6); + assert.deepEqual(objHeap.pop(), { value: 6 }); + assert.deepEqual(heap.size(), 7); + assert.deepEqual(objHeap.size(), 7); + + assert.deepEqual(heap.pop(), 7); + assert.deepEqual(objHeap.pop(), { value: 7 }); + assert.deepEqual(heap.size(), 6); + assert.deepEqual(objHeap.size(), 6); + + assert.deepEqual(heap.pop(), 20); + assert.deepEqual(objHeap.pop(), { value: 20 }); + assert.deepEqual(heap.size(), 5); + assert.deepEqual(objHeap.size(), 5); + + assert.deepEqual(heap.pop(), 22); + assert.deepEqual(objHeap.pop(), { value: 22 }); + assert.deepEqual(heap.size(), 4); + assert.deepEqual(objHeap.size(), 4); + + assert.deepEqual(heap.pop(), 31); + assert.deepEqual(objHeap.pop(), { value: 31 }); + assert.deepEqual(heap.size(), 3); + assert.deepEqual(objHeap.size(), 3); + + assert.deepEqual(heap.pop(), 32); + assert.deepEqual(objHeap.pop(), { value: 32 }); + assert.deepEqual(heap.size(), 2); + assert.deepEqual(objHeap.size(), 2); + + assert.deepEqual(heap.pop(), 33); + assert.deepEqual(objHeap.pop(), { value: 33 }); + assert.deepEqual(heap.size(), 1); + assert.deepEqual(objHeap.size(), 1); + + assert.deepEqual(heap.pop(), 34); + assert.deepEqual(objHeap.pop(), { value: 34 }); + assert.deepEqual(heap.size(), 0); + assert.deepEqual(objHeap.size(), 0); + }); +}); diff --git a/test/unit/DSBinaryHeap/index.push.test.js b/test/unit/DSBinaryHeap/index.push.test.js new file mode 100644 index 0000000..8c71571 --- /dev/null +++ b/test/unit/DSBinaryHeap/index.push.test.js @@ -0,0 +1,99 @@ +describe('DSBinaryHeap.push(node)', function () { + it('should push items to the front of the DSBinaryHeap.', function () { + var heap = new DSBinaryHeap(); + var objHeap = new DSBinaryHeap(function (x) { + return x.value; + }); + var items = [20, 4, 33, 1, 0, 34, 22, 31, 32, 5, 6, 7], + objItems = []; + for (var i = 0; i < items.length; i++) { + objItems.push({ + value: items[i] + }); + } + + heap.push(items[0]); + objHeap.push(objItems[0]); + assert.deepEqual(heap.size(), 1); + assert.deepEqual(objHeap.size(), 1); + assert.deepEqual(heap.peek(), items[0]); + assert.deepEqual(objHeap.peek(), objItems[0]); + + heap.push(items[1]); + objHeap.push(objItems[1]); + assert.deepEqual(heap.size(), 2); + assert.deepEqual(objHeap.size(), 2); + assert.deepEqual(heap.peek(), items[1]); + assert.deepEqual(objHeap.peek(), objItems[1]); + + heap.push(items[2]); + objHeap.push(objItems[2]); + assert.deepEqual(heap.size(), 3); + assert.deepEqual(objHeap.size(), 3); + assert.deepEqual(heap.peek(), items[1]); + assert.deepEqual(objHeap.peek(), objItems[1]); + + heap.push(items[3]); + objHeap.push(objItems[3]); + assert.deepEqual(heap.size(), 4); + assert.deepEqual(objHeap.size(), 4); + assert.deepEqual(heap.peek(), items[3]); + assert.deepEqual(objHeap.peek(), objItems[3]); + + heap.push(items[4]); + objHeap.push(objItems[4]); + assert.deepEqual(heap.size(), 5); + assert.deepEqual(objHeap.size(), 5); + assert.deepEqual(heap.peek(), items[4]); + assert.deepEqual(objHeap.peek(), objItems[4]); + + heap.push(items[5]); + objHeap.push(objItems[5]); + assert.deepEqual(heap.size(), 6); + assert.deepEqual(objHeap.size(), 6); + assert.deepEqual(heap.peek(), items[4]); + assert.deepEqual(objHeap.peek(), objItems[4]); + + heap.push(items[6]); + objHeap.push(objItems[6]); + assert.deepEqual(heap.size(), 7); + assert.deepEqual(objHeap.size(), 7); + assert.deepEqual(heap.peek(), items[4]); + assert.deepEqual(objHeap.peek(), objItems[4]); + + heap.push(items[7]); + objHeap.push(objItems[7]); + assert.deepEqual(heap.size(), 8); + assert.deepEqual(objHeap.size(), 8); + assert.deepEqual(heap.peek(), items[4]); + assert.deepEqual(objHeap.peek(), objItems[4]); + + heap.push(items[8]); + objHeap.push(objItems[8]); + assert.deepEqual(heap.size(), 9); + assert.deepEqual(objHeap.size(), 9); + assert.deepEqual(heap.peek(), items[4]); + assert.deepEqual(objHeap.peek(), objItems[4]); + + heap.push(items[9]); + objHeap.push(objItems[9]); + assert.deepEqual(heap.size(), 10); + assert.deepEqual(objHeap.size(), 10); + assert.deepEqual(heap.peek(), items[4]); + assert.deepEqual(objHeap.peek(), objItems[4]); + + heap.push(items[10]); + objHeap.push(objItems[10]); + assert.deepEqual(heap.size(), 11); + assert.deepEqual(objHeap.size(), 11); + assert.deepEqual(heap.peek(), items[4]); + assert.deepEqual(objHeap.peek(), objItems[4]); + + heap.push(items[11]); + objHeap.push(objItems[11]); + assert.deepEqual(heap.size(), 12); + assert.deepEqual(objHeap.size(), 12); + assert.deepEqual(heap.peek(), items[4]); + assert.deepEqual(objHeap.peek(), objItems[4]); + }); +}); diff --git a/test/unit/DSBinaryHeap/index.remove.test.js b/test/unit/DSBinaryHeap/index.remove.test.js new file mode 100644 index 0000000..0290f31 --- /dev/null +++ b/test/unit/DSBinaryHeap/index.remove.test.js @@ -0,0 +1,75 @@ +describe('DSBinaryHeap.remove(node)', function () { + it('should remove the item from the heap.', function () { + var heap = new DSBinaryHeap(); + var objHeap = new DSBinaryHeap(function (x) { + return x.value; + }); + var items = [20, 4, 33, 1, 0, 34, 22, 31, 32, 5, 6, 7]; + for (var i = 0; i < items.length; i++) { + heap.push(items[i]); + objHeap.push({ + value: items[i] + }); + } + + assert.deepEqual(heap.remove(0), 0); + assert.deepEqual(objHeap.remove({ value: 0 }), { value: 0 }); + assert.deepEqual(heap.peek(), 1); + assert.deepEqual(objHeap.peek(), { value: 1 }); + + assert.deepEqual(heap.remove(1), 1); + assert.deepEqual(objHeap.remove({ value: 1 }), { value: 1 }); + assert.deepEqual(heap.peek(), 4); + assert.deepEqual(objHeap.peek(), { value: 4 }); + + assert.deepEqual(heap.remove(4), 4); + assert.deepEqual(objHeap.remove({ value: 4 }), { value: 4 }); + assert.deepEqual(heap.peek(), 5); + assert.deepEqual(objHeap.peek(), { value: 5 }); + + assert.deepEqual(heap.remove(5), 5); + assert.deepEqual(objHeap.remove({ value: 5 }), { value: 5 }); + assert.deepEqual(heap.peek(), 6); + assert.deepEqual(objHeap.peek(), { value: 6 }); + + assert.deepEqual(heap.remove(6), 6); + assert.deepEqual(objHeap.remove({ value: 6 }), { value: 6 }); + assert.deepEqual(heap.peek(), 7); + assert.deepEqual(objHeap.peek(), { value: 7 }); + + assert.deepEqual(heap.remove(7), 7); + assert.deepEqual(objHeap.remove({ value: 7 }), { value: 7 }); + assert.deepEqual(heap.peek(), 20); + assert.deepEqual(objHeap.peek(), { value: 20 }); + + assert.deepEqual(heap.remove(20), 20); + assert.deepEqual(objHeap.remove({ value: 20 }), { value: 20 }); + assert.deepEqual(heap.peek(), 22); + assert.deepEqual(objHeap.peek(), { value: 22 }); + + assert.deepEqual(heap.remove(22), 22); + assert.deepEqual(objHeap.remove({ value: 22 }), { value: 22 }); + assert.deepEqual(heap.peek(), 31); + assert.deepEqual(objHeap.peek(), { value: 31 }); + + assert.deepEqual(heap.remove(31), 31); + assert.deepEqual(objHeap.remove({ value: 31 }), { value: 31 }); + assert.deepEqual(heap.peek(), 32); + assert.deepEqual(objHeap.peek(), { value: 32 }); + + assert.deepEqual(heap.remove(32), 32); + assert.deepEqual(objHeap.remove({ value: 32 }), { value: 32 }); + assert.deepEqual(heap.peek(), 33); + assert.deepEqual(objHeap.peek(), { value: 33 }); + + assert.deepEqual(heap.remove(33), 33); + assert.deepEqual(objHeap.remove({ value: 33 }), { value: 33 }); + assert.deepEqual(heap.peek(), 34); + assert.deepEqual(objHeap.peek(), { value: 34 }); + + assert.deepEqual(heap.remove(34), 34); + assert.deepEqual(objHeap.remove({ value: 34 }), { value: 34 }); + assert.deepEqual(heap.peek()); + assert.isUndefined(objHeap.peek()); + }); +}); diff --git a/test/unit/DSBinaryHeap/index.removeAll.test.js b/test/unit/DSBinaryHeap/index.removeAll.test.js new file mode 100644 index 0000000..820f27d --- /dev/null +++ b/test/unit/DSBinaryHeap/index.removeAll.test.js @@ -0,0 +1,27 @@ +describe('DSBinaryHeap.removeAll()', function () { + it('should remove all items from the heap.', function () { + var heap = new DSBinaryHeap(); + var objHeap = new DSBinaryHeap(function (x) { + return x.value; + }); + var items = [20, 4, 33, 1, 0, 34, 22, 31, 32, 5, 6, 7]; + for (var i = 0; i < items.length; i++) { + heap.push(items[i]); + objHeap.push({ + value: items[i] + }); + } + + assert.deepEqual(heap.size(), 12); + assert.deepEqual(objHeap.size(), 12); + + heap.removeAll(); + objHeap.removeAll(); + + assert.deepEqual(heap.size(), 0); + assert.deepEqual(objHeap.size(), 0); + + assert.isUndefined(heap.peek()); + assert.isUndefined(objHeap.peek()); + }); +}); diff --git a/test/unit/DSBinaryHeap/index.test.js b/test/unit/DSBinaryHeap/index.test.js new file mode 100644 index 0000000..6531696 --- /dev/null +++ b/test/unit/DSBinaryHeap/index.test.js @@ -0,0 +1,23 @@ +describe('DSBinaryHeap(weightFunc)', function () { + it('should create an empty heap with size 0.', function () { + var heap = new DSBinaryHeap(); + assert.deepEqual(heap.size(), 0); + }); + it('should throw an error if "weightFunc" is not a function.', function () { + var heap; + for (var i = 0; i < TYPES_EXCEPT_FUNCTION.length; i++) { + try { + heap = new DSBinaryHeap(TYPES_EXCEPT_FUNCTION[i]); + if (TYPES_EXCEPT_FUNCTION[i]) { + fail(); + } + } catch (err) { + assert.equal(err.message, 'DSBinaryHeap(weightFunc): weightFunc: must be a function!'); + continue; + } + if (TYPES_EXCEPT_FUNCTION[i]) { + fail(); + } + } + }); +}); diff --git a/test/unit/DSCache/index.destroy.test.js b/test/unit/DSCache/index.destroy.test.js new file mode 100644 index 0000000..0e2ccad --- /dev/null +++ b/test/unit/DSCache/index.destroy.test.js @@ -0,0 +1,39 @@ +describe('DSCache.destroy()', function () { + it('should destroy the cache and remove all traces of its existence.', function () { + var cache = DSCacheFactory('cache'); + cache.destroy(); + try { + assert.equal(cache.info(), { size: 0 }); + fail('should not be able to use a cache after destroying it'); + } catch (err) { + + } + assert.isUndefined(DSCacheFactory.get('cache')); + }); + it('should remove items from localStorage when storageMode is used.', function () { + var localStorageCache = DSCacheFactory('localStorageCache', { storageMode: 'localStorage', storageImpl: $window.localStorage, storagePrefix: 'acc.' }), + sessionStorageCache = DSCacheFactory('sessionStorageCache', { storageMode: 'sessionStorage', storageImpl: $window.sessionStorage }); + + localStorageCache.put('item1', 'value1'); + sessionStorageCache.put('item1', 'value1'); + localStorageCache.put('item2', 'value2'); + sessionStorageCache.put('item2', 'value2'); + + assert.equal(angular.fromJson($window.localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.data.item1')).value, 'value1'); + assert.equal(angular.fromJson($window.localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.data.item2')).value, 'value2'); + assert.equal($window.localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.keys'), '["item1","item2"]'); + assert.equal(angular.fromJson($window.sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.data.item1')).value, 'value1'); + assert.equal(angular.fromJson($window.sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.data.item2')).value, 'value2'); + assert.equal($window.sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.keys'), '["item1","item2"]'); + + localStorageCache.destroy(); + sessionStorageCache.destroy(); + + assert.isNull(angular.fromJson($window.localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.data.item1'))); + assert.isNull(angular.fromJson($window.localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.data.item2'))); + assert.isNull($window.localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.keys')); + assert.isNull(angular.fromJson($window.sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.data.item1'))); + assert.isNull(angular.fromJson($window.sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.data.item2'))); + assert.isNull($window.sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.keys')); + }); +}); diff --git a/test/angularCache.get-test.js b/test/unit/DSCache/index.get.test.js similarity index 56% rename from test/angularCache.get-test.js rename to test/unit/DSCache/index.get.test.js index 7b1ba92..902f68b 100644 --- a/test/angularCache.get-test.js +++ b/test/unit/DSCache/index.get.test.js @@ -1,30 +1,30 @@ -describe('AngularCache.get(key)', function () { +describe('asCache.get(key)', function () { it('should do nothing if the cache is disabled.', function () { - var cache = $angularCacheFactory('cache'); + var cache = DSCacheFactory('cache'); - expect(cache.info().size).toEqual(0); + assert.equal(cache.info().size, 0); cache.put('1', 'item'); - expect(cache.get('1')).toEqual('item'); - expect(cache.info().size).toEqual(1); + assert.equal(cache.get('1'), 'item'); + assert.equal(cache.info().size, 1); cache.setOptions({ disabled: true }); - expect(cache.info().size).toEqual(1); - expect(cache.get('1')).toBeUndefined(); + assert.equal(cache.info().size, 1); + assert.isUndefined(cache.get('1')); }); it('should throw an error if "key" is not a string or array.', function () { - var cache = $angularCacheFactory('cache'); + var cache = DSCacheFactory('cache'); for (var i = 0; i < TYPES_EXCEPT_STRING_OR_ARRAY_OR_NUMBER.length; i++) { try { cache.get(TYPES_EXCEPT_STRING_OR_ARRAY_OR_NUMBER[i]); fail(TYPES_EXCEPT_STRING_OR_ARRAY_OR_NUMBER[i]); } catch (err) { - expect(err.message).toEqual('AngularCache.get(key, options): key: must be a string!'); + assert.equal(err.message, '[ng:areq] Expected key to be a string! Found: ' + typeof TYPES_EXCEPT_STRING_OR_ARRAY_OR_NUMBER[i] + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof TYPES_EXCEPT_STRING_OR_ARRAY_OR_NUMBER[i]); continue; } fail(TYPES_EXCEPT_STRING_OR_ARRAY_OR_NUMBER[i]); } }); it('should throw an error if "options" is not an object.', function () { - var cache = $angularCacheFactory('cache'); + var cache = DSCacheFactory('cache'); for (var i = 0; i < TYPES_EXCEPT_OBJECT.length; i++) { try { cache.get('item', TYPES_EXCEPT_OBJECT[i]); @@ -32,7 +32,7 @@ describe('AngularCache.get(key)', function () { fail(TYPES_EXCEPT_OBJECT[i]); } } catch (err) { - expect(err.message).toEqual('AngularCache.get(key, options): options: must be an object!'); + assert.equal(err.message, '[ng:areq] Expected options to be an object! Found: ' + typeof TYPES_EXCEPT_OBJECT[i] + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof TYPES_EXCEPT_OBJECT[i]); continue; } if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { @@ -41,15 +41,18 @@ describe('AngularCache.get(key)', function () { } }); it('should throw an error if "onExpire" is not a function.', function () { - var cache = $angularCacheFactory('cache'); + var cache = DSCacheFactory('cache'); for (var i = 0; i < TYPES_EXCEPT_FUNCTION.length; i++) { try { + if (!TYPES_EXCEPT_FUNCTION[i]) { + continue; + } cache.get('item', { onExpire: TYPES_EXCEPT_FUNCTION[i] }); if (TYPES_EXCEPT_FUNCTION[i] !== null && TYPES_EXCEPT_FUNCTION[i] !== undefined && TYPES_EXCEPT_FUNCTION[i] !== false) { fail(TYPES_EXCEPT_FUNCTION[i]); } } catch (err) { - expect(err.message).toEqual('AngularCache.get(key, options): onExpire: must be a function!'); + assert.equal(err.message, '[ng:areq] Expected options.onExpire to be a function! Found: ' + typeof TYPES_EXCEPT_FUNCTION[i] + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof TYPES_EXCEPT_FUNCTION[i]); continue; } if (TYPES_EXCEPT_FUNCTION[i] !== null && TYPES_EXCEPT_FUNCTION[i] !== undefined && TYPES_EXCEPT_FUNCTION[i] !== false) { @@ -58,7 +61,7 @@ describe('AngularCache.get(key)', function () { } }); it('should return the correct value for the specified key.', function () { - var cache = $angularCacheFactory('cache'); + var cache = DSCacheFactory('cache'); var value1 = 'value1', value2 = 2, value3 = { @@ -67,65 +70,67 @@ describe('AngularCache.get(key)', function () { cache.put('item1', value1); cache.put('item2', value2); cache.put('item3', value3); - expect(cache.get('item1')).toEqual(value1); - expect(cache.get('item2')).toEqual(value2); - expect(cache.get('item3')).toEqual(value3); + assert.equal(cache.get('item1'), value1); + assert.equal(cache.get('item2'), value2); + assert.equal(cache.get('item3'), value3); }); it('should return undefined if the key isn\'t in the cache.', function () { - var cache = $angularCacheFactory('cache'); - expect(cache.get('item')).toEqual(undefined); + var cache = DSCacheFactory('cache'); + assert.isUndefined(cache.get('item')); }); - it('should execute globally configured "onExpire" callback if the item is expired in passive mode and global "onExpire" callback is configured.', function () { - var cache = $angularCacheFactory('cache', { + it('should execute globally configured "onExpire" callback if the item is expired in passive mode and global "onExpire" callback is configured.', function (done) { + var cache = DSCacheFactory('cache', { maxAge: 10, recycleFreq: 20, deleteOnExpire: 'passive', - onExpire: function (key, value, done) { - done(key, value, 'executed global callback'); + onExpire: function (key, value, done2) { + done2(key, value, 'executed global callback'); } }); cache.put('item', 'value'); - waits(100); - runs(function () { + setTimeout(function () { cache.get('item', { onExpire: function (key, value, test) { - expect(key).toEqual('item'); - expect(value).toEqual('value'); - expect(test).toEqual('executed global callback'); + assert.equal(key, 'item'); + assert.equal(value, 'value'); + assert.equal(test, 'executed global callback'); }}); - }); + done(); + }, 100); }); - it('should execute globally configured "onExpire" callback when an item is aggressively deleted and global "onExpire" callback is configured.', function () { - var onExpire = jasmine.createSpy(); - var cache = $angularCacheFactory('cache', { + it('should execute globally configured "onExpire" callback when an item is aggressively deleted and global "onExpire" callback is configured.', function (done) { + var options = { maxAge: 10, recycleFreq: 20, deleteOnExpire: 'aggressive', - onExpire: onExpire - }); + onExpire: function (key, value) { + } + }; + sinon.spy(options, 'onExpire'); + var cache = DSCacheFactory('cache', options); cache.put('item', 'value'); - waits(100); - runs(function () { - expect(onExpire).toHaveBeenCalled(); - expect(onExpire).toHaveBeenCalledWith('item', 'value'); - }); + setTimeout(function () { + assert.isTrue(options.onExpire.called); + assert.isTrue(options.onExpire.calledWith('item', 'value')); + done(); + }, 100); }); - it('should execute local "onExpire" callback if the item is expired in passive mode and global "onExpire" callback is NOT configured.', function () { - var cache = $angularCacheFactory('cache', { + it('should execute local "onExpire" callback if the item is expired in passive mode and global "onExpire" callback is NOT configured.', function (done) { + var cache = DSCacheFactory('cache', { maxAge: 10, deleteOnExpire: 'passive', recycleFreq: 20 }); cache.put('item', 'value'); - waits(100); - runs(function () { + setTimeout(function () { cache.get('item', { onExpire: function (key, value) { - expect(key).toEqual('item'); - expect(value).toEqual('value'); + assert.equal(key, 'item'); + assert.equal(value, 'value'); }}); - }); + done(); + }, 100); }); it('should return the correct values for multiple keys.', function () { - var cache = $angularCacheFactory('cache'); + var cache = DSCacheFactory('cache'); var value1 = 'value1', value2 = 2, value3 = { @@ -134,14 +139,14 @@ describe('AngularCache.get(key)', function () { cache.put('item1', value1); cache.put('item2', value2); cache.put('item3', value3); - expect(cache.get(['item1', 'item2', 'item3'])).toEqual([value1, value2, value3]); + assert.deepEqual(cache.get(['item1', 'item2', 'item3']), [value1, value2, value3]); }); it('should not return undefined values for multiple keys.', function () { - var cache = $angularCacheFactory('cache'); + var cache = DSCacheFactory('cache'); var value1 = 'value1', value2 = 2; cache.put('item1', value1); cache.put('item2', value2); - expect(cache.get(['item1', 'item2', 'item3'])).toEqual([value1, value2]); + assert.deepEqual(cache.get(['item1', 'item2', 'item3']), [value1, value2]); }); }); diff --git a/test/unit/DSCache/index.info.test.js b/test/unit/DSCache/index.info.test.js new file mode 100644 index 0000000..6e163e8 --- /dev/null +++ b/test/unit/DSCache/index.info.test.js @@ -0,0 +1,76 @@ +describe('DSCache.info()', function () { + it('should return the correct values.', function () { + var onExpire = function () { + }; + var cache = DSCacheFactory('cache'), + cache2 = DSCacheFactory('cache2', { maxAge: 1000 }), + cache3 = DSCacheFactory('cache3', { cacheFlushInterval: 1000 }), + cache4 = DSCacheFactory('cache4', { capacity: 1000 }), + cache5 = DSCacheFactory('cache5', { storageMode: 'localStorage' }), + cache6 = DSCacheFactory('cache6', { storageMode: 'sessionStorage' }), + cache7 = DSCacheFactory('cache7', { maxAge: 100, onExpire: onExpire }); + var cacheInfo = cache.info(); + assert.equal(cacheInfo.id, 'cache'); + assert.equal(cacheInfo.capacity, Number.MAX_VALUE); + assert.equal(cacheInfo.size, 0); + assert.equal(cacheInfo.recycleFreq, 1000); + assert.equal(cacheInfo.maxAge, Number.MAX_VALUE); + assert.equal(cacheInfo.cacheFlushInterval, null); + assert.equal(cacheInfo.deleteOnExpire, 'none'); + assert.equal(cacheInfo.storageMode, 'memory'); + assert.equal(cacheInfo.onExpire, null); + cache.put('item', 'value'); + cache.put('item2', 'value2'); + + // DSCache#info(key) + assert.isUndefined(cache.info('non-existent item')); + assert.isNumber(cache.info('item').created); + assert.isNumber(cache.info('item').expires); + assert.isFalse(cache.info('item').isExpired); + assert.isNumber(cache.info('item').accessed); + assert.isNumber(cache.info('item2').created); + assert.isNumber(cache.info('item2').expires); + assert.isFalse(cache.info('item2').isExpired); + assert.isNumber(cache.info('item2').accessed); + + assert.equal(cache.info().size, 2); + + var cacheInfo2 = cache2.info(); + assert.equal(cacheInfo2.id, 'cache2'); + assert.equal(cacheInfo2.capacity, Number.MAX_VALUE); + assert.equal(cacheInfo2.size, 0); + assert.equal(cacheInfo2.recycleFreq, 1000); + assert.equal(cacheInfo2.maxAge, 1000); + assert.equal(cacheInfo2.cacheFlushInterval, null); + assert.equal(cacheInfo2.deleteOnExpire, 'none'); + assert.equal(cacheInfo2.storageMode, 'memory'); + assert.equal(cacheInfo2.onExpire, null); + + assert.equal(cache3.info().id, 'cache3'); + assert.equal(cache3.info().capacity, Number.MAX_VALUE); + assert.equal(cache3.info().cacheFlushInterval, 1000); + assert.equal(cache3.info().size, 0); + + var cacheInfo4 = cache4.info(); + assert.equal(cacheInfo4.id, 'cache4'); + assert.equal(cacheInfo4.capacity, 1000); + assert.equal(cacheInfo4.size, 0); + assert.equal(cacheInfo4.recycleFreq, 1000); + assert.equal(cacheInfo4.maxAge, Number.MAX_VALUE); + assert.equal(cacheInfo4.cacheFlushInterval, null); + assert.equal(cacheInfo4.deleteOnExpire, 'none'); + assert.equal(cacheInfo4.storageMode, 'memory'); + assert.equal(cacheInfo4.onExpire, null); + if (localStorage) { + assert.equal(cache5.info().storageMode, 'localStorage'); + } else { + assert.equal(cache5.info().storageMode, null); + } + if (sessionStorage) { + assert.equal(cache6.info().storageMode, 'sessionStorage'); + } else { + assert.equal(cache6.info().storageMode, null); + } + assert.equal(cache7.info().onExpire, onExpire); + }); +}); diff --git a/test/unit/DSCache/index.keySet.test.js b/test/unit/DSCache/index.keySet.test.js new file mode 100644 index 0000000..25d0958 --- /dev/null +++ b/test/unit/DSCache/index.keySet.test.js @@ -0,0 +1,31 @@ +describe('DSCache.keySet()', function () { + it('should return the set of keys of all items in the cache.', function () { + var itemKeys = ['item1', 'item2', 'item3']; + + var cache = DSCacheFactory('DSCache.keySet.cache'); + + cache.put(itemKeys[0], itemKeys[0]); + cache.put(itemKeys[1], itemKeys[1]); + cache.put(itemKeys[2], itemKeys[2]); + + var keySet = cache.keySet(); + + assert.equal(keySet.hasOwnProperty(itemKeys[0]), true); + assert.equal(keySet.hasOwnProperty(itemKeys[1]), true); + assert.equal(keySet.hasOwnProperty(itemKeys[2]), true); + + assert.equal(keySet[itemKeys[0]], itemKeys[0]); + assert.equal(keySet[itemKeys[1]], itemKeys[1]); + assert.equal(keySet[itemKeys[2]], itemKeys[2]); + + cache.remove(itemKeys[0]); + cache.remove(itemKeys[1]); + cache.remove(itemKeys[2]); + + keySet = cache.keySet(); + + assert.equal(keySet.hasOwnProperty(itemKeys[0]), false); + assert.equal(keySet.hasOwnProperty(itemKeys[1]), false); + assert.equal(keySet.hasOwnProperty(itemKeys[2]), false); + }); +}); diff --git a/test/unit/DSCache/index.keys.test.js b/test/unit/DSCache/index.keys.test.js new file mode 100644 index 0000000..4818307 --- /dev/null +++ b/test/unit/DSCache/index.keys.test.js @@ -0,0 +1,25 @@ +describe('DSCache.keys()', function () { + it('should return the array of keys of all items in the cache.', function () { + var itemKeys = ['item1', 'item2', 'item3']; + + var cache = DSCacheFactory('DSCache.keys.cache'); + + cache.put(itemKeys[0], itemKeys[0]); + cache.put(itemKeys[1], itemKeys[1]); + cache.put(itemKeys[2], itemKeys[2]); + + var keys = cache.keys(); + + assert.equal(keys[0], itemKeys[0]); + assert.equal(keys[1], itemKeys[1]); + assert.equal(keys[2], itemKeys[2]); + + cache.remove(itemKeys[0]); + cache.remove(itemKeys[1]); + cache.remove(itemKeys[2]); + + keys = cache.keys(); + + assert.equal(keys.length, 0); + }); +}); diff --git a/test/unit/DSCache/index.put.test.js b/test/unit/DSCache/index.put.test.js new file mode 100644 index 0000000..7bef52e --- /dev/null +++ b/test/unit/DSCache/index.put.test.js @@ -0,0 +1,108 @@ +describe('DSCache.put(key, value, options)', function () { + it('should do nothing if the cache is disabled.', function () { + var cache = DSCacheFactory('DSCache.put.cache', { disabled: true }); + + assert.equal(cache.info().size, 0); + assert.isUndefined(cache.put('1', 'item')); + assert.equal(cache.info().size, 0); + }); + it('should throw an error if "key" is not a string.', function () { + var cache = DSCacheFactory('DSCache.put.cache'); + for (var i = 0; i < TYPES_EXCEPT_STRING_OR_NUMBER.length; i++) { + try { + cache.put(TYPES_EXCEPT_STRING_OR_NUMBER[i], 'value'); + fail(TYPES_EXCEPT_STRING_OR_NUMBER[i]); + } catch (err) { + assert.equal(err.message, '[ng:areq] Expected key to be a string! Found: ' + typeof TYPES_EXCEPT_STRING_OR_NUMBER[i] + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof TYPES_EXCEPT_STRING_OR_NUMBER[i]); + continue; + } + fail(TYPES_EXCEPT_STRING_OR_NUMBER[i]); + } + }); + it('should not add values that are not defined.', function () { + var cache = DSCacheFactory('cache'); + cache.put('item', null); + assert.equal(cache.get('item'), undefined); + cache.put('item', undefined); + assert.equal(cache.get('item'), undefined); + }); + it('should increase the size of the cache by one.', function () { + var cache = DSCacheFactory('cache'); + assert.equal(cache.info().size, 0); + cache.put('item', 'value1'); + assert.equal(cache.info().size, 1); + cache.put('item2', 'value2'); + assert.equal(cache.info().size, 2); + }); + it('should overwrite an item if it is re-added to the cache.', function () { + var cache = DSCacheFactory('cache'); + assert.equal(cache.info().size, 0); + cache.put('item', 'value1'); + assert.equal(cache.info().size, 1); + cache.put('item', 'value2'); + assert.equal(cache.info().size, 1); + assert.equal(cache.get('item'), 'value2'); + }); + it('should remove the least recently used item if the capacity has been reached.', function () { + var cache = DSCacheFactory('cache', { capacity: 2 }); + assert.equal(cache.info().size, 0); + cache.put('item1', 'value1'); + assert.equal(cache.info().size, 1); + cache.put('item2', 'value2'); + assert.equal(cache.info().size, 2); + cache.put('item3', 'value3'); + assert.equal(cache.info().size, 2); + assert.equal(cache.get('item1'), undefined); + assert.equal(cache.get('item2'), 'value2'); + assert.equal(cache.get('item3'), 'value3'); + cache.get('item2'); + cache.put('item1', 'value1'); + assert.equal(cache.get('item3'), undefined); + assert.equal(cache.get('item1'), 'value1'); + assert.equal(cache.get('item2'), 'value2'); + }); + it('should not delete items if maxAge is specified and deleteOnExpire is set to "none".', function (done) { + var cache = DSCacheFactory('cache', { maxAge: 10, deleteOnExpire: 'none', recycleFreq: 20 }); + cache.put('item1', 'value1'); + assert.equal(cache.get('item1'), 'value1'); + setTimeout(function () { + assert.equal(cache.get('item1'), 'value1'); + assert.equal(cache.info('item1').isExpired, true); + done(); + }, 100); + }); + it('should remove items if maxAge is specified and deleteOnExpire is set to "aggressive".', function (done) { + var cache = DSCacheFactory('cache', { maxAge: 10, deleteOnExpire: 'aggressive', recycleFreq: 20 }); + cache.put('item1', 'value1'); + assert.equal(cache.get('item1'), 'value1'); + setTimeout(function () { + assert.isUndefined(cache.info('item1')); + assert.isUndefined(cache.get('item1')); + + done(); + }, 100); + }); + it('should should lazy delete an item when maxAge is specified and deleteOnExpire is set to "passive".', function (done) { + var cache = DSCacheFactory('cache', { maxAge: 10, deleteOnExpire: 'passive' }); + cache.put('item1', 'value1'); + assert.equal(cache.get('item1'), 'value1'); + setTimeout(function () { + assert.isTrue(cache.info('item1').isExpired); + assert.isUndefined(cache.get('item1')); + + done(); + }, 100); + }); + it('should save data to localStorage when storageMode is used.', function () { + var localStorageCache = DSCacheFactory('localStorageCache', { storageMode: 'localStorage', storageImpl: $window.localStorage }), + sessionStorageCache = DSCacheFactory('sessionStorageCache', { storageMode: 'sessionStorage', storageImpl: $window.sessionStorage }); + + localStorageCache.put('item1', 'value1'); + sessionStorageCache.put('item1', 'value1'); + + assert.equal(angular.fromJson($window.localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.data.item1')).value, 'value1'); + assert.equal($window.localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.keys'), '["item1"]'); + assert.equal(angular.fromJson($window.sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.data.item1')).value, 'value1'); + assert.equal($window.sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.keys'), '["item1"]'); + }); +}); diff --git a/test/unit/DSCache/index.remove.test.js b/test/unit/DSCache/index.remove.test.js new file mode 100644 index 0000000..7f870bf --- /dev/null +++ b/test/unit/DSCache/index.remove.test.js @@ -0,0 +1,54 @@ +describe('DSCache.remove(key)', function () { + it('should remove the item with the specified key.', function () { + var cache = DSCacheFactory('cache'); + var value1 = 'value1', + value2 = 2, + value3 = { + value3: 'stuff' + }; + cache.put('item1', value1); + cache.put('item2', value2); + cache.put('item3', value3); + cache.remove('item1'); + assert.isUndefined(cache.get('item1')); + cache.remove('item2'); + assert.isUndefined(cache.get('item2')); + cache.remove('item3'); + assert.isUndefined(cache.get('item3')); + }); + it('should reduce the size of the cache by one if the size is greater than zero.', function () { + var cache = DSCacheFactory('cache'); + cache.put('item1', 'value1'); + assert.equal(cache.info().size, 1); + cache.put('item2', 'value2'); + assert.equal(cache.info().size, 2); + cache.remove('item1'); + assert.equal(cache.info().size, 1); + cache.remove('item2'); + assert.equal(cache.info().size, 0); + cache.remove('item1'); + assert.equal(cache.info().size, 0); + cache.remove('item2'); + assert.equal(cache.info().size, 0); + }); + it('should remove items from localStorage when storageMode is used.', function () { + var localStorageCache = DSCacheFactory('localStorageCache', { storageMode: 'localStorage', storageImpl: $window.localStorage }), + sessionStorageCache = DSCacheFactory('sessionStorageCache', { storageMode: 'sessionStorage', storageImpl: $window.sessionStorage }); + + localStorageCache.put('item1', 'value1'); + sessionStorageCache.put('item1', 'value1'); + + assert.equal(angular.fromJson($window.localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.data.item1')).value, 'value1'); + assert.equal($window.localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.keys'), '["item1"]'); + assert.equal(angular.fromJson($window.sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.data.item1')).value, 'value1'); + assert.equal($window.sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.keys'), '["item1"]'); + + localStorageCache.remove('item1'); + sessionStorageCache.remove('item1'); + + assert.isNull(angular.fromJson($window.localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.data.item1'))); + assert.equal($window.localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.keys'), '[]'); + assert.isNull(angular.fromJson($window.sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.data.item1'))); + assert.equal($window.sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.keys'), '[]'); + }); +}); diff --git a/test/unit/DSCache/index.removeAll.test.js b/test/unit/DSCache/index.removeAll.test.js new file mode 100644 index 0000000..344623f --- /dev/null +++ b/test/unit/DSCache/index.removeAll.test.js @@ -0,0 +1,43 @@ +describe('DSCache.removeAll()', function () { + it('should remove all items in the cache.', function () { + var cache = DSCacheFactory('DSCache.removeAll.cache'); + var value1 = 'value1', + value2 = 2, + value3 = { + value3: 'stuff' + }; + cache.put('item1', value1); + cache.put('item2', value2); + cache.put('item3', value3); + cache.removeAll(); + assert.isUndefined(cache.get('item1')); + assert.isUndefined(cache.get('item2')); + assert.isUndefined(cache.get('item3')); + }); + it('should remove items from localStorage when storageMode is used.', function () { + var localStorageCache = DSCacheFactory('DSCache.removeAll.localStorageCache', { storageMode: 'localStorage', storageImpl: $window.localStorage }), + sessionStorageCache = DSCacheFactory('DSCache.removeAll.sessionStorageCache', { storageMode: 'sessionStorage', storageImpl: $window.sessionStorage, storagePrefix: 'affas' }); + + localStorageCache.put('item1', 'value1'); + sessionStorageCache.put('item1', 'value1'); + localStorageCache.put('item2', 'value2'); + sessionStorageCache.put('item2', 'value2'); + + assert.equal(angular.fromJson($window.localStorage.getItem(localStorageCache.$$storagePrefix + 'DSCache.removeAll.localStorageCache.data.item1')).value, 'value1'); + assert.equal(angular.fromJson($window.localStorage.getItem(localStorageCache.$$storagePrefix + 'DSCache.removeAll.localStorageCache.data.item2')).value, 'value2'); + assert.equal($window.localStorage.getItem(localStorageCache.$$storagePrefix + 'DSCache.removeAll.localStorageCache.keys'), '["item1","item2"]'); + assert.equal(angular.fromJson($window.sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'DSCache.removeAll.sessionStorageCache.data.item1')).value, 'value1'); + assert.equal(angular.fromJson($window.sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'DSCache.removeAll.sessionStorageCache.data.item2')).value, 'value2'); + assert.equal($window.sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'DSCache.removeAll.sessionStorageCache.keys'), '["item1","item2"]'); + + localStorageCache.removeAll(); + sessionStorageCache.removeAll(); + + assert.isNull(angular.fromJson($window.localStorage.getItem(localStorageCache.$$storagePrefix + 'DSCache.removeAll.localStorageCache.data.item1'))); + assert.isNull(angular.fromJson($window.localStorage.getItem(localStorageCache.$$storagePrefix + 'DSCache.removeAll.localStorageCache.data.item2'))); + assert.equal($window.localStorage.getItem(localStorageCache.$$storagePrefix + 'DSCache.removeAll.localStorageCache.keys'), '[]'); + assert.isNull(angular.fromJson($window.sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'DSCache.removeAll.sessionStorageCache.data.item1'))); + assert.isNull(angular.fromJson($window.sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'DSCache.removeAll.sessionStorageCache.data.item2'))); + assert.equal($window.sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'DSCache.removeAll.sessionStorageCache.keys'), '[]'); + }); +}); diff --git a/test/unit/DSCache/index.removeExpired.test.js b/test/unit/DSCache/index.removeExpired.test.js new file mode 100644 index 0000000..45861f4 --- /dev/null +++ b/test/unit/DSCache/index.removeExpired.test.js @@ -0,0 +1,79 @@ +describe('DSCache.removeExpired()', function () { + it('should remove all expired items when deleteOnExpire is "none".', function (done) { + var cache = DSCacheFactory('cache', { + deleteOnExpire: 'none', + maxAge: 10, + recycleFreq: 20 + }); + var value1 = 'value1', + value2 = 2, + value3 = { + value3: 'stuff' + }; + cache.put('item1', value1); + cache.put('item2', value2); + cache.put('item3', value3); + setTimeout(function () { + var expired = cache.removeExpired(); + assert.deepEqual(expired, { + item1: value1, + item2: value2, + item3: value3 + }); + assert.equal(cache.info().size, 0); + cache.put('item3', value3); + assert.equal(cache.info().size, 1); + done(); + }, 100); + }); +// it('should remove all expired items when deleteOnExpire is "passive".', function (done) { +// var cache = DSCacheFactory('cache', { +// deleteOnExpire: 'passive', +// maxAge: 10, +// recycleFreq: 20 +// }); +// var value1 = 'value1', +// value2 = 2, +// value3 = { +// value3: 'stuff' +// }; +// cache.put('item1', value1); +// cache.put('item2', value2); +// cache.put('item3', value3); +// setTimeout(function () { +// var expired = cache.removeExpired(); +// assert.deepEqual(expired, { +// item1: value1, +// item2: value2, +// item3: value3 +// }); +// assert.equal(cache.info().size, 0); +// cache.put('item3', value3); +// assert.equal(cache.info().size, 1); +// done(); +// }, 100); +// }); +// it('should remove all expired items when deleteOnExpire is "aggressive".', function (done) { +// var cache = DSCacheFactory('cache', { +// deleteOnExpire: 'aggressive', +// maxAge: 10, +// recycleFreq: 20 +// }); +// var value1 = 'value1', +// value2 = 2, +// value3 = { +// value3: 'stuff' +// }; +// cache.put('item1', value1); +// cache.put('item2', value2); +// cache.put('item3', value3); +// setTimeout(function () { +// var expired = cache.removeExpired(); +// assert.deepEqual(expired, {}); +// assert.equal(cache.info().size, 0); +// cache.put('item3', value3); +// assert.equal(cache.info().size, 1); +// done(); +// }, 100); +// }); +}); diff --git a/test/unit/DSCache/index.setOptions.test.js b/test/unit/DSCache/index.setOptions.test.js new file mode 100644 index 0000000..57efad6 --- /dev/null +++ b/test/unit/DSCache/index.setOptions.test.js @@ -0,0 +1,189 @@ +describe('DSCache.setOptions()', function () { + it('should throw an error if "options" is not an object.', function () { + var cache = DSCacheFactory('cache'); + for (var i = 0; i < TYPES_EXCEPT_OBJECT.length; i++) { + try { + cache.setOptions(TYPES_EXCEPT_OBJECT[i]); + if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { + fail(TYPES_EXCEPT_OBJECT[i]); + } + } catch (err) { + assert.equal(err.message, '[ng:areq] Expected cacheOptions to be an object! Found: ' + typeof TYPES_EXCEPT_OBJECT[i] + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof TYPES_EXCEPT_OBJECT[i]); + continue; + } + if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { + fail(TYPES_EXCEPT_OBJECT[i]); + } + } + }); + it('should correctly reset to defaults if strict mode is true', function () { + var onExpire = function () { + }; + var cache = DSCacheFactory('cache', { + maxAge: 100, + cacheFlushInterval: 200, + onExpire: onExpire, + storageMode: 'localStorage', + disabled: true + }); + assert.equal(cache.info().maxAge, 100); + assert.equal(cache.info().cacheFlushInterval, 200); + assert.equal(cache.info().onExpire, onExpire); + assert.equal(cache.info().storageMode, 'localStorage'); + assert.isTrue(cache.info().disabled); + cache.setOptions({ }, true); + assert.equal(cache.info().maxAge, Number.MAX_VALUE); + assert.isNull(cache.info().cacheFlushInterval); + assert.isNull(cache.info().onExpire); + assert.equal(cache.info().storageMode, 'localStorage'); + assert.isFalse(cache.info().disabled); + }); + it('should correctly modify the capacity of a cache', function (done) { + var cache = DSCacheFactory('cache'); + assert.equal(cache.info().capacity, Number.MAX_VALUE); + cache.setOptions({ capacity: 5 }, false); + assert.equal(cache.info().capacity, 5); + cache.put('item1', 1); + cache.put('item2', 2); + cache.put('item3', 3); + cache.put('item4', 4); + cache.put('item5', 5); + cache.put('item6', 6); + assert.isUndefined(cache.get('item1')); + setTimeout(function () { + cache.get('item2'); + cache.get('item3'); + cache.get('item6'); + cache.setOptions({ capacity: 3 }, false); + // Least-recently used items over the new capacity should have been removed. + assert.isUndefined(cache.get('item4')); + assert.isUndefined(cache.get('item5')); + assert.equal(cache.info().size, 3); + + done(); + }, 50); + }); + it('should correctly modify the maxAge of a cache', function (done) { + var cache = DSCacheFactory('cache'); + assert.equal(cache.info().maxAge, Number.MAX_VALUE); + cache.setOptions({ maxAge: 1000, deleteOnExpire: 'aggressive', recycleFreq: 20 }, false); + assert.equal(cache.info().maxAge, 1000); + cache.put('item1', 1); + cache.put('item2', 2); + setTimeout(function () { + assert.isDefined(cache.get('item1')); + assert.isDefined(cache.get('item2')); + cache.setOptions({ maxAge: 10, deleteOnExpire: 'aggressive', recycleFreq: 20 }, false); + assert.equal(cache.info().maxAge, 10); + cache.put('item1', 1); + cache.put('item2', 2); + // The new items should be removed after 500 ms (the new maxAge) + setTimeout(function () { + assert.isUndefined(cache.get('item1')); + assert.isUndefined(cache.get('item2')); + cache.put('item1', 1); + cache.put('item2', 2); + cache.setOptions({ maxAge: null, deleteOnExpire: 'none', recycleFreq: 20 }, false); + assert.equal(cache.info().maxAge, Number.MAX_VALUE); + // The new items should be removed after 500 ms (the new maxAge) + setTimeout(function () { + assert.equal(cache.get('item1'), 1); + assert.isNumber(cache.info('item1').expires); + assert.equal(cache.get('item2'), 2); + assert.isNumber(cache.info('item2').expires); + + cache.setOptions({ maxAge: 1000, deleteOnExpire: 'aggressive', recycleFreq: 20 }, false); + cache.put('item1', 1); + cache.put('item2', 2); + // The new items should be removed after 500 ms (the new maxAge) + setTimeout(function () { + cache.setOptions({ maxAge: 10, deleteOnExpire: 'aggressive' }, false); + assert.isUndefined(cache.get('item1')); + assert.isUndefined(cache.get('item2')); + + done(); + }, 100); + }, 100); + }, 100); + }, 100); + }); + it('should correctly modify the cacheFlushInterval of a cache', function (done) { + var cache = DSCacheFactory('cache'); + assert.isNull(cache.info().cacheFlushInterval); + cache.setOptions({ cacheFlushInterval: 10 }, false); + assert.equal(cache.info().cacheFlushInterval, 10); + cache.put('item1', 1); + cache.put('item2', 2); + // The first items should be removed after 2000 ms + setTimeout(function () { + assert.isUndefined(cache.get('item1')); + assert.isUndefined(cache.get('item2')); + cache.setOptions({ cacheFlushInterval: 20 }, false); + assert.equal(cache.info().cacheFlushInterval, 20); + cache.put('item1', 1); + cache.put('item2', 2); + // The new items should be removed after 500 ms (the new maxAge) + setTimeout(function () { + assert.isUndefined(cache.get('item1')); + assert.isUndefined(cache.get('item2')); + cache.setOptions({ cacheFlushInterval: 20 }); + assert.equal(cache.info().cacheFlushInterval, 20); + cache.put('item1', 1); + cache.put('item2', 2); + // The new items should be removed after 500 ms (the new maxAge) + setTimeout(function () { + assert.isUndefined(cache.get('item1')); + assert.isUndefined(cache.get('item2')); + + done(); + }, 100); + }, 100); + }, 100); + }); + it('should correctly modify the deleteOnExpire of a cache', function (done) { + var cache = DSCacheFactory('cache', { maxAge: 10 }); + assert.equal(cache.info().deleteOnExpire, 'none'); + cache.setOptions({ deleteOnExpire: 'passive' }); + assert.equal(cache.info().deleteOnExpire, 'passive'); + cache.put('item1', 1); + cache.put('item2', 2); + // The first items should be removed after 2000 ms + setTimeout(function () { + assert.isUndefined(cache.get('item1')); + assert.isUndefined(cache.get('item2')); + cache.setOptions({ maxAge: 10, deleteOnExpire: 'aggressive', recycleFreq: 20 }, false); + assert.equal(cache.info().deleteOnExpire, 'aggressive'); + cache.put('item1', 1); + cache.put('item2', 2); + // The new items should be removed after 500 ms (the new maxAge) + setTimeout(function () { + assert.isUndefined(cache.get('item1')); + assert.isUndefined(cache.get('item2')); + + done(); + }, 100); + }, 100); + }); + it('should correctly set configuration to default when "strict" is true', function () { + var cache = DSCacheFactory('cache', { + capacity: 10, + maxAge: 1000, + cacheFlushInterval: 1000, + deleteOnExpire: 'aggressive', + recycleFreq: 20000, + storageMode: 'localStorage' + }); + cache.setOptions({}, true); + var cacheInfo = cache.info(); + assert.equal(cacheInfo.id, 'cache'); + assert.equal(cacheInfo.capacity, Number.MAX_VALUE); + assert.equal(cacheInfo.size, 0); + assert.equal(cacheInfo.recycleFreq, 1000); + assert.equal(cacheInfo.maxAge, Number.MAX_VALUE); + assert.equal(cacheInfo.cacheFlushInterval, null); + assert.equal(cacheInfo.deleteOnExpire, 'none'); + // Storage mode is not dynamically configurable + assert.equal(cacheInfo.storageMode, 'localStorage'); + assert.equal(cacheInfo.onExpire, null); + }); +}); diff --git a/test/unit/DSCacheFactory/DSCacheFactoryProvider.setCacheDefaults.test.js b/test/unit/DSCacheFactory/DSCacheFactoryProvider.setCacheDefaults.test.js new file mode 100644 index 0000000..7f275e0 --- /dev/null +++ b/test/unit/DSCacheFactory/DSCacheFactoryProvider.setCacheDefaults.test.js @@ -0,0 +1,71 @@ +describe('DSCacheFactoryProvider.setCacheDefaults(options)', function () { + it('should have the correct defaults.', function () { + var cache = DSCacheFactory('DSCacheFactoryProvider.setCacheDefaults.cache'); + assert.isDefined(cache); + assert.equal(cache.info().id, 'DSCacheFactoryProvider.setCacheDefaults.cache'); + assert.equal(cache.info().capacity, CACHE_DEFAULTS.capacity); + assert.equal(cache.info().maxAge, CACHE_DEFAULTS.maxAge); + assert.equal(cache.info().cacheFlushInterval, CACHE_DEFAULTS.cacheFlushInterval); + assert.equal(cache.info().deleteOnExpire, CACHE_DEFAULTS.deleteOnExpire); + assert.equal(cache.info().onExpire, CACHE_DEFAULTS.onExpire); + assert.equal(cache.info().recycleFreq, CACHE_DEFAULTS.recycleFreq); + assert.equal(cache.info().storageMode, CACHE_DEFAULTS.storageMode); + assert.equal(cache.info().storageImpl, CACHE_DEFAULTS.storageImpl); + assert.equal(cache.info().disabled, CACHE_DEFAULTS.disabled); + }); + it('should set the default options.', function () { + var options = { + capacity: Math.floor((Math.random() * 100000) + 1), + maxAge: Math.floor((Math.random() * 100000) + 1), + cacheFlushInterval: Math.floor((Math.random() * 100000) + 1), + deleteOnExpire: 'aggressive', + storageMode: 'localStorage', + localStorageImpl: { + setItem: function () { + }, + getItem: function () { + }, + removeItem: function () { + } + }, + verifyIntegrity: false, + recycleFreq: 2000, + disabled: true, + onExpire: function () { + } + }; + DSCacheFactoryProvider.setCacheDefaults(options); + var cache = DSCacheFactory('cache'); + assert.isDefined(cache); + assert.equal(cache.info().id, 'cache'); + assert.equal(cache.info().capacity, options.capacity); + assert.equal(cache.info().maxAge, options.maxAge); + assert.equal(cache.info().cacheFlushInterval, options.cacheFlushInterval); + assert.equal(cache.info().deleteOnExpire, options.deleteOnExpire); + assert.equal(cache.info().storageMode, options.storageMode); + assert.isUndefined(cache.info().localStorageImpl); // We don't expose this to the user + assert.equal(cache.info().onExpire, options.onExpire); + assert.equal(cache.info().disabled, options.disabled); + }); + it('should require options to be an object.', function (done) { + for (var i = 0; i < TYPES_EXCEPT_OBJECT.length; i++) { + if (!TYPES_EXCEPT_OBJECT[i]) { + continue; + } + try { + DSCacheFactoryProvider.setCacheDefaults(TYPES_EXCEPT_OBJECT[i]); + if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { + fail(TYPES_EXCEPT_OBJECT[i]); + } + } catch (err) { + assert.equal(err.message, '[ng:areq] Expected options to be an object! Found: ' + typeof TYPES_EXCEPT_OBJECT[i] + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof TYPES_EXCEPT_OBJECT[i]); + continue; + } + if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { + fail(TYPES_EXCEPT_OBJECT[i]); + } + } + + done(); + }); +}); diff --git a/test/unit/DSCacheFactory/index.clearAll.test.js b/test/unit/DSCacheFactory/index.clearAll.test.js new file mode 100644 index 0000000..1ca16cb --- /dev/null +++ b/test/unit/DSCacheFactory/index.clearAll.test.js @@ -0,0 +1,63 @@ +describe('DSCacheFactory.clearAll()', function () { + it('should call "removeAll()" on all caches.', function (done) { + var cacheKeys = ['DSCacheFactory.clearAll.cache', 'DSCacheFactory.clearAll.cache1', 'DSCacheFactory.clearAll.cache2'], + caches = []; + + caches.push(DSCacheFactory(cacheKeys[0])); + caches[0].put('item', 'value'); + caches[0].put('item2', 'value2'); + caches.push(DSCacheFactory(cacheKeys[1])); + caches[1].put('item', 'value'); + caches[1].put('item2', 'value2'); + caches.push(DSCacheFactory(cacheKeys[2])); + caches[2].put('item', 'value'); + caches[2].put('item2', 'value2'); + + sinon.spy(caches[0], 'removeAll'); + sinon.spy(caches[1], 'removeAll'); + sinon.spy(caches[2], 'removeAll'); + + DSCacheFactory.clearAll(); + + assert.equal(caches[0].removeAll.callCount, 1); + assert.equal(caches[1].removeAll.callCount, 1); + assert.equal(caches[2].removeAll.callCount, 1); + + done(); + }); + it('should result in each cache being cleared.', function (done) { + var cacheKeys = ['DSCacheFactory.clearAll.cache', 'DSCacheFactory.clearAll.cache1', 'DSCacheFactory.clearAll.cache2'], + caches = []; + + caches.push(DSCacheFactory(cacheKeys[0])); + caches[0].put('item', 'value'); + caches[0].put('item2', 'value2'); + caches.push(DSCacheFactory(cacheKeys[1])); + caches[1].put('item', 'value'); + caches[1].put('item2', 'value2'); + caches.push(DSCacheFactory(cacheKeys[2])); + caches[2].put('item', 'value'); + caches[2].put('item2', 'value2'); + + assert.isDefined(caches[0].get('item')); + assert.isDefined(caches[1].get('item')); + assert.isDefined(caches[2].get('item')); + assert.isDefined(caches[0].get('item2')); + assert.isDefined(caches[1].get('item2')); + assert.isDefined(caches[2].get('item2')); + + DSCacheFactory.clearAll(); + + assert.isUndefined(caches[0].get('item')); + assert.isUndefined(caches[1].get('item')); + assert.isUndefined(caches[2].get('item')); + assert.isUndefined(caches[0].get('item2')); + assert.isUndefined(caches[1].get('item2')); + assert.isUndefined(caches[2].get('item2')); + assert.equal(caches[0].info().size, 0); + assert.equal(caches[1].info().size, 0); + assert.equal(caches[2].info().size, 0); + + done(); + }); +}); diff --git a/test/unit/DSCacheFactory/index.destroyAll.test.js b/test/unit/DSCacheFactory/index.destroyAll.test.js new file mode 100644 index 0000000..f16d32b --- /dev/null +++ b/test/unit/DSCacheFactory/index.destroyAll.test.js @@ -0,0 +1,37 @@ +describe('DSCacheFactory.destroyAll()', function () { + it('should call "destroy()" on all caches currently owned by the factory.', function (done) { + var cacheKeys = ['DSCacheFactory.destroyAll.cache', 'DSCacheFactory.destroyAll.cache1', 'DSCacheFactory.destroyAll.cache2'], + caches = []; + + caches.push(DSCacheFactory(cacheKeys[0])); + caches.push(DSCacheFactory(cacheKeys[1])); + caches.push(DSCacheFactory(cacheKeys[2])); + + sinon.spy(caches[0], 'destroy'); + sinon.spy(caches[1], 'destroy'); + sinon.spy(caches[2], 'destroy'); + DSCacheFactory.destroyAll(); + + assert.equal(caches[0].destroy.callCount, 1); + assert.equal(caches[1].destroy.callCount, 1); + assert.equal(caches[2].destroy.callCount, 1); + + done(); + }); + it('should result in all caches being removed from DSCacheFactory.', function (done) { + var cacheKeys = ['DSCacheFactory.destroyAll.cache', 'DSCacheFactory.destroyAll.cache1', 'DSCacheFactory.destroyAll.cache2'], + caches = []; + + caches.push(DSCacheFactory(cacheKeys[0])); + caches.push(DSCacheFactory(cacheKeys[1])); + caches.push(DSCacheFactory(cacheKeys[2])); + + DSCacheFactory.destroyAll(); + + assert.isUndefined(DSCacheFactory.get(cacheKeys[0])); + assert.isUndefined(DSCacheFactory.get(cacheKeys[1])); + assert.isUndefined(DSCacheFactory.get(cacheKeys[2])); + + done(); + }); +}); diff --git a/test/unit/DSCacheFactory/index.disableAll.test.js b/test/unit/DSCacheFactory/index.disableAll.test.js new file mode 100644 index 0000000..81fde9d --- /dev/null +++ b/test/unit/DSCacheFactory/index.disableAll.test.js @@ -0,0 +1,21 @@ +describe('DSCacheFactory.disableAll()', function () { + it('should disable all caches in DSCacheFactory.', function (done) { + var cacheKeys = ['DSCacheFactory.disableAll.cache', 'DSCacheFactory.disableAll.cache1', 'DSCacheFactory.disableAll.cache2']; + + DSCacheFactory(cacheKeys[0]); + DSCacheFactory(cacheKeys[1], { disabled: true }); + DSCacheFactory(cacheKeys[2]); + + assert.equal(DSCacheFactory.get(cacheKeys[0]).info().disabled, false); + assert.equal(DSCacheFactory.get(cacheKeys[1]).info().disabled, true); + assert.equal(DSCacheFactory.get(cacheKeys[2]).info().disabled, false); + + DSCacheFactory.disableAll(); + + assert.equal(DSCacheFactory.get(cacheKeys[0]).info().disabled, true); + assert.equal(DSCacheFactory.get(cacheKeys[1]).info().disabled, true); + assert.equal(DSCacheFactory.get(cacheKeys[2]).info().disabled, true); + + done(); + }); +}); diff --git a/test/unit/DSCacheFactory/index.enableAll.test.js b/test/unit/DSCacheFactory/index.enableAll.test.js new file mode 100644 index 0000000..7777d93 --- /dev/null +++ b/test/unit/DSCacheFactory/index.enableAll.test.js @@ -0,0 +1,21 @@ +describe('DSCacheFactory.enableAll()', function () { + it('should enable all caches in DSCacheFactory.', function (done) { + var cacheKeys = ['DSCacheFactory.enableAll.cache', 'DSCacheFactory.enableAll.cache1', 'DSCacheFactory.enableAll.cache2']; + + DSCacheFactory(cacheKeys[0], { disabled: true }); + DSCacheFactory(cacheKeys[1]); + DSCacheFactory(cacheKeys[2], { disabled: true }); + + assert.equal(DSCacheFactory.get(cacheKeys[0]).info().disabled, true); + assert.equal(DSCacheFactory.get(cacheKeys[1]).info().disabled, false); + assert.equal(DSCacheFactory.get(cacheKeys[2]).info().disabled, true); + + DSCacheFactory.enableAll(); + + assert.equal(DSCacheFactory.get(cacheKeys[0]).info().disabled, false); + assert.equal(DSCacheFactory.get(cacheKeys[1]).info().disabled, false); + assert.equal(DSCacheFactory.get(cacheKeys[2]).info().disabled, false); + + done(); + }); +}); diff --git a/test/unit/DSCacheFactory/index.get.test.js b/test/unit/DSCacheFactory/index.get.test.js new file mode 100644 index 0000000..f21edba --- /dev/null +++ b/test/unit/DSCacheFactory/index.get.test.js @@ -0,0 +1,28 @@ +describe('DSCacheFactory.get(cacheId)', function () { + it('should throw an exception if "cacheId" is not a string.', function (done) { + for (var i = 0; i < TYPES_EXCEPT_STRING.length; i++) { + try { + DSCacheFactory.get(TYPES_EXCEPT_STRING[i]); + fail(); + } catch (err) { + assert.equal(err.message, '[ng:areq] Expected cacheId to be a string! Found: ' + typeof TYPES_EXCEPT_STRING[i] + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof TYPES_EXCEPT_STRING[i]); + continue; + } + fail(); + } + done(); + }); + it('should return "undefined" if the cache does not exist.', function (done) { + assert.isUndefined(DSCacheFactory.get('someNonExistentCache')); + done(); + }); + it('should return the correct cache with the specified cacheId.', function (done) { + var cache = DSCacheFactory('DSCacheFactory.get.cache'), + cache2 = DSCacheFactory('DSCacheFactory.get.cache2'); + assert.equal(DSCacheFactory.get('DSCacheFactory.get.cache'), cache); + assert.equal(DSCacheFactory.get('DSCacheFactory.get.cache2'), cache2); + assert.notEqual(cache, cache2); + + done(); + }); +}); diff --git a/test/unit/DSCacheFactory/index.info.test.js b/test/unit/DSCacheFactory/index.info.test.js new file mode 100644 index 0000000..9bbdea9 --- /dev/null +++ b/test/unit/DSCacheFactory/index.info.test.js @@ -0,0 +1,46 @@ +describe('DSCacheFactory.info()', function () { + it('should return the correct info for DSCacheFactory.', function (done) { + var options = { + capacity: Math.floor((Math.random() * 100000) + 1), + maxAge: Math.floor((Math.random() * 100000) + 1), + cacheFlushInterval: Math.floor((Math.random() * 100000) + 1) + }, + caches = []; + + caches.push(DSCacheFactory('cache')); + caches.push(DSCacheFactory('cache2', { + maxAge: options.maxAge + })); + caches.push(DSCacheFactory('cache3', { + capacity: options.capacity, + cacheFlushInterval: options.cacheFlushInterval + })); + var info = DSCacheFactory.info(); + assert.equal(info.size, 3); + + assert.equal(info.cacheDefaults.capacity, CACHE_DEFAULTS.capacity); + assert.equal(info.cacheDefaults.maxAge, CACHE_DEFAULTS.maxAge); + assert.equal(info.cacheDefaults.cacheFlushInterval, CACHE_DEFAULTS.cacheFlushInterval); + assert.equal(info.cacheDefaults.deleteOnExpire, CACHE_DEFAULTS.deleteOnExpire); + assert.equal(info.cacheDefaults.onExpire, CACHE_DEFAULTS.onExpire); + assert.equal(info.cacheDefaults.recycleFreq, CACHE_DEFAULTS.recycleFreq); + assert.equal(info.cacheDefaults.storageMode, CACHE_DEFAULTS.storageMode); + assert.equal(info.cacheDefaults.storageImpl, CACHE_DEFAULTS.storageImpl); + + assert.equal(info.caches.cache.id, caches[0].info().id); + assert.equal(info.caches.cache.capacity, caches[0].info().capacity); + assert.equal(info.caches.cache.size, caches[0].info().size); + + assert.equal(info.caches.cache2.id, caches[1].info().id); + assert.equal(info.caches.cache2.capacity, caches[1].info().capacity); + assert.equal(info.caches.cache2.size, caches[1].info().size); + assert.equal(info.caches.cache2.maxAge, caches[1].info().maxAge); + + assert.equal(info.caches.cache3.id, caches[2].info().id); + assert.equal(info.caches.cache3.capacity, caches[2].info().capacity); + assert.equal(info.caches.cache3.size, caches[2].info().size); + assert.equal(info.caches.cache3.cacheFlushInterval, caches[2].info().cacheFlushInterval); + + done(); + }); +}); diff --git a/test/unit/DSCacheFactory/index.keySet.test.js b/test/unit/DSCacheFactory/index.keySet.test.js new file mode 100644 index 0000000..078f351 --- /dev/null +++ b/test/unit/DSCacheFactory/index.keySet.test.js @@ -0,0 +1,50 @@ +describe('DSCacheFactory.keySet()', function () { + it('should return the set of keys of all caches in DSCacheFactory.', function (done) { + var cacheKeys = ['DSCacheFactory.keySet.cache', 'DSCacheFactory.keySet.cache1', 'DSCacheFactory.keySet.cache2']; + + DSCacheFactory(cacheKeys[0]); + DSCacheFactory(cacheKeys[1]); + DSCacheFactory(cacheKeys[2]); + + var keySet = DSCacheFactory.keySet(); + + assert.equal(keySet.hasOwnProperty(cacheKeys[0]), true); + assert.equal(keySet.hasOwnProperty(cacheKeys[1]), true); + assert.equal(keySet.hasOwnProperty(cacheKeys[2]), true); + + assert.equal(keySet[cacheKeys[0]], cacheKeys[0]); + assert.equal(keySet[cacheKeys[1]], cacheKeys[1]); + assert.equal(keySet[cacheKeys[2]], cacheKeys[2]); + + DSCacheFactory.get(cacheKeys[0]).destroy(); + keySet = DSCacheFactory.keySet(); + assert.equal(keySet.hasOwnProperty(cacheKeys[0]), false); + assert.equal(keySet.hasOwnProperty(cacheKeys[1]), true); + assert.equal(keySet.hasOwnProperty(cacheKeys[2]), true); + assert.isUndefined(keySet[cacheKeys[0]]); + assert.equal(keySet[cacheKeys[1]], cacheKeys[1]); + assert.equal(keySet[cacheKeys[2]], cacheKeys[2]); + + DSCacheFactory.get(cacheKeys[1]).destroy(); + keySet = DSCacheFactory.keySet(); + assert.equal(keySet.hasOwnProperty(cacheKeys[0]), false); + assert.equal(keySet.hasOwnProperty(cacheKeys[1]), false); + assert.equal(keySet.hasOwnProperty(cacheKeys[2]), true); + assert.isUndefined(keySet[cacheKeys[0]]); + assert.isUndefined(keySet[cacheKeys[1]]); + assert.equal(keySet[cacheKeys[2]], cacheKeys[2]); + + DSCacheFactory.get(cacheKeys[2]).destroy(); + + keySet = DSCacheFactory.keySet(); + + assert.equal(keySet.hasOwnProperty(cacheKeys[0]), false); + assert.equal(keySet.hasOwnProperty(cacheKeys[1]), false); + assert.equal(keySet.hasOwnProperty(cacheKeys[2]), false); + assert.isUndefined(keySet[cacheKeys[0]]); + assert.isUndefined(keySet[cacheKeys[1]]); + assert.isUndefined(keySet[cacheKeys[2]]); + + done(); + }); +}); diff --git a/test/unit/DSCacheFactory/index.keys.test.js b/test/unit/DSCacheFactory/index.keys.test.js new file mode 100644 index 0000000..a0c95ad --- /dev/null +++ b/test/unit/DSCacheFactory/index.keys.test.js @@ -0,0 +1,34 @@ +describe('DSCacheFactory.keys()', function () { + it('should return the array of keys of all caches in DSCacheFactory.', function (done) { + var cacheKeys = ['cache', 'cache1', 'cache2']; + + DSCacheFactory(cacheKeys[0]); + DSCacheFactory(cacheKeys[1]); + DSCacheFactory(cacheKeys[2]); + + var keys = DSCacheFactory.keys(); + assert.equal(keys.length, 3); + assert.equal(keys[0], cacheKeys[0]); + assert.equal(keys[1], cacheKeys[1]); + assert.equal(keys[2], cacheKeys[2]); + + DSCacheFactory.get(cacheKeys[0]).destroy(); + keys = DSCacheFactory.keys(); + assert.equal(keys.length, 2); + assert.notEqual(keys.indexOf(cacheKeys[1]), -1); + assert.notEqual(keys.indexOf(cacheKeys[2]), -1); + + DSCacheFactory.get(cacheKeys[1]).destroy(); + keys = DSCacheFactory.keys(); + assert.equal(keys.length, 1); + assert.notEqual(keys.indexOf(cacheKeys[2]), -1); + + DSCacheFactory.get(cacheKeys[2]).destroy(); + + keys = DSCacheFactory.keys(); + + assert.equal(keys.length, 0); + + done(); + }); +}); diff --git a/test/unit/DSCacheFactory/index.test.js b/test/unit/DSCacheFactory/index.test.js new file mode 100644 index 0000000..a66d271 --- /dev/null +++ b/test/unit/DSCacheFactory/index.test.js @@ -0,0 +1,321 @@ +describe('DSCacheFactory(cacheId, options)', function () { + it('should be able to create a default cache.', function (done) { + var cache = DSCacheFactory('DSCacheFactory.cache'); + assert.isDefined(cache); + assert.equal(cache.info().id, 'DSCacheFactory.cache'); + assert.equal(cache.info().capacity, CACHE_DEFAULTS.capacity); + assert.equal(cache.info().maxAge, CACHE_DEFAULTS.maxAge); + assert.equal(cache.info().cacheFlushInterval, CACHE_DEFAULTS.cacheFlushInterval); + assert.equal(cache.info().deleteOnExpire, CACHE_DEFAULTS.deleteOnExpire); + assert.equal(cache.info().onExpire, CACHE_DEFAULTS.onExpire); + assert.equal(cache.info().recycleFreq, CACHE_DEFAULTS.recycleFreq); + assert.equal(cache.info().storageMode, CACHE_DEFAULTS.storageMode); + assert.equal(cache.info().storageImpl, CACHE_DEFAULTS.storageImpl); + + done(); + }); + it('should be able to create a cache with options.', function (done) { + var options = { + capacity: Math.floor((Math.random() * 100000) + 1), + maxAge: Math.floor((Math.random() * 100000) + 1), + cacheFlushInterval: Math.floor((Math.random() * 100000) + 1), + deleteOnExpire: 'aggressive', + storageMode: 'localStorage', + localStorageImpl: { + setItem: function () { + }, + getItem: function () { + }, + removeItem: function () { + } + }, + recycleFreq: 2000, + onExpire: function () { + }, + readOnGet: false + }; + var cache = DSCacheFactory('DSCacheFactory.cache', options); + assert.isDefined(cache); + assert.equal(cache.info().id, 'DSCacheFactory.cache'); + assert.equal(cache.info().capacity, options.capacity); + assert.equal(cache.info().maxAge, options.maxAge); + assert.equal(cache.info().cacheFlushInterval, options.cacheFlushInterval); + assert.equal(cache.info().deleteOnExpire, options.deleteOnExpire); + assert.equal(cache.info().storageMode, options.storageMode); + assert.isUndefined(cache.info().localStorageImpl); // We don't expose this to the user + assert.equal(cache.info().onExpire, options.onExpire); + done(); + }); + it('should throw an exception if "capacity" is not a number or is less than zero.', function (done) { + var capacity = Math.floor((Math.random() * 100000) + 1) * -1; + try { + DSCacheFactory('capacityCache99', { capacity: capacity }); + fail(); + } catch (err) { + var msg = err.message; + } + assert.equal(msg, '[ng:areq] Expected capacity to be greater than zero! Found: ' + capacity + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + capacity); + for (var i = 0; i < TYPES_EXCEPT_NUMBER.length; i++) { + if (TYPES_EXCEPT_NUMBER[i] === null) { + continue; + } + try { + DSCacheFactory('capacityCache' + i, { capacity: TYPES_EXCEPT_NUMBER[i] }); + fail(); + } catch (err) { + assert.equal(err.message, '[ng:areq] Expected capacity to be a number! Found: ' + typeof TYPES_EXCEPT_NUMBER[i] + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof TYPES_EXCEPT_NUMBER[i]); + continue; + } + fail(); + } + done(); + }); + it('should validate maxAge.', function (done) { + var maxAge = Math.floor((Math.random() * 100000) + 1) * -1; + try { + DSCacheFactory('maxAgeCache99', { maxAge: maxAge }); + fail(); + } catch (err) { + var msg = err.message; + } + assert.equal(msg, '[ng:areq] Expected maxAge to be greater than zero! Found: ' + maxAge + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + maxAge); + for (var i = 0; i < TYPES_EXCEPT_NUMBER.length; i++) { + try { + DSCacheFactory('maxAgeCache' + i, { maxAge: TYPES_EXCEPT_NUMBER[i] }); + if (TYPES_EXCEPT_NUMBER[i] !== null) { + fail(TYPES_EXCEPT_NUMBER[i]); + } + } catch (err) { + assert.equal(err.message, '[ng:areq] Expected maxAge to be a number! Found: ' + typeof TYPES_EXCEPT_NUMBER[i] + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof TYPES_EXCEPT_NUMBER[i]); + continue; + } + if (TYPES_EXCEPT_NUMBER[i] !== null) { + fail(TYPES_EXCEPT_NUMBER[i]); + } + } + + done(); + }); + it('should validate cacheFlushInterval.', function (done) { + var cacheFlushInterval = Math.floor((Math.random() * 100000) + 1) * -1; + try { + DSCacheFactory('cacheFlushIntervalCache99', { cacheFlushInterval: cacheFlushInterval }); + fail(); + } catch (err) { + var msg = err.message; + } + assert.equal(msg, '[ng:areq] Expected cacheFlushInterval to be greater than zero! Found: ' + cacheFlushInterval + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + cacheFlushInterval); + for (var i = 0; i < TYPES_EXCEPT_NUMBER.length; i++) { + try { + DSCacheFactory('cacheFlushIntervalCache' + i, { cacheFlushInterval: TYPES_EXCEPT_NUMBER[i] }); + if (TYPES_EXCEPT_NUMBER[i] !== null) { + fail(); + } + } catch (err) { + assert.equal(err.message, '[ng:areq] Expected cacheFlushInterval to be a number! Found: ' + typeof TYPES_EXCEPT_NUMBER[i] + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof TYPES_EXCEPT_NUMBER[i]); + continue; + } + if (TYPES_EXCEPT_NUMBER[i] !== null) { + fail(); + } + } + + done(); + }); + it('should validate recycleFreq.', function (done) { + var recycleFreq = Math.floor((Math.random() * 100000) + 1) * -1; + try { + DSCacheFactory('recycleFreqCache99', { recycleFreq: recycleFreq }); + fail(); + } catch (err) { + var msg = err.message; + } + assert.equal(msg, '[ng:areq] Expected recycleFreq to be greater than zero! Found: ' + recycleFreq + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + recycleFreq); + for (var i = 0; i < TYPES_EXCEPT_NUMBER.length; i++) { + try { + DSCacheFactory('recycleFreqCache' + i, { recycleFreq: TYPES_EXCEPT_NUMBER[i] }); + if (TYPES_EXCEPT_NUMBER[i] !== null) { + fail(); + } + } catch (err) { + assert.equal(err.message, '[ng:areq] Expected recycleFreq to be a number! Found: ' + typeof TYPES_EXCEPT_NUMBER[i] + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof TYPES_EXCEPT_NUMBER[i]); + continue; + } + if (TYPES_EXCEPT_NUMBER[i] !== null) { + fail(); + } + } + + done(); + }); + it('should validate onExpire.', function (done) { + var onExpire = 234; + try { + DSCacheFactory('onExpireCache99', { onExpire: onExpire }); + assert.equal('should not reach this!', false); + } catch (err) { + var msg = err.message; + } + assert.equal(msg, '[ng:areq] Expected onExpire to be a function! Found: ' + typeof onExpire + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof onExpire); + for (var i = 0; i < TYPES_EXCEPT_FUNCTION.length; i++) { + try { + if (TYPES_EXCEPT_FUNCTION[i]) { + DSCacheFactory('onExpireCache' + i, { onExpire: TYPES_EXCEPT_FUNCTION[i] }); + } else { + continue; + } + } catch (err) { + assert.equal(err.message, '[ng:areq] Expected onExpire to be a function! Found: ' + typeof TYPES_EXCEPT_FUNCTION[i] + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof TYPES_EXCEPT_FUNCTION[i]); + continue; + } + if (TYPES_EXCEPT_FUNCTION[i] !== null) { + fail(); + } + } + + done(); + }); + it('should validate deleteOnExpire.', function (done) { + var deleteOnExpire = 'fail'; + try { + DSCacheFactory('cache', { deleteOnExpire: deleteOnExpire }); + fail('should not reach this!'); + } catch (err) { + var msg = err.message; + } + assert.equal(msg, '[ng:areq] Expected deleteOnExpire to be "none", "passive" or "aggressive"! Found: ' + deleteOnExpire + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + deleteOnExpire); + for (var i = 0; i < TYPES_EXCEPT_STRING.length; i++) { + if (TYPES_EXCEPT_STRING[i] === null) { + continue; + } + try { + DSCacheFactory('deleteOnExpireCache' + i, { deleteOnExpire: TYPES_EXCEPT_STRING[i] }); + fail(TYPES_EXCEPT_STRING[i]); + } catch (err) { + assert.equal(err.message, '[ng:areq] Expected deleteOnExpire to be a string! Found: ' + typeof TYPES_EXCEPT_STRING[i] + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof TYPES_EXCEPT_STRING[i]); + continue; + } + fail(TYPES_EXCEPT_STRING[i]); + } + + done(); + }); + it('should validate storageMode.', function (done) { + var storageMode = 'fail'; + try { + DSCacheFactory('cache', { storageMode: storageMode }); + assert.equal('should not reach this!', false); + } catch (err) { + var msg = err.message; + } + assert.equal(msg, '[ng:areq] Expected storageMode to be "memory", "localStorage" or "sessionStorage"! Found: ' + storageMode + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + storageMode); + for (var i = 0; i < TYPES_EXCEPT_STRING.length; i++) { + try { + DSCacheFactory('storageModeCache' + i, { storageMode: TYPES_EXCEPT_STRING[i] }); + fail(TYPES_EXCEPT_STRING[i]); + } catch (err) { + assert.equal(err.message, '[ng:areq] Expected storageMode to be a string! Found: ' + typeof TYPES_EXCEPT_STRING[i] + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof TYPES_EXCEPT_STRING[i]); + continue; + } + fail(TYPES_EXCEPT_STRING[i]); + } + + done(); + }); + it('should validate storageImpl.', function (done) { + var storageImpl = 'fail'; + try { + DSCacheFactory('cache', { storageMode: 'localStorage', storageImpl: storageImpl }); + assert.equal('should not reach this!', false); + } catch (err) { + var msg = err.message; + } + assert.equal(msg, '[ng:areq] Expected storageImpl to be an object! Found: ' + typeof storageImpl + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof storageImpl); + for (var i = 0; i < TYPES_EXCEPT_OBJECT.length; i++) { + try { + DSCacheFactory('storageImplCache' + i, { storageMode: 'localStorage', storageImpl: TYPES_EXCEPT_OBJECT[i] }); + if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { + fail(TYPES_EXCEPT_OBJECT[i]); + } + } catch (err) { + assert.equal(err.message, '[ng:areq] Expected storageImpl to be an object! Found: ' + typeof TYPES_EXCEPT_OBJECT[i] + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof TYPES_EXCEPT_OBJECT[i]); + continue; + } + if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { + fail(TYPES_EXCEPT_OBJECT[i]); + } + } + try { + storageImpl = { + getItem: function () { + }, + removeItem: function () { + } + }; + DSCacheFactory('storageImplCache-noSetItem', { + storageMode: 'localStorage', + storageImpl: storageImpl + }); + fail(); + } catch (err) { + assert.equal(err.message, '[ng:areq] Expected storageImpl to implement "setItem(key, value)"! Found: ' + typeof storageImpl.setItem + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof storageImpl.setItem); + } + try { + storageImpl = { + setItem: function () { + }, + removeItem: function () { + } + }; + DSCacheFactory('storageImplCache-noGetItem', { + storageMode: 'localStorage', + storageImpl: storageImpl + }); + fail(); + } catch (err) { + assert.equal(err.message, '[ng:areq] Expected storageImpl to implement "getItem(key)"! Found: ' + typeof storageImpl.getItem + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof storageImpl.getItem); + } + try { + storageImpl = { + getItem: function () { + }, + setItem: function () { + } + }; + DSCacheFactory('storageImplCache-noRemoveItem', { + storageMode: 'localStorage', + storageImpl: storageImpl + }); + fail(); + } catch (err) { + assert.equal(err.message, '[ng:areq] Expected storageImpl to implement "removeItem(key)"! Found: ' + typeof storageImpl.removeItem + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof storageImpl.removeItem); + } + + done(); + }); + it('should prevent a cache from being duplicated.', function (done) { + try { + DSCacheFactory('cache'); + DSCacheFactory('cache'); + } catch (err) { + var msg = err.message; + } + assert.equal(msg, '[$cacheFactory:iid] CacheId \'cache\' is already taken!\nhttp://errors.angularjs.org/' + angular.version.full + '/$cacheFactory/iid?p0=cache'); + + done(); + }); + it('should require cacheId to be a string.', function (done) { + for (var i = 0; i < TYPES_EXCEPT_STRING.length; i++) { + try { + DSCacheFactory(TYPES_EXCEPT_STRING[i]); + fail(TYPES_EXCEPT_STRING[i]); + } catch (err) { + assert.equal(err.message, '[ng:areq] Expected cacheId to be a string! Found: ' + typeof TYPES_EXCEPT_STRING[i] + '.\nhttp://errors.angularjs.org/' + angular.version.full + '/ng/areq?p0=' + typeof TYPES_EXCEPT_STRING[i]); + continue; + } + fail(TYPES_EXCEPT_STRING[i]); + } + + done(); + }); +}); diff --git a/test/unit/defaults/index.test.js b/test/unit/defaults/index.test.js new file mode 100644 index 0000000..ba7e20c --- /dev/null +++ b/test/unit/defaults/index.test.js @@ -0,0 +1,5 @@ +describe('Defaults', function () { + it('should have the correct defaults on the prototype.', function () { + var Defaults = require('Defaults'); + }); +}); diff --git a/test/vendor/angular-1.1.5.min.js b/test/vendor/angular-1.1.5.min.js deleted file mode 100644 index ac033dc..0000000 --- a/test/vendor/angular-1.1.5.min.js +++ /dev/null @@ -1,178 +0,0 @@ -/* - AngularJS v1.1.5 - (c) 2010-2012 Google, Inc. http://angularjs.org - License: MIT -*/ -(function(M,T,p){'use strict';function lc(){var b=M.angular;M.angular=mc;return b}function Xa(b){return!b||typeof b.length!=="number"?!1:typeof b.hasOwnProperty!="function"&&typeof b.constructor!="function"?!0:b instanceof R||ga&&b instanceof ga||Ea.call(b)!=="[object Object]"||typeof b.callee==="function"}function n(b,a,c){var d;if(b)if(H(b))for(d in b)d!="prototype"&&d!="length"&&d!="name"&&b.hasOwnProperty(d)&&a.call(c,b[d],d);else if(b.forEach&&b.forEach!==n)b.forEach(a,c);else if(Xa(b))for(d= -0;d=0&&b.splice(c,1);return a}function V(b,a){if(sa(b)||b&&b.$evalAsync&&b.$watch)throw Error("Can't copy Window or Scope");if(a){if(b===a)throw Error("Can't copy equivalent objects or arrays");if(F(b))for(var c=a.length=0;c2?ka.call(arguments,2):[];return H(a)&&!(a instanceof RegExp)?c.length?function(){return arguments.length?a.apply(b,c.concat(ka.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}:a}function qc(b,a){var c=a;/^\$+/.test(b)?c=p:sa(a)?c="$WINDOW":a&&T===a?c="$DOCUMENT":a&&a.$evalAsync&& -a.$watch&&(c="$SCOPE");return c}function ha(b,a){return JSON.stringify(b,qc,a?" ":null)}function ub(b){return E(b)?JSON.parse(b):b}function ua(b){b&&b.length!==0?(b=I(""+b),b=!(b=="f"||b=="0"||b=="false"||b=="no"||b=="n"||b=="[]")):b=!1;return b}function va(b){b=w(b).clone();try{b.html("")}catch(a){}var c=w("
").append(b).html();try{return b[0].nodeType===3?I(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+I(b)})}catch(d){return I(c)}}function vb(b){var a={},c,d;n((b|| -"").split("&"),function(b){b&&(c=b.split("="),d=decodeURIComponent(c[0]),a[d]=B(c[1])?decodeURIComponent(c[1]):!0)});return a}function wb(b){var a=[];n(b,function(b,d){a.push(wa(d,!0)+(b===!0?"":"="+wa(b,!0)))});return a.length?a.join("&"):""}function ab(b){return wa(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function wa(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,a?"%20":"+")}function rc(b, -a){function c(a){a&&d.push(a)}var d=[b],e,g,i=["ng:app","ng-app","x-ng-app","data-ng-app"],f=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;n(i,function(a){i[a]=!0;c(T.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(n(b.querySelectorAll("."+a),c),n(b.querySelectorAll("."+a+"\\:"),c),n(b.querySelectorAll("["+a+"]"),c))});n(d,function(a){if(!e){var b=f.exec(" "+a.className+" ");b?(e=a,g=(b[2]||"").replace(/\s+/g,",")):n(a.attributes,function(b){if(!e&&i[b.name])e=a,g=b.value})}});e&&a(e,g?[g]:[])} -function xb(b,a){var c=function(){b=w(b);a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");var c=yb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector","$animator",function(a,b,c,d,e){a.$apply(function(){b.data("$injector",d);c(b)(a)});e.enabled(!0)}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;if(M&&!d.test(M.name))return c();M.name=M.name.replace(d,"");Ha.resumeBootstrap=function(b){n(b,function(b){a.push(b)});c()}}function bb(b,a){a=a||"_";return b.replace(sc, -function(b,d){return(d?a:"")+b.toLowerCase()})}function cb(b,a,c){if(!b)throw Error("Argument '"+(a||"?")+"' is "+(c||"required"));return b}function xa(b,a,c){c&&F(b)&&(b=b[b.length-1]);cb(H(b),a,"not a function, got "+(b&&typeof b=="object"?b.constructor.name||"Object":typeof b));return b}function tc(b){function a(a,b,e){return a[b]||(a[b]=e())}return a(a(b,"angular",Object),"module",function(){var b={};return function(d,e,g){e&&b.hasOwnProperty(d)&&(b[d]=null);return a(b,d,function(){function a(c, -d,e){return function(){b[e||"push"]([c,d,arguments]);return m}}if(!e)throw Error("No module: "+d);var b=[],c=[],j=a("$injector","invoke"),m={_invokeQueue:b,_runBlocks:c,requires:e,name:d,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),animation:a("$animationProvider","register"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider", -"directive"),config:j,run:function(a){c.push(a);return this}};g&&j(g);return m})}})}function Ia(b){return b.replace(uc,function(a,b,d,e){return e?d.toUpperCase():d}).replace(vc,"Moz$1")}function db(b,a){function c(){var e;for(var b=[this],c=a,i,f,h,j,m,k;b.length;){i=b.shift();f=0;for(h=i.length;f 
"+b;a.removeChild(a.firstChild);eb(this,a.childNodes);this.remove()}else eb(this,b)}function fb(b){return b.cloneNode(!0)}function ya(b){zb(b);for(var a=0,b=b.childNodes||[];a-1}function Cb(b,a){a&&n(a.split(" "),function(a){b.className=U((" "+b.className+" ").replace(/[\n\t]/g," ").replace(" "+U(a)+" "," "))})}function Db(b,a){a&&n(a.split(" "),function(a){if(!La(b,a))b.className=U(b.className+" "+U(a))})}function eb(b,a){if(a)for(var a=!a.nodeName&&B(a.length)&&!sa(a)?a:[a],c=0;c 4096 bytes)!")}else{if(h.cookie!==D){D=h.cookie;d=D.split("; ");G={};for(f=0;f0&&(a=unescape(e.substring(0,j)),G[a]===p&&(G[a]=unescape(e.substring(j+1))))}return G}};f.defer=function(a,b){var c;o++;c=k(function(){delete u[c];e(a)},b||0);u[c]=!0;return c};f.defer.cancel=function(a){return u[a]?(delete u[a],l(a),e(q),!0):!1}}function Ec(){this.$get= -["$window","$log","$sniffer","$document",function(b,a,c,d){return new Dc(b,d,a,c)}]}function Fc(){this.$get=function(){function b(b,d){function e(a){if(a!=k){if(l){if(l==a)l=a.n}else l=a;g(a.n,a.p);g(a,k);k=a;k.n=null}}function g(a,b){if(a!=b){if(a)a.p=b;if(b)b.n=a}}if(b in a)throw Error("cacheId "+b+" taken");var i=0,f=t({},d,{id:b}),h={},j=d&&d.capacity||Number.MAX_VALUE,m={},k=null,l=null;return a[b]={put:function(a,b){var c=m[a]||(m[a]={key:a});e(c);if(!C(b))return a in h||i++,h[a]=b,i>j&&this.remove(l.key), -b},get:function(a){var b=m[a];if(b)return e(b),h[a]},remove:function(a){var b=m[a];if(b){if(b==k)k=b.p;if(b==l)l=b.n;g(b.n,b.p);delete m[a];delete h[a];i--}},removeAll:function(){h={};i=0;m={};k=l=null},destroy:function(){m=f=h=null;delete a[b]},info:function(){return t({},f,{size:i})}}}var a={};b.info=function(){var b={};n(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function Gc(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function Jb(b){var a= -{},c="Directive",d=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,e=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,g="Template must have exactly one root element. was: ",i=/^\s*(https?|ftp|mailto|file):/;this.directive=function h(d,e){E(d)?(cb(e,"directive"),a.hasOwnProperty(d)||(a[d]=[],b.factory(d+c,["$injector","$exceptionHandler",function(b,c){var e=[];n(a[d],function(a){try{var g=b.invoke(a);if(H(g))g={compile:S(g)};else if(!g.compile&&g.link)g.compile=S(g.link);g.priority=g.priority||0;g.name=g.name||d;g.require= -g.require||g.controller&&g.name;g.restrict=g.restrict||"A";e.push(g)}catch(h){c(h)}});return e}])),a[d].push(e)):n(d,rb(h));return this};this.urlSanitizationWhitelist=function(a){return B(a)?(i=a,this):i};this.$get=["$injector","$interpolate","$exceptionHandler","$http","$templateCache","$parse","$controller","$rootScope","$document",function(b,j,m,k,l,u,o,z,r){function y(a,b,c){a instanceof w||(a=w(a));n(a,function(b,c){b.nodeType==3&&b.nodeValue.match(/\S+/)&&(a[c]=w(b).wrap("").parent()[0])}); -var d=W(a,b,a,c);return function(b,c){cb(b,"scope");for(var e=c?Ba.clone.call(a):a,j=0,g=e.length;js.priority)break;if(t=s.scope)O("isolated scope",K,s,J),L(t)&&(x(J,"ng-isolate-scope"),K=s),x(J,"ng-scope"),r=r||s;A=s.name;if(t=s.controller)q=q||{},O("'"+A+"' controller",q[A],s,J),q[A]=s;if(t=s.transclude)O("transclusion",G,s,J),G=s,l=s.priority,t=="element"?(Y=w(b),J=c.$$element=w(T.createComment(" "+A+": "+c[A]+" ")),b=J[0],ja(e,w(Y[0]),b),P=y(Y,d,l)):(Y=w(fb(b)).contents(), -J.html(""),P=y(Y,d));if(s.template)if(O("template",W,s,J),W=s,t=H(s.template)?s.template(J,c):s.template,t=Lb(t),s.replace){Y=w("
"+U(t)+"
").contents();b=Y[0];if(Y.length!=1||b.nodeType!==1)throw Error(g+t);ja(e,J,b);A={$attr:{}};a=a.concat(v(b,a.splice(B+1,a.length-(B+1)),A));D(c,A);C=a.length}else J.html(t);if(s.templateUrl)O("template",W,s,J),W=s,k=$(a.splice(B,a.length-B),k,J,c,e,s.replace,P),C=a.length;else if(s.compile)try{na=s.compile(J,c,P),H(na)?h(null,na):na&&h(na.pre,na.post)}catch(I){m(I, -va(J))}if(s.terminal)k.terminal=!0,l=Math.max(l,s.priority)}k.scope=r&&r.scope;k.transclude=G&&P;return k}function G(d,e,j,g){var l=!1;if(a.hasOwnProperty(e))for(var k,e=b.get(e+c),i=0,o=e.length;ik.priority)&&k.restrict.indexOf(j)!=-1)d.push(k),l=!0}catch(u){m(u)}return l}function D(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;n(a,function(d,e){e.charAt(0)!="$"&&(b[e]&&(d+=(e==="style"?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});n(b,function(b,j){j=="class"?(x(e,b),a["class"]= -(a["class"]?a["class"]+" ":"")+b):j=="style"?e.attr("style",e.attr("style")+";"+b):j.charAt(0)!="$"&&!a.hasOwnProperty(j)&&(a[j]=b,d[j]=c[j])})}function $(a,b,c,d,e,j,h){var i=[],o,m,u=c[0],z=a.shift(),r=t({},z,{controller:null,templateUrl:null,transclude:null,scope:null}),z=H(z.templateUrl)?z.templateUrl(c,d):z.templateUrl;c.html("");k.get(z,{cache:l}).success(function(l){var k,z,l=Lb(l);if(j){z=w("
"+U(l)+"
").contents();k=z[0];if(z.length!=1||k.nodeType!==1)throw Error(g+l);l={$attr:{}}; -ja(e,c,k);v(k,a,l);D(d,l)}else k=u,c.html(l);a.unshift(r);o=A(a,k,d,h);for(m=W(c[0].childNodes,h);i.length;){var ea=i.shift(),l=i.shift();z=i.shift();var x=i.shift(),y=k;l!==u&&(y=fb(k),ja(z,w(l),y));o(function(){b(m,ea,y,e,x)},ea,y,e,x)}i=null}).error(function(a,b,c,d){throw Error("Failed to load template: "+d.url);});return function(a,c,d,e,j){i?(i.push(c),i.push(d),i.push(e),i.push(j)):o(function(){b(m,c,d,e,j)},c,d,e,j)}}function K(a,b){return b.priority-a.priority}function O(a,b,c,d){if(b)throw Error("Multiple directives ["+ -b.name+", "+c.name+"] asking for "+a+" on: "+va(d));}function P(a,b){var c=j(b,!0);c&&a.push({priority:0,compile:S(function(a,b){var d=b.parent(),e=d.data("$binding")||[];e.push(c);x(d.data("$binding",e),"ng-binding");a.$watch(c,function(a){b[0].nodeValue=a})})})}function s(a,b,c,d){var e=j(c,!0);e&&b.push({priority:100,compile:S(function(a,b,c){b=c.$$observers||(c.$$observers={});if(e=j(c[d],!0))c[d]=e(a),(b[d]||(b[d]=[])).$$inter=!0,(c.$$observers&&c.$$observers[d].$$scope||a).$watch(e,function(a){c.$set(d, -a)})})})}function ja(a,b,c){var d=b[0],e=d.parentNode,j,g;if(a){j=0;for(g=a.length;j0){var e=O[0],f=e.text;if(f==a||f==b||f==c||f==d||!a&&!b&&!c&&!d)return e}return!1}function f(b, -c,d,f){return(b=i(b,c,d,f))?(a&&!b.json&&e("is not valid json",b),O.shift(),b):!1}function h(a){f(a)||e("is unexpected, expecting ["+a+"]",i())}function j(a,b){return t(function(c,d){return a(c,d,b)},{constant:b.constant})}function m(a,b,c){return t(function(d,e){return a(d,e)?b(d,e):c(d,e)},{constant:a.constant&&b.constant&&c.constant})}function k(a,b,c){return t(function(d,e){return b(d,e,a,c)},{constant:a.constant&&c.constant})}function l(){for(var a=[];;)if(O.length>0&&!i("}",")",";","]")&&a.push(w()), -!f(";"))return a.length==1?a[0]:function(b,c){for(var d,e=0;e","<=",">="))a=k(a,b.fn,x());return a}function n(){for(var a=v(),b;b=f("*","/","%");)a=k(a,b.fn,v());return a}function v(){var a;return f("+")?A():(a=f("-"))?k($,a.fn,v()):(a=f("!"))?j(a.fn,v()):A()}function A(){var a;if(f("("))a=w(),h(")");else if(f("["))a=G();else if(f("{"))a=D();else{var b=f();(a= -b.fn)||e("not a primary expression",b);if(b.json)a.constant=a.literal=!0}for(var c;b=f("(","[",".");)b.text==="("?(a=s(a,c),c=null):b.text==="["?(c=a,a=ma(a)):b.text==="."?(c=a,a=ja(a)):e("IMPOSSIBLE");return a}function G(){var a=[],b=!0;if(g().text!="]"){do{var c=P();a.push(c);c.constant||(b=!1)}while(f(","))}h("]");return t(function(b,c){for(var d=[],e=0;e1;d++){var e=a.shift(),g=b[e];g||(g={},b[e]=g);b=g}return b[a.shift()]=c}function ib(b,a,c){if(!a)return b;for(var a=a.split("."),d,e=b,g=a.length,i=0;ia)for(b in g++,e)e.hasOwnProperty(b)&&!f.hasOwnProperty(b)&&(x--,delete e[b])}else e!==f&&(e=f,g++);return g}, -function(){b(f,e,c)})},$digest:function(){var a,d,e,i,u=this.$$asyncQueue,o,z,r=b,n,x=[],p,v;g("$digest");do{z=!1;for(n=this;u.length;)try{n.$eval(u.shift())}catch(A){c(A)}do{if(i=n.$$watchers)for(o=i.length;o--;)try{if(a=i[o],(d=a.get(n))!==(e=a.last)&&!(a.eq?ia(d,e):typeof d=="number"&&typeof e=="number"&&isNaN(d)&&isNaN(e)))z=!0,a.last=a.eq?V(d):d,a.fn(d,e===f?d:e,n),r<5&&(p=4-r,x[p]||(x[p]=[]),v=H(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,v+="; newVal: "+ha(d)+"; oldVal: "+ha(e),x[p].push(v))}catch(G){c(G)}if(!(i= -n.$$childHead||n!==this&&n.$$nextSibling))for(;n!==this&&!(i=n.$$nextSibling);)n=n.$parent}while(n=i);if(z&&!r--)throw h.$$phase=null,Error(b+" $digest() iterations reached. Aborting!\nWatchers fired in the last 5 iterations: "+ha(x));}while(z||u.length);h.$$phase=null},$destroy:function(){if(!(h==this||this.$$destroyed)){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;if(a.$$childHead==this)a.$$childHead=this.$$nextSibling;if(a.$$childTail==this)a.$$childTail=this.$$prevSibling; -if(this.$$prevSibling)this.$$prevSibling.$$nextSibling=this.$$nextSibling;if(this.$$nextSibling)this.$$nextSibling.$$prevSibling=this.$$prevSibling;this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null}},$eval:function(a,b){return d(a)(this,b)},$evalAsync:function(a){this.$$asyncQueue.push(a)},$apply:function(a){try{return g("$apply"),this.$eval(a)}catch(b){c(b)}finally{h.$$phase=null;try{h.$digest()}catch(d){throw c(d),d;}}},$on:function(a,b){var c=this.$$listeners[a]; -c||(this.$$listeners[a]=c=[]);c.push(b);return function(){c[Ga(c,b)]=null}},$emit:function(a,b){var d=[],e,f=this,g=!1,i={name:a,targetScope:f,stopPropagation:function(){g=!0},preventDefault:function(){i.defaultPrevented=!0},defaultPrevented:!1},h=[i].concat(ka.call(arguments,1)),n,x;do{e=f.$$listeners[a]||d;i.currentScope=f;n=0;for(x=e.length;n7),hasEvent:function(a){if(a=="input"&&Z==9)return!1;if(C(c[a])){var b= -e.createElement("div");c[a]="on"+a in b}return c[a]},csp:e.securityPolicy?e.securityPolicy.isActive:!1,vendorPrefix:g,transitions:h,animations:j}}]}function Zc(){this.$get=S(M)}function Wb(b){var a={},c,d,e;if(!b)return a;n(b.split("\n"),function(b){e=b.indexOf(":");c=I(U(b.substr(0,e)));d=U(b.substr(e+1));c&&(a[c]?a[c]+=", "+d:a[c]=d)});return a}function $c(b,a){var c=ad.exec(b);if(c==null)return!0;var d={protocol:c[2],host:c[4],port:N(c[6])||Oa[c[2]]||null,relativeProtocol:c[2]===p||c[2]===""}, -c=jb.exec(a),c={protocol:c[1],host:c[3],port:N(c[5])||Oa[c[1]]||null};return(d.protocol==c.protocol||d.relativeProtocol)&&d.host==c.host&&(d.port==c.port||d.relativeProtocol&&c.port==Oa[c.protocol])}function Xb(b){var a=L(b)?b:p;return function(c){a||(a=Wb(b));return c?a[I(c)]||null:a}}function Yb(b,a,c){if(H(c))return c(b,a);n(c,function(c){b=c(b,a)});return b}function bd(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d={"Content-Type":"application/json;charset=utf-8"},e=this.defaults= -{transformResponse:[function(d){E(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=ub(d,!0)));return d}],transformRequest:[function(a){return L(a)&&Ea.apply(a)!=="[object File]"?ha(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:d,put:d,patch:d},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN"},g=this.interceptors=[],i=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,d,k,l){function u(a){function c(a){var b= -t({},a,{data:Yb(a.data,a.headers,d.transformResponse)});return 200<=a.status&&a.status<300?b:k.reject(b)}var d={transformRequest:e.transformRequest,transformResponse:e.transformResponse},f={};t(d,a);d.headers=f;d.method=oa(d.method);t(f,e.headers.common,e.headers[I(d.method)],a.headers);(a=$c(d.url,b.url())?b.cookies()[d.xsrfCookieName||e.xsrfCookieName]:p)&&(f[d.xsrfHeaderName||e.xsrfHeaderName]=a);var g=[function(a){var b=Yb(a.data,Xb(f),a.transformRequest);C(a.data)&&delete f["Content-Type"];if(C(a.withCredentials)&& -!C(e.withCredentials))a.withCredentials=e.withCredentials;return o(a,b,f).then(c,c)},p],j=k.when(d);for(n(y,function(a){(a.request||a.requestError)&&g.unshift(a.request,a.requestError);(a.response||a.responseError)&&g.push(a.response,a.responseError)});g.length;)var a=g.shift(),i=g.shift(),j=j.then(a,i);j.success=function(a){j.then(function(b){a(b.data,b.status,b.headers,d)});return j};j.error=function(a){j.then(null,function(b){a(b.data,b.status,b.headers,d)});return j};return j}function o(b,c,g){function j(a, -b,c){n&&(200<=a&&a<300?n.put(s,[a,b,Wb(c)]):n.remove(s));i(b,a,c);d.$$phase||d.$apply()}function i(a,c,d){c=Math.max(c,0);(200<=c&&c<300?l.resolve:l.reject)({data:a,status:c,headers:Xb(d),config:b})}function h(){var a=Ga(u.pendingRequests,b);a!==-1&&u.pendingRequests.splice(a,1)}var l=k.defer(),o=l.promise,n,p,s=z(b.url,b.params);u.pendingRequests.push(b);o.then(h,h);if((b.cache||e.cache)&&b.cache!==!1&&b.method=="GET")n=L(b.cache)?b.cache:L(e.cache)?e.cache:r;if(n)if(p=n.get(s))if(p.then)return p.then(h, -h),p;else F(p)?i(p[1],p[0],V(p[2])):i(p,200,{});else n.put(s,o);p||a(b.method,s,c,j,g,b.timeout,b.withCredentials,b.responseType);return o}function z(a,b){if(!b)return a;var c=[];nc(b,function(a,b){a==null||a==p||(F(a)||(a=[a]),n(a,function(a){L(a)&&(a=ha(a));c.push(wa(b)+"="+wa(a))}))});return a+(a.indexOf("?")==-1?"?":"&")+c.join("&")}var r=c("$http"),y=[];n(g,function(a){y.unshift(E(a)?l.get(a):l.invoke(a))});n(i,function(a,b){var c=E(a)?l.get(a):l.invoke(a);y.splice(b,0,{response:function(a){return c(k.when(a))}, -responseError:function(a){return c(k.reject(a))}})});u.pendingRequests=[];(function(a){n(arguments,function(a){u[a]=function(b,c){return u(t(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){n(arguments,function(a){u[a]=function(b,c,d){return u(t(d||{},{method:a,url:b,data:c}))}})})("post","put");u.defaults=e;return u}]}function cd(){this.$get=["$browser","$window","$document",function(b,a,c){return dd(b,ed,b.defer,a.angular.callbacks,c[0],a.location.protocol.replace(":",""))}]} -function dd(b,a,c,d,e,g){function i(a,b){var c=e.createElement("script"),d=function(){e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;Z?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror=d;e.body.appendChild(c);return d}return function(e,h,j,m,k,l,u,o){function z(){p=-1;t&&t();v&&v.abort()}function r(a,d,e,f){var j=(h.match(jb)||["",g])[1];A&&c.cancel(A);t=v=null;d=j=="file"?e?200:404:d;a(d==1223?204:d,e,f);b.$$completeOutstandingRequest(q)} -var p;b.$$incOutstandingRequestCount();h=h||b.url();if(I(e)=="jsonp"){var x="_"+(d.counter++).toString(36);d[x]=function(a){d[x].data=a};var t=i(h.replace("JSON_CALLBACK","angular.callbacks."+x),function(){d[x].data?r(m,200,d[x].data):r(m,p||-2);delete d[x]})}else{var v=new a;v.open(e,h,!0);n(k,function(a,b){a&&v.setRequestHeader(b,a)});v.onreadystatechange=function(){if(v.readyState==4){var a=v.getAllResponseHeaders(),b=["Cache-Control","Content-Language","Content-Type","Expires","Last-Modified", -"Pragma"];a||(a="",n(b,function(b){var c=v.getResponseHeader(b);c&&(a+=b+": "+c+"\n")}));r(m,p||v.status,v.responseType?v.response:v.responseText,a)}};if(u)v.withCredentials=!0;if(o)v.responseType=o;v.send(j||"")}if(l>0)var A=c(z,l);else l&&l.then&&l.then(z)}}function fd(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4", -posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),SHORTMONTH:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),DAY:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),SHORTDAY:"Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(","),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y", -mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return b===1?"one":"other"}}}}function gd(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,f,h){var j=c.defer(),m=j.promise,k=B(h)&&!h,f=a.defer(function(){try{j.resolve(e())}catch(a){j.reject(a),d(a)}k||b.$apply()},f),h=function(){delete g[m.$$timeoutId]};m.$$timeoutId=f;g[f]=j;m.then(h,h);return m}var g={};e.cancel=function(b){return b&&b.$$timeoutId in -g?(g[b.$$timeoutId].reject("canceled"),a.defer.cancel(b.$$timeoutId)):!1};return e}]}function Zb(b){function a(a,e){return b.factory(a+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+c)}}];a("currency",$b);a("date",ac);a("filter",hd);a("json",id);a("limitTo",jd);a("lowercase",kd);a("number",bc);a("orderBy",cc);a("uppercase",ld)}function hd(){return function(b,a,c){if(!F(b))return b;var d=[];d.check=function(a){for(var b=0;b-1}}var e=function(a,b){if(typeof b=="string"&&b.charAt(0)==="!")return!e(a,b.substr(1));switch(typeof a){case "boolean":case "number":case "string":return c(a,b);case "object":switch(typeof b){case "object":return c(a,b);default:for(var d in a)if(d.charAt(0)!=="$"&&e(a[d],b))return!0}return!1;case "array":for(d= -0;de+1?i="0":(f=i,j=!0)}if(!j){i=(i.split(ec)[1]||"").length;C(e)&&(e=Math.min(Math.max(a.minFrac,i), -a.maxFrac));var i=Math.pow(10,e),b=Math.round(b*i)/i,b=(""+b).split(ec),i=b[0],b=b[1]||"",j=0,m=a.lgSize,k=a.gSize;if(i.length>=m+k)for(var j=i.length-m,l=0;l0||e>-c)e+=c;e===0&&c==-12&&(e=12);return nb(e,a,d)}}function Qa(b,a){return function(c,d){var e=c["get"+b](),g=oa(a?"SHORT"+b:b);return d[g][e]}}function ac(b){function a(a){var b;if(b=a.match(c)){var a=new Date(0),g=0,i=0,f=b[8]?a.setUTCFullYear:a.setFullYear,h=b[8]?a.setUTCHours:a.setHours;b[9]&&(g=N(b[9]+b[10]),i=N(b[9]+b[11]));f.call(a,N(b[1]),N(b[2])-1,N(b[3]));g=N(b[4]||0)-g;i=N(b[5]||0)-i;f= -N(b[6]||0);b=Math.round(parseFloat("0."+(b[7]||0))*1E3);h.call(a,g,i,f,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e){var g="",i=[],f,h,e=e||"mediumDate",e=b.DATETIME_FORMATS[e]||e;E(c)&&(c=md.test(c)?N(c):a(c));Ya(c)&&(c=new Date(c));if(!ra(c))return c;for(;e;)(h=nd.exec(e))?(i=i.concat(ka.call(h,1)),e=i.pop()):(i.push(e),e=null);n(i,function(a){f=od[a];g+=f?f(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g, -"").replace(/''/g,"'")});return g}}function id(){return function(b){return ha(b,!0)}}function jd(){return function(b,a){if(!F(b)&&!E(b))return b;a=N(a);if(E(b))return a?a>=0?b.slice(0,a):b.slice(a,b.length):"";var c=[],d,e;a>b.length?a=b.length:a<-b.length&&(a=-b.length);a>0?(d=0,e=a):(d=b.length+a,e=b.length);for(;dl?(d.$setValidity("maxlength",!1),p):(d.$setValidity("maxlength",!0),a)};d.$parsers.push(e);d.$formatters.push(e)}}function ob(b,a){b="ngClass"+b;return aa(function(c,d,e){function g(b){if(a===!0||c.$index%2===a)h&&!ia(b,h)&&i(h),f(b);h=V(b)}function i(a){L(a)&&!F(a)&&(a=Za(a,function(a,b){if(a)return b}));d.removeClass(F(a)?a.join(" "):a)}function f(a){L(a)&&!F(a)&&(a=Za(a, -function(a,b){if(a)return b}));a&&d.addClass(F(a)?a.join(" "):a)}var h=p;c.$watch(e[b],g,!0);e.$observe("class",function(){var a=c.$eval(e[b]);g(a,a)});b!=="ngClass"&&c.$watch("$index",function(d,g){var h=d&1;h!==g&1&&(h===a?f(c.$eval(e[b])):i(c.$eval(e[b])))})})}var I=function(b){return E(b)?b.toLowerCase():b},oa=function(b){return E(b)?b.toUpperCase():b},Z=N((/msie (\d+)/.exec(I(navigator.userAgent))||[])[1]),w,ga,ka=[].slice,Wa=[].push,Ea=Object.prototype.toString,mc=M.angular,Ha=M.angular||(M.angular= -{}),Aa,hb,ba=["0","0","0"];q.$inject=[];qa.$inject=[];hb=Z<9?function(b){b=b.nodeName?b:b[0];return b.scopeName&&b.scopeName!="HTML"?oa(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};var sc=/[A-Z]/g,pd={full:"1.1.5",major:1,minor:1,dot:5,codeName:"triangle-squarification"},Ka=R.cache={},Ja=R.expando="ng-"+(new Date).getTime(),wc=1,gc=M.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},gb= -M.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},uc=/([\:\-\_]+(.))/g,vc=/^moz([A-Z])/,Ba=R.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;T.readyState==="complete"?setTimeout(a):(this.bind("DOMContentLoaded",a),R(M).bind("load",a))},toString:function(){var b=[];n(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return b>=0?w(this[b]):w(this[this.length+b])},length:0,push:Wa,sort:[].sort, -splice:[].splice},Na={};n("multiple,selected,checked,disabled,readOnly,required,open".split(","),function(b){Na[I(b)]=b});var Gb={};n("input,select,option,textarea,button,form,details".split(","),function(b){Gb[oa(b)]=!0});n({data:Bb,inheritedData:Ma,scope:function(b){return Ma(b,"$scope")},controller:Eb,injector:function(b){return Ma(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:La,css:function(b,a,c){a=Ia(a);if(B(c))b.style[a]=c;else{var d;Z<=8&&(d=b.currentStyle&&b.currentStyle[a], -d===""&&(d="auto"));d=d||b.style[a];Z<=8&&(d=d===""?p:d);return d}},attr:function(b,a,c){var d=I(a);if(Na[d])if(B(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||q).specified?d:p;else if(B(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),b===null?p:b},prop:function(b,a,c){if(B(c))b[a]=c;else return b[a]},text:t(Z<9?function(b,a){if(b.nodeType==1){if(C(a))return b.innerText;b.innerText=a}else{if(C(a))return b.nodeValue; -b.nodeValue=a}}:function(b,a){if(C(a))return b.textContent;b.textContent=a},{$dv:""}),val:function(b,a){if(C(a))return b.value;b.value=a},html:function(b,a){if(C(a))return b.innerHTML;for(var c=0,d=b.childNodes;c0||parseFloat(h[a+"Duration"])> -0)g="animation",i=a,j=Math.max(parseInt(h[g+"IterationCount"])||0,parseInt(h[i+"IterationCount"])||0,j);f=Math.max(x(h[g+"Delay"]),x(h[i+"Delay"]));g=Math.max(x(h[g+"Duration"]),x(h[i+"Duration"]));d=Math.max(f+j*g,d)}});e.setTimeout(v,d*1E3)}else v()}function v(){if(!v.run)v.run=!0,o(m,r,p),m.removeClass(w),m.removeClass(K),m.removeData(a)}var A=c.$eval(i.ngAnimate),w=A?L(A)?A[j]:A+"-"+j:"",D=d(w),A=D&&D.setup,$=D&&D.start,D=D&&D.cancel;if(w){var K=w+"-active";r||(r=p?p.parent():m.parent());if(!g.transitions&& -!A&&!$||(r.inheritedData(a)||q).running)k(m,r,p),o(m,r,p);else{var O=m.data(a)||{};O.running&&((D||q)(m),O.done());m.data(a,{running:!0,done:v});m.addClass(w);k(m,r,p);if(m.length==0)return v();var P=(A||q)(m);e.setTimeout(t,1)}}else k(m,r,p),o(m,r,p)}}function m(a,c,d){d?d.after(a):c.append(a)}var k={};k.enter=j("enter",m,q);k.leave=j("leave",q,function(a){a.remove()});k.move=j("move",function(a,c,d){m(a,c,d)},q);k.show=j("show",function(a){a.css("display","")},q);k.hide=j("hide",q,function(a){a.css("display", -"none")});k.animate=function(a,c){j(a,q,q)(c)};return k};i.enabled=function(a){if(arguments.length)c.running=!a;return!c.running};return i}]},Kb="Non-assignable model expression: ";Jb.$inject=["$provide"];var Ic=/^(x[\:\-_]|data[\:\-_])/i,jb=/^([^:]+):\/\/(\w+:{0,1}\w*@)?(\{?[\w\.-]*\}?)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,Pb=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,Oa={http:80,https:443,ftp:21};Rb.prototype=lb.prototype=Qb.prototype={$$replace:!1,absUrl:Pa("$$absUrl"),url:function(a,c){if(C(a))return this.$$url; -var d=Pb.exec(a);d[1]&&this.path(decodeURIComponent(d[1]));if(d[2]||d[1])this.search(d[3]||"");this.hash(d[5]||"",c);return this},protocol:Pa("$$protocol"),host:Pa("$$host"),port:Pa("$$port"),path:Sb("$$path",function(a){return a.charAt(0)=="/"?a:"/"+a}),search:function(a,c){if(C(a))return this.$$search;B(c)?c===null?delete this.$$search[a]:this.$$search[a]=c:this.$$search=E(a)?vb(a):a;this.$$compose();return this},hash:Sb("$$hash",qa),replace:function(){this.$$replace=!0;return this}};var Da={"null":function(){return null}, -"true":function(){return!0},"false":function(){return!1},undefined:q,"+":function(a,c,d,e){d=d(a,c);e=e(a,c);return B(d)?B(e)?d+e:d:B(e)?e:p},"-":function(a,c,d,e){d=d(a,c);e=e(a,c);return(B(d)?d:0)-(B(e)?e:0)},"*":function(a,c,d,e){return d(a,c)*e(a,c)},"/":function(a,c,d,e){return d(a,c)/e(a,c)},"%":function(a,c,d,e){return d(a,c)%e(a,c)},"^":function(a,c,d,e){return d(a,c)^e(a,c)},"=":q,"===":function(a,c,d,e){return d(a,c)===e(a,c)},"!==":function(a,c,d,e){return d(a,c)!==e(a,c)},"==":function(a, -c,d,e){return d(a,c)==e(a,c)},"!=":function(a,c,d,e){return d(a,c)!=e(a,c)},"<":function(a,c,d,e){return d(a,c)":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Qc={n:"\n",f:"\u000c",r:"\r", -t:"\t",v:"\u000b","'":"'",'"':'"'},mb={},ad=/^(([^:]+):)?\/\/(\w+:{0,1}\w*@)?([\w\.-]*)?(:([0-9]+))?(.*)$/,ed=M.XMLHttpRequest||function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(c){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(d){}throw Error("This browser does not support XMLHttpRequest.");};Zb.$inject=["$provide"];$b.$inject=["$locale"];bc.$inject=["$locale"];var ec=".",od={yyyy:Q("FullYear",4),yy:Q("FullYear", -2,0,!0),y:Q("FullYear",1),MMMM:Qa("Month"),MMM:Qa("Month",!0),MM:Q("Month",2,1),M:Q("Month",1,1),dd:Q("Date",2),d:Q("Date",1),HH:Q("Hours",2),H:Q("Hours",1),hh:Q("Hours",2,-12),h:Q("Hours",1,-12),mm:Q("Minutes",2),m:Q("Minutes",1),ss:Q("Seconds",2),s:Q("Seconds",1),sss:Q("Milliseconds",3),EEEE:Qa("Day"),EEE:Qa("Day",!0),a:function(a,c){return a.getHours()<12?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){var a=-1*a.getTimezoneOffset(),c=a>=0?"+":"";c+=nb(Math[a>0?"floor":"ceil"](a/60),2)+nb(Math.abs(a%60), -2);return c}},nd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,md=/^\d+$/;ac.$inject=["$locale"];var kd=S(I),ld=S(oa);cc.$inject=["$parse"];var rd=S({restrict:"E",compile:function(a,c){Z<=8&&(!c.href&&!c.name&&c.$set("href",""),a.append(T.createComment("IE fix")));return function(a,c){c.bind("click",function(a){c.attr("href")||a.preventDefault()})}}}),pb={};n(Na,function(a,c){var d=da("ng-"+c);pb[d]=function(){return{priority:100,compile:function(){return function(a, -g,i){a.$watch(i[d],function(a){i.$set(c,!!a)})}}}}});n(["src","srcset","href"],function(a){var c=da("ng-"+a);pb[c]=function(){return{priority:99,link:function(d,e,g){g.$observe(c,function(c){c&&(g.$set(a,c),Z&&e.prop(a,g[a]))})}}}});var Ta={$addControl:q,$removeControl:q,$setValidity:q,$setDirty:q,$setPristine:q};fc.$inject=["$element","$attrs","$scope"];var Wa=function(a){return["$timeout",function(c){var d={name:"form",restrict:"E",controller:fc,compile:function(){return{pre:function(a,d,i,f){if(!i.action){var h= -function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};gc(d[0],"submit",h);d.bind("$destroy",function(){c(function(){gb(d[0],"submit",h)},0,!1)})}var j=d.parent().controller("form"),m=i.name||i.ngForm;m&&(a[m]=f);j&&d.bind("$destroy",function(){j.$removeControl(f);m&&(a[m]=p);t(f,Ta)})}}}};return a?t(V(d),{restrict:"EAC"}):d}]},sd=Wa(),td=Wa(!0),ud=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,vd=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/, -wd=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,hc={text:Va,number:function(a,c,d,e,g,i){Va(a,c,d,e,g,i);e.$parsers.push(function(a){var c=X(a);return c||wd.test(a)?(e.$setValidity("number",!0),a===""?null:c?a:parseFloat(a)):(e.$setValidity("number",!1),p)});e.$formatters.push(function(a){return X(a)?"":""+a});if(d.min){var f=parseFloat(d.min),a=function(a){return!X(a)&&ah?(e.$setValidity("max",!1),p):(e.$setValidity("max",!0),a)};e.$parsers.push(d);e.$formatters.push(d)}e.$formatters.push(function(a){return X(a)||Ya(a)?(e.$setValidity("number",!0),a):(e.$setValidity("number",!1),p)})},url:function(a,c,d,e,g,i){Va(a,c,d,e,g,i);a=function(a){return X(a)||ud.test(a)?(e.$setValidity("url",!0),a):(e.$setValidity("url",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a,c,d,e,g,i){Va(a,c,d,e,g,i);a=function(a){return X(a)||vd.test(a)? -(e.$setValidity("email",!0),a):(e.$setValidity("email",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){C(d.name)&&c.attr("name",Fa());c.bind("click",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var g=d.ngTrueValue,i=d.ngFalseValue;E(g)||(g=!0);E(i)||(i=!1);c.bind("click",function(){a.$apply(function(){e.$setViewValue(c[0].checked)})}); -e.$render=function(){c[0].checked=e.$viewValue};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:i})},hidden:q,button:q,submit:q,reset:q},ic=["$browser","$sniffer",function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,i){i&&(hc[I(g.type)]||hc.text)(d,e,g,i,c,a)}}}],Sa="ng-valid",Ra="ng-invalid",pa="ng-pristine",Ua="ng-dirty",xd=["$scope","$exceptionHandler","$attrs","$element","$parse",function(a,c,d,e,g){function i(a,c){c=c?"-"+bb(c,"-"):""; -e.removeClass((a?Ra:Sa)+c).addClass((a?Sa:Ra)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var f=g(d.ngModel),h=f.assign;if(!h)throw Error(Kb+d.ngModel+" ("+va(e)+")");this.$render=q;var j=e.inheritedData("$formController")||Ta,m=0,k=this.$error={};e.addClass(pa);i(!0);this.$setValidity=function(a,c){if(k[a]!==!c){if(c){if(k[a]&&m--,!m)i(!0),this.$valid= -!0,this.$invalid=!1}else i(!1),this.$invalid=!0,this.$valid=!1,m++;k[a]=!c;i(c,a);j.$setValidity(a,c,this)}};this.$setPristine=function(){this.$dirty=!1;this.$pristine=!0;e.removeClass(Ua).addClass(pa)};this.$setViewValue=function(d){this.$viewValue=d;if(this.$pristine)this.$dirty=!0,this.$pristine=!1,e.removeClass(pa).addClass(Ua),j.$setDirty();n(this.$parsers,function(a){d=a(d)});if(this.$modelValue!==d)this.$modelValue=d,h(a,d),n(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})}; -var l=this;a.$watch(function(){var c=f(a);if(l.$modelValue!==c){var d=l.$formatters,e=d.length;for(l.$modelValue=c;e--;)c=d[e](c);if(l.$viewValue!==c)l.$viewValue=c,l.$render()}})}],yd=function(){return{require:["ngModel","^?form"],controller:xd,link:function(a,c,d,e){var g=e[0],i=e[1]||Ta;i.$addControl(g);c.bind("$destroy",function(){i.$removeControl(g)})}}},zd=S({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),jc=function(){return{require:"?ngModel", -link:function(a,c,d,e){if(e){d.required=!0;var g=function(a){if(d.required&&(X(a)||a===!1))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},Ad=function(){return{require:"ngModel",link:function(a,c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){var c=[];a&&n(a.split(g),function(a){a&&c.push(U(a))});return c});e.$formatters.push(function(a){return F(a)? -a.join(", "):p})}}},Bd=/^(true|false|\d+)$/,Cd=function(){return{priority:100,compile:function(a,c){return Bd.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a,c,g){a.$watch(g.ngValue,function(a){g.$set("value",a,!1)})}}}},Dd=aa(function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==p?"":a)})}),Ed=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding", -c);e.$observe("ngBindTemplate",function(a){d.text(a)})}}],Fd=[function(){return function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBindHtmlUnsafe);a.$watch(d.ngBindHtmlUnsafe,function(a){c.html(a||"")})}}],Gd=ob("",!0),Hd=ob("Odd",0),Id=ob("Even",1),Jd=aa({compile:function(a,c){c.$set("ngCloak",p);a.removeClass("ng-cloak")}}),Kd=[function(){return{scope:!0,controller:"@"}}],Ld=["$sniffer",function(a){return{priority:1E3,compile:function(){a.csp=!0}}}],kc={};n("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress".split(" "), -function(a){var c=da("ng-"+a);kc[c]=["$parse",function(d){return function(e,g,i){var f=d(i[c]);g.bind(I(a),function(a){e.$apply(function(){f(e,{$event:a})})})}}]});var Md=aa(function(a,c,d){c.bind("submit",function(){a.$apply(d.ngSubmit)})}),Nd=["$animator",function(a){return{transclude:"element",priority:1E3,terminal:!0,restrict:"A",compile:function(c,d,e){return function(c,d,f){var h=a(c,f),j,m;c.$watch(f.ngIf,function(a){j&&(h.leave(j),j=p);m&&(m.$destroy(),m=p);ua(a)&&(m=c.$new(),e(m,function(a){j= -a;h.enter(a,d.parent(),d)}))})}}}}],Od=["$http","$templateCache","$anchorScroll","$compile","$animator",function(a,c,d,e,g){return{restrict:"ECA",terminal:!0,compile:function(i,f){var h=f.ngInclude||f.src,j=f.onload||"",m=f.autoscroll;return function(f,i,n){var o=g(f,n),p=0,r,t=function(){r&&(r.$destroy(),r=null);o.leave(i.contents(),i)};f.$watch(h,function(g){var h=++p;g?(a.get(g,{cache:c}).success(function(a){h===p&&(r&&r.$destroy(),r=f.$new(),o.leave(i.contents(),i),a=w("
").html(a).contents(), -o.enter(a,i),e(a)(r),B(m)&&(!m||f.$eval(m))&&d(),r.$emit("$includeContentLoaded"),f.$eval(j))}).error(function(){h===p&&t()}),f.$emit("$includeContentRequested")):t()})}}}}],Pd=aa({compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Qd=aa({terminal:!0,priority:1E3}),Rd=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,g,i){var f=i.count,h=g.attr(i.$attr.when),j=i.offset||0,m=e.$eval(h),k={},l=c.startSymbol(),p=c.endSymbol();n(m,function(a,e){k[e]= -c(a.replace(d,l+f+"-"+j+p))});e.$watch(function(){var c=parseFloat(e.$eval(f));return isNaN(c)?"":(c in m||(c=a.pluralCat(c-j)),k[c](e,g,!0))},function(a){g.text(a)})}}}],Sd=["$parse","$animator",function(a,c){return{transclude:"element",priority:1E3,terminal:!0,compile:function(d,e,g){return function(d,e,h){var j=c(d,h),m=h.ngRepeat,k=m.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),l,p,o,z,r,t={$id:la};if(!k)throw Error("Expected ngRepeat in form of '_item_ in _collection_[ track by _id_]' but got '"+ -m+"'.");h=k[1];o=k[2];(k=k[4])?(l=a(k),p=function(a,c,e){r&&(t[r]=a);t[z]=c;t.$index=e;return l(d,t)}):p=function(a,c){return la(c)};k=h.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!k)throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '"+h+"'.");z=k[3]||k[1];r=k[2];var x={};d.$watchCollection(o,function(a){var c,h,k=e,l,o={},t,q,w,s,B,y,C=[];if(Xa(a))B=a;else{B=[];for(w in a)a.hasOwnProperty(w)&&w.charAt(0)!="$"&&B.push(w);B.sort()}t=B.length;h= -C.length=B.length;for(c=0;c
").html(k).contents();o.enter(k,c);var k=g(k),m=d.current;l=m.scope=a.$new();if(m.controller)f.$scope= -l,f=i(m.controller,f),m.controllerAs&&(l[m.controllerAs]=f),c.children().data("$ngControllerController",f);k(l);l.$emit("$viewContentLoaded");l.$eval(n);e()}else o.leave(c.contents(),c),l&&(l.$destroy(),l=null)}var l,n=m.onload||"",o=f(a,m);a.$on("$routeChangeSuccess",k);k()}}}],ae=["$templateCache",function(a){return{restrict:"E",terminal:!0,compile:function(c,d){d.type=="text/ng-template"&&a.put(d.id,c[0].text)}}}],be=S({terminal:!0}),ce=["$compile","$parse",function(a,c){var d=/^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/, -e={$setViewValue:q};return{restrict:"E",require:["select","?ngModel"],controller:["$element","$scope","$attrs",function(a,c,d){var h=this,j={},m=e,k;h.databound=d.ngModel;h.init=function(a,c,d){m=a;k=d};h.addOption=function(c){j[c]=!0;m.$viewValue==c&&(a.val(c),k.parent()&&k.remove())};h.removeOption=function(a){this.hasOption(a)&&(delete j[a],m.$viewValue==a&&this.renderUnknownOption(a))};h.renderUnknownOption=function(c){c="? "+la(c)+" ?";k.val(c);a.prepend(k);a.val(c);k.prop("selected",!0)};h.hasOption= -function(a){return j.hasOwnProperty(a)};c.$on("$destroy",function(){h.renderUnknownOption=q})}],link:function(e,i,f,h){function j(a,c,d,e){d.$render=function(){var a=d.$viewValue;e.hasOption(a)?(v.parent()&&v.remove(),c.val(a),a===""&&t.prop("selected",!0)):C(a)&&t?c.val(""):e.renderUnknownOption(a)};c.bind("change",function(){a.$apply(function(){v.parent()&&v.remove();d.$setViewValue(c.val())})})}function m(a,c,d){var e;d.$render=function(){var a=new za(d.$viewValue);n(c.find("option"),function(c){c.selected= -B(a.get(c.value))})};a.$watch(function(){ia(e,d.$viewValue)||(e=V(d.$viewValue),d.$render())});c.bind("change",function(){a.$apply(function(){var a=[];n(c.find("option"),function(c){c.selected&&a.push(c.value)});d.$setViewValue(a)})})}function k(e,f,g){function i(){var a={"":[]},c=[""],d,h,q,v,s;q=g.$modelValue;v=u(e)||[];var z=l?qb(v):v,B,y,A;y={};s=!1;var C,D;if(o)if(t&&F(q)){s=new za([]);for(h=0;hA;)v.pop().element.remove()}for(;w.length>y;)w.pop()[0].element.remove()} -var h;if(!(h=q.match(d)))throw Error("Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_ (track by _expr_)?' but got '"+q+"'.");var j=c(h[2]||h[1]),k=h[4]||h[6],l=h[5],m=c(h[3]||""),n=c(h[2]?h[1]:k),u=c(h[7]),t=h[8]?c(h[8]):null,w=[[{element:f,label:""}]];r&&(a(r)(e),r.removeClass("ng-scope"),r.remove());f.html("");f.bind("change",function(){e.$apply(function(){var a,c=u(e)||[],d={},h,i,j,m,q,r;if(o){i=[];m=0;for(r=w.length;m@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}'); diff --git a/test/vendor/angular-mocks-1.1.5.js b/test/vendor/angular-mocks-1.1.5.js deleted file mode 100644 index f9b0263..0000000 --- a/test/vendor/angular-mocks-1.1.5.js +++ /dev/null @@ -1,1886 +0,0 @@ -/** - * @license AngularJS v1.1.5 - * (c) 2010-2012 Google, Inc. http://angularjs.org - * License: MIT - * - * TODO(vojta): wrap whole file into closure during build - */ - -/** - * @ngdoc overview - * @name angular.mock - * @description - * - * Namespace from 'angular-mocks.js' which contains testing related code. - */ -angular.mock = {}; - -/** - * ! This is a private undocumented service ! - * - * @name ngMock.$browser - * - * @description - * This service is a mock implementation of {@link ng.$browser}. It provides fake - * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr, - * cookies, etc... - * - * The api of this service is the same as that of the real {@link ng.$browser $browser}, except - * that there are several helper methods available which can be used in tests. - */ -angular.mock.$BrowserProvider = function() { - this.$get = function(){ - return new angular.mock.$Browser(); - }; -}; - -angular.mock.$Browser = function() { - var self = this; - - this.isMock = true; - self.$$url = "http://server/"; - self.$$lastUrl = self.$$url; // used by url polling fn - self.pollFns = []; - - // TODO(vojta): remove this temporary api - self.$$completeOutstandingRequest = angular.noop; - self.$$incOutstandingRequestCount = angular.noop; - - - // register url polling fn - - self.onUrlChange = function(listener) { - self.pollFns.push( - function() { - if (self.$$lastUrl != self.$$url) { - self.$$lastUrl = self.$$url; - listener(self.$$url); - } - } - ); - - return listener; - }; - - self.cookieHash = {}; - self.lastCookieHash = {}; - self.deferredFns = []; - self.deferredNextId = 0; - - self.defer = function(fn, delay) { - delay = delay || 0; - self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId}); - self.deferredFns.sort(function(a,b){ return a.time - b.time;}); - return self.deferredNextId++; - }; - - - self.defer.now = 0; - - - self.defer.cancel = function(deferId) { - var fnIndex; - - angular.forEach(self.deferredFns, function(fn, index) { - if (fn.id === deferId) fnIndex = index; - }); - - if (fnIndex !== undefined) { - self.deferredFns.splice(fnIndex, 1); - return true; - } - - return false; - }; - - - /** - * @name ngMock.$browser#defer.flush - * @methodOf ngMock.$browser - * - * @description - * Flushes all pending requests and executes the defer callbacks. - * - * @param {number=} number of milliseconds to flush. See {@link #defer.now} - */ - self.defer.flush = function(delay) { - if (angular.isDefined(delay)) { - self.defer.now += delay; - } else { - if (self.deferredFns.length) { - self.defer.now = self.deferredFns[self.deferredFns.length-1].time; - } else { - throw Error('No deferred tasks to be flushed'); - } - } - - while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { - self.deferredFns.shift().fn(); - } - }; - /** - * @name ngMock.$browser#defer.now - * @propertyOf ngMock.$browser - * - * @description - * Current milliseconds mock time. - */ - - self.$$baseHref = ''; - self.baseHref = function() { - return this.$$baseHref; - }; -}; -angular.mock.$Browser.prototype = { - -/** - * @name ngMock.$browser#poll - * @methodOf ngMock.$browser - * - * @description - * run all fns in pollFns - */ - poll: function poll() { - angular.forEach(this.pollFns, function(pollFn){ - pollFn(); - }); - }, - - addPollFn: function(pollFn) { - this.pollFns.push(pollFn); - return pollFn; - }, - - url: function(url, replace) { - if (url) { - this.$$url = url; - return this; - } - - return this.$$url; - }, - - cookies: function(name, value) { - if (name) { - if (value == undefined) { - 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(); - } -}; - - -/** - * @ngdoc object - * @name ngMock.$exceptionHandlerProvider - * - * @description - * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors passed - * into the `$exceptionHandler`. - */ - -/** - * @ngdoc object - * @name ngMock.$exceptionHandler - * - * @description - * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed - * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration - * information. - * - * - *
- *   describe('$exceptionHandlerProvider', function() {
- *
- *     it('should capture log messages and exceptions', function() {
- *
- *       module(function($exceptionHandlerProvider) {
- *         $exceptionHandlerProvider.mode('log');
- *       });
- *
- *       inject(function($log, $exceptionHandler, $timeout) {
- *         $timeout(function() { $log.log(1); });
- *         $timeout(function() { $log.log(2); throw 'banana peel'; });
- *         $timeout(function() { $log.log(3); });
- *         expect($exceptionHandler.errors).toEqual([]);
- *         expect($log.assertEmpty());
- *         $timeout.flush();
- *         expect($exceptionHandler.errors).toEqual(['banana peel']);
- *         expect($log.log.logs).toEqual([[1], [2], [3]]);
- *       });
- *     });
- *   });
- * 
- */ - -angular.mock.$ExceptionHandlerProvider = function() { - var handler; - - /** - * @ngdoc method - * @name ngMock.$exceptionHandlerProvider#mode - * @methodOf ngMock.$exceptionHandlerProvider - * - * @description - * Sets the logging mode. - * - * @param {string} mode Mode of operation, defaults to `rethrow`. - * - * - `rethrow`: If any errors are passed into the handler in tests, it typically - * means that there is a bug in the application or test, so this mock will - * make these tests fail. - * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` mode stores an - * array of errors in `$exceptionHandler.errors`, to allow later assertion of them. - * See {@link ngMock.$log#assertEmpty assertEmpty()} and - * {@link ngMock.$log#reset reset()} - */ - this.mode = function(mode) { - switch(mode) { - case 'rethrow': - handler = function(e) { - throw e; - }; - break; - case 'log': - var errors = []; - - handler = function(e) { - if (arguments.length == 1) { - errors.push(e); - } else { - errors.push([].slice.call(arguments, 0)); - } - }; - - handler.errors = errors; - break; - default: - throw Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); - } - }; - - this.$get = function() { - return handler; - }; - - this.mode('rethrow'); -}; - - -/** - * @ngdoc service - * @name ngMock.$log - * - * @description - * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays - * (one array per logging level). These arrays are exposed as `logs` property of each of the - * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. - * - */ -angular.mock.$LogProvider = function() { - - function concat(array1, array2, index) { - return array1.concat(Array.prototype.slice.call(array2, index)); - } - - - this.$get = function () { - var $log = { - log: function() { $log.log.logs.push(concat([], arguments, 0)); }, - warn: function() { $log.warn.logs.push(concat([], arguments, 0)); }, - info: function() { $log.info.logs.push(concat([], arguments, 0)); }, - error: function() { $log.error.logs.push(concat([], arguments, 0)); } - }; - - /** - * @ngdoc method - * @name ngMock.$log#reset - * @methodOf ngMock.$log - * - * @description - * Reset all of the logging arrays to empty. - */ - $log.reset = function () { - /** - * @ngdoc property - * @name ngMock.$log#log.logs - * @propertyOf ngMock.$log - * - * @description - * Array of messages logged using {@link ngMock.$log#log}. - * - * @example - *
-       * $log.log('Some Log');
-       * var first = $log.log.logs.unshift();
-       * 
- */ - $log.log.logs = []; - /** - * @ngdoc property - * @name ngMock.$log#warn.logs - * @propertyOf ngMock.$log - * - * @description - * Array of messages logged using {@link ngMock.$log#warn}. - * - * @example - *
-       * $log.warn('Some Warning');
-       * var first = $log.warn.logs.unshift();
-       * 
- */ - $log.warn.logs = []; - /** - * @ngdoc property - * @name ngMock.$log#info.logs - * @propertyOf ngMock.$log - * - * @description - * Array of messages logged using {@link ngMock.$log#info}. - * - * @example - *
-       * $log.info('Some Info');
-       * var first = $log.info.logs.unshift();
-       * 
- */ - $log.info.logs = []; - /** - * @ngdoc property - * @name ngMock.$log#error.logs - * @propertyOf ngMock.$log - * - * @description - * Array of messages logged using {@link ngMock.$log#error}. - * - * @example - *
-       * $log.log('Some Error');
-       * var first = $log.error.logs.unshift();
-       * 
- */ - $log.error.logs = []; - }; - - /** - * @ngdoc method - * @name ngMock.$log#assertEmpty - * @methodOf ngMock.$log - * - * @description - * Assert that the all of the logging methods have no logged messages. If messages present, an exception is thrown. - */ - $log.assertEmpty = function() { - var errors = []; - angular.forEach(['error', 'warn', 'info', 'log'], function(logLevel) { - angular.forEach($log[logLevel].logs, function(log) { - angular.forEach(log, function (logItem) { - errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + (logItem.stack || '')); - }); - }); - }); - if (errors.length) { - errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or an expected " + - "log message was not checked and removed:"); - errors.push(''); - throw new Error(errors.join('\n---------\n')); - } - }; - - $log.reset(); - return $log; - }; -}; - - -(function() { - var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; - - function jsonStringToDate(string){ - var match; - if (match = string.match(R_ISO8061_STR)) { - var date = new Date(0), - tzHour = 0, - tzMin = 0; - if (match[9]) { - tzHour = int(match[9] + match[10]); - tzMin = int(match[9] + match[11]); - } - date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3])); - date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0)); - return date; - } - return string; - } - - function int(str) { - return parseInt(str, 10); - } - - function padNumber(num, digits, trim) { - var neg = ''; - if (num < 0) { - neg = '-'; - num = -num; - } - num = '' + num; - while(num.length < digits) num = '0' + num; - if (trim) - num = num.substr(num.length - digits); - return neg + num; - } - - - /** - * @ngdoc object - * @name angular.mock.TzDate - * @description - * - * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. - * - * Mock of the Date type which has its timezone specified via constructor arg. - * - * The main purpose is to create Date-like instances with timezone fixed to the specified timezone - * offset, so that we can test code that depends on local timezone settings without dependency on - * the time zone settings of the machine where the code is running. - * - * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) - * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* - * - * @example - * !!!! WARNING !!!!! - * This is not a complete Date object so only methods that were implemented can be called safely. - * To make matters worse, TzDate instances inherit stuff from Date via a prototype. - * - * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is - * incomplete we might be missing some non-standard methods. This can result in errors like: - * "Date.prototype.foo called on incompatible Object". - * - *
-   * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
-   * newYearInBratislava.getTimezoneOffset() => -60;
-   * newYearInBratislava.getFullYear() => 2010;
-   * newYearInBratislava.getMonth() => 0;
-   * newYearInBratislava.getDate() => 1;
-   * newYearInBratislava.getHours() => 0;
-   * newYearInBratislava.getMinutes() => 0;
-   * newYearInBratislava.getSeconds() => 0;
-   * 
- * - */ - angular.mock.TzDate = function (offset, timestamp) { - var self = new Date(0); - if (angular.isString(timestamp)) { - var tsStr = timestamp; - - self.origDate = jsonStringToDate(timestamp); - - timestamp = self.origDate.getTime(); - if (isNaN(timestamp)) - throw { - name: "Illegal Argument", - message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" - }; - } else { - self.origDate = new Date(timestamp); - } - - var localOffset = new Date(timestamp).getTimezoneOffset(); - self.offsetDiff = localOffset*60*1000 - offset*1000*60*60; - self.date = new Date(timestamp + self.offsetDiff); - - self.getTime = function() { - return self.date.getTime() - self.offsetDiff; - }; - - self.toLocaleDateString = function() { - return self.date.toLocaleDateString(); - }; - - self.getFullYear = function() { - return self.date.getFullYear(); - }; - - self.getMonth = function() { - return self.date.getMonth(); - }; - - self.getDate = function() { - return self.date.getDate(); - }; - - self.getHours = function() { - return self.date.getHours(); - }; - - self.getMinutes = function() { - return self.date.getMinutes(); - }; - - self.getSeconds = function() { - return self.date.getSeconds(); - }; - - self.getMilliseconds = function() { - return self.date.getMilliseconds(); - }; - - self.getTimezoneOffset = function() { - return offset * 60; - }; - - self.getUTCFullYear = function() { - return self.origDate.getUTCFullYear(); - }; - - self.getUTCMonth = function() { - return self.origDate.getUTCMonth(); - }; - - self.getUTCDate = function() { - return self.origDate.getUTCDate(); - }; - - self.getUTCHours = function() { - return self.origDate.getUTCHours(); - }; - - self.getUTCMinutes = function() { - return self.origDate.getUTCMinutes(); - }; - - self.getUTCSeconds = function() { - return self.origDate.getUTCSeconds(); - }; - - self.getUTCMilliseconds = function() { - return self.origDate.getUTCMilliseconds(); - }; - - self.getDay = function() { - return self.date.getDay(); - }; - - // provide this method only on browsers that already have it - if (self.toISOString) { - self.toISOString = function() { - return padNumber(self.origDate.getUTCFullYear(), 4) + '-' + - padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + - padNumber(self.origDate.getUTCDate(), 2) + 'T' + - padNumber(self.origDate.getUTCHours(), 2) + ':' + - padNumber(self.origDate.getUTCMinutes(), 2) + ':' + - padNumber(self.origDate.getUTCSeconds(), 2) + '.' + - padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z' - } - } - - //hide all methods not implemented in this mock that the Date prototype exposes - var unimplementedMethods = ['getUTCDay', - 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', - 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', - 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', - 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', - 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; - - angular.forEach(unimplementedMethods, function(methodName) { - self[methodName] = function() { - throw Error("Method '" + methodName + "' is not implemented in the TzDate mock"); - }; - }); - - return self; - }; - - //make "tzDateInstance instanceof Date" return true - angular.mock.TzDate.prototype = Date.prototype; -})(); - -/** - * @ngdoc function - * @name angular.mock.createMockWindow - * @description - * - * This function creates a mock window object useful for controlling access ot setTimeout, but mocking out - * sufficient window's properties to allow Angular to execute. - * - * @example - * - *
-    beforeEach(module(function($provide) {
-      $provide.value('$window', window = angular.mock.createMockWindow());
-    }));
-
-    it('should do something', inject(function($window) {
-      var val = null;
-      $window.setTimeout(function() { val = 123; }, 10);
-      expect(val).toEqual(null);
-      window.setTimeout.expect(10).process();
-      expect(val).toEqual(123);
-    });
- * 
- * - */ -angular.mock.createMockWindow = function() { - var mockWindow = {}; - var setTimeoutQueue = []; - - mockWindow.document = window.document; - mockWindow.getComputedStyle = angular.bind(window, window.getComputedStyle); - mockWindow.scrollTo = angular.bind(window, window.scrollTo); - mockWindow.navigator = window.navigator; - mockWindow.setTimeout = function(fn, delay) { - setTimeoutQueue.push({fn: fn, delay: delay}); - }; - mockWindow.setTimeout.queue = setTimeoutQueue; - mockWindow.setTimeout.expect = function(delay) { - if (setTimeoutQueue.length > 0) { - return { - process: function() { - var tick = setTimeoutQueue.shift(); - expect(tick.delay).toEqual(delay); - tick.fn(); - } - }; - } else { - expect('SetTimoutQueue empty. Expecting delay of ').toEqual(delay); - } - }; - - return mockWindow; -}; - -/** - * @ngdoc function - * @name angular.mock.dump - * @description - * - * *NOTE*: this is not an injectable instance, just a globally available function. - * - * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for debugging. - * - * This method is also available on window, where it can be used to display objects on debug console. - * - * @param {*} object - any object to turn into string. - * @return {string} a serialized string of the argument - */ -angular.mock.dump = function(object) { - return serialize(object); - - function serialize(object) { - var out; - - if (angular.isElement(object)) { - object = angular.element(object); - out = angular.element('
'); - angular.forEach(object, function(element) { - out.append(angular.element(element).clone()); - }); - out = out.html(); - } else if (angular.isArray(object)) { - out = []; - angular.forEach(object, function(o) { - out.push(serialize(o)); - }); - out = '[ ' + out.join(', ') + ' ]'; - } else if (angular.isObject(object)) { - if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { - out = serializeScope(object); - } else if (object instanceof Error) { - out = object.stack || ('' + object.name + ': ' + object.message); - } else { - out = angular.toJson(object, true); - } - } else { - out = String(object); - } - - return out; - } - - function serializeScope(scope, offset) { - offset = offset || ' '; - var log = [offset + 'Scope(' + scope.$id + '): {']; - for ( var key in scope ) { - if (scope.hasOwnProperty(key) && !key.match(/^(\$|this)/)) { - log.push(' ' + key + ': ' + angular.toJson(scope[key])); - } - } - var child = scope.$$childHead; - while(child) { - log.push(serializeScope(child, offset + ' ')); - child = child.$$nextSibling; - } - log.push('}'); - return log.join('\n' + offset); - } -}; - -/** - * @ngdoc object - * @name ngMock.$httpBackend - * @description - * Fake HTTP backend implementation suitable for unit testing applications that use the - * {@link ng.$http $http service}. - * - * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less - * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. - * - * During unit testing, we want our unit tests to run quickly and have no external dependencies so - * we don’t want to send {@link https://developer.mozilla.org/en/xmlhttprequest XHR} or - * {@link http://en.wikipedia.org/wiki/JSONP JSONP} requests to a real server. All we really need is - * to verify whether a certain request has been sent or not, or alternatively just let the - * application make requests, respond with pre-trained responses and assert that the end result is - * what we expect it to be. - * - * This mock implementation can be used to respond with static or dynamic responses via the - * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). - * - * When an Angular application needs some data from a server, it calls the $http service, which - * sends the request to a real server using $httpBackend service. With dependency injection, it is - * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify - * the requests and respond with some testing data without sending a request to real server. - * - * There are two ways to specify what test data should be returned as http responses by the mock - * backend when the code under test makes http requests: - * - * - `$httpBackend.expect` - specifies a request expectation - * - `$httpBackend.when` - specifies a backend definition - * - * - * # Request Expectations vs Backend Definitions - * - * Request expectations provide a way to make assertions about requests made by the application and - * to define responses for those requests. The test will fail if the expected requests are not made - * or they are made in the wrong order. - * - * Backend definitions allow you to define a fake backend for your application which doesn't assert - * if a particular request was made or not, it just returns a trained response if a request is made. - * The test will pass whether or not the request gets made during testing. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Request expectationsBackend definitions
Syntax.expect(...).respond(...).when(...).respond(...)
Typical usagestrict unit testsloose (black-box) unit testing
Fulfills multiple requestsNOYES
Order of requests mattersYESNO
Request requiredYESNO
Response requiredoptional (see below)YES
- * - * In cases where both backend definitions and request expectations are specified during unit - * testing, the request expectations are evaluated first. - * - * If a request expectation has no response specified, the algorithm will search your backend - * definitions for an appropriate response. - * - * If a request didn't match any expectation or if the expectation doesn't have the response - * defined, the backend definitions are evaluated in sequential order to see if any of them match - * the request. The response from the first matched definition is returned. - * - * - * # Flushing HTTP requests - * - * The $httpBackend used in production, always responds to requests with responses asynchronously. - * If we preserved this behavior in unit testing, we'd have to create async unit tests, which are - * hard to write, follow and maintain. At the same time the testing mock, can't respond - * synchronously because that would change the execution of the code under test. For this reason the - * mock $httpBackend has a `flush()` method, which allows the test to explicitly flush pending - * requests and thus preserving the async api of the backend, while allowing the test to execute - * synchronously. - * - * - * # Unit testing with mock $httpBackend - * - *
-   // controller
-   function MyController($scope, $http) {
-     $http.get('/auth.py').success(function(data) {
-       $scope.user = data;
-     });
-
-     this.saveMessage = function(message) {
-       $scope.status = 'Saving...';
-       $http.post('/add-msg.py', message).success(function(response) {
-         $scope.status = '';
-       }).error(function() {
-         $scope.status = 'ERROR!';
-       });
-     };
-   }
-
-   // testing controller
-   var $httpBackend;
-
-   beforeEach(inject(function($injector) {
-     $httpBackend = $injector.get('$httpBackend');
-
-     // backend definition common for all tests
-     $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
-   }));
-
-
-   afterEach(function() {
-     $httpBackend.verifyNoOutstandingExpectation();
-     $httpBackend.verifyNoOutstandingRequest();
-   });
-
-
-   it('should fetch authentication token', function() {
-     $httpBackend.expectGET('/auth.py');
-     var controller = scope.$new(MyController);
-     $httpBackend.flush();
-   });
-
-
-   it('should send msg to server', function() {
-     // now you don’t care about the authentication, but
-     // the controller will still send the request and
-     // $httpBackend will respond without you having to
-     // specify the expectation and response for this request
-     $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, '');
-
-     var controller = scope.$new(MyController);
-     $httpBackend.flush();
-     controller.saveMessage('message content');
-     expect(controller.status).toBe('Saving...');
-     $httpBackend.flush();
-     expect(controller.status).toBe('');
-   });
-
-
-   it('should send auth header', function() {
-     $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
-       // check if the header was send, if it wasn't the expectation won't
-       // match the request and the test will fail
-       return headers['Authorization'] == 'xxx';
-     }).respond(201, '');
-
-     var controller = scope.$new(MyController);
-     controller.saveMessage('whatever');
-     $httpBackend.flush();
-   });
-   
- */ -angular.mock.$HttpBackendProvider = function() { - this.$get = ['$rootScope', createHttpBackendMock]; -}; - -/** - * General factory function for $httpBackend mock. - * Returns instance for unit testing (when no arguments specified): - * - passing through is disabled - * - auto flushing is disabled - * - * Returns instance for e2e testing (when `$delegate` and `$browser` specified): - * - passing through (delegating request to real backend) is enabled - * - auto flushing is enabled - * - * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified) - * @param {Object=} $browser Auto-flushing enabled if specified - * @return {Object} Instance of $httpBackend mock - */ -function createHttpBackendMock($rootScope, $delegate, $browser) { - var definitions = [], - expectations = [], - responses = [], - responsesPush = angular.bind(responses, responses.push); - - function createResponse(status, data, headers) { - if (angular.isFunction(status)) return status; - - return function() { - return angular.isNumber(status) - ? [status, data, headers] - : [200, status, data]; - }; - } - - // TODO(vojta): change params to: method, url, data, headers, callback - function $httpBackend(method, url, data, callback, headers, timeout) { - var xhr = new MockXhr(), - expectation = expectations[0], - wasExpected = false; - - function prettyPrint(data) { - return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) - ? data - : angular.toJson(data); - } - - function wrapResponse(wrapped) { - if (!$browser && timeout && timeout.then) timeout.then(handleTimeout); - - return handleResponse; - - function handleResponse() { - var response = wrapped.response(method, url, data, headers); - xhr.$$respHeaders = response[2]; - callback(response[0], response[1], xhr.getAllResponseHeaders()); - } - - function handleTimeout() { - for (var i = 0, ii = responses.length; i < ii; i++) { - if (responses[i] === handleResponse) { - responses.splice(i, 1); - callback(-1, undefined, ''); - break; - } - } - } - } - - if (expectation && expectation.match(method, url)) { - if (!expectation.matchData(data)) - throw Error('Expected ' + expectation + ' with different data\n' + - 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); - - if (!expectation.matchHeaders(headers)) - throw Error('Expected ' + expectation + ' with different headers\n' + - 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + - prettyPrint(headers)); - - expectations.shift(); - - if (expectation.response) { - responses.push(wrapResponse(expectation)); - return; - } - wasExpected = true; - } - - var i = -1, definition; - while ((definition = definitions[++i])) { - if (definition.match(method, url, data, headers || {})) { - if (definition.response) { - // if $browser specified, we do auto flush all requests - ($browser ? $browser.defer : responsesPush)(wrapResponse(definition)); - } else if (definition.passThrough) { - $delegate(method, url, data, callback, headers, timeout); - } else throw Error('No response defined !'); - return; - } - } - throw wasExpected ? - Error('No response defined !') : - Error('Unexpected request: ' + method + ' ' + url + '\n' + - (expectation ? 'Expected ' + expectation : 'No more request expected')); - } - - /** - * @ngdoc method - * @name ngMock.$httpBackend#when - * @methodOf ngMock.$httpBackend - * @description - * Creates a new backend definition. - * - * @param {string} method HTTP method. - * @param {string|RegExp} url HTTP url. - * @param {(string|RegExp)=} data HTTP request body. - * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header - * object and returns true if the headers match the current definition. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. - * - * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` - * – The respond method takes a set of static data to be returned or a function that can return - * an array containing response status (number), response data (string) and response headers - * (Object). - */ - $httpBackend.when = function(method, url, data, headers) { - var definition = new MockHttpExpectation(method, url, data, headers), - chain = { - respond: function(status, data, headers) { - definition.response = createResponse(status, data, headers); - } - }; - - if ($browser) { - chain.passThrough = function() { - definition.passThrough = true; - }; - } - - definitions.push(definition); - return chain; - }; - - /** - * @ngdoc method - * @name ngMock.$httpBackend#whenGET - * @methodOf ngMock.$httpBackend - * @description - * Creates a new backend definition for GET requests. For more info see `when()`. - * - * @param {string|RegExp} url HTTP url. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. - */ - - /** - * @ngdoc method - * @name ngMock.$httpBackend#whenHEAD - * @methodOf ngMock.$httpBackend - * @description - * Creates a new backend definition for HEAD requests. For more info see `when()`. - * - * @param {string|RegExp} url HTTP url. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. - */ - - /** - * @ngdoc method - * @name ngMock.$httpBackend#whenDELETE - * @methodOf ngMock.$httpBackend - * @description - * Creates a new backend definition for DELETE requests. For more info see `when()`. - * - * @param {string|RegExp} url HTTP url. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. - */ - - /** - * @ngdoc method - * @name ngMock.$httpBackend#whenPOST - * @methodOf ngMock.$httpBackend - * @description - * Creates a new backend definition for POST requests. For more info see `when()`. - * - * @param {string|RegExp} url HTTP url. - * @param {(string|RegExp)=} data HTTP request body. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. - */ - - /** - * @ngdoc method - * @name ngMock.$httpBackend#whenPUT - * @methodOf ngMock.$httpBackend - * @description - * Creates a new backend definition for PUT requests. For more info see `when()`. - * - * @param {string|RegExp} url HTTP url. - * @param {(string|RegExp)=} data HTTP request body. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. - */ - - /** - * @ngdoc method - * @name ngMock.$httpBackend#whenJSONP - * @methodOf ngMock.$httpBackend - * @description - * Creates a new backend definition for JSONP requests. For more info see `when()`. - * - * @param {string|RegExp} url HTTP url. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. - */ - createShortMethods('when'); - - - /** - * @ngdoc method - * @name ngMock.$httpBackend#expect - * @methodOf ngMock.$httpBackend - * @description - * Creates a new request expectation. - * - * @param {string} method HTTP method. - * @param {string|RegExp} url HTTP url. - * @param {(string|RegExp)=} data HTTP request body. - * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header - * object and returns true if the headers match the current expectation. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. - * - * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` - * – The respond method takes a set of static data to be returned or a function that can return - * an array containing response status (number), response data (string) and response headers - * (Object). - */ - $httpBackend.expect = function(method, url, data, headers) { - var expectation = new MockHttpExpectation(method, url, data, headers); - expectations.push(expectation); - return { - respond: function(status, data, headers) { - expectation.response = createResponse(status, data, headers); - } - }; - }; - - - /** - * @ngdoc method - * @name ngMock.$httpBackend#expectGET - * @methodOf ngMock.$httpBackend - * @description - * Creates a new request expectation for GET requests. For more info see `expect()`. - * - * @param {string|RegExp} url HTTP url. - * @param {Object=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. See #expect for more info. - */ - - /** - * @ngdoc method - * @name ngMock.$httpBackend#expectHEAD - * @methodOf ngMock.$httpBackend - * @description - * Creates a new request expectation for HEAD requests. For more info see `expect()`. - * - * @param {string|RegExp} url HTTP url. - * @param {Object=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. - */ - - /** - * @ngdoc method - * @name ngMock.$httpBackend#expectDELETE - * @methodOf ngMock.$httpBackend - * @description - * Creates a new request expectation for DELETE requests. For more info see `expect()`. - * - * @param {string|RegExp} url HTTP url. - * @param {Object=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. - */ - - /** - * @ngdoc method - * @name ngMock.$httpBackend#expectPOST - * @methodOf ngMock.$httpBackend - * @description - * Creates a new request expectation for POST requests. For more info see `expect()`. - * - * @param {string|RegExp} url HTTP url. - * @param {(string|RegExp)=} data HTTP request body. - * @param {Object=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. - */ - - /** - * @ngdoc method - * @name ngMock.$httpBackend#expectPUT - * @methodOf ngMock.$httpBackend - * @description - * Creates a new request expectation for PUT requests. For more info see `expect()`. - * - * @param {string|RegExp} url HTTP url. - * @param {(string|RegExp)=} data HTTP request body. - * @param {Object=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. - */ - - /** - * @ngdoc method - * @name ngMock.$httpBackend#expectPATCH - * @methodOf ngMock.$httpBackend - * @description - * Creates a new request expectation for PATCH requests. For more info see `expect()`. - * - * @param {string|RegExp} url HTTP url. - * @param {(string|RegExp)=} data HTTP request body. - * @param {Object=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. - */ - - /** - * @ngdoc method - * @name ngMock.$httpBackend#expectJSONP - * @methodOf ngMock.$httpBackend - * @description - * Creates a new request expectation for JSONP requests. For more info see `expect()`. - * - * @param {string|RegExp} url HTTP url. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. - */ - createShortMethods('expect'); - - - /** - * @ngdoc method - * @name ngMock.$httpBackend#flush - * @methodOf ngMock.$httpBackend - * @description - * Flushes all pending requests using the trained responses. - * - * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, - * all pending requests will be flushed. If there are no pending requests when the flush method - * is called an exception is thrown (as this typically a sign of programming error). - */ - $httpBackend.flush = function(count) { - $rootScope.$digest(); - if (!responses.length) throw Error('No pending request to flush !'); - - if (angular.isDefined(count)) { - while (count--) { - if (!responses.length) throw Error('No more pending request to flush !'); - responses.shift()(); - } - } else { - while (responses.length) { - responses.shift()(); - } - } - $httpBackend.verifyNoOutstandingExpectation(); - }; - - - /** - * @ngdoc method - * @name ngMock.$httpBackend#verifyNoOutstandingExpectation - * @methodOf ngMock.$httpBackend - * @description - * Verifies that all of the requests defined via the `expect` api were made. If any of the - * requests were not made, verifyNoOutstandingExpectation throws an exception. - * - * Typically, you would call this method following each test case that asserts requests using an - * "afterEach" clause. - * - *
-   *   afterEach($httpBackend.verifyExpectations);
-   * 
- */ - $httpBackend.verifyNoOutstandingExpectation = function() { - $rootScope.$digest(); - if (expectations.length) { - throw Error('Unsatisfied requests: ' + expectations.join(', ')); - } - }; - - - /** - * @ngdoc method - * @name ngMock.$httpBackend#verifyNoOutstandingRequest - * @methodOf ngMock.$httpBackend - * @description - * Verifies that there are no outstanding requests that need to be flushed. - * - * Typically, you would call this method following each test case that asserts requests using an - * "afterEach" clause. - * - *
-   *   afterEach($httpBackend.verifyNoOutstandingRequest);
-   * 
- */ - $httpBackend.verifyNoOutstandingRequest = function() { - if (responses.length) { - throw Error('Unflushed requests: ' + responses.length); - } - }; - - - /** - * @ngdoc method - * @name ngMock.$httpBackend#resetExpectations - * @methodOf ngMock.$httpBackend - * @description - * Resets all request expectations, but preserves all backend definitions. Typically, you would - * call resetExpectations during a multiple-phase test when you want to reuse the same instance of - * $httpBackend mock. - */ - $httpBackend.resetExpectations = function() { - expectations.length = 0; - responses.length = 0; - }; - - return $httpBackend; - - - function createShortMethods(prefix) { - angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) { - $httpBackend[prefix + method] = function(url, headers) { - return $httpBackend[prefix](method, url, undefined, headers) - } - }); - - angular.forEach(['PUT', 'POST', 'PATCH'], function(method) { - $httpBackend[prefix + method] = function(url, data, headers) { - return $httpBackend[prefix](method, url, data, headers) - } - }); - } -} - -function MockHttpExpectation(method, url, data, headers) { - - this.data = data; - this.headers = headers; - - this.match = function(m, u, d, h) { - if (method != m) return false; - if (!this.matchUrl(u)) return false; - if (angular.isDefined(d) && !this.matchData(d)) return false; - if (angular.isDefined(h) && !this.matchHeaders(h)) return false; - return true; - }; - - this.matchUrl = function(u) { - if (!url) return true; - if (angular.isFunction(url.test)) return url.test(u); - return url == u; - }; - - this.matchHeaders = function(h) { - if (angular.isUndefined(headers)) return true; - if (angular.isFunction(headers)) return headers(h); - return angular.equals(headers, h); - }; - - this.matchData = function(d) { - if (angular.isUndefined(data)) return true; - if (data && angular.isFunction(data.test)) return data.test(d); - if (data && !angular.isString(data)) return angular.toJson(data) == d; - return data == d; - }; - - this.toString = function() { - return method + ' ' + url; - }; -} - -function MockXhr() { - - // hack for testing $http, $httpBackend - MockXhr.$$lastInstance = this; - - this.open = function(method, url, async) { - this.$$method = method; - this.$$url = url; - this.$$async = async; - this.$$reqHeaders = {}; - this.$$respHeaders = {}; - }; - - this.send = function(data) { - this.$$data = data; - }; - - this.setRequestHeader = function(key, value) { - this.$$reqHeaders[key] = value; - }; - - this.getResponseHeader = function(name) { - // the lookup must be case insensitive, that's why we try two quick lookups and full scan at last - var header = this.$$respHeaders[name]; - if (header) return header; - - name = angular.lowercase(name); - header = this.$$respHeaders[name]; - if (header) return header; - - header = undefined; - angular.forEach(this.$$respHeaders, function(headerVal, headerName) { - if (!header && angular.lowercase(headerName) == name) header = headerVal; - }); - return header; - }; - - this.getAllResponseHeaders = function() { - var lines = []; - - angular.forEach(this.$$respHeaders, function(value, key) { - lines.push(key + ': ' + value); - }); - return lines.join('\n'); - }; - - this.abort = angular.noop; -} - - -/** - * @ngdoc function - * @name ngMock.$timeout - * @description - * - * This service is just a simple decorator for {@link ng.$timeout $timeout} service - * that adds a "flush" and "verifyNoPendingTasks" methods. - */ - -angular.mock.$TimeoutDecorator = function($delegate, $browser) { - - /** - * @ngdoc method - * @name ngMock.$timeout#flush - * @methodOf ngMock.$timeout - * @description - * - * Flushes the queue of pending tasks. - */ - $delegate.flush = function() { - $browser.defer.flush(); - }; - - /** - * @ngdoc method - * @name ngMock.$timeout#verifyNoPendingTasks - * @methodOf ngMock.$timeout - * @description - * - * Verifies that there are no pending tasks that need to be flushed. - */ - $delegate.verifyNoPendingTasks = function() { - if ($browser.deferredFns.length) { - throw Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' + - formatPendingTasksAsString($browser.deferredFns)); - } - }; - - function formatPendingTasksAsString(tasks) { - var result = []; - angular.forEach(tasks, function(task) { - result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}'); - }); - - return result.join(', '); - } - - return $delegate; -}; - -/** - * - */ -angular.mock.$RootElementProvider = function() { - this.$get = function() { - return angular.element('
'); - } -}; - -/** - * @ngdoc overview - * @name ngMock - * @description - * - * The `ngMock` is an angular module which is used with `ng` module and adds unit-test configuration as well as useful - * mocks to the {@link AUTO.$injector $injector}. - */ -angular.module('ngMock', ['ng']).provider({ - $browser: angular.mock.$BrowserProvider, - $exceptionHandler: angular.mock.$ExceptionHandlerProvider, - $log: angular.mock.$LogProvider, - $httpBackend: angular.mock.$HttpBackendProvider, - $rootElement: angular.mock.$RootElementProvider -}).config(function($provide) { - $provide.decorator('$timeout', angular.mock.$TimeoutDecorator); -}); - -/** - * @ngdoc overview - * @name ngMockE2E - * @description - * - * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing. - * Currently there is only one mock present in this module - - * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. - */ -angular.module('ngMockE2E', ['ng']).config(function($provide) { - $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); -}); - -/** - * @ngdoc object - * @name ngMockE2E.$httpBackend - * @description - * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of - * applications that use the {@link ng.$http $http service}. - * - * *Note*: For fake http backend implementation suitable for unit testing please see - * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. - * - * This implementation can be used to respond with static or dynamic responses via the `when` api - * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the - * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch - * templates from a webserver). - * - * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application - * is being developed with the real backend api replaced with a mock, it is often desirable for - * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch - * templates or static files from the webserver). To configure the backend with this behavior - * use the `passThrough` request handler of `when` instead of `respond`. - * - * Additionally, we don't want to manually have to flush mocked out requests like we do during unit - * testing. For this reason the e2e $httpBackend automatically flushes mocked out requests - * automatically, closely simulating the behavior of the XMLHttpRequest object. - * - * To setup the application to run with this http backend, you have to create a module that depends - * on the `ngMockE2E` and your application modules and defines the fake backend: - * - *
- *   myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']);
- *   myAppDev.run(function($httpBackend) {
- *     phones = [{name: 'phone1'}, {name: 'phone2'}];
- *
- *     // returns the current list of phones
- *     $httpBackend.whenGET('/phones').respond(phones);
- *
- *     // adds a new phone to the phones array
- *     $httpBackend.whenPOST('/phones').respond(function(method, url, data) {
- *       phones.push(angular.fromJSON(data));
- *     });
- *     $httpBackend.whenGET(/^\/templates\//).passThrough();
- *     //...
- *   });
- * 
- * - * Afterwards, bootstrap your app with this new module. - */ - -/** - * @ngdoc method - * @name ngMockE2E.$httpBackend#when - * @methodOf ngMockE2E.$httpBackend - * @description - * Creates a new backend definition. - * - * @param {string} method HTTP method. - * @param {string|RegExp} url HTTP url. - * @param {(string|RegExp)=} data HTTP request body. - * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header - * object and returns true if the headers match the current definition. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. - * - * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` - * – The respond method takes a set of static data to be returned or a function that can return - * an array containing response status (number), response data (string) and response headers - * (Object). - * - passThrough – `{function()}` – Any request matching a backend definition with `passThrough` - * handler, will be pass through to the real backend (an XHR request will be made to the - * server. - */ - -/** - * @ngdoc method - * @name ngMockE2E.$httpBackend#whenGET - * @methodOf ngMockE2E.$httpBackend - * @description - * Creates a new backend definition for GET requests. For more info see `when()`. - * - * @param {string|RegExp} url HTTP url. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. - */ - -/** - * @ngdoc method - * @name ngMockE2E.$httpBackend#whenHEAD - * @methodOf ngMockE2E.$httpBackend - * @description - * Creates a new backend definition for HEAD requests. For more info see `when()`. - * - * @param {string|RegExp} url HTTP url. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. - */ - -/** - * @ngdoc method - * @name ngMockE2E.$httpBackend#whenDELETE - * @methodOf ngMockE2E.$httpBackend - * @description - * Creates a new backend definition for DELETE requests. For more info see `when()`. - * - * @param {string|RegExp} url HTTP url. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. - */ - -/** - * @ngdoc method - * @name ngMockE2E.$httpBackend#whenPOST - * @methodOf ngMockE2E.$httpBackend - * @description - * Creates a new backend definition for POST requests. For more info see `when()`. - * - * @param {string|RegExp} url HTTP url. - * @param {(string|RegExp)=} data HTTP request body. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. - */ - -/** - * @ngdoc method - * @name ngMockE2E.$httpBackend#whenPUT - * @methodOf ngMockE2E.$httpBackend - * @description - * Creates a new backend definition for PUT requests. For more info see `when()`. - * - * @param {string|RegExp} url HTTP url. - * @param {(string|RegExp)=} data HTTP request body. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. - */ - -/** - * @ngdoc method - * @name ngMockE2E.$httpBackend#whenPATCH - * @methodOf ngMockE2E.$httpBackend - * @description - * Creates a new backend definition for PATCH requests. For more info see `when()`. - * - * @param {string|RegExp} url HTTP url. - * @param {(string|RegExp)=} data HTTP request body. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. - */ - -/** - * @ngdoc method - * @name ngMockE2E.$httpBackend#whenJSONP - * @methodOf ngMockE2E.$httpBackend - * @description - * Creates a new backend definition for JSONP requests. For more info see `when()`. - * - * @param {string|RegExp} url HTTP url. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. - */ -angular.mock.e2e = {}; -angular.mock.e2e.$httpBackendDecorator = ['$rootScope', '$delegate', '$browser', createHttpBackendMock]; - - -angular.mock.clearDataCache = function() { - var key, - cache = angular.element.cache; - - for(key in cache) { - if (cache.hasOwnProperty(key)) { - var handle = cache[key].handle; - - handle && angular.element(handle.elem).unbind(); - delete cache[key]; - } - } -}; - - -window.jstestdriver && (function(window) { - /** - * Global method to output any number of objects into JSTD console. Useful for debugging. - */ - window.dump = function() { - var args = []; - angular.forEach(arguments, function(arg) { - args.push(angular.mock.dump(arg)); - }); - jstestdriver.console.log.apply(jstestdriver.console, args); - if (window.console) { - window.console.log.apply(window.console, args); - } - }; -})(window); - - -(window.jasmine || window.mocha) && (function(window) { - - var currentSpec = null; - - beforeEach(function() { - currentSpec = this; - }); - - afterEach(function() { - var injector = currentSpec.$injector; - - currentSpec.$injector = null; - currentSpec.$modules = null; - currentSpec = null; - - if (injector) { - injector.get('$rootElement').unbind(); - injector.get('$browser').pollFns.length = 0; - } - - angular.mock.clearDataCache(); - - // clean up jquery's fragment cache - angular.forEach(angular.element.fragments, function(val, key) { - delete angular.element.fragments[key]; - }); - - MockXhr.$$lastInstance = null; - - angular.forEach(angular.callbacks, function(val, key) { - delete angular.callbacks[key]; - }); - angular.callbacks.counter = 0; - }); - - function isSpecRunning() { - return currentSpec && (window.mocha || currentSpec.queue.running); - } - - /** - * @ngdoc function - * @name angular.mock.module - * @description - * - * *NOTE*: This function is also published on window for easy access.
- * - * This function registers a module configuration code. It collects the configuration information - * which will be used when the injector is created by {@link angular.mock.inject inject}. - * - * See {@link angular.mock.inject inject} for usage example - * - * @param {...(string|Function)} fns any number of modules which are represented as string - * aliases or as anonymous module initialization functions. The modules are used to - * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. - */ - window.module = angular.mock.module = function() { - var moduleFns = Array.prototype.slice.call(arguments, 0); - return isSpecRunning() ? workFn() : workFn; - ///////////////////// - function workFn() { - if (currentSpec.$injector) { - throw Error('Injector already created, can not register a module!'); - } else { - var modules = currentSpec.$modules || (currentSpec.$modules = []); - angular.forEach(moduleFns, function(module) { - modules.push(module); - }); - } - } - }; - - /** - * @ngdoc function - * @name angular.mock.inject - * @description - * - * *NOTE*: This function is also published on window for easy access.
- * - * The inject function wraps a function into an injectable function. The inject() creates new - * instance of {@link AUTO.$injector $injector} per test, which is then used for - * resolving references. - * - * See also {@link angular.mock.module module} - * - * Example of what a typical jasmine tests looks like with the inject method. - *
-   *
-   *   angular.module('myApplicationModule', [])
-   *       .value('mode', 'app')
-   *       .value('version', 'v1.0.1');
-   *
-   *
-   *   describe('MyApp', function() {
-   *
-   *     // You need to load modules that you want to test,
-   *     // it loads only the "ng" module by default.
-   *     beforeEach(module('myApplicationModule'));
-   *
-   *
-   *     // inject() is used to inject arguments of all given functions
-   *     it('should provide a version', inject(function(mode, version) {
-   *       expect(version).toEqual('v1.0.1');
-   *       expect(mode).toEqual('app');
-   *     }));
-   *
-   *
-   *     // The inject and module method can also be used inside of the it or beforeEach
-   *     it('should override a version and test the new version is injected', function() {
-   *       // module() takes functions or strings (module aliases)
-   *       module(function($provide) {
-   *         $provide.value('version', 'overridden'); // override version here
-   *       });
-   *
-   *       inject(function(version) {
-   *         expect(version).toEqual('overridden');
-   *       });
-   *     ));
-   *   });
-   *
-   * 
- * - * @param {...Function} fns any number of functions which will be injected using the injector. - */ - window.inject = angular.mock.inject = function() { - var blockFns = Array.prototype.slice.call(arguments, 0); - var errorForStack = new Error('Declaration Location'); - return isSpecRunning() ? workFn() : workFn; - ///////////////////// - function workFn() { - var modules = currentSpec.$modules || []; - - modules.unshift('ngMock'); - modules.unshift('ng'); - var injector = currentSpec.$injector; - if (!injector) { - injector = currentSpec.$injector = angular.injector(modules); - } - for(var i = 0, ii = blockFns.length; i < ii; i++) { - try { - injector.invoke(blockFns[i] || angular.noop, this); - } catch (e) { - if(e.stack && errorForStack) e.stack += '\n' + errorForStack.stack; - throw e; - } finally { - errorForStack = null; - } - } - } - }; -})(window);