Repository: tantaman/LargeLocalStorage Branch: master Commit: e4fc5d03be1d Files: 33 Total size: 510.1 KB Directory structure: gitextract_1j73emsu/ ├── .gitignore ├── Gruntfile.js ├── LICENSE-MIT.txt ├── README.md ├── bower.json ├── dist/ │ ├── LargeLocalStorage.js │ └── contrib/ │ ├── S3Link.js │ └── URLCache.js ├── examples/ │ └── album/ │ ├── app.js │ ├── index.html │ └── main.css ├── package.json ├── src/ │ ├── LargeLocalStorage.js │ ├── contrib/ │ │ ├── S3Link.js │ │ └── URLCache.js │ ├── errors.js │ ├── footer.js │ ├── header.js │ ├── impls/ │ │ ├── FilesystemAPIProvider.js │ │ ├── IndexedDBProvider.js │ │ ├── LocalStorageProvider.js │ │ ├── WebSQLProvider.js │ │ └── utils.js │ └── pipeline.js └── test/ ├── index.html ├── lib/ │ ├── chai.js │ ├── expect.js │ ├── mocha/ │ │ ├── mocha.css │ │ └── mocha.js │ └── sinon.js ├── runner/ │ └── mocha.js └── spec/ ├── LargeLocalStorageTest.js └── URLCacheTest.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ bower_components/ node_modules/ doc/ temp.out ================================================ FILE: Gruntfile.js ================================================ 'use strict'; module.exports = function (grunt) { require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), concat: { options: { seperator: ';' }, scripts: { src: ['src/header.js', 'src/pipeline.js', 'src/impls/utils.js', 'src/impls/FilesystemAPIProvider.js', 'src/impls/IndexedDBProvider.js', 'src/impls/LocalStorageProvider.js', 'src/impls/WebSQLProvider.js', 'src/LargeLocalStorage.js', 'src/footer.js'], dest: 'dist/LargeLocalStorage.js' } }, uglify: { options: { mangle: { except: ['Q'] } }, scripts: { files: { 'dist/LargeLocalStorage.min.js': ['dist/LargeLocalStorage.js'] } } }, watch: { scripts: { files: ["src/**/*.js"], tasks: ["concat"] }, contrib: { files: ["src/contrib/**/*.js"], tasks: ["copy:contrib"] } }, copy: { contrib: { files: [{expand: true, cwd: "src/contrib/", src: "**", dest: "dist/contrib/"}] } }, connect: { server: { options: { port: 9001, base: '.' } } }, // docview: { // compile: { // files: { // "doc/LargeLocalStorage.html": "doc/library.handlebars" // } // } // }, yuidoc: { compile: { name: '<%= pkg.name %>', description: '<%= pkg.description %>', version: '<%= pkg.version %>', url: '<%= pkg.homepage %>', options: { paths: 'src', themedir: 'node_modules/yuidoc-library-theme', helpers: ['node_modules/yuidoc-library-theme/helpers/helpers.js'], outdir: 'doc', // parseOnly: true } } } }); grunt.registerTask('default', ['concat', 'copy', 'connect', 'watch']); grunt.registerTask('docs', ['yuidoc']); grunt.registerTask('build', ['concat', 'copy', 'uglify']); }; ================================================ FILE: LICENSE-MIT.txt ================================================ Copyright (c) 2013 Matt Crinklaw-Vogt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ LargeLocalStorage ================= **Problem:** You need a large key-value store in the browser. To make things worse: * DOMStorage only gives you 5mb * Chrome doesn't let you store blobs in IndexedDB * Safari doesn't support IndexedDB, * IE and Firefox both support IndexedDB but not the FilesystemAPI. `LargeLocalStorage` bridges all of that to give you a large capacity (up to several GB when authorized by the user) key-value store in the browser (IE 10, Chrome, Safari 6+, Firefox, Opera). * [docs](http://tantaman.github.io/LargeLocalStorage/doc/classes/LargeLocalStorage.html) * [tests](http://tantaman.github.io/LargeLocalStorage/test/) * [demo app](http://tantaman.github.io/LargeLocalStorage/examples/album/) ## Basic Rundown / Examples ### Creating a database ```javascript // Specify desired capacity in bytes var desiredCapacity = 125 * 1024 * 1024; // Create a 125MB key-value store var storage = new LargeLocalStorage({size: desiredCapacity, name: 'myDb'}); // Await initialization of the storage area storage.initialized.then(function(grantedCapacity) { // Check to see how much space the user authorized us to actually use. // Some browsers don't indicate how much space was granted in which case // grantedCapacity will be 1. if (grantedCapacity != -1 && grantedCapacity != desiredCapacity) { } }); ``` ### Setting data ```javascript // You can set the contents of "documents" which are identified by a key. // Documents can only contains strings for their values but binary // data can be added as attachments. // All operations are asynchronous and return Q promises storage.setContents('docKey', "the contents...").then(function() { alert('doc created/updated'); }); // Attachments can be added to documents. // Attachments are Blobs or any subclass of Blob (e.g, File). // Attachments can be added whether or not a corresponding document exists. // setAttachment returns a promise so you know when the set has completed. storage.setAttachment('myDoc', 'titleImage', blob).then(function() { alert('finished setting the titleImage attachment'); }); ``` ### Retrieving Data ```javascript // get the contents of a document storage.getContents('myDoc').then(function(content) { }); // Call getAttachment with the docKey and attachmentKey storage.getAttachment('myDoc', 'titleImage').then(function(titleImage) { // Create an image element with the retrieved attachment // (or video or sound or whatever you decide to attach and use) var img = new Image(); img.src = URL.createObjectURL(titleImage); document.body.appendChild(img); URL.revokeObjectURL(titleImage); }); // If you just need a URL to your attachment you can get // the attachment URL instead of the attachment itself storge.getAttachmentURL('somePreviouslySavedDoc', 'someAttachment').then(function(url) { // do something with the attachment URL // ... // revoke the URL storage.revokeAttachmentURL(url); }); ``` ### Listing ```javascript // You can do an ls to get all of the keys in your data store storage.ls().then(function(listing) { // listing is a list of all of the document keys alert(listing); }); // Or get a listing of a document's attachments storage.ls('somePreviouslySavedDoc').then(function(listing) { // listing is a list of all attachments belonging to `somePreviouslySavedDoc` alert(listing); }); ``` ### Removing ```javascript // you can remove a document with rm // removing a document also removes all of that document's // attachments. storage.rm('somePreviouslySavedDoc'); // you can also rm an attachment storage.rmAttachment('someOtherDocKey', 'attachmentKey'); // removals return promises as well so you know when the removal completes (or fails). storage.rm('docKey').then(function() { alert('Removed!'); }, function(err) { console.error('Failed removal'); console.error(err); }); // clear the entire database storage.clear(); ``` More: * Read the [docs](http://tantaman.github.io/LargeLocalStorage/doc/classes/LargeLocalStorage.html) * Run the [tests](http://tantaman.github.io/LargeLocalStorage/test/) * View the [demo app](http://tantaman.github.io/LargeLocalStorage/examples/album/) ##Including Include it on your page with a script tag: ``` ``` Or load it as an amd module: ``` define(['components/lls/dist/LargeLocalStorage'], function(lls) { var storage = new lls({size: 100 * 1024 * 1024}); }); ``` LLS depends on [Q](https://github.com/kriskowal/q) so you'll have to make sure you have that dependency. ##Getting downlad it directly * (dev) https://raw.github.com/tantaman/LargeLocalStorage/master/dist/LargeLocalStorage.js * (min) https://raw.github.com/tantaman/LargeLocalStorage/master/dist/LargeLocalStorage.min.js Or `bower install lls` ================================================ FILE: bower.json ================================================ { "name": "lls", "version": "0.1.3", "main": "dist/LargeLocalStorage.js", "ignore": [ "examples", "src", "test", "Gruntfile.js", "todo.txt", "package.json" ], "dependencies": { "q": "~0.9.7" }, "devDependencies": { "jquery": "~2.0.3", "bootstrap": "~3.0.0" } } ================================================ FILE: dist/LargeLocalStorage.js ================================================ (function(glob) { var undefined = {}.a; function definition(Q) { /** @author Matt Crinklaw-Vogt */ function PipeContext(handlers, nextMehod, end) { this._handlers = handlers; this._next = nextMehod; this._end = end; this._i = 0; } PipeContext.prototype = { next: function() { // var args = Array.prototype.slice.call(arguments, 0); // args.unshift(this); this.__pipectx = this; return this._next.apply(this, arguments); }, _nextHandler: function() { if (this._i >= this._handlers.length) return this._end; var handler = this._handlers[this._i].handler; this._i += 1; return handler; }, length: function() { return this._handlers.length; } }; function indexOfHandler(handlers, len, target) { for (var i = 0; i < len; ++i) { var handler = handlers[i]; if (handler.name === target || handler.handler === target) { return i; } } return -1; } function forward(ctx) { return ctx.next.apply(ctx, Array.prototype.slice.call(arguments, 1)); } function coerce(methodNames, handler) { methodNames.forEach(function(meth) { if (!handler[meth]) handler[meth] = forward; }); } var abstractPipeline = { addFirst: function(name, handler) { coerce(this._pipedMethodNames, handler); this._handlers.unshift({name: name, handler: handler}); }, addLast: function(name, handler) { coerce(this._pipedMethodNames, handler); this._handlers.push({name: name, handler: handler}); }, /** Add the handler with the given name after the handler specified by target. Target can be a handler name or a handler instance. */ addAfter: function(target, name, handler) { coerce(this._pipedMethodNames, handler); var handlers = this._handlers; var len = handlers.length; var i = indexOfHandler(handlers, len, target); if (i >= 0) { handlers.splice(i+1, 0, {name: name, handler: handler}); } }, /** Add the handler with the given name after the handler specified by target. Target can be a handler name or a handler instance. */ addBefore: function(target, name, handler) { coerce(this._pipedMethodNames, handler); var handlers = this._handlers; var len = handlers.length; var i = indexOfHandler(handlers, len, target); if (i >= 0) { handlers.splice(i, 0, {name: name, handler: handler}); } }, /** Replace the handler specified by target. */ replace: function(target, newName, handler) { coerce(this._pipedMethodNames, handler); var handlers = this._handlers; var len = handlers.length; var i = indexOfHandler(handlers, len, target); if (i >= 0) { handlers.splice(i, 1, {name: newName, handler: handler}); } }, removeFirst: function() { return this._handlers.shift(); }, removeLast: function() { return this._handlers.pop(); }, remove: function(target) { var handlers = this._handlers; var len = handlers.length; var i = indexOfHandler(handlers, len, target); if (i >= 0) handlers.splice(i, 1); }, getHandler: function(name) { var i = indexOfHandler(this._handlers, this._handlers.length, name); if (i >= 0) return this._handlers[i].handler; return null; } }; function createPipeline(pipedMethodNames) { var end = {}; var endStubFunc = function() { return end; }; var nextMethods = {}; function Pipeline(pipedMethodNames) { this.pipe = { _handlers: [], _contextCtor: PipeContext, _nextMethods: nextMethods, end: end, _pipedMethodNames: pipedMethodNames }; } var pipeline = new Pipeline(pipedMethodNames); for (var k in abstractPipeline) { pipeline.pipe[k] = abstractPipeline[k]; } pipedMethodNames.forEach(function(name) { end[name] = endStubFunc; nextMethods[name] = new Function( "var handler = this._nextHandler();" + "handler.__pipectx = this.__pipectx;" + "return handler." + name + ".apply(handler, arguments);"); pipeline[name] = new Function( "var ctx = new this.pipe._contextCtor(this.pipe._handlers, this.pipe._nextMethods." + name + ", this.pipe.end);" + "return ctx.next.apply(ctx, arguments);"); }); return pipeline; } createPipeline.isPipeline = function(obj) { return obj instanceof Pipeline; } var utils = (function() { return { convertToBase64: function(blob, cb) { var fr = new FileReader(); fr.onload = function(e) { cb(e.target.result); }; fr.onerror = function(e) { }; fr.onabort = function(e) { }; fr.readAsDataURL(blob); }, dataURLToBlob: function(dataURL) { var BASE64_MARKER = ';base64,'; if (dataURL.indexOf(BASE64_MARKER) == -1) { var parts = dataURL.split(','); var contentType = parts[0].split(':')[1]; var raw = parts[1]; return new Blob([raw], {type: contentType}); } var parts = dataURL.split(BASE64_MARKER); var contentType = parts[0].split(':')[1]; var raw = window.atob(parts[1]); var rawLength = raw.length; var uInt8Array = new Uint8Array(rawLength); for (var i = 0; i < rawLength; ++i) { uInt8Array[i] = raw.charCodeAt(i); } return new Blob([uInt8Array.buffer], {type: contentType}); }, splitAttachmentPath: function(path) { var parts = path.split('/'); if (parts.length == 1) parts.unshift('__nodoc__'); return parts; }, mapAsync: function(fn, promise) { var deferred = Q.defer(); promise.then(function(data) { _mapAsync(fn, data, [], deferred); }, function(e) { deferred.reject(e); }); return deferred.promise; }, countdown: function(n, cb) { var args = []; return function() { for (var i = 0; i < arguments.length; ++i) args.push(arguments[i]); n -= 1; if (n == 0) cb.apply(this, args); } } }; function _mapAsync(fn, data, result, deferred) { fn(data[result.length], function(v) { result.push(v); if (result.length == data.length) deferred.resolve(result); else _mapAsync(fn, data, result, deferred); }, function(err) { deferred.reject(err); }) } })(); var requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; var persistentStorage = navigator.persistentStorage || navigator.webkitPersistentStorage; var FilesystemAPIProvider = (function(Q) { function makeErrorHandler(deferred, finalDeferred) { // TODO: normalize the error so // we can handle it upstream return function(e) { if (e.code == 1) { deferred.resolve(undefined); } else { if (finalDeferred) finalDeferred.reject(e); else deferred.reject(e); } } } function getAttachmentPath(docKey, attachKey) { docKey = docKey.replace(/\//g, '--'); var attachmentsDir = docKey + "-attachments"; return { dir: attachmentsDir, path: attachmentsDir + "/" + attachKey }; } function readDirEntries(reader, result) { var deferred = Q.defer(); _readDirEntries(reader, result, deferred); return deferred.promise; } function _readDirEntries(reader, result, deferred) { reader.readEntries(function(entries) { if (entries.length == 0) { deferred.resolve(result); } else { result = result.concat(entries); _readDirEntries(reader, result, deferred); } }, function(err) { deferred.reject(err); }); } function entryToFile(entry, cb, eb) { entry.file(cb, eb); } function entryToURL(entry) { return entry.toURL(); } function FSAPI(fs, numBytes, prefix) { this._fs = fs; this._capacity = numBytes; this._prefix = prefix; this.type = "FileSystemAPI"; } FSAPI.prototype = { getContents: function(path, options) { var deferred = Q.defer(); path = this._prefix + path; this._fs.root.getFile(path, {}, function(fileEntry) { fileEntry.file(function(file) { var reader = new FileReader(); reader.onloadend = function(e) { var data = e.target.result; var err; if (options && options.json) { try { data = JSON.parse(data); } catch(e) { err = new Error('unable to parse JSON for ' + path); } } if (err) { deferred.reject(err); } else { deferred.resolve(data); } }; reader.readAsText(file); }, makeErrorHandler(deferred)); }, makeErrorHandler(deferred)); return deferred.promise; }, // create a file at path // and write `data` to it setContents: function(path, data, options) { var deferred = Q.defer(); if (options && options.json) data = JSON.stringify(data); path = this._prefix + path; this._fs.root.getFile(path, {create:true}, function(fileEntry) { fileEntry.createWriter(function(fileWriter) { var blob; fileWriter.onwriteend = function(e) { fileWriter.onwriteend = function() { deferred.resolve(); }; fileWriter.truncate(blob.size); } fileWriter.onerror = makeErrorHandler(deferred); if (data instanceof Blob) { blob = data; } else { blob = new Blob([data], {type: 'text/plain'}); } fileWriter.write(blob); }, makeErrorHandler(deferred)); }, makeErrorHandler(deferred)); return deferred.promise; }, ls: function(docKey) { var isRoot = false; if (!docKey) {docKey = this._prefix; isRoot = true;} else docKey = this._prefix + docKey + "-attachments"; var deferred = Q.defer(); this._fs.root.getDirectory(docKey, {create:false}, function(entry) { var reader = entry.createReader(); readDirEntries(reader, []).then(function(entries) { var listing = []; entries.forEach(function(entry) { if (!entry.isDirectory) { listing.push(entry.name); } }); deferred.resolve(listing); }); }, function(error) { deferred.reject(error); }); return deferred.promise; }, clear: function() { var deferred = Q.defer(); var failed = false; var ecb = function(err) { failed = true; deferred.reject(err); } this._fs.root.getDirectory(this._prefix, {}, function(entry) { var reader = entry.createReader(); reader.readEntries(function(entries) { var latch = utils.countdown(entries.length, function() { if (!failed) deferred.resolve(); }); entries.forEach(function(entry) { if (entry.isDirectory) { entry.removeRecursively(latch, ecb); } else { entry.remove(latch, ecb); } }); if (entries.length == 0) deferred.resolve(); }, ecb); }, ecb); return deferred.promise; }, rm: function(path) { var deferred = Q.defer(); var finalDeferred = Q.defer(); // remove attachments that go along with the path path = this._prefix + path; var attachmentsDir = path + "-attachments"; this._fs.root.getFile(path, {create:false}, function(entry) { entry.remove(function() { deferred.promise.then(finalDeferred.resolve); }, function(err) { finalDeferred.reject(err); }); }, makeErrorHandler(finalDeferred)); this._fs.root.getDirectory(attachmentsDir, {}, function(entry) { entry.removeRecursively(function() { deferred.resolve(); }, function(err) { finalDeferred.reject(err); }); }, makeErrorHandler(deferred, finalDeferred)); return finalDeferred.promise; }, getAttachment: function(docKey, attachKey) { var attachmentPath = this._prefix + getAttachmentPath(docKey, attachKey).path; var deferred = Q.defer(); this._fs.root.getFile(attachmentPath, {}, function(fileEntry) { fileEntry.file(function(file) { if (file.size == 0) deferred.resolve(undefined); else deferred.resolve(file); }, makeErrorHandler(deferred)); }, function(err) { if (err.code == 1) { deferred.resolve(undefined); } else { deferred.reject(err); } }); return deferred.promise; }, getAttachmentURL: function(docKey, attachKey) { var attachmentPath = this._prefix + getAttachmentPath(docKey, attachKey).path; var deferred = Q.defer(); var url = 'filesystem:' + window.location.protocol + '//' + window.location.host + '/persistent/' + attachmentPath; deferred.resolve(url); // this._fs.root.getFile(attachmentPath, {}, function(fileEntry) { // deferred.resolve(fileEntry.toURL()); // }, makeErrorHandler(deferred, "getting attachment file entry")); return deferred.promise; }, getAllAttachments: function(docKey) { var deferred = Q.defer(); var attachmentsDir = this._prefix + docKey + "-attachments"; this._fs.root.getDirectory(attachmentsDir, {}, function(entry) { var reader = entry.createReader(); deferred.resolve( utils.mapAsync(function(entry, cb, eb) { entry.file(function(file) { cb({ data: file, docKey: docKey, attachKey: entry.name }); }, eb); }, readDirEntries(reader, []))); }, function(err) { deferred.resolve([]); }); return deferred.promise; }, getAllAttachmentURLs: function(docKey) { var deferred = Q.defer(); var attachmentsDir = this._prefix + docKey + "-attachments"; this._fs.root.getDirectory(attachmentsDir, {}, function(entry) { var reader = entry.createReader(); readDirEntries(reader, []).then(function(entries) { deferred.resolve(entries.map( function(entry) { return { url: entry.toURL(), docKey: docKey, attachKey: entry.name }; })); }); }, function(err) { deferred.reject(err); }); return deferred.promise; }, revokeAttachmentURL: function(url) { // we return FS urls so this is a no-op // unless someone is being silly and doing // createObjectURL(getAttachment()) ...... }, // Create a folder at dirname(path)+"-attachments" // add attachment under that folder as basename(path) setAttachment: function(docKey, attachKey, data) { var attachInfo = getAttachmentPath(docKey, attachKey); var deferred = Q.defer(); var self = this; this._fs.root.getDirectory(this._prefix + attachInfo.dir, {create:true}, function(dirEntry) { deferred.resolve(self.setContents(attachInfo.path, data)); }, makeErrorHandler(deferred)); return deferred.promise; }, // rm the thing at dirname(path)+"-attachments/"+basename(path) rmAttachment: function(docKey, attachKey) { var attachmentPath = getAttachmentPath(docKey, attachKey).path; var deferred = Q.defer(); this._fs.root.getFile(this._prefix + attachmentPath, {create:false}, function(entry) { entry.remove(function() { deferred.resolve(); }, makeErrorHandler(deferred)); }, makeErrorHandler(deferred)); return deferred.promise; }, getCapacity: function() { return this._capacity; } }; return { init: function(config) { var deferred = Q.defer(); if (!requestFileSystem) { deferred.reject("No FS API"); return deferred.promise; } var prefix = config.name + '/'; persistentStorage.requestQuota(config.size, function(numBytes) { requestFileSystem(window.PERSISTENT, numBytes, function(fs) { fs.root.getDirectory(config.name, {create: true}, function() { deferred.resolve(new FSAPI(fs, numBytes, prefix)); }, function(err) { console.error(err); deferred.reject(err); }); }, function(err) { // TODO: implement various error messages. console.error(err); deferred.reject(err); }); }, function(err) { // TODO: implement various error messages. console.error(err); deferred.reject(err); }); return deferred.promise; }, isAvailable: function() { return requestFileSystem != null; } } })(Q); var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB; var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.OIDBTransaction || window.msIDBTransaction; var IndexedDBProvider = (function(Q) { var URL = window.URL || window.webkitURL; var convertToBase64 = utils.convertToBase64; var dataURLToBlob = utils.dataURLToBlob; function IDB(db) { this._db = db; this.type = 'IndexedDB'; var transaction = this._db.transaction(['attachments'], 'readwrite'); this._supportsBlobs = true; try { transaction.objectStore('attachments') .put(Blob(["sdf"], {type: "text/plain"}), "featurecheck"); } catch (e) { this._supportsBlobs = false; } } // TODO: normalize returns and errors. IDB.prototype = { getContents: function(docKey) { var deferred = Q.defer(); var transaction = this._db.transaction(['files'], 'readonly'); var get = transaction.objectStore('files').get(docKey); get.onsuccess = function(e) { deferred.resolve(e.target.result); }; get.onerror = function(e) { deferred.reject(e); }; return deferred.promise; }, setContents: function(docKey, data) { var deferred = Q.defer(); var transaction = this._db.transaction(['files'], 'readwrite'); var put = transaction.objectStore('files').put(data, docKey); put.onsuccess = function(e) { deferred.resolve(e); }; put.onerror = function(e) { deferred.reject(e); }; return deferred.promise; }, rm: function(docKey) { var deferred = Q.defer(); var finalDeferred = Q.defer(); var transaction = this._db.transaction(['files', 'attachments'], 'readwrite'); var del = transaction.objectStore('files').delete(docKey); del.onsuccess = function(e) { deferred.promise.then(function() { finalDeferred.resolve(); }); }; del.onerror = function(e) { deferred.promise.catch(function() { finalDeferred.reject(e); }); }; var attachmentsStore = transaction.objectStore('attachments'); var index = attachmentsStore.index('fname'); var cursor = index.openCursor(IDBKeyRange.only(docKey)); cursor.onsuccess = function(e) { var cursor = e.target.result; if (cursor) { cursor.delete(); cursor.continue(); } else { deferred.resolve(); } }; cursor.onerror = function(e) { deferred.reject(e); } return finalDeferred.promise; }, getAttachment: function(docKey, attachKey) { var deferred = Q.defer(); var transaction = this._db.transaction(['attachments'], 'readonly'); var get = transaction.objectStore('attachments').get(docKey + '/' + attachKey); var self = this; get.onsuccess = function(e) { if (!e.target.result) { deferred.resolve(undefined); return; } var data = e.target.result.data; if (!self._supportsBlobs) { data = dataURLToBlob(data); } deferred.resolve(data); }; get.onerror = function(e) { deferred.reject(e); }; return deferred.promise; }, ls: function(docKey) { var deferred = Q.defer(); if (!docKey) { // list docs var store = 'files'; } else { // list attachments var store = 'attachments'; } var transaction = this._db.transaction([store], 'readonly'); var cursor = transaction.objectStore(store).openCursor(); var listing = []; cursor.onsuccess = function(e) { var cursor = e.target.result; if (cursor) { listing.push(!docKey ? cursor.key : cursor.key.split('/')[1]); cursor.continue(); } else { deferred.resolve(listing); } }; cursor.onerror = function(e) { deferred.reject(e); }; return deferred.promise; }, clear: function() { var deferred = Q.defer(); var finalDeferred = Q.defer(); var t = this._db.transaction(['attachments', 'files'], 'readwrite'); var req1 = t.objectStore('attachments').clear(); var req2 = t.objectStore('files').clear(); req1.onsuccess = function() { deferred.promise.then(finalDeferred.resolve); }; req2.onsuccess = function() { deferred.resolve(); }; req1.onerror = function(err) { finalDeferred.reject(err); }; req2.onerror = function(err) { finalDeferred.reject(err); }; return finalDeferred.promise; }, getAllAttachments: function(docKey) { var deferred = Q.defer(); var self = this; var transaction = this._db.transaction(['attachments'], 'readonly'); var index = transaction.objectStore('attachments').index('fname'); var cursor = index.openCursor(IDBKeyRange.only(docKey)); var values = []; cursor.onsuccess = function(e) { var cursor = e.target.result; if (cursor) { var data; if (!self._supportsBlobs) { data = dataURLToBlob(cursor.value.data) } else { data = cursor.value.data; } values.push({ data: data, docKey: docKey, attachKey: cursor.primaryKey.split('/')[1] // TODO }); cursor.continue(); } else { deferred.resolve(values); } }; cursor.onerror = function(e) { deferred.reject(e); }; return deferred.promise; }, getAllAttachmentURLs: function(docKey) { var deferred = Q.defer(); this.getAllAttachments(docKey).then(function(attachments) { var urls = attachments.map(function(a) { a.url = URL.createObjectURL(a.data); delete a.data; return a; }); deferred.resolve(urls); }, function(e) { deferred.reject(e); }); return deferred.promise; }, getAttachmentURL: function(docKey, attachKey) { var deferred = Q.defer(); this.getAttachment(docKey, attachKey).then(function(attachment) { deferred.resolve(URL.createObjectURL(attachment)); }, function(e) { deferred.reject(e); }); return deferred.promise; }, revokeAttachmentURL: function(url) { URL.revokeObjectURL(url); }, setAttachment: function(docKey, attachKey, data) { var deferred = Q.defer(); if (data instanceof Blob && !this._supportsBlobs) { var self = this; convertToBase64(data, function(data) { continuation.call(self, data); }); } else { continuation.call(this, data); } function continuation(data) { var obj = { path: docKey + '/' + attachKey, fname: docKey, data: data }; var transaction = this._db.transaction(['attachments'], 'readwrite'); var put = transaction.objectStore('attachments').put(obj); put.onsuccess = function(e) { deferred.resolve(e); }; put.onerror = function(e) { deferred.reject(e); }; } return deferred.promise; }, rmAttachment: function(docKey, attachKey) { var deferred = Q.defer(); var transaction = this._db.transaction(['attachments'], 'readwrite'); var del = transaction.objectStore('attachments').delete(docKey + '/' + attachKey); del.onsuccess = function(e) { deferred.resolve(e); }; del.onerror = function(e) { deferred.reject(e); }; return deferred.promise; } }; return { init: function(config) { var deferred = Q.defer(); var dbVersion = 2; if (!indexedDB || !IDBTransaction) { deferred.reject("No IndexedDB"); return deferred.promise; } var request = indexedDB.open(config.name, dbVersion); function createObjectStore(db) { db.createObjectStore("files"); var attachStore = db.createObjectStore("attachments", {keyPath: 'path'}); attachStore.createIndex('fname', 'fname', {unique: false}) } // TODO: normalize errors request.onerror = function (event) { deferred.reject(event); }; request.onsuccess = function (event) { var db = request.result; db.onerror = function (event) { console.log(event); }; // Chrome workaround if (db.setVersion) { if (db.version != dbVersion) { var setVersion = db.setVersion(dbVersion); setVersion.onsuccess = function () { createObjectStore(db); deferred.resolve(); }; } else { deferred.resolve(new IDB(db)); } } else { deferred.resolve(new IDB(db)); } } request.onupgradeneeded = function (event) { createObjectStore(event.target.result); }; return deferred.promise; }, isAvailable: function() { return indexedDB != null && IDBTransaction != null; } } })(Q); var LocalStorageProvider = (function(Q) { return { init: function() { return Q({type: 'LocalStorage'}); } } })(Q); var openDb = window.openDatabase; var WebSQLProvider = (function(Q) { var URL = window.URL || window.webkitURL; var convertToBase64 = utils.convertToBase64; var dataURLToBlob = utils.dataURLToBlob; function WSQL(db) { this._db = db; this.type = 'WebSQL'; } WSQL.prototype = { getContents: function(docKey, options) { var deferred = Q.defer(); this._db.transaction(function(tx) { tx.executeSql('SELECT value FROM files WHERE fname = ?', [docKey], function(tx, res) { if (res.rows.length == 0) { deferred.resolve(undefined); } else { var data = res.rows.item(0).value; if (options && options.json) data = JSON.parse(data); deferred.resolve(data); } }); }, function(err) { consol.log(err); deferred.reject(err); }); return deferred.promise; }, setContents: function(docKey, data, options) { var deferred = Q.defer(); if (options && options.json) data = JSON.stringify(data); this._db.transaction(function(tx) { tx.executeSql( 'INSERT OR REPLACE INTO files (fname, value) VALUES(?, ?)', [docKey, data]); }, function(err) { console.log(err); deferred.reject(err); }, function() { deferred.resolve(); }); return deferred.promise; }, rm: function(docKey) { var deferred = Q.defer(); this._db.transaction(function(tx) { tx.executeSql('DELETE FROM files WHERE fname = ?', [docKey]); tx.executeSql('DELETE FROM attachments WHERE fname = ?', [docKey]); }, function(err) { console.log(err); deferred.reject(err); }, function() { deferred.resolve(); }); return deferred.promise; }, getAttachment: function(fname, akey) { var deferred = Q.defer(); this._db.transaction(function(tx){ tx.executeSql('SELECT value FROM attachments WHERE fname = ? AND akey = ?', [fname, akey], function(tx, res) { if (res.rows.length == 0) { deferred.resolve(undefined); } else { deferred.resolve(dataURLToBlob(res.rows.item(0).value)); } }); }, function(err) { deferred.reject(err); }); return deferred.promise; }, getAttachmentURL: function(docKey, attachKey) { var deferred = Q.defer(); this.getAttachment(docKey, attachKey).then(function(blob) { deferred.resolve(URL.createObjectURL(blob)); }, function() { deferred.reject(); }); return deferred.promise; }, ls: function(docKey) { var deferred = Q.defer(); var select; var field; if (!docKey) { select = 'SELECT fname FROM files'; field = 'fname'; } else { select = 'SELECT akey FROM attachments WHERE fname = ?'; field = 'akey'; } this._db.transaction(function(tx) { tx.executeSql(select, docKey ? [docKey] : [], function(tx, res) { var listing = []; for (var i = 0; i < res.rows.length; ++i) { listing.push(res.rows.item(i)[field]); } deferred.resolve(listing); }, function(err) { deferred.reject(err); }); }); return deferred.promise; }, clear: function() { var deffered1 = Q.defer(); var deffered2 = Q.defer(); this._db.transaction(function(tx) { tx.executeSql('DELETE FROM files', function() { deffered1.resolve(); }); tx.executeSql('DELETE FROM attachments', function() { deffered2.resolve(); }); }, function(err) { deffered1.reject(err); deffered2.reject(err); }); return Q.all([deffered1, deffered2]); }, getAllAttachments: function(fname) { var deferred = Q.defer(); this._db.transaction(function(tx) { tx.executeSql('SELECT value, akey FROM attachments WHERE fname = ?', [fname], function(tx, res) { // TODO: ship this work off to a webworker // since there could be many of these conversions? var result = []; for (var i = 0; i < res.rows.length; ++i) { var item = res.rows.item(i); result.push({ docKey: fname, attachKey: item.akey, data: dataURLToBlob(item.value) }); } deferred.resolve(result); }); }, function(err) { deferred.reject(err); }); return deferred.promise; }, getAllAttachmentURLs: function(fname) { var deferred = Q.defer(); this.getAllAttachments(fname).then(function(attachments) { var urls = attachments.map(function(a) { a.url = URL.createObjectURL(a.data); delete a.data; return a; }); deferred.resolve(urls); }, function(e) { deferred.reject(e); }); return deferred.promise; }, revokeAttachmentURL: function(url) { URL.revokeObjectURL(url); }, setAttachment: function(fname, akey, data) { var deferred = Q.defer(); var self = this; convertToBase64(data, function(data) { self._db.transaction(function(tx) { tx.executeSql( 'INSERT OR REPLACE INTO attachments (fname, akey, value) VALUES(?, ?, ?)', [fname, akey, data]); }, function(err) { deferred.reject(err); }, function() { deferred.resolve(); }); }); return deferred.promise; }, rmAttachment: function(fname, akey) { var deferred = Q.defer(); this._db.transaction(function(tx) { tx.executeSql('DELETE FROM attachments WHERE fname = ? AND akey = ?', [fname, akey]); }, function(err) { deferred.reject(err); }, function() { deferred.resolve(); }); return deferred.promise; } }; return { init: function(config) { var deferred = Q.defer(); if (!openDb) { deferred.reject("No WebSQL"); return deferred.promise; } var db = openDb(config.name, '1.0', 'large local storage', config.size); db.transaction(function(tx) { tx.executeSql('CREATE TABLE IF NOT EXISTS files (fname unique, value)'); tx.executeSql('CREATE TABLE IF NOT EXISTS attachments (fname, akey, value)'); tx.executeSql('CREATE INDEX IF NOT EXISTS fname_index ON attachments (fname)'); tx.executeSql('CREATE INDEX IF NOT EXISTS akey_index ON attachments (akey)'); tx.executeSql('CREATE UNIQUE INDEX IF NOT EXISTS uniq_attach ON attachments (fname, akey)') }, function(err) { deferred.reject(err); }, function() { deferred.resolve(new WSQL(db)); }); return deferred.promise; }, isAvailable: function() { return openDb != null; } } })(Q); var LargeLocalStorage = (function(Q) { var sessionMeta = localStorage.getItem('LargeLocalStorage-meta'); if (sessionMeta) sessionMeta = JSON.parse(sessionMeta); else sessionMeta = {}; window.addEventListener('beforeunload', function() { localStorage.setItem('LargeLocalStorage-meta', JSON.stringify(sessionMeta)); }); function defaults(options, defaultOptions) { for (var k in defaultOptions) { if (options[k] === undefined) options[k] = defaultOptions[k]; } return options; } var providers = { FileSystemAPI: FilesystemAPIProvider, IndexedDB: IndexedDBProvider, WebSQL: WebSQLProvider // LocalStorage: LocalStorageProvider } var defaultConfig = { size: 10 * 1024 * 1024, name: 'lls' }; function selectImplementation(config) { if (!config) config = {}; config = defaults(config, defaultConfig); if (config.forceProvider) { return providers[config.forceProvider].init(config); } return FilesystemAPIProvider.init(config).then(function(impl) { return Q(impl); }, function() { return IndexedDBProvider.init(config); }).then(function(impl) { return Q(impl); }, function() { return WebSQLProvider.init(config); }).then(function(impl) { return Q(impl); }, function() { console.error('Unable to create any storage implementations. Using LocalStorage'); return LocalStorageProvider.init(config); }); } function copy(obj) { var result = {}; Object.keys(obj).forEach(function(key) { result[key] = obj[key]; }); return result; } function handleDataMigration(storageInstance, config, previousProviderType, currentProivderType) { var previousProviderType = sessionMeta[config.name] && sessionMeta[config.name].lastStorageImpl; if (config.migrate) { if (previousProviderType != currentProivderType && previousProviderType in providers) { config = copy(config); config.forceProvider = previousProviderType; selectImplementation(config).then(function(prevImpl) { config.migrate(null, prevImpl, storageInstance, config); }, function(e) { config.migrate(e); }); } else { if (config.migrationComplete) config.migrationComplete(); } } } /** * * LargeLocalStorage (or LLS) gives you a large capacity * (up to several gig with permission from the user) * key-value store in the browser. * * For storage, LLS uses the [FilesystemAPI](https://developer.mozilla.org/en-US/docs/WebGuide/API/File_System) * when running in Chrome and Opera, * [IndexedDB](https://developer.mozilla.org/en-US/docs/IndexedDB) in Firefox and IE * and [WebSQL](http://www.w3.org/TR/webdatabase/) in Safari. * * When IndexedDB becomes available in Safari, LLS will * update to take advantage of that storage implementation. * * * Upon construction a LargeLocalStorage (LLS) object will be * immediately returned but not necessarily immediately ready for use. * * A LLS object has an `initialized` property which is a promise * that is resolved when the LLS object is ready for us. * * Usage of LLS would typically be: * ``` * var storage = new LargeLocalStorage({size: 75*1024*1024}); * storage.initialized.then(function(grantedCapacity) { * // storage ready to be used. * }); * ``` * * The reason that LLS may not be immediately ready for * use is that some browsers require confirmation from the * user before a storage area may be created. Also, * the browser's native storage APIs are asynchronous. * * If an LLS instance is used before the storage * area is ready then any * calls to it will throw an exception with code: "NO_IMPLEMENTATION" * * This behavior is useful when you want the application * to continue to function--regardless of whether or * not the user has allowed it to store data--and would * like to know when your storage calls fail at the point * of those calls. * * LLS-contrib has utilities to queue storage calls until * the implementation is ready. If an implementation * is never ready this could obviously lead to memory issues * which is why it is not the default behavior. * * @example * var desiredCapacity = 50 * 1024 * 1024; // 50MB * var storage = new LargeLocalStorage({ * // desired capacity, in bytes. * size: desiredCapacity, * * // optional name for your LLS database. Defaults to lls. * // This is the name given to the underlying * // IndexedDB or WebSQL DB or FSAPI Folder. * // LLS's with different names are independent. * name: 'myStorage' * * // the following is an optional param * // that is useful for debugging. * // force LLS to use a specific storage implementation * // forceProvider: 'IndexedDB' or 'WebSQL' or 'FilesystemAPI' * * // These parameters can be used to migrate data from one * // storage implementation to another * // migrate: LargeLocalStorage.copyOldData, * // migrationComplete: function(err) { * // db is initialized and old data has been copied. * // } * }); * storage.initialized.then(function(capacity) { * if (capacity != -1 && capacity != desiredCapacity) { * // the user didn't authorize your storage request * // so instead you have some limitation on your storage * } * }) * * @class LargeLocalStorage * @constructor * @param {object} config {size: sizeInByes, [forceProvider: force a specific implementation]} * @return {LargeLocalStorage} */ function LargeLocalStorage(config) { var deferred = Q.defer(); /** * @property {promise} initialized */ this.initialized = deferred.promise; var piped = createPipeline([ 'ready', 'ls', 'rm', 'clear', 'getContents', 'setContents', 'getAttachment', 'setAttachment', 'getAttachmentURL', 'getAllAttachments', 'getAllAttachmentURLs', 'revokeAttachmentURL', 'rmAttachment', 'getCapacity', 'initialized']); piped.pipe.addLast('lls', this); piped.initialized = this.initialized; var self = this; selectImplementation(config).then(function(impl) { self._impl = impl; handleDataMigration(piped, config, self._impl.type); sessionMeta[config.name] = sessionMeta[config.name] || {}; sessionMeta[config.name].lastStorageImpl = impl.type; deferred.resolve(piped); }).catch(function(e) { // This should be impossible console.log(e); deferred.reject('No storage provider found'); }); return piped; } LargeLocalStorage.prototype = { /** * Whether or not LLS is ready to store data. * The `initialized` property can be used to * await initialization. * @example * // may or may not be true * storage.ready(); * * storage.initialized.then(function() { * // always true * storage.ready(); * }) * @method ready */ ready: function() { return this._impl != null; }, /** * List all attachments under a given key. * * List all documents if no key is provided. * * Returns a promise that is fulfilled with * the listing. * * @example * storage.ls().then(function(docKeys) { * console.log(docKeys); * }) * * @method ls * @param {string} [docKey] * @returns {promise} resolved with the listing, rejected if the listing fails. */ ls: function(docKey) { this._checkAvailability(); return this._impl.ls(docKey); }, /** * Remove the specified document and all * of its attachments. * * Returns a promise that is fulfilled when the * removal completes. * * If no docKey is specified, this throws an error. * * To remove all files in LargeLocalStorage call * `lls.clear();` * * To remove all attachments that were written without * a docKey, call `lls.rm('__emptydoc__');` * * rm works this way to ensure you don't lose * data due to an accidently undefined variable. * * @example * stoarge.rm('exampleDoc').then(function() { * alert('doc and all attachments were removed'); * }) * * @method rm * @param {string} docKey * @returns {promise} resolved when removal completes, rejected if the removal fails. */ rm: function(docKey) { this._checkAvailability(); return this._impl.rm(docKey); }, /** * An explicit way to remove all documents and * attachments from LargeLocalStorage. * * @example * storage.clear().then(function() { * alert('all data has been removed'); * }); * * @returns {promise} resolve when clear completes, rejected if clear fails. */ clear: function() { this._checkAvailability(); return this._impl.clear(); }, /** * Get the contents of a document identified by `docKey` * TODO: normalize all implementations to allow storage * and retrieval of JS objects? * * @example * storage.getContents('exampleDoc').then(function(contents) { * alert(contents); * }); * * @method getContents * @param {string} docKey * @returns {promise} resolved with the contents when the get completes */ getContents: function(docKey, options) { this._checkAvailability(); return this._impl.getContents(docKey, options); }, /** * Set the contents identified by `docKey` to `data`. * The document will be created if it does not exist. * * @example * storage.setContents('exampleDoc', 'some data...').then(function() { * alert('doc written'); * }); * * @method setContents * @param {string} docKey * @param {any} data * @returns {promise} fulfilled when set completes */ setContents: function(docKey, data, options) { this._checkAvailability(); return this._impl.setContents(docKey, data, options); }, /** * Get the attachment identified by `docKey` and `attachKey` * * @example * storage.getAttachment('exampleDoc', 'examplePic').then(function(attachment) { * var url = URL.createObjectURL(attachment); * var image = new Image(url); * document.body.appendChild(image); * URL.revokeObjectURL(url); * }) * * @method getAttachment * @param {string} [docKey] Defaults to `__emptydoc__` * @param {string} attachKey key of the attachment * @returns {promise} fulfilled with the attachment or * rejected if it could not be found. code: 1 */ getAttachment: function(docKey, attachKey) { if (!docKey) docKey = '__emptydoc__'; this._checkAvailability(); return this._impl.getAttachment(docKey, attachKey); }, /** * Set an attachment for a given document. Identified * by `docKey` and `attachKey`. * * @example * storage.setAttachment('myDoc', 'myPic', blob).then(function() { * alert('Attachment written'); * }) * * @method setAttachment * @param {string} [docKey] Defaults to `__emptydoc__` * @param {string} attachKey key for the attachment * @param {any} attachment data * @returns {promise} resolved when the write completes. Rejected * if an error occurs. */ setAttachment: function(docKey, attachKey, data) { if (!docKey) docKey = '__emptydoc__'; this._checkAvailability(); return this._impl.setAttachment(docKey, attachKey, data); }, /** * Get the URL for a given attachment. * * @example * storage.getAttachmentURL('myDoc', 'myPic').then(function(url) { * var image = new Image(); * image.src = url; * document.body.appendChild(image); * storage.revokeAttachmentURL(url); * }) * * This is preferrable to getting the attachment and then getting the * URL via `createObjectURL` (on some systems) as LLS can take advantage of * lower level details to improve performance. * * @method getAttachmentURL * @param {string} [docKey] Identifies the document. Defaults to `__emptydoc__` * @param {string} attachKey Identifies the attachment. * @returns {promose} promise that is resolved with the attachment url. */ getAttachmentURL: function(docKey, attachKey) { if (!docKey) docKey = '__emptydoc__'; this._checkAvailability(); return this._impl.getAttachmentURL(docKey, attachKey); }, /** * Gets all of the attachments for a document. * * @example * storage.getAllAttachments('exampleDoc').then(function(attachEntries) { * attachEntries.map(function(entry) { * var a = entry.data; * // do something with it... * if (a.type.indexOf('image') == 0) { * // show image... * } else if (a.type.indexOf('audio') == 0) { * // play audio... * } else ... * }) * }) * * @method getAllAttachments * @param {string} [docKey] Identifies the document. Defaults to `__emptydoc__` * @returns {promise} Promise that is resolved with all of the attachments for * the given document. */ getAllAttachments: function(docKey) { if (!docKey) docKey = '__emptydoc__'; this._checkAvailability(); return this._impl.getAllAttachments(docKey); }, /** * Gets all attachments URLs for a document. * * @example * storage.getAllAttachmentURLs('exampleDoc').then(function(urlEntries) { * urlEntries.map(function(entry) { * var url = entry.url; * // do something with the url... * }) * }) * * @method getAllAttachmentURLs * @param {string} [docKey] Identifies the document. Defaults to the `__emptydoc__` document. * @returns {promise} Promise that is resolved with all of the attachment * urls for the given doc. */ getAllAttachmentURLs: function(docKey) { if (!docKey) docKey = '__emptydoc__'; this._checkAvailability(); return this._impl.getAllAttachmentURLs(docKey); }, /** * Revoke the attachment URL as required by the underlying * storage system. * * This is akin to `URL.revokeObjectURL(url)` * URLs that come from `getAttachmentURL` or `getAllAttachmentURLs` * should be revoked by LLS and not `URL.revokeObjectURL` * * @example * storage.getAttachmentURL('doc', 'attach').then(function(url) { * // do something with the URL * storage.revokeAttachmentURL(url); * }) * * @method revokeAttachmentURL * @param {string} url The URL as returned by `getAttachmentURL` or `getAttachmentURLs` * @returns {void} */ revokeAttachmentURL: function(url) { this._checkAvailability(); return this._impl.revokeAttachmentURL(url); }, /** * Remove an attachment from a document. * * @example * storage.rmAttachment('exampleDoc', 'someAttachment').then(function() { * alert('exampleDoc/someAttachment removed'); * }).catch(function(e) { * alert('Attachment removal failed: ' + e); * }); * * @method rmAttachment * @param {string} docKey * @param {string} attachKey * @returns {promise} Promise that is resolved once the remove completes */ rmAttachment: function(docKey, attachKey) { if (!docKey) docKey = '__emptydoc__'; this._checkAvailability(); return this._impl.rmAttachment(docKey, attachKey); }, /** * Returns the actual capacity of the storage or -1 * if it is unknown. If the user denies your request for * storage you'll get back some smaller amount of storage than what you * actually requested. * * TODO: return an estimated capacity if actual capacity is unknown? * -Firefox is 50MB until authorized to go above, * -Chrome is some % of available disk space, * -Safari unlimited as long as the user keeps authorizing size increases * -Opera same as safari? * * @example * // the initialized property will call you back with the capacity * storage.initialized.then(function(capacity) { * console.log('Authorized to store: ' + capacity + ' bytes'); * }); * // or if you know your storage is already available * // you can call getCapacity directly * storage.getCapacity() * * @method getCapacity * @returns {number} Capacity, in bytes, of the storage. -1 if unknown. */ getCapacity: function() { this._checkAvailability(); if (this._impl.getCapacity) return this._impl.getCapacity(); else return -1; }, _checkAvailability: function() { if (!this._impl) { throw { msg: "No storage implementation is available yet. The user most likely has not granted you app access to FileSystemAPI or IndexedDB", code: "NO_IMPLEMENTATION" }; } } }; LargeLocalStorage.contrib = {}; function writeAttachments(docKey, attachments, storage) { var promises = []; attachments.forEach(function(attachment) { promises.push(storage.setAttachment(docKey, attachment.attachKey, attachment.data)); }); return Q.all(promises); } function copyDocs(docKeys, oldStorage, newStorage) { var promises = []; docKeys.forEach(function(key) { promises.push(oldStorage.getContents(key).then(function(contents) { return newStorage.setContents(key, contents); })); }); docKeys.forEach(function(key) { promises.push(oldStorage.getAllAttachments(key).then(function(attachments) { return writeAttachments(key, attachments, newStorage); })); }); return Q.all(promises); } LargeLocalStorage.copyOldData = function(err, oldStorage, newStorage, config) { if (err) { throw err; } oldStorage.ls().then(function(docKeys) { return copyDocs(docKeys, oldStorage, newStorage) }).then(function() { if (config.migrationComplete) config.migrationComplete(); }, function(e) { config.migrationComplete(e); }); }; LargeLocalStorage._sessionMeta = sessionMeta; var availableProviders = []; Object.keys(providers).forEach(function(potentialProvider) { if (providers[potentialProvider].isAvailable()) availableProviders.push(potentialProvider); }); LargeLocalStorage.availableProviders = availableProviders; return LargeLocalStorage; })(Q); return LargeLocalStorage; } if (typeof define === 'function' && define.amd) { define(['Q'], definition); } else { glob.LargeLocalStorage = definition.call(glob, Q); } }).call(this, this); ================================================ FILE: dist/contrib/S3Link.js ================================================ LargeLocalStorage.contrib.S3Link = (function() { function S3Link(config) { } S3Link.prototype = { push: function(docKey, options) { } }; return S3Link; })(); ================================================ FILE: dist/contrib/URLCache.js ================================================ LargeLocalStorage.contrib.URLCache = (function() { var defaultOptions = { manageRevocation: true }; function defaults(options, defaultOptions) { for (var k in defaultOptions) { if (options[k] === undefined) options[k] = defaultOptions[k]; } return options; } function add(docKey, attachKey, url) { if (this.options.manageRevocation) expunge.call(this, docKey, attachKey, true); var mainCache = this.cache.main; var docCache = mainCache[docKey]; if (!docCache) { docCache = {}; mainCache[docKey] = docCache; } docCache[attachKey] = url; this.cache.reverse[url] = {docKey: docKey, attachKey: attachKey}; } function addAll(urlEntries) { urlEntries.forEach(function(entry) { add.call(this, entry.docKey, entry.attachKey, entry.url); }, this); } function expunge(docKey, attachKey, needsRevoke) { function delAndRevoke(attachKey) { var url = docCache[attachKey]; delete docCache[attachKey]; delete this.cache.reverse[url]; if (this.options.manageRevocation && needsRevoke) this.llshandler.revokeAttachmentURL(url, {bypassUrlCache: true}); } var docCache = this.cache.main[docKey]; if (docCache) { if (attachKey) { delAndRevoke.call(this, attachKey); } else { for (var attachKey in docCache) { delAndRevoke.call(this, attachKey); } delete this.cache.main[docKey]; } } } function expungeByUrl(url) { var keys = this.cache.reverse[url]; if (keys) { expunge.call(this, keys.docKey, keys.attachKey, false); } } function URLCache(llspipe, options) { options = options || {}; this.options = defaults(options, defaultOptions); this.llshandler = llspipe.pipe.getHandler('lls'); this.pending = {}; this.cache = { main: {}, reverse: {} }; } URLCache.prototype = { setAttachment: function(docKey, attachKey, blob) { expunge.call(this, docKey, attachKey); return this.__pipectx.next(docKey, attachKey, blob); }, rmAttachment: function(docKey, attachKey) { expunge.call(this, docKey, attachKey); return this.__pipectx.next(docKey, attachKey); }, rm: function(docKey) { expunge.call(this, docKey); return this.__pipectx.next(docKey); }, revokeAttachmentURL: function(url, options) { if (!options || !options.bypassUrlCache) expungeByUrl.call(this, url); return this.__pipectx.next(url, options); }, getAttachmentURL: function(docKey, attachKey) { var pendingKey = docKey + attachKey; var pending = this.pending[pendingKey]; if (pending) return pending; var promise = this.__pipectx.next(docKey, attachKey); var self = this; promise.then(function(url) { add.call(self, docKey, attachKey, url); delete self.pending[pendingKey]; }); this.pending[pendingKey] = promise; return promise; }, // TODO: pending between this and getAttachmentURL... // Execute this as an ls and then // a loop on getAttachmentURL instead??? // doing it the way mentiond above // will prevent us from leaking blobs. getAllAttachmentURLs: function(docKey) { var promise = this.__pipectx.next(docKey); var self = this; promise.then(function(urlEntries) { addAll.call(self, urlEntries); }); return promise; }, clear: function() { this.revokeAllCachedURLs(); return this.__pipectx.next(); }, revokeAllCachedURLs: function() { for (var url in this.cache.reverse) { this.llshandler.revokeAttachmentURL(url, {bypassUrlCache: true}); } this.cache.reverse = {}; this.cache.main = {}; } }; return { addTo: function(lls, options) { var cache = new URLCache(lls, options); lls.pipe.addFirst('URLCache', cache); return lls; } } })(); ================================================ FILE: examples/album/app.js ================================================ (function() { 'use strict'; var storage = new LargeLocalStorage({ size: 20 * 1024 * 1024, name: 'lls-album-example' // forceProvider: 'IndexedDB' // forceProvider: 'WebSQL' }); storage.initialized.then(function() { console.log(storage.getCapacity()); var $storageNotice = $('.storageNotice'); $storageNotice.css('opacity', 0); setTimeout(function() { $storageNotice.css('display', 'none'); }, 1100); bind(); }, function() { console.log('denied'); }); function bind() { var dndArea = new Album($('.dndArea')); } function Album($el) { this.$el = $el; this._drop = this._drop.bind(this); this._photoAdded = this._photoAdded.bind(this); this._appendImage = this._appendImage.bind(this); this.$el.on('dragover', copyDragover); this.$el.on('drop', this._drop); this.$thumbs = this.$el.find('.thumbnails'); this.$usage = this.$el.find('.usage'); var self = this; $('#clear').click(function() { storage.clear().then(function() { self.$thumbs.empty(); }).done(); }); this._renderExistingPhotos(); } Album.prototype = { _drop: function(e) { e.stopPropagation(); e.preventDefault(); e = e.originalEvent; foreach(this._photoAdded, keep(isImage, e.dataTransfer.files)); }, _photoAdded: function(file) { // TOOD: see if already exists?? storage.setAttachment('album', file.name, file) .then(function() { return storage.getAttachmentURL('album', file.name); }).then(this._appendImage); }, _appendImage: function(url) { if (this.$usage) { this.$usage.remove(); this.$usage = null; } var container = $('
'); var image = new Image(); image.src = url; var self = this; image.onload = function() { var scale = 171 / image.naturalWidth; var newHeight = scale * image.naturalHeight; if (newHeight > 180) { scale = 180 / image.naturalHeight; newHeight = 180; } var newWidth = scale * image.naturalWidth; image.width = newWidth; image.height = newHeight; container.append(image); self.$thumbs.append(container); }; storage.revokeAttachmentURL(url); }, _renderExistingPhotos: function() { var self = this; storage.getAllAttachmentURLs('album') .then(function(urls) { urls = urls.map(function(u) { return u.url; }); foreach(self._appendImage, urls); }); } }; function copyDragover(e) { e.stopPropagation(); e.preventDefault(); e = e.originalEvent; e.dataTransfer.dropEffect = 'copy'; } function foreach(cb, arr) { for (var i = 0; i < arr.length; ++i) { cb(arr[i]); } } function isImage(file) { return file.type.indexOf('image') == 0; } function keep(pred, arr) { return filter(not(pred), arr); } function not(pred) { return function(e) { return !pred(e); } } function filter(pred, arr) { var result = []; for (var i = 0; i < arr.length; ++i) { var e = arr[i]; if (!pred(e)) result.push(e); } return result; } })(); ================================================ FILE: examples/album/index.html ================================================
Drag and drop images to add to your album.
In order to keep your photos you need to grant this app the ability to save your photos! Please click accept on the browser's prompt.
Clear Album ================================================ FILE: examples/album/main.css ================================================ .dndArea { margin-top: 20px; border: 2px dashed #bbb; border-radius: 5px; min-height: 240px; padding: 20px; } .storageNotice { position: absolute; width: 100%; height: 100%; top: 20px; text-align: center; -webkit-transition: opacity 1s; transition: all 1s; } .usage { width: 100%; height: 100%; text-align: center; font-size: 32px; color: #CCC; } ================================================ FILE: package.json ================================================ { "name": "lls", "version": "0.1.3", "description": "Storage large files and blob in a cross platform way, in the browser", "main": "Gruntfile.js", "directories": { "test": "test" }, "scripts": { "test": "test" }, "repository": { "type": "git", "url": "git://github.com/tantaman/LargeLocalStorage.git" }, "keywords": [ "LocalStorage", "key", "value", "key-value", "storage", "browser", "indexeddb", "websql", "filesystemapi" ], "author": "Matt Crinklaw-Vogt", "license": "MIT", "bugs": { "url": "https://github.com/tantaman/LargeLocalStorage/issues" }, "devDependencies": { "grunt": "~0.4.1", "grunt-contrib-requirejs": "~0.4.1", "matchdep": "~0.3.0", "grunt-contrib-concat": "~0.3.0", "grunt-contrib-watch": "~0.5.3", "grunt-contrib-connect": "~0.5.0", "grunt-contrib-yuidoc": "~0.5.0", "yuidoc-library-theme": "git://github.com/tantaman/yuidoc-library-theme.git", "grunt-contrib-copy": "~0.4.1", "grunt-contrib-uglify": "~0.2.4" } } ================================================ FILE: src/LargeLocalStorage.js ================================================ var LargeLocalStorage = (function(Q) { var sessionMeta = localStorage.getItem('LargeLocalStorage-meta'); if (sessionMeta) sessionMeta = JSON.parse(sessionMeta); else sessionMeta = {}; window.addEventListener('beforeunload', function() { localStorage.setItem('LargeLocalStorage-meta', JSON.stringify(sessionMeta)); }); function defaults(options, defaultOptions) { for (var k in defaultOptions) { if (options[k] === undefined) options[k] = defaultOptions[k]; } return options; } var providers = { FileSystemAPI: FilesystemAPIProvider, IndexedDB: IndexedDBProvider, WebSQL: WebSQLProvider // LocalStorage: LocalStorageProvider } var defaultConfig = { size: 10 * 1024 * 1024, name: 'lls' }; function selectImplementation(config) { if (!config) config = {}; config = defaults(config, defaultConfig); if (config.forceProvider) { return providers[config.forceProvider].init(config); } return FilesystemAPIProvider.init(config).then(function(impl) { return Q(impl); }, function() { return IndexedDBProvider.init(config); }).then(function(impl) { return Q(impl); }, function() { return WebSQLProvider.init(config); }).then(function(impl) { return Q(impl); }, function() { console.error('Unable to create any storage implementations. Using LocalStorage'); return LocalStorageProvider.init(config); }); } function copy(obj) { var result = {}; Object.keys(obj).forEach(function(key) { result[key] = obj[key]; }); return result; } function handleDataMigration(storageInstance, config, previousProviderType, currentProivderType) { var previousProviderType = sessionMeta[config.name] && sessionMeta[config.name].lastStorageImpl; if (config.migrate) { if (previousProviderType != currentProivderType && previousProviderType in providers) { config = copy(config); config.forceProvider = previousProviderType; selectImplementation(config).then(function(prevImpl) { config.migrate(null, prevImpl, storageInstance, config); }, function(e) { config.migrate(e); }); } else { if (config.migrationComplete) config.migrationComplete(); } } } /** * * LargeLocalStorage (or LLS) gives you a large capacity * (up to several gig with permission from the user) * key-value store in the browser. * * For storage, LLS uses the [FilesystemAPI](https://developer.mozilla.org/en-US/docs/WebGuide/API/File_System) * when running in Chrome and Opera, * [IndexedDB](https://developer.mozilla.org/en-US/docs/IndexedDB) in Firefox and IE * and [WebSQL](http://www.w3.org/TR/webdatabase/) in Safari. * * When IndexedDB becomes available in Safari, LLS will * update to take advantage of that storage implementation. * * * Upon construction a LargeLocalStorage (LLS) object will be * immediately returned but not necessarily immediately ready for use. * * A LLS object has an `initialized` property which is a promise * that is resolved when the LLS object is ready for us. * * Usage of LLS would typically be: * ``` * var storage = new LargeLocalStorage({size: 75*1024*1024}); * storage.initialized.then(function(grantedCapacity) { * // storage ready to be used. * }); * ``` * * The reason that LLS may not be immediately ready for * use is that some browsers require confirmation from the * user before a storage area may be created. Also, * the browser's native storage APIs are asynchronous. * * If an LLS instance is used before the storage * area is ready then any * calls to it will throw an exception with code: "NO_IMPLEMENTATION" * * This behavior is useful when you want the application * to continue to function--regardless of whether or * not the user has allowed it to store data--and would * like to know when your storage calls fail at the point * of those calls. * * LLS-contrib has utilities to queue storage calls until * the implementation is ready. If an implementation * is never ready this could obviously lead to memory issues * which is why it is not the default behavior. * * @example * var desiredCapacity = 50 * 1024 * 1024; // 50MB * var storage = new LargeLocalStorage({ * // desired capacity, in bytes. * size: desiredCapacity, * * // optional name for your LLS database. Defaults to lls. * // This is the name given to the underlying * // IndexedDB or WebSQL DB or FSAPI Folder. * // LLS's with different names are independent. * name: 'myStorage' * * // the following is an optional param * // that is useful for debugging. * // force LLS to use a specific storage implementation * // forceProvider: 'IndexedDB' or 'WebSQL' or 'FilesystemAPI' * * // These parameters can be used to migrate data from one * // storage implementation to another * // migrate: LargeLocalStorage.copyOldData, * // migrationComplete: function(err) { * // db is initialized and old data has been copied. * // } * }); * storage.initialized.then(function(capacity) { * if (capacity != -1 && capacity != desiredCapacity) { * // the user didn't authorize your storage request * // so instead you have some limitation on your storage * } * }) * * @class LargeLocalStorage * @constructor * @param {object} config {size: sizeInByes, [forceProvider: force a specific implementation]} * @return {LargeLocalStorage} */ function LargeLocalStorage(config) { var deferred = Q.defer(); /** * @property {promise} initialized */ this.initialized = deferred.promise; var piped = createPipeline([ 'ready', 'ls', 'rm', 'clear', 'getContents', 'setContents', 'getAttachment', 'setAttachment', 'getAttachmentURL', 'getAllAttachments', 'getAllAttachmentURLs', 'revokeAttachmentURL', 'rmAttachment', 'getCapacity', 'initialized']); piped.pipe.addLast('lls', this); piped.initialized = this.initialized; var self = this; selectImplementation(config).then(function(impl) { self._impl = impl; handleDataMigration(piped, config, self._impl.type); sessionMeta[config.name] = sessionMeta[config.name] || {}; sessionMeta[config.name].lastStorageImpl = impl.type; deferred.resolve(piped); }).catch(function(e) { // This should be impossible console.log(e); deferred.reject('No storage provider found'); }); return piped; } LargeLocalStorage.prototype = { /** * Whether or not LLS is ready to store data. * The `initialized` property can be used to * await initialization. * @example * // may or may not be true * storage.ready(); * * storage.initialized.then(function() { * // always true * storage.ready(); * }) * @method ready */ ready: function() { return this._impl != null; }, /** * List all attachments under a given key. * * List all documents if no key is provided. * * Returns a promise that is fulfilled with * the listing. * * @example * storage.ls().then(function(docKeys) { * console.log(docKeys); * }) * * @method ls * @param {string} [docKey] * @returns {promise} resolved with the listing, rejected if the listing fails. */ ls: function(docKey) { this._checkAvailability(); return this._impl.ls(docKey); }, /** * Remove the specified document and all * of its attachments. * * Returns a promise that is fulfilled when the * removal completes. * * If no docKey is specified, this throws an error. * * To remove all files in LargeLocalStorage call * `lls.clear();` * * To remove all attachments that were written without * a docKey, call `lls.rm('__emptydoc__');` * * rm works this way to ensure you don't lose * data due to an accidently undefined variable. * * @example * stoarge.rm('exampleDoc').then(function() { * alert('doc and all attachments were removed'); * }) * * @method rm * @param {string} docKey * @returns {promise} resolved when removal completes, rejected if the removal fails. */ rm: function(docKey) { this._checkAvailability(); return this._impl.rm(docKey); }, /** * An explicit way to remove all documents and * attachments from LargeLocalStorage. * * @example * storage.clear().then(function() { * alert('all data has been removed'); * }); * * @returns {promise} resolve when clear completes, rejected if clear fails. */ clear: function() { this._checkAvailability(); return this._impl.clear(); }, /** * Get the contents of a document identified by `docKey` * TODO: normalize all implementations to allow storage * and retrieval of JS objects? * * @example * storage.getContents('exampleDoc').then(function(contents) { * alert(contents); * }); * * @method getContents * @param {string} docKey * @returns {promise} resolved with the contents when the get completes */ getContents: function(docKey, options) { this._checkAvailability(); return this._impl.getContents(docKey, options); }, /** * Set the contents identified by `docKey` to `data`. * The document will be created if it does not exist. * * @example * storage.setContents('exampleDoc', 'some data...').then(function() { * alert('doc written'); * }); * * @method setContents * @param {string} docKey * @param {any} data * @returns {promise} fulfilled when set completes */ setContents: function(docKey, data, options) { this._checkAvailability(); return this._impl.setContents(docKey, data, options); }, /** * Get the attachment identified by `docKey` and `attachKey` * * @example * storage.getAttachment('exampleDoc', 'examplePic').then(function(attachment) { * var url = URL.createObjectURL(attachment); * var image = new Image(url); * document.body.appendChild(image); * URL.revokeObjectURL(url); * }) * * @method getAttachment * @param {string} [docKey] Defaults to `__emptydoc__` * @param {string} attachKey key of the attachment * @returns {promise} fulfilled with the attachment or * rejected if it could not be found. code: 1 */ getAttachment: function(docKey, attachKey) { if (!docKey) docKey = '__emptydoc__'; this._checkAvailability(); return this._impl.getAttachment(docKey, attachKey); }, /** * Set an attachment for a given document. Identified * by `docKey` and `attachKey`. * * @example * storage.setAttachment('myDoc', 'myPic', blob).then(function() { * alert('Attachment written'); * }) * * @method setAttachment * @param {string} [docKey] Defaults to `__emptydoc__` * @param {string} attachKey key for the attachment * @param {any} attachment data * @returns {promise} resolved when the write completes. Rejected * if an error occurs. */ setAttachment: function(docKey, attachKey, data) { if (!docKey) docKey = '__emptydoc__'; this._checkAvailability(); return this._impl.setAttachment(docKey, attachKey, data); }, /** * Get the URL for a given attachment. * * @example * storage.getAttachmentURL('myDoc', 'myPic').then(function(url) { * var image = new Image(); * image.src = url; * document.body.appendChild(image); * storage.revokeAttachmentURL(url); * }) * * This is preferrable to getting the attachment and then getting the * URL via `createObjectURL` (on some systems) as LLS can take advantage of * lower level details to improve performance. * * @method getAttachmentURL * @param {string} [docKey] Identifies the document. Defaults to `__emptydoc__` * @param {string} attachKey Identifies the attachment. * @returns {promose} promise that is resolved with the attachment url. */ getAttachmentURL: function(docKey, attachKey) { if (!docKey) docKey = '__emptydoc__'; this._checkAvailability(); return this._impl.getAttachmentURL(docKey, attachKey); }, /** * Gets all of the attachments for a document. * * @example * storage.getAllAttachments('exampleDoc').then(function(attachEntries) { * attachEntries.map(function(entry) { * var a = entry.data; * // do something with it... * if (a.type.indexOf('image') == 0) { * // show image... * } else if (a.type.indexOf('audio') == 0) { * // play audio... * } else ... * }) * }) * * @method getAllAttachments * @param {string} [docKey] Identifies the document. Defaults to `__emptydoc__` * @returns {promise} Promise that is resolved with all of the attachments for * the given document. */ getAllAttachments: function(docKey) { if (!docKey) docKey = '__emptydoc__'; this._checkAvailability(); return this._impl.getAllAttachments(docKey); }, /** * Gets all attachments URLs for a document. * * @example * storage.getAllAttachmentURLs('exampleDoc').then(function(urlEntries) { * urlEntries.map(function(entry) { * var url = entry.url; * // do something with the url... * }) * }) * * @method getAllAttachmentURLs * @param {string} [docKey] Identifies the document. Defaults to the `__emptydoc__` document. * @returns {promise} Promise that is resolved with all of the attachment * urls for the given doc. */ getAllAttachmentURLs: function(docKey) { if (!docKey) docKey = '__emptydoc__'; this._checkAvailability(); return this._impl.getAllAttachmentURLs(docKey); }, /** * Revoke the attachment URL as required by the underlying * storage system. * * This is akin to `URL.revokeObjectURL(url)` * URLs that come from `getAttachmentURL` or `getAllAttachmentURLs` * should be revoked by LLS and not `URL.revokeObjectURL` * * @example * storage.getAttachmentURL('doc', 'attach').then(function(url) { * // do something with the URL * storage.revokeAttachmentURL(url); * }) * * @method revokeAttachmentURL * @param {string} url The URL as returned by `getAttachmentURL` or `getAttachmentURLs` * @returns {void} */ revokeAttachmentURL: function(url) { this._checkAvailability(); return this._impl.revokeAttachmentURL(url); }, /** * Remove an attachment from a document. * * @example * storage.rmAttachment('exampleDoc', 'someAttachment').then(function() { * alert('exampleDoc/someAttachment removed'); * }).catch(function(e) { * alert('Attachment removal failed: ' + e); * }); * * @method rmAttachment * @param {string} docKey * @param {string} attachKey * @returns {promise} Promise that is resolved once the remove completes */ rmAttachment: function(docKey, attachKey) { if (!docKey) docKey = '__emptydoc__'; this._checkAvailability(); return this._impl.rmAttachment(docKey, attachKey); }, /** * Returns the actual capacity of the storage or -1 * if it is unknown. If the user denies your request for * storage you'll get back some smaller amount of storage than what you * actually requested. * * TODO: return an estimated capacity if actual capacity is unknown? * -Firefox is 50MB until authorized to go above, * -Chrome is some % of available disk space, * -Safari unlimited as long as the user keeps authorizing size increases * -Opera same as safari? * * @example * // the initialized property will call you back with the capacity * storage.initialized.then(function(capacity) { * console.log('Authorized to store: ' + capacity + ' bytes'); * }); * // or if you know your storage is already available * // you can call getCapacity directly * storage.getCapacity() * * @method getCapacity * @returns {number} Capacity, in bytes, of the storage. -1 if unknown. */ getCapacity: function() { this._checkAvailability(); if (this._impl.getCapacity) return this._impl.getCapacity(); else return -1; }, _checkAvailability: function() { if (!this._impl) { throw { msg: "No storage implementation is available yet. The user most likely has not granted you app access to FileSystemAPI or IndexedDB", code: "NO_IMPLEMENTATION" }; } } }; LargeLocalStorage.contrib = {}; function writeAttachments(docKey, attachments, storage) { var promises = []; attachments.forEach(function(attachment) { promises.push(storage.setAttachment(docKey, attachment.attachKey, attachment.data)); }); return Q.all(promises); } function copyDocs(docKeys, oldStorage, newStorage) { var promises = []; docKeys.forEach(function(key) { promises.push(oldStorage.getContents(key).then(function(contents) { return newStorage.setContents(key, contents); })); }); docKeys.forEach(function(key) { promises.push(oldStorage.getAllAttachments(key).then(function(attachments) { return writeAttachments(key, attachments, newStorage); })); }); return Q.all(promises); } LargeLocalStorage.copyOldData = function(err, oldStorage, newStorage, config) { if (err) { throw err; } oldStorage.ls().then(function(docKeys) { return copyDocs(docKeys, oldStorage, newStorage) }).then(function() { if (config.migrationComplete) config.migrationComplete(); }, function(e) { config.migrationComplete(e); }); }; LargeLocalStorage._sessionMeta = sessionMeta; var availableProviders = []; Object.keys(providers).forEach(function(potentialProvider) { if (providers[potentialProvider].isAvailable()) availableProviders.push(potentialProvider); }); LargeLocalStorage.availableProviders = availableProviders; return LargeLocalStorage; })(Q); ================================================ FILE: src/contrib/S3Link.js ================================================ LargeLocalStorage.contrib.S3Link = (function() { function S3Link(config) { } S3Link.prototype = { push: function(docKey, options) { } }; return S3Link; })(); ================================================ FILE: src/contrib/URLCache.js ================================================ LargeLocalStorage.contrib.URLCache = (function() { var defaultOptions = { manageRevocation: true }; function defaults(options, defaultOptions) { for (var k in defaultOptions) { if (options[k] === undefined) options[k] = defaultOptions[k]; } return options; } function add(docKey, attachKey, url) { if (this.options.manageRevocation) expunge.call(this, docKey, attachKey, true); var mainCache = this.cache.main; var docCache = mainCache[docKey]; if (!docCache) { docCache = {}; mainCache[docKey] = docCache; } docCache[attachKey] = url; this.cache.reverse[url] = {docKey: docKey, attachKey: attachKey}; } function addAll(urlEntries) { urlEntries.forEach(function(entry) { add.call(this, entry.docKey, entry.attachKey, entry.url); }, this); } function expunge(docKey, attachKey, needsRevoke) { function delAndRevoke(attachKey) { var url = docCache[attachKey]; delete docCache[attachKey]; delete this.cache.reverse[url]; if (this.options.manageRevocation && needsRevoke) this.llshandler.revokeAttachmentURL(url, {bypassUrlCache: true}); } var docCache = this.cache.main[docKey]; if (docCache) { if (attachKey) { delAndRevoke.call(this, attachKey); } else { for (var attachKey in docCache) { delAndRevoke.call(this, attachKey); } delete this.cache.main[docKey]; } } } function expungeByUrl(url) { var keys = this.cache.reverse[url]; if (keys) { expunge.call(this, keys.docKey, keys.attachKey, false); } } function URLCache(llspipe, options) { options = options || {}; this.options = defaults(options, defaultOptions); this.llshandler = llspipe.pipe.getHandler('lls'); this.pending = {}; this.cache = { main: {}, reverse: {} }; } URLCache.prototype = { setAttachment: function(docKey, attachKey, blob) { expunge.call(this, docKey, attachKey); return this.__pipectx.next(docKey, attachKey, blob); }, rmAttachment: function(docKey, attachKey) { expunge.call(this, docKey, attachKey); return this.__pipectx.next(docKey, attachKey); }, rm: function(docKey) { expunge.call(this, docKey); return this.__pipectx.next(docKey); }, revokeAttachmentURL: function(url, options) { if (!options || !options.bypassUrlCache) expungeByUrl.call(this, url); return this.__pipectx.next(url, options); }, getAttachmentURL: function(docKey, attachKey) { var pendingKey = docKey + attachKey; var pending = this.pending[pendingKey]; if (pending) return pending; var promise = this.__pipectx.next(docKey, attachKey); var self = this; promise.then(function(url) { add.call(self, docKey, attachKey, url); delete self.pending[pendingKey]; }); this.pending[pendingKey] = promise; return promise; }, // TODO: pending between this and getAttachmentURL... // Execute this as an ls and then // a loop on getAttachmentURL instead??? // doing it the way mentiond above // will prevent us from leaking blobs. getAllAttachmentURLs: function(docKey) { var promise = this.__pipectx.next(docKey); var self = this; promise.then(function(urlEntries) { addAll.call(self, urlEntries); }); return promise; }, clear: function() { this.revokeAllCachedURLs(); return this.__pipectx.next(); }, revokeAllCachedURLs: function() { for (var url in this.cache.reverse) { this.llshandler.revokeAttachmentURL(url, {bypassUrlCache: true}); } this.cache.reverse = {}; this.cache.main = {}; } }; return { addTo: function(lls, options) { var cache = new URLCache(lls, options); lls.pipe.addFirst('URLCache', cache); return lls; } } })(); ================================================ FILE: src/errors.js ================================================ define({ }); ================================================ FILE: src/footer.js ================================================ return LargeLocalStorage; } if (typeof define === 'function' && define.amd) { define(['Q'], definition); } else { glob.LargeLocalStorage = definition.call(glob, Q); } }).call(this, this); ================================================ FILE: src/header.js ================================================ (function(glob) { var undefined = {}.a; function definition(Q) { ================================================ FILE: src/impls/FilesystemAPIProvider.js ================================================ var requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; var persistentStorage = navigator.persistentStorage || navigator.webkitPersistentStorage; var FilesystemAPIProvider = (function(Q) { function makeErrorHandler(deferred, finalDeferred) { // TODO: normalize the error so // we can handle it upstream return function(e) { if (e.code == 1) { deferred.resolve(undefined); } else { if (finalDeferred) finalDeferred.reject(e); else deferred.reject(e); } } } function getAttachmentPath(docKey, attachKey) { docKey = docKey.replace(/\//g, '--'); var attachmentsDir = docKey + "-attachments"; return { dir: attachmentsDir, path: attachmentsDir + "/" + attachKey }; } function readDirEntries(reader, result) { var deferred = Q.defer(); _readDirEntries(reader, result, deferred); return deferred.promise; } function _readDirEntries(reader, result, deferred) { reader.readEntries(function(entries) { if (entries.length == 0) { deferred.resolve(result); } else { result = result.concat(entries); _readDirEntries(reader, result, deferred); } }, function(err) { deferred.reject(err); }); } function entryToFile(entry, cb, eb) { entry.file(cb, eb); } function entryToURL(entry) { return entry.toURL(); } function FSAPI(fs, numBytes, prefix) { this._fs = fs; this._capacity = numBytes; this._prefix = prefix; this.type = "FileSystemAPI"; } FSAPI.prototype = { getContents: function(path, options) { var deferred = Q.defer(); path = this._prefix + path; this._fs.root.getFile(path, {}, function(fileEntry) { fileEntry.file(function(file) { var reader = new FileReader(); reader.onloadend = function(e) { var data = e.target.result; var err; if (options && options.json) { try { data = JSON.parse(data); } catch(e) { err = new Error('unable to parse JSON for ' + path); } } if (err) { deferred.reject(err); } else { deferred.resolve(data); } }; reader.readAsText(file); }, makeErrorHandler(deferred)); }, makeErrorHandler(deferred)); return deferred.promise; }, // create a file at path // and write `data` to it setContents: function(path, data, options) { var deferred = Q.defer(); if (options && options.json) data = JSON.stringify(data); path = this._prefix + path; this._fs.root.getFile(path, {create:true}, function(fileEntry) { fileEntry.createWriter(function(fileWriter) { var blob; fileWriter.onwriteend = function(e) { fileWriter.onwriteend = function() { deferred.resolve(); }; fileWriter.truncate(blob.size); } fileWriter.onerror = makeErrorHandler(deferred); if (data instanceof Blob) { blob = data; } else { blob = new Blob([data], {type: 'text/plain'}); } fileWriter.write(blob); }, makeErrorHandler(deferred)); }, makeErrorHandler(deferred)); return deferred.promise; }, ls: function(docKey) { var isRoot = false; if (!docKey) {docKey = this._prefix; isRoot = true;} else docKey = this._prefix + docKey + "-attachments"; var deferred = Q.defer(); this._fs.root.getDirectory(docKey, {create:false}, function(entry) { var reader = entry.createReader(); readDirEntries(reader, []).then(function(entries) { var listing = []; entries.forEach(function(entry) { if (!entry.isDirectory) { listing.push(entry.name); } }); deferred.resolve(listing); }); }, function(error) { deferred.reject(error); }); return deferred.promise; }, clear: function() { var deferred = Q.defer(); var failed = false; var ecb = function(err) { failed = true; deferred.reject(err); } this._fs.root.getDirectory(this._prefix, {}, function(entry) { var reader = entry.createReader(); reader.readEntries(function(entries) { var latch = utils.countdown(entries.length, function() { if (!failed) deferred.resolve(); }); entries.forEach(function(entry) { if (entry.isDirectory) { entry.removeRecursively(latch, ecb); } else { entry.remove(latch, ecb); } }); if (entries.length == 0) deferred.resolve(); }, ecb); }, ecb); return deferred.promise; }, rm: function(path) { var deferred = Q.defer(); var finalDeferred = Q.defer(); // remove attachments that go along with the path path = this._prefix + path; var attachmentsDir = path + "-attachments"; this._fs.root.getFile(path, {create:false}, function(entry) { entry.remove(function() { deferred.promise.then(finalDeferred.resolve); }, function(err) { finalDeferred.reject(err); }); }, makeErrorHandler(finalDeferred)); this._fs.root.getDirectory(attachmentsDir, {}, function(entry) { entry.removeRecursively(function() { deferred.resolve(); }, function(err) { finalDeferred.reject(err); }); }, makeErrorHandler(deferred, finalDeferred)); return finalDeferred.promise; }, getAttachment: function(docKey, attachKey) { var attachmentPath = this._prefix + getAttachmentPath(docKey, attachKey).path; var deferred = Q.defer(); this._fs.root.getFile(attachmentPath, {}, function(fileEntry) { fileEntry.file(function(file) { if (file.size == 0) deferred.resolve(undefined); else deferred.resolve(file); }, makeErrorHandler(deferred)); }, function(err) { if (err.code == 1) { deferred.resolve(undefined); } else { deferred.reject(err); } }); return deferred.promise; }, getAttachmentURL: function(docKey, attachKey) { var attachmentPath = this._prefix + getAttachmentPath(docKey, attachKey).path; var deferred = Q.defer(); var url = 'filesystem:' + window.location.protocol + '//' + window.location.host + '/persistent/' + attachmentPath; deferred.resolve(url); // this._fs.root.getFile(attachmentPath, {}, function(fileEntry) { // deferred.resolve(fileEntry.toURL()); // }, makeErrorHandler(deferred, "getting attachment file entry")); return deferred.promise; }, getAllAttachments: function(docKey) { var deferred = Q.defer(); var attachmentsDir = this._prefix + docKey + "-attachments"; this._fs.root.getDirectory(attachmentsDir, {}, function(entry) { var reader = entry.createReader(); deferred.resolve( utils.mapAsync(function(entry, cb, eb) { entry.file(function(file) { cb({ data: file, docKey: docKey, attachKey: entry.name }); }, eb); }, readDirEntries(reader, []))); }, function(err) { deferred.resolve([]); }); return deferred.promise; }, getAllAttachmentURLs: function(docKey) { var deferred = Q.defer(); var attachmentsDir = this._prefix + docKey + "-attachments"; this._fs.root.getDirectory(attachmentsDir, {}, function(entry) { var reader = entry.createReader(); readDirEntries(reader, []).then(function(entries) { deferred.resolve(entries.map( function(entry) { return { url: entry.toURL(), docKey: docKey, attachKey: entry.name }; })); }); }, function(err) { deferred.reject(err); }); return deferred.promise; }, revokeAttachmentURL: function(url) { // we return FS urls so this is a no-op // unless someone is being silly and doing // createObjectURL(getAttachment()) ...... }, // Create a folder at dirname(path)+"-attachments" // add attachment under that folder as basename(path) setAttachment: function(docKey, attachKey, data) { var attachInfo = getAttachmentPath(docKey, attachKey); var deferred = Q.defer(); var self = this; this._fs.root.getDirectory(this._prefix + attachInfo.dir, {create:true}, function(dirEntry) { deferred.resolve(self.setContents(attachInfo.path, data)); }, makeErrorHandler(deferred)); return deferred.promise; }, // rm the thing at dirname(path)+"-attachments/"+basename(path) rmAttachment: function(docKey, attachKey) { var attachmentPath = getAttachmentPath(docKey, attachKey).path; var deferred = Q.defer(); this._fs.root.getFile(this._prefix + attachmentPath, {create:false}, function(entry) { entry.remove(function() { deferred.resolve(); }, makeErrorHandler(deferred)); }, makeErrorHandler(deferred)); return deferred.promise; }, getCapacity: function() { return this._capacity; } }; return { init: function(config) { var deferred = Q.defer(); if (!requestFileSystem) { deferred.reject("No FS API"); return deferred.promise; } var prefix = config.name + '/'; persistentStorage.requestQuota(config.size, function(numBytes) { requestFileSystem(window.PERSISTENT, numBytes, function(fs) { fs.root.getDirectory(config.name, {create: true}, function() { deferred.resolve(new FSAPI(fs, numBytes, prefix)); }, function(err) { console.error(err); deferred.reject(err); }); }, function(err) { // TODO: implement various error messages. console.error(err); deferred.reject(err); }); }, function(err) { // TODO: implement various error messages. console.error(err); deferred.reject(err); }); return deferred.promise; }, isAvailable: function() { return requestFileSystem != null; } } })(Q); ================================================ FILE: src/impls/IndexedDBProvider.js ================================================ var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB; var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.OIDBTransaction || window.msIDBTransaction; var IndexedDBProvider = (function(Q) { var URL = window.URL || window.webkitURL; var convertToBase64 = utils.convertToBase64; var dataURLToBlob = utils.dataURLToBlob; function IDB(db) { this._db = db; this.type = 'IndexedDB'; var transaction = this._db.transaction(['attachments'], 'readwrite'); this._supportsBlobs = true; try { transaction.objectStore('attachments') .put(Blob(["sdf"], {type: "text/plain"}), "featurecheck"); } catch (e) { this._supportsBlobs = false; } } // TODO: normalize returns and errors. IDB.prototype = { getContents: function(docKey) { var deferred = Q.defer(); var transaction = this._db.transaction(['files'], 'readonly'); var get = transaction.objectStore('files').get(docKey); get.onsuccess = function(e) { deferred.resolve(e.target.result); }; get.onerror = function(e) { deferred.reject(e); }; return deferred.promise; }, setContents: function(docKey, data) { var deferred = Q.defer(); var transaction = this._db.transaction(['files'], 'readwrite'); var put = transaction.objectStore('files').put(data, docKey); put.onsuccess = function(e) { deferred.resolve(e); }; put.onerror = function(e) { deferred.reject(e); }; return deferred.promise; }, rm: function(docKey) { var deferred = Q.defer(); var finalDeferred = Q.defer(); var transaction = this._db.transaction(['files', 'attachments'], 'readwrite'); var del = transaction.objectStore('files').delete(docKey); del.onsuccess = function(e) { deferred.promise.then(function() { finalDeferred.resolve(); }); }; del.onerror = function(e) { deferred.promise.catch(function() { finalDeferred.reject(e); }); }; var attachmentsStore = transaction.objectStore('attachments'); var index = attachmentsStore.index('fname'); var cursor = index.openCursor(IDBKeyRange.only(docKey)); cursor.onsuccess = function(e) { var cursor = e.target.result; if (cursor) { cursor.delete(); cursor.continue(); } else { deferred.resolve(); } }; cursor.onerror = function(e) { deferred.reject(e); } return finalDeferred.promise; }, getAttachment: function(docKey, attachKey) { var deferred = Q.defer(); var transaction = this._db.transaction(['attachments'], 'readonly'); var get = transaction.objectStore('attachments').get(docKey + '/' + attachKey); var self = this; get.onsuccess = function(e) { if (!e.target.result) { deferred.resolve(undefined); return; } var data = e.target.result.data; if (!self._supportsBlobs) { data = dataURLToBlob(data); } deferred.resolve(data); }; get.onerror = function(e) { deferred.reject(e); }; return deferred.promise; }, ls: function(docKey) { var deferred = Q.defer(); if (!docKey) { // list docs var store = 'files'; } else { // list attachments var store = 'attachments'; } var transaction = this._db.transaction([store], 'readonly'); var cursor = transaction.objectStore(store).openCursor(); var listing = []; cursor.onsuccess = function(e) { var cursor = e.target.result; if (cursor) { listing.push(!docKey ? cursor.key : cursor.key.split('/')[1]); cursor.continue(); } else { deferred.resolve(listing); } }; cursor.onerror = function(e) { deferred.reject(e); }; return deferred.promise; }, clear: function() { var deferred = Q.defer(); var finalDeferred = Q.defer(); var t = this._db.transaction(['attachments', 'files'], 'readwrite'); var req1 = t.objectStore('attachments').clear(); var req2 = t.objectStore('files').clear(); req1.onsuccess = function() { deferred.promise.then(finalDeferred.resolve); }; req2.onsuccess = function() { deferred.resolve(); }; req1.onerror = function(err) { finalDeferred.reject(err); }; req2.onerror = function(err) { finalDeferred.reject(err); }; return finalDeferred.promise; }, getAllAttachments: function(docKey) { var deferred = Q.defer(); var self = this; var transaction = this._db.transaction(['attachments'], 'readonly'); var index = transaction.objectStore('attachments').index('fname'); var cursor = index.openCursor(IDBKeyRange.only(docKey)); var values = []; cursor.onsuccess = function(e) { var cursor = e.target.result; if (cursor) { var data; if (!self._supportsBlobs) { data = dataURLToBlob(cursor.value.data) } else { data = cursor.value.data; } values.push({ data: data, docKey: docKey, attachKey: cursor.primaryKey.split('/')[1] // TODO }); cursor.continue(); } else { deferred.resolve(values); } }; cursor.onerror = function(e) { deferred.reject(e); }; return deferred.promise; }, getAllAttachmentURLs: function(docKey) { var deferred = Q.defer(); this.getAllAttachments(docKey).then(function(attachments) { var urls = attachments.map(function(a) { a.url = URL.createObjectURL(a.data); delete a.data; return a; }); deferred.resolve(urls); }, function(e) { deferred.reject(e); }); return deferred.promise; }, getAttachmentURL: function(docKey, attachKey) { var deferred = Q.defer(); this.getAttachment(docKey, attachKey).then(function(attachment) { deferred.resolve(URL.createObjectURL(attachment)); }, function(e) { deferred.reject(e); }); return deferred.promise; }, revokeAttachmentURL: function(url) { URL.revokeObjectURL(url); }, setAttachment: function(docKey, attachKey, data) { var deferred = Q.defer(); if (data instanceof Blob && !this._supportsBlobs) { var self = this; convertToBase64(data, function(data) { continuation.call(self, data); }); } else { continuation.call(this, data); } function continuation(data) { var obj = { path: docKey + '/' + attachKey, fname: docKey, data: data }; var transaction = this._db.transaction(['attachments'], 'readwrite'); var put = transaction.objectStore('attachments').put(obj); put.onsuccess = function(e) { deferred.resolve(e); }; put.onerror = function(e) { deferred.reject(e); }; } return deferred.promise; }, rmAttachment: function(docKey, attachKey) { var deferred = Q.defer(); var transaction = this._db.transaction(['attachments'], 'readwrite'); var del = transaction.objectStore('attachments').delete(docKey + '/' + attachKey); del.onsuccess = function(e) { deferred.resolve(e); }; del.onerror = function(e) { deferred.reject(e); }; return deferred.promise; } }; return { init: function(config) { var deferred = Q.defer(); var dbVersion = 2; if (!indexedDB || !IDBTransaction) { deferred.reject("No IndexedDB"); return deferred.promise; } var request = indexedDB.open(config.name, dbVersion); function createObjectStore(db) { db.createObjectStore("files"); var attachStore = db.createObjectStore("attachments", {keyPath: 'path'}); attachStore.createIndex('fname', 'fname', {unique: false}) } // TODO: normalize errors request.onerror = function (event) { deferred.reject(event); }; request.onsuccess = function (event) { var db = request.result; db.onerror = function (event) { console.log(event); }; // Chrome workaround if (db.setVersion) { if (db.version != dbVersion) { var setVersion = db.setVersion(dbVersion); setVersion.onsuccess = function () { createObjectStore(db); deferred.resolve(); }; } else { deferred.resolve(new IDB(db)); } } else { deferred.resolve(new IDB(db)); } } request.onupgradeneeded = function (event) { createObjectStore(event.target.result); }; return deferred.promise; }, isAvailable: function() { return indexedDB != null && IDBTransaction != null; } } })(Q); ================================================ FILE: src/impls/LocalStorageProvider.js ================================================ var LocalStorageProvider = (function(Q) { return { init: function() { return Q({type: 'LocalStorage'}); } } })(Q); ================================================ FILE: src/impls/WebSQLProvider.js ================================================ var openDb = window.openDatabase; var WebSQLProvider = (function(Q) { var URL = window.URL || window.webkitURL; var convertToBase64 = utils.convertToBase64; var dataURLToBlob = utils.dataURLToBlob; function WSQL(db) { this._db = db; this.type = 'WebSQL'; } WSQL.prototype = { getContents: function(docKey, options) { var deferred = Q.defer(); this._db.transaction(function(tx) { tx.executeSql('SELECT value FROM files WHERE fname = ?', [docKey], function(tx, res) { if (res.rows.length == 0) { deferred.resolve(undefined); } else { var data = res.rows.item(0).value; if (options && options.json) data = JSON.parse(data); deferred.resolve(data); } }); }, function(err) { consol.log(err); deferred.reject(err); }); return deferred.promise; }, setContents: function(docKey, data, options) { var deferred = Q.defer(); if (options && options.json) data = JSON.stringify(data); this._db.transaction(function(tx) { tx.executeSql( 'INSERT OR REPLACE INTO files (fname, value) VALUES(?, ?)', [docKey, data]); }, function(err) { console.log(err); deferred.reject(err); }, function() { deferred.resolve(); }); return deferred.promise; }, rm: function(docKey) { var deferred = Q.defer(); this._db.transaction(function(tx) { tx.executeSql('DELETE FROM files WHERE fname = ?', [docKey]); tx.executeSql('DELETE FROM attachments WHERE fname = ?', [docKey]); }, function(err) { console.log(err); deferred.reject(err); }, function() { deferred.resolve(); }); return deferred.promise; }, getAttachment: function(fname, akey) { var deferred = Q.defer(); this._db.transaction(function(tx){ tx.executeSql('SELECT value FROM attachments WHERE fname = ? AND akey = ?', [fname, akey], function(tx, res) { if (res.rows.length == 0) { deferred.resolve(undefined); } else { deferred.resolve(dataURLToBlob(res.rows.item(0).value)); } }); }, function(err) { deferred.reject(err); }); return deferred.promise; }, getAttachmentURL: function(docKey, attachKey) { var deferred = Q.defer(); this.getAttachment(docKey, attachKey).then(function(blob) { deferred.resolve(URL.createObjectURL(blob)); }, function() { deferred.reject(); }); return deferred.promise; }, ls: function(docKey) { var deferred = Q.defer(); var select; var field; if (!docKey) { select = 'SELECT fname FROM files'; field = 'fname'; } else { select = 'SELECT akey FROM attachments WHERE fname = ?'; field = 'akey'; } this._db.transaction(function(tx) { tx.executeSql(select, docKey ? [docKey] : [], function(tx, res) { var listing = []; for (var i = 0; i < res.rows.length; ++i) { listing.push(res.rows.item(i)[field]); } deferred.resolve(listing); }, function(err) { deferred.reject(err); }); }); return deferred.promise; }, clear: function() { var deffered1 = Q.defer(); var deffered2 = Q.defer(); this._db.transaction(function(tx) { tx.executeSql('DELETE FROM files', function() { deffered1.resolve(); }); tx.executeSql('DELETE FROM attachments', function() { deffered2.resolve(); }); }, function(err) { deffered1.reject(err); deffered2.reject(err); }); return Q.all([deffered1, deffered2]); }, getAllAttachments: function(fname) { var deferred = Q.defer(); this._db.transaction(function(tx) { tx.executeSql('SELECT value, akey FROM attachments WHERE fname = ?', [fname], function(tx, res) { // TODO: ship this work off to a webworker // since there could be many of these conversions? var result = []; for (var i = 0; i < res.rows.length; ++i) { var item = res.rows.item(i); result.push({ docKey: fname, attachKey: item.akey, data: dataURLToBlob(item.value) }); } deferred.resolve(result); }); }, function(err) { deferred.reject(err); }); return deferred.promise; }, getAllAttachmentURLs: function(fname) { var deferred = Q.defer(); this.getAllAttachments(fname).then(function(attachments) { var urls = attachments.map(function(a) { a.url = URL.createObjectURL(a.data); delete a.data; return a; }); deferred.resolve(urls); }, function(e) { deferred.reject(e); }); return deferred.promise; }, revokeAttachmentURL: function(url) { URL.revokeObjectURL(url); }, setAttachment: function(fname, akey, data) { var deferred = Q.defer(); var self = this; convertToBase64(data, function(data) { self._db.transaction(function(tx) { tx.executeSql( 'INSERT OR REPLACE INTO attachments (fname, akey, value) VALUES(?, ?, ?)', [fname, akey, data]); }, function(err) { deferred.reject(err); }, function() { deferred.resolve(); }); }); return deferred.promise; }, rmAttachment: function(fname, akey) { var deferred = Q.defer(); this._db.transaction(function(tx) { tx.executeSql('DELETE FROM attachments WHERE fname = ? AND akey = ?', [fname, akey]); }, function(err) { deferred.reject(err); }, function() { deferred.resolve(); }); return deferred.promise; } }; return { init: function(config) { var deferred = Q.defer(); if (!openDb) { deferred.reject("No WebSQL"); return deferred.promise; } var db = openDb(config.name, '1.0', 'large local storage', config.size); db.transaction(function(tx) { tx.executeSql('CREATE TABLE IF NOT EXISTS files (fname unique, value)'); tx.executeSql('CREATE TABLE IF NOT EXISTS attachments (fname, akey, value)'); tx.executeSql('CREATE INDEX IF NOT EXISTS fname_index ON attachments (fname)'); tx.executeSql('CREATE INDEX IF NOT EXISTS akey_index ON attachments (akey)'); tx.executeSql('CREATE UNIQUE INDEX IF NOT EXISTS uniq_attach ON attachments (fname, akey)') }, function(err) { deferred.reject(err); }, function() { deferred.resolve(new WSQL(db)); }); return deferred.promise; }, isAvailable: function() { return openDb != null; } } })(Q); ================================================ FILE: src/impls/utils.js ================================================ var utils = (function() { return { convertToBase64: function(blob, cb) { var fr = new FileReader(); fr.onload = function(e) { cb(e.target.result); }; fr.onerror = function(e) { }; fr.onabort = function(e) { }; fr.readAsDataURL(blob); }, dataURLToBlob: function(dataURL) { var BASE64_MARKER = ';base64,'; if (dataURL.indexOf(BASE64_MARKER) == -1) { var parts = dataURL.split(','); var contentType = parts[0].split(':')[1]; var raw = parts[1]; return new Blob([raw], {type: contentType}); } var parts = dataURL.split(BASE64_MARKER); var contentType = parts[0].split(':')[1]; var raw = window.atob(parts[1]); var rawLength = raw.length; var uInt8Array = new Uint8Array(rawLength); for (var i = 0; i < rawLength; ++i) { uInt8Array[i] = raw.charCodeAt(i); } return new Blob([uInt8Array.buffer], {type: contentType}); }, splitAttachmentPath: function(path) { var parts = path.split('/'); if (parts.length == 1) parts.unshift('__nodoc__'); return parts; }, mapAsync: function(fn, promise) { var deferred = Q.defer(); promise.then(function(data) { _mapAsync(fn, data, [], deferred); }, function(e) { deferred.reject(e); }); return deferred.promise; }, countdown: function(n, cb) { var args = []; return function() { for (var i = 0; i < arguments.length; ++i) args.push(arguments[i]); n -= 1; if (n == 0) cb.apply(this, args); } } }; function _mapAsync(fn, data, result, deferred) { fn(data[result.length], function(v) { result.push(v); if (result.length == data.length) deferred.resolve(result); else _mapAsync(fn, data, result, deferred); }, function(err) { deferred.reject(err); }) } })(); ================================================ FILE: src/pipeline.js ================================================ /** @author Matt Crinklaw-Vogt */ function PipeContext(handlers, nextMehod, end) { this._handlers = handlers; this._next = nextMehod; this._end = end; this._i = 0; } PipeContext.prototype = { next: function() { // var args = Array.prototype.slice.call(arguments, 0); // args.unshift(this); this.__pipectx = this; return this._next.apply(this, arguments); }, _nextHandler: function() { if (this._i >= this._handlers.length) return this._end; var handler = this._handlers[this._i].handler; this._i += 1; return handler; }, length: function() { return this._handlers.length; } }; function indexOfHandler(handlers, len, target) { for (var i = 0; i < len; ++i) { var handler = handlers[i]; if (handler.name === target || handler.handler === target) { return i; } } return -1; } function forward(ctx) { return ctx.next.apply(ctx, Array.prototype.slice.call(arguments, 1)); } function coerce(methodNames, handler) { methodNames.forEach(function(meth) { if (!handler[meth]) handler[meth] = forward; }); } var abstractPipeline = { addFirst: function(name, handler) { coerce(this._pipedMethodNames, handler); this._handlers.unshift({name: name, handler: handler}); }, addLast: function(name, handler) { coerce(this._pipedMethodNames, handler); this._handlers.push({name: name, handler: handler}); }, /** Add the handler with the given name after the handler specified by target. Target can be a handler name or a handler instance. */ addAfter: function(target, name, handler) { coerce(this._pipedMethodNames, handler); var handlers = this._handlers; var len = handlers.length; var i = indexOfHandler(handlers, len, target); if (i >= 0) { handlers.splice(i+1, 0, {name: name, handler: handler}); } }, /** Add the handler with the given name after the handler specified by target. Target can be a handler name or a handler instance. */ addBefore: function(target, name, handler) { coerce(this._pipedMethodNames, handler); var handlers = this._handlers; var len = handlers.length; var i = indexOfHandler(handlers, len, target); if (i >= 0) { handlers.splice(i, 0, {name: name, handler: handler}); } }, /** Replace the handler specified by target. */ replace: function(target, newName, handler) { coerce(this._pipedMethodNames, handler); var handlers = this._handlers; var len = handlers.length; var i = indexOfHandler(handlers, len, target); if (i >= 0) { handlers.splice(i, 1, {name: newName, handler: handler}); } }, removeFirst: function() { return this._handlers.shift(); }, removeLast: function() { return this._handlers.pop(); }, remove: function(target) { var handlers = this._handlers; var len = handlers.length; var i = indexOfHandler(handlers, len, target); if (i >= 0) handlers.splice(i, 1); }, getHandler: function(name) { var i = indexOfHandler(this._handlers, this._handlers.length, name); if (i >= 0) return this._handlers[i].handler; return null; } }; function createPipeline(pipedMethodNames) { var end = {}; var endStubFunc = function() { return end; }; var nextMethods = {}; function Pipeline(pipedMethodNames) { this.pipe = { _handlers: [], _contextCtor: PipeContext, _nextMethods: nextMethods, end: end, _pipedMethodNames: pipedMethodNames }; } var pipeline = new Pipeline(pipedMethodNames); for (var k in abstractPipeline) { pipeline.pipe[k] = abstractPipeline[k]; } pipedMethodNames.forEach(function(name) { end[name] = endStubFunc; nextMethods[name] = new Function( "var handler = this._nextHandler();" + "handler.__pipectx = this.__pipectx;" + "return handler." + name + ".apply(handler, arguments);"); pipeline[name] = new Function( "var ctx = new this.pipe._contextCtor(this.pipe._handlers, this.pipe._nextMethods." + name + ", this.pipe.end);" + "return ctx.next.apply(ctx, arguments);"); }); return pipeline; } createPipeline.isPipeline = function(obj) { return obj instanceof Pipeline; } ================================================ FILE: test/index.html ================================================ Mocha Spec Runner
================================================ FILE: test/lib/chai.js ================================================ !function (name, context, definition) { if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') { module.exports = definition(); } else if (typeof define === 'function' && typeof define.amd === 'object') { define(function () { return definition(); }); } else { context[name] = definition(); } }('chai', this, function () { function require(p) { var path = require.resolve(p) , mod = require.modules[path]; if (!mod) throw new Error('failed to require "' + p + '"'); if (!mod.exports) { mod.exports = {}; mod.call(mod.exports, mod, mod.exports, require.relative(path)); } return mod.exports; } require.modules = {}; require.resolve = function (path) { var orig = path , reg = path + '.js' , index = path + '/index.js'; return require.modules[reg] && reg || require.modules[index] && index || orig; }; require.register = function (path, fn) { require.modules[path] = fn; }; require.relative = function (parent) { return function(p){ if ('.' != p[0]) return require(p); var path = parent.split('/') , segs = p.split('/'); path.pop(); for (var i = 0; i < segs.length; i++) { var seg = segs[i]; if ('..' == seg) path.pop(); else if ('.' != seg) path.push(seg); } return require(path.join('/')); }; }; require.alias = function (from, to) { var fn = require.modules[from]; require.modules[to] = fn; }; require.register("chai.js", function(module, exports, require){ /*! * chai * Copyright(c) 2011-2012 Jake Luer * MIT Licensed */ var used = [] , exports = module.exports = {}; /*! * Chai version */ exports.version = '1.2.0'; /*! * Primary `Assertion` prototype */ exports.Assertion = require('./chai/assertion'); /*! * Assertion Error */ exports.AssertionError = require('./chai/browser/error'); /*! * Utils for plugins (not exported) */ var util = require('./chai/utils'); /** * # .use(function) * * Provides a way to extend the internals of Chai * * @param {Function} * @returns {this} for chaining * @api public */ exports.use = function (fn) { if (!~used.indexOf(fn)) { fn(this, util); used.push(fn); } return this; }; /*! * Core Assertions */ var core = require('./chai/core/assertions'); exports.use(core); /*! * Expect interface */ var expect = require('./chai/interface/expect'); exports.use(expect); /*! * Should interface */ var should = require('./chai/interface/should'); exports.use(should); /*! * Assert interface */ var assert = require('./chai/interface/assert'); exports.use(assert); }); // module: chai.js require.register("chai/assertion.js", function(module, exports, require){ /*! * chai * http://chaijs.com * Copyright(c) 2011-2012 Jake Luer * MIT Licensed */ /*! * Module dependencies. */ var AssertionError = require('./browser/error') , util = require('./utils') , flag = util.flag; /*! * Module export. */ module.exports = Assertion; /*! * Assertion Constructor * * Creates object for chaining. * * @api private */ function Assertion (obj, msg, stack) { flag(this, 'ssfi', stack || arguments.callee); flag(this, 'object', obj); flag(this, 'message', msg); } /*! * ### Assertion.includeStack * * User configurable property, influences whether stack trace * is included in Assertion error message. Default of false * suppresses stack trace in the error message * * Assertion.includeStack = true; // enable stack on error * * @api public */ Assertion.includeStack = false; Assertion.addProperty = function (name, fn) { util.addProperty(this.prototype, name, fn); }; Assertion.addMethod = function (name, fn) { util.addMethod(this.prototype, name, fn); }; Assertion.addChainableMethod = function (name, fn, chainingBehavior) { util.addChainableMethod(this.prototype, name, fn, chainingBehavior); }; Assertion.overwriteProperty = function (name, fn) { util.overwriteProperty(this.prototype, name, fn); }; Assertion.overwriteMethod = function (name, fn) { util.overwriteMethod(this.prototype, name, fn); }; /*! * ### .assert(expression, message, negateMessage, expected, actual) * * Executes an expression and check expectations. Throws AssertionError for reporting if test doesn't pass. * * @name assert * @param {Philosophical} expression to be tested * @param {String} message to display if fails * @param {String} negatedMessage to display if negated expression fails * @param {Mixed} expected value (remember to check for negation) * @param {Mixed} actual (optional) will default to `this.obj` * @api private */ Assertion.prototype.assert = function (expr, msg, negateMsg, expected, _actual) { var ok = util.test(this, arguments); if (!ok) { var msg = util.getMessage(this, arguments) , actual = util.getActual(this, arguments); throw new AssertionError({ message: msg , actual: actual , expected: expected , stackStartFunction: (Assertion.includeStack) ? this.assert : flag(this, 'ssfi') }); } }; /*! * ### ._obj * * Quick reference to stored `actual` value for plugin developers. * * @api private */ Object.defineProperty(Assertion.prototype, '_obj', { get: function () { return flag(this, 'object'); } , set: function (val) { flag(this, 'object', val); } }); }); // module: chai/assertion.js require.register("chai/browser/error.js", function(module, exports, require){ /*! * chai * Copyright(c) 2011-2012 Jake Luer * MIT Licensed */ module.exports = AssertionError; function AssertionError (options) { options = options || {}; this.message = options.message; this.actual = options.actual; this.expected = options.expected; this.operator = options.operator; if (options.stackStartFunction && Error.captureStackTrace) { var stackStartFunction = options.stackStartFunction; Error.captureStackTrace(this, stackStartFunction); } } AssertionError.prototype = Object.create(Error.prototype); AssertionError.prototype.name = 'AssertionError'; AssertionError.prototype.constructor = AssertionError; AssertionError.prototype.toString = function() { return this.message; }; }); // module: chai/browser/error.js require.register("chai/core/assertions.js", function(module, exports, require){ /*! * chai * http://chaijs.com * Copyright(c) 2011-2012 Jake Luer * MIT Licensed */ module.exports = function (chai, _) { var Assertion = chai.Assertion , toString = Object.prototype.toString , flag = _.flag; /** * ### Language Chains * * The following are provide as chainable getters to * improve the readability of your assertions. They * do not provide an testing capability unless they * have been overwritten by a plugin. * * **Chains** * * - to * - be * - been * - is * - that * - and * - have * - with * * @name language chains * @api public */ [ 'to', 'be', 'been' , 'is', 'and', 'have' , 'with', 'that' ].forEach(function (chain) { Assertion.addProperty(chain, function () { return this; }); }); /** * ### .not * * Negates any of assertions following in the chain. * * expect(foo).to.not.equal('bar'); * expect(goodFn).to.not.throw(Error); * expect({ foo: 'baz' }).to.have.property('foo') * .and.not.equal('bar'); * * @name not * @api public */ Assertion.addProperty('not', function () { flag(this, 'negate', true); }); /** * ### .deep * * Sets the `deep` flag, later used by the `equal` and * `property` assertions. * * expect(foo).to.deep.equal({ bar: 'baz' }); * expect({ foo: { bar: { baz: 'quux' } } }) * .to.have.deep.property('foo.bar.baz', 'quux'); * * @name deep * @api public */ Assertion.addProperty('deep', function () { flag(this, 'deep', true); }); /** * ### .a(type) * * The `a` and `an` assertions are aliases that can be * used either as language chains or to assert a value's * type (as revealed by `Object.prototype.toString`). * * // typeof * expect('test').to.be.a('string'); * expect({ foo: 'bar' }).to.be.an('object'); * expect(null).to.be.a('null'); * expect(undefined).to.be.an('undefined'); * * // language chain * expect(foo).to.be.an.instanceof(Foo); * * @name a * @alias an * @param {String} type * @api public */ function an(type) { var obj = flag(this, 'object') , klassStart = type.charAt(0).toUpperCase() , klass = klassStart + type.slice(1) , article = ~[ 'A', 'E', 'I', 'O', 'U' ].indexOf(klassStart) ? 'an ' : 'a '; this.assert( '[object ' + klass + ']' === toString.call(obj) , 'expected #{this} to be ' + article + type , 'expected #{this} not to be ' + article + type ); } Assertion.addChainableMethod('an', an); Assertion.addChainableMethod('a', an); /** * ### .include(value) * * The `include` and `contain` assertions can be used as either property * based language chains or as methods to assert the inclusion of an object * in an array or a substring in a string. When used as language chains, * they toggle the `contain` flag for the `keys` assertion. * * expect([1,2,3]).to.include(2); * expect('foobar').to.contain('foo'); * expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo'); * * @name include * @alias contain * @param {Object|String|Number} obj * @api public */ function includeChainingBehavior () { flag(this, 'contains', true); } function include (val) { var obj = flag(this, 'object') this.assert( ~obj.indexOf(val) , 'expected #{this} to include ' + _.inspect(val) , 'expected #{this} to not include ' + _.inspect(val)); } Assertion.addChainableMethod('include', include, includeChainingBehavior); Assertion.addChainableMethod('contain', include, includeChainingBehavior); /** * ### .ok * * Asserts that the target is truthy. * * expect('everthing').to.be.ok; * expect(1).to.be.ok; * expect(false).to.not.be.ok; * expect(undefined).to.not.be.ok; * expect(null).to.not.be.ok; * * @name ok * @api public */ Assertion.addProperty('ok', function () { this.assert( flag(this, 'object') , 'expected #{this} to be truthy' , 'expected #{this} to be falsy'); }); /** * ### .true * * Asserts that the target is `true`. * * expect(true).to.be.true; * expect(1).to.not.be.true; * * @name true * @api public */ Assertion.addProperty('true', function () { this.assert( true === flag(this, 'object') , 'expected #{this} to be true' , 'expected #{this} to be false' , this.negate ? false : true ); }); /** * ### .false * * Asserts that the target is `false`. * * expect(false).to.be.false; * expect(0).to.not.be.false; * * @name false * @api public */ Assertion.addProperty('false', function () { this.assert( false === flag(this, 'object') , 'expected #{this} to be false' , 'expected #{this} to be true' , this.negate ? true : false ); }); /** * ### .null * * Asserts that the target is `null`. * * expect(null).to.be.null; * expect(undefined).not.to.be.null; * * @name null * @api public */ Assertion.addProperty('null', function () { this.assert( null === flag(this, 'object') , 'expected #{this} to be null' , 'expected #{this} not to be null' ); }); /** * ### .undefined * * Asserts that the target is `undefined`. * * expect(undefined).to.be.undefined; * expect(null).to.not.be.undefined; * * @name undefined * @api public */ Assertion.addProperty('undefined', function () { this.assert( undefined === flag(this, 'object') , 'expected #{this} to be undefined' , 'expected #{this} not to be undefined' ); }); /** * ### .exist * * Asserts that the target is neither `null` nor `undefined`. * * var foo = 'hi' * , bar = null * , baz; * * expect(foo).to.exist; * expect(bar).to.not.exist; * expect(baz).to.not.exist; * * @name exist * @api public */ Assertion.addProperty('exist', function () { this.assert( null != flag(this, 'object') , 'expected #{this} to exist' , 'expected #{this} to not exist' ); }); /** * ### .empty * * Asserts that the target's length is `0`. For arrays, it checks * the `length` property. For objects, it gets the count of * enumerable keys. * * expect([]).to.be.empty; * expect('').to.be.empty; * expect({}).to.be.empty; * * @name empty * @api public */ Assertion.addProperty('empty', function () { var obj = flag(this, 'object') , expected = obj; if (Array.isArray(obj) || 'string' === typeof object) { expected = obj.length; } else if (typeof obj === 'object') { expected = Object.keys(obj).length; } this.assert( !expected , 'expected #{this} to be empty' , 'expected #{this} not to be empty' ); }); /** * ### .arguments * * Asserts that the target is an arguments object. * * function test () { * expect(arguments).to.be.arguments; * } * * @name arguments * @alias Arguments * @api public */ function checkArguments () { var obj = flag(this, 'object') , type = Object.prototype.toString.call(obj); this.assert( '[object Arguments]' === type , 'expected #{this} to be arguments but got ' + type , 'expected #{this} to not be arguments' ); } Assertion.addProperty('arguments', checkArguments); Assertion.addProperty('Arguments', checkArguments); /** * ### .equal(value) * * Asserts that the target is strictly equal (`===`) to `value`. * Alternately, if the `deep` flag is set, asserts that * the target is deeply equal to `value`. * * expect('hello').to.equal('hello'); * expect(42).to.equal(42); * expect(1).to.not.equal(true); * expect({ foo: 'bar' }).to.not.equal({ foo: 'bar' }); * expect({ foo: 'bar' }).to.deep.equal({ foo: 'bar' }); * * @name equal * @alias equals * @alias eq * @alias deep.equal * @param {Mixed} value * @api public */ function assertEqual (val) { var obj = flag(this, 'object'); if (flag(this, 'deep')) { return this.eql(val); } else { this.assert( val === obj , 'expected #{this} to equal #{exp}' , 'expected #{this} to not equal #{exp}' , val ); } } Assertion.addMethod('equal', assertEqual); Assertion.addMethod('equals', assertEqual); Assertion.addMethod('eq', assertEqual); /** * ### .eql(value) * * Asserts that the target is deeply equal to `value`. * * expect({ foo: 'bar' }).to.eql({ foo: 'bar' }); * expect([ 1, 2, 3 ]).to.eql([ 1, 2, 3 ]); * * @name eql * @param {Mixed} value * @api public */ Assertion.addMethod('eql', function (obj) { this.assert( _.eql(obj, flag(this, 'object')) , 'expected #{this} to deeply equal #{exp}' , 'expected #{this} to not deeply equal #{exp}' , obj ); }); /** * ### .above(value) * * Asserts that the target is greater than `value`. * * expect(10).to.be.above(5); * * Can also be used in conjunction with `length` to * assert a minimum length. The benefit being a * more informative error message than if the length * was supplied directly. * * expect('foo').to.have.length.above(2); * expect([ 1, 2, 3 ]).to.have.length.above(2); * * @name above * @alias gt * @alias greaterThan * @param {Number} value * @api public */ function assertAbove (n) { var obj = flag(this, 'object'); if (flag(this, 'doLength')) { new Assertion(obj).to.have.property('length'); var len = obj.length; this.assert( len > n , 'expected #{this} to have a length above #{exp} but got #{act}' , 'expected #{this} to not have a length above #{exp}' , n , len ); } else { this.assert( obj > n , 'expected #{this} to be above ' + n , 'expected #{this} to be below ' + n ); } } Assertion.addMethod('above', assertAbove); Assertion.addMethod('gt', assertAbove); Assertion.addMethod('greaterThan', assertAbove); /** * ### .below(value) * * Asserts that the target is less than `value`. * * expect(5).to.be.below(10); * * Can also be used in conjunction with `length` to * assert a maximum length. The benefit being a * more informative error message than if the length * was supplied directly. * * expect('foo').to.have.length.below(4); * expect([ 1, 2, 3 ]).to.have.length.below(4); * * @name below * @alias lt * @alias lessThan * @param {Number} value * @api public */ function assertBelow (n) { var obj = flag(this, 'object'); if (flag(this, 'doLength')) { new Assertion(obj).to.have.property('length'); var len = obj.length; this.assert( len < n , 'expected #{this} to have a length below #{exp} but got #{act}' , 'expected #{this} to not have a length below #{exp}' , n , len ); } else { this.assert( obj < n , 'expected #{this} to be below ' + n , 'expected #{this} to be above ' + n ); } } Assertion.addMethod('below', assertBelow); Assertion.addMethod('lt', assertBelow); Assertion.addMethod('lessThan', assertBelow); /** * ### .within(start, finish) * * Asserts that the target is within a range. * * expect(7).to.be.within(5,10); * * Can also be used in conjunction with `length` to * assert a length range. The benefit being a * more informative error message than if the length * was supplied directly. * * expect('foo').to.have.length.within(2,4); * expect([ 1, 2, 3 ]).to.have.length.within(2,4); * * @name within * @param {Number} start lowerbound inclusive * @param {Number} finish upperbound inclusive * @api public */ Assertion.addMethod('within', function (start, finish) { var obj = flag(this, 'object') , range = start + '..' + finish; if (flag(this, 'doLength')) { new Assertion(obj).to.have.property('length'); var len = obj.length; this.assert( len >= start && len <= finish , 'expected #{this} to have a length within ' + range , 'expected #{this} to not have a length within ' + range ); } else { this.assert( obj >= start && obj <= finish , 'expected #{this} to be within ' + range , 'expected #{this} to not be within ' + range ); } }); /** * ### .instanceof(constructor) * * Asserts that the target is an instance of `constructor`. * * var Tea = function (name) { this.name = name; } * , Chai = new Tea('chai'); * * expect(Chai).to.be.an.instanceof(Tea); * expect([ 1, 2, 3 ]).to.be.instanceof(Array); * * @name instanceof * @param {Constructor} constructor * @alias instanceOf * @api public */ function assertInstanceOf (constructor) { var name = _.getName(constructor); this.assert( flag(this, 'object') instanceof constructor , 'expected #{this} to be an instance of ' + name , 'expected #{this} to not be an instance of ' + name ); }; Assertion.addMethod('instanceof', assertInstanceOf); Assertion.addMethod('instanceOf', assertInstanceOf); /** * ### .property(name, [value]) * * Asserts that the target has a property `name`, optionally asserting that * the value of that property is strictly equal to `value`. * If the `deep` flag is set, you can use dot- and bracket-notation for deep * references into objects and arrays. * * // simple referencing * var obj = { foo: 'bar' }; * expect(obj).to.have.property('foo'); * expect(obj).to.have.property('foo', 'bar'); * * // deep referencing * var deepObj = { * green: { tea: 'matcha' } * , teas: [ 'chai', 'matcha', { tea: 'konacha' } ] * }; * expect(deepObj).to.have.deep.property('green.tea', 'matcha'); * expect(deepObj).to.have.deep.property('teas[1]', 'matcha'); * expect(deepObj).to.have.deep.property('teas[2].tea', 'konacha'); * * You can also use an array as the starting point of a `deep.property` * assertion, or traverse nested arrays. * * var arr = [ * [ 'chai', 'matcha', 'konacha' ] * , [ { tea: 'chai' } * , { tea: 'matcha' } * , { tea: 'konacha' } ] * ]; * * expect(arr).to.have.deep.property('[0][1]', 'matcha'); * expect(arr).to.have.deep.property('[1][2].tea', 'konacha'); * * Furthermore, `property` changes the subject of the assertion * to be the value of that property from the original object. This * permits for further chainable assertions on that property. * * expect(obj).to.have.property('foo') * .that.is.a('string'); * expect(deepObj).to.have.property('green') * .that.is.an('object') * .that.deep.equals({ tea: 'matcha' }); * expect(deepObj).to.have.property('teas') * .that.is.an('array') * .with.deep.property('[2]') * .that.deep.equals({ tea: 'konacha' }); * * @name property * @alias deep.property * @param {String} name * @param {Mixed} value (optional) * @returns value of property for chaining * @api public */ Assertion.addMethod('property', function (name, val) { var descriptor = flag(this, 'deep') ? 'deep property ' : 'property ' , negate = flag(this, 'negate') , obj = flag(this, 'object') , value = flag(this, 'deep') ? _.getPathValue(name, obj) : obj[name]; if (negate && undefined !== val) { if (undefined === value) { throw new Error(_.inspect(obj) + ' has no ' + descriptor + _.inspect(name)); } } else { this.assert( undefined !== value , 'expected #{this} to have a ' + descriptor + _.inspect(name) , 'expected #{this} to not have ' + descriptor + _.inspect(name)); } if (undefined !== val) { this.assert( val === value , 'expected #{this} to have a ' + descriptor + _.inspect(name) + ' of #{exp}, but got #{act}' , 'expected #{this} to not have a ' + descriptor + _.inspect(name) + ' of #{act}' , val , value ); } flag(this, 'object', value); }); /** * ### .ownProperty(name) * * Asserts that the target has an own property `name`. * * expect('test').to.have.ownProperty('length'); * * @name ownProperty * @alias haveOwnProperty * @param {String} name * @api public */ function assertOwnProperty (name) { var obj = flag(this, 'object'); this.assert( obj.hasOwnProperty(name) , 'expected #{this} to have own property ' + _.inspect(name) , 'expected #{this} to not have own property ' + _.inspect(name) ); } Assertion.addMethod('ownProperty', assertOwnProperty); Assertion.addMethod('haveOwnProperty', assertOwnProperty); /** * ### .length(value) * * Asserts that the target's `length` property has * the expected value. * * expect([ 1, 2, 3]).to.have.length(3); * expect('foobar').to.have.length(6); * * Can also be used as a chain precursor to a value * comparison for the length property. * * expect('foo').to.have.length.above(2); * expect([ 1, 2, 3 ]).to.have.length.above(2); * expect('foo').to.have.length.below(4); * expect([ 1, 2, 3 ]).to.have.length.below(4); * expect('foo').to.have.length.within(2,4); * expect([ 1, 2, 3 ]).to.have.length.within(2,4); * * @name length * @alias lengthOf * @param {Number} length * @api public */ function assertLengthChain () { flag(this, 'doLength', true); } function assertLength (n) { var obj = flag(this, 'object'); new Assertion(obj).to.have.property('length'); var len = obj.length; this.assert( len == n , 'expected #{this} to have a length of #{exp} but got #{act}' , 'expected #{this} to not have a length of #{act}' , n , len ); } Assertion.addChainableMethod('length', assertLength, assertLengthChain); Assertion.addMethod('lengthOf', assertLength, assertLengthChain); /** * ### .match(regexp) * * Asserts that the target matches a regular expression. * * expect('foobar').to.match(/^foo/); * * @name match * @param {RegExp} RegularExpression * @api public */ Assertion.addMethod('match', function (re) { var obj = flag(this, 'object'); this.assert( re.exec(obj) , 'expected #{this} to match ' + re , 'expected #{this} not to match ' + re ); }); /** * ### .string(string) * * Asserts that the string target contains another string. * * expect('foobar').to.have.string('bar'); * * @name string * @param {String} string * @api public */ Assertion.addMethod('string', function (str) { var obj = flag(this, 'object'); new Assertion(obj).is.a('string'); this.assert( ~obj.indexOf(str) , 'expected #{this} to contain ' + _.inspect(str) , 'expected #{this} to not contain ' + _.inspect(str) ); }); /** * ### .keys(key1, [key2], [...]) * * Asserts that the target has exactly the given keys, or * asserts the inclusion of some keys when using the * `include` or `contain` modifiers. * * expect({ foo: 1, bar: 2 }).to.have.keys(['foo', 'bar']); * expect({ foo: 1, bar: 2, baz: 3 }).to.contain.keys('foo', 'bar'); * * @name keys * @alias key * @param {String...|Array} keys * @api public */ function assertKeys (keys) { var obj = flag(this, 'object') , str , ok = true; keys = keys instanceof Array ? keys : Array.prototype.slice.call(arguments); if (!keys.length) throw new Error('keys required'); var actual = Object.keys(obj) , len = keys.length; // Inclusion ok = keys.every(function(key){ return ~actual.indexOf(key); }); // Strict if (!flag(this, 'negate') && !flag(this, 'contains')) { ok = ok && keys.length == actual.length; } // Key string if (len > 1) { keys = keys.map(function(key){ return _.inspect(key); }); var last = keys.pop(); str = keys.join(', ') + ', and ' + last; } else { str = _.inspect(keys[0]); } // Form str = (len > 1 ? 'keys ' : 'key ') + str; // Have / include str = (flag(this, 'contains') ? 'contain ' : 'have ') + str; // Assertion this.assert( ok , 'expected #{this} to ' + str , 'expected #{this} to not ' + str ); } Assertion.addMethod('keys', assertKeys); Assertion.addMethod('key', assertKeys); /** * ### .throw(constructor) * * Asserts that the function target will throw a specific error, or specific type of error * (as determined using `instanceof`), optionally with a RegExp or string inclusion test * for the error's message. * * var err = new ReferenceError('This is a bad function.'); * var fn = function () { throw err; } * expect(fn).to.throw(ReferenceError); * expect(fn).to.throw(Error); * expect(fn).to.throw(/bad function/); * expect(fn).to.not.throw('good function'); * expect(fn).to.throw(ReferenceError, /bad function/); * expect(fn).to.throw(err); * expect(fn).to.not.throw(new RangeError('Out of range.')); * * Please note that when a throw expectation is negated, it will check each * parameter independently, starting with error constructor type. The appropriate way * to check for the existence of a type of error but for a message that does not match * is to use `and`. * * expect(fn).to.throw(ReferenceError) * .and.not.throw(/good function/); * * @name throw * @alias throws * @alias Throw * @param {ErrorConstructor} constructor * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types * @api public */ function assertThrows (constructor, msg) { var obj = flag(this, 'object'); new Assertion(obj).is.a('function'); var thrown = false , desiredError = null , name = null; if (arguments.length === 0) { msg = null; constructor = null; } else if (constructor && (constructor instanceof RegExp || 'string' === typeof constructor)) { msg = constructor; constructor = null; } else if (constructor && constructor instanceof Error) { desiredError = constructor; constructor = null; msg = null; } else if (typeof constructor === 'function') { name = (new constructor()).name; } else { constructor = null; } try { obj(); } catch (err) { // first, check desired error if (desiredError) { this.assert( err === desiredError , 'expected #{this} to throw ' + _.inspect(desiredError) + ' but ' + _.inspect(err) + ' was thrown' , 'expected #{this} to not throw ' + _.inspect(desiredError) ); return this; } // next, check constructor if (constructor) { this.assert( err instanceof constructor , 'expected #{this} to throw ' + name + ' but a ' + err.name + ' was thrown' , 'expected #{this} to not throw ' + name ); if (!msg) return this; } // next, check message if (err.message && msg && msg instanceof RegExp) { this.assert( msg.exec(err.message) , 'expected #{this} to throw error matching ' + msg + ' but got ' + _.inspect(err.message) , 'expected #{this} to throw error not matching ' + msg ); return this; } else if (err.message && msg && 'string' === typeof msg) { this.assert( ~err.message.indexOf(msg) , 'expected #{this} to throw error including #{exp} but got #{act}' , 'expected #{this} to throw error not including #{act}' , msg , err.message ); return this; } else { thrown = true; } } var expectedThrown = name ? name : desiredError ? _.inspect(desiredError) : 'an error'; this.assert( thrown === true , 'expected #{this} to throw ' + expectedThrown , 'expected #{this} to not throw ' + expectedThrown ); }; Assertion.addMethod('throw', assertThrows); Assertion.addMethod('throws', assertThrows); Assertion.addMethod('Throw', assertThrows); /** * ### .respondTo(method) * * Asserts that the object or class target will respond to a method. * * Klass.prototype.bar = function(){}; * expect(Klass).to.respondTo('bar'); * expect(obj).to.respondTo('bar'); * * To check if a constructor will respond to a static function, * set the `itself` flag. * * Klass.baz = function(){}; * expect(Klass).itself.to.respondTo('baz'); * * @name respondTo * @param {String} method * @api public */ Assertion.addMethod('respondTo', function (method) { var obj = flag(this, 'object') , itself = flag(this, 'itself') , context = ('function' === typeof obj && !itself) ? obj.prototype[method] : obj[method]; this.assert( 'function' === typeof context , 'expected #{this} to respond to ' + _.inspect(method) , 'expected #{this} to not respond to ' + _.inspect(method) ); }); /** * ### .itself * * Sets the `itself` flag, later used by the `respondTo` assertion. * * function Foo() {} * Foo.bar = function() {} * Foo.prototype.baz = function() {} * * expect(Foo).itself.to.respondTo('bar'); * expect(Foo).itself.not.to.respondTo('baz'); * * @name itself * @api public */ Assertion.addProperty('itself', function () { flag(this, 'itself', true); }); /** * ### .satisfy(method) * * Asserts that the target passes a given truth test. * * expect(1).to.satisfy(function(num) { return num > 0; }); * * @name satisfy * @param {Function} matcher * @api public */ Assertion.addMethod('satisfy', function (matcher) { var obj = flag(this, 'object'); this.assert( matcher(obj) , 'expected #{this} to satisfy ' + _.inspect(matcher) , 'expected #{this} to not satisfy' + _.inspect(matcher) , this.negate ? false : true , matcher(obj) ); }); /** * ### .closeTo(expected, delta) * * Asserts that the target is equal `expected`, to within a +/- `delta` range. * * expect(1.5).to.be.closeTo(1, 0.5); * * @name closeTo * @param {Number} expected * @param {Number} delta * @api public */ Assertion.addMethod('closeTo', function (expected, delta) { var obj = flag(this, 'object'); this.assert( Math.abs(obj - expected) <= delta , 'expected #{this} to be close to ' + expected + ' +/- ' + delta , 'expected #{this} not to be close to ' + expected + ' +/- ' + delta ); }); }; }); // module: chai/core/assertions.js require.register("chai/interface/assert.js", function(module, exports, require){ /*! * chai * Copyright(c) 2011-2012 Jake Luer * MIT Licensed */ module.exports = function (chai, util) { /*! * Chai dependencies. */ var Assertion = chai.Assertion , flag = util.flag; /*! * Module export. */ /** * ### assert(expression, message) * * Write your own test expressions. * * assert('foo' !== 'bar', 'foo is not bar'); * assert(Array.isArray([]), 'empty arrays are arrays'); * * @param {Mixed} expression to test for truthiness * @param {String} message to display on error * @name assert * @api public */ var assert = chai.assert = function (express, errmsg) { var test = new Assertion(null); test.assert( express , errmsg , '[ negation message unavailable ]' ); }; /** * ### .fail(actual, expected, [message], [operator]) * * Throw a failure. Node.js `assert` module-compatible. * * @name fail * @param {Mixed} actual * @param {Mixed} expected * @param {String} message * @param {String} operator * @api public */ assert.fail = function (actual, expected, message, operator) { throw new chai.AssertionError({ actual: actual , expected: expected , message: message , operator: operator , stackStartFunction: assert.fail }); }; /** * ### .ok(object, [message]) * * Asserts that `object` is truthy. * * assert.ok('everything', 'everything is ok'); * assert.ok(false, 'this will fail'); * * @name ok * @param {Mixed} object to test * @param {String} message * @api public */ assert.ok = function (val, msg) { new Assertion(val, msg).is.ok; }; /** * ### .equal(actual, expected, [message]) * * Asserts non-strict equality (`==`) of `actual` and `expected`. * * assert.equal(3, '3', '== coerces values to strings'); * * @name equal * @param {Mixed} actual * @param {Mixed} expected * @param {String} message * @api public */ assert.equal = function (act, exp, msg) { var test = new Assertion(act, msg); test.assert( exp == flag(test, 'object') , 'expected #{this} to equal #{exp}' , 'expected #{this} to not equal #{act}' , exp , act ); }; /** * ### .notEqual(actual, expected, [message]) * * Asserts non-strict inequality (`!=`) of `actual` and `expected`. * * assert.notEqual(3, 4, 'these numbers are not equal'); * * @name notEqual * @param {Mixed} actual * @param {Mixed} expected * @param {String} message * @api public */ assert.notEqual = function (act, exp, msg) { var test = new Assertion(act, msg); test.assert( exp != flag(test, 'object') , 'expected #{this} to not equal #{exp}' , 'expected #{this} to equal #{act}' , exp , act ); }; /** * ### .strictEqual(actual, expected, [message]) * * Asserts strict equality (`===`) of `actual` and `expected`. * * assert.strictEqual(true, true, 'these booleans are strictly equal'); * * @name strictEqual * @param {Mixed} actual * @param {Mixed} expected * @param {String} message * @api public */ assert.strictEqual = function (act, exp, msg) { new Assertion(act, msg).to.equal(exp); }; /** * ### .notStrictEqual(actual, expected, [message]) * * Asserts strict inequality (`!==`) of `actual` and `expected`. * * assert.notStrictEqual(3, '3', 'no coercion for strict equality'); * * @name notStrictEqual * @param {Mixed} actual * @param {Mixed} expected * @param {String} message * @api public */ assert.notStrictEqual = function (act, exp, msg) { new Assertion(act, msg).to.not.equal(exp); }; /** * ### .deepEqual(actual, expected, [message]) * * Asserts that `actual` is deeply equal to `expected`. * * assert.deepEqual({ tea: 'green' }, { tea: 'green' }); * * @name deepEqual * @param {Mixed} actual * @param {Mixed} expected * @param {String} message * @api public */ assert.deepEqual = function (act, exp, msg) { new Assertion(act, msg).to.eql(exp); }; /** * ### .notDeepEqual(actual, expected, [message]) * * Assert that `actual` is not deeply equal to `expected`. * * assert.notDeepEqual({ tea: 'green' }, { tea: 'jasmine' }); * * @name notDeepEqual * @param {Mixed} actual * @param {Mixed} expected * @param {String} message * @api public */ assert.notDeepEqual = function (act, exp, msg) { new Assertion(act, msg).to.not.eql(exp); }; /** * ### .isTrue(value, [message]) * * Asserts that `value` is true. * * var teaServed = true; * assert.isTrue(teaServed, 'the tea has been served'); * * @name isTrue * @param {Mixed} value * @param {String} message * @api public */ assert.isTrue = function (val, msg) { new Assertion(val, msg).is['true']; }; /** * ### .isFalse(value, [message]) * * Asserts that `value` is false. * * var teaServed = false; * assert.isFalse(teaServed, 'no tea yet? hmm...'); * * @name isFalse * @param {Mixed} value * @param {String} message * @api public */ assert.isFalse = function (val, msg) { new Assertion(val, msg).is['false']; }; /** * ### .isNull(value, [message]) * * Asserts that `value` is null. * * assert.isNull(err, 'there was no error'); * * @name isNull * @param {Mixed} value * @param {String} message * @api public */ assert.isNull = function (val, msg) { new Assertion(val, msg).to.equal(null); }; /** * ### .isNotNull(value, [message]) * * Asserts that `value` is not null. * * var tea = 'tasty chai'; * assert.isNotNull(tea, 'great, time for tea!'); * * @name isNotNull * @param {Mixed} value * @param {String} message * @api public */ assert.isNotNull = function (val, msg) { new Assertion(val, msg).to.not.equal(null); }; /** * ### .isUndefined(value, [message]) * * Asserts that `value` is `undefined`. * * var tea; * assert.isUndefined(tea, 'no tea defined'); * * @name isUndefined * @param {Mixed} value * @param {String} message * @api public */ assert.isUndefined = function (val, msg) { new Assertion(val, msg).to.equal(undefined); }; /** * ### .isDefined(value, [message]) * * Asserts that `value` is not `undefined`. * * var tea = 'cup of chai'; * assert.isDefined(tea, 'tea has been defined'); * * @name isUndefined * @param {Mixed} value * @param {String} message * @api public */ assert.isDefined = function (val, msg) { new Assertion(val, msg).to.not.equal(undefined); }; /** * ### .isFunction(value, [message]) * * Asserts that `value` is a function. * * function serveTea() { return 'cup of tea'; }; * assert.isFunction(serveTea, 'great, we can have tea now'); * * @name isFunction * @param {Mixed} value * @param {String} message * @api public */ assert.isFunction = function (val, msg) { new Assertion(val, msg).to.be.a('function'); }; /** * ### .isNotFunction(value, [message]) * * Asserts that `value` is _not_ a function. * * var serveTea = [ 'heat', 'pour', 'sip' ]; * assert.isNotFunction(serveTea, 'great, we have listed the steps'); * * @name isNotFunction * @param {Mixed} value * @param {String} message * @api public */ assert.isNotFunction = function (val, msg) { new Assertion(val, msg).to.not.be.a('function'); }; /** * ### .isObject(value, [message]) * * Asserts that `value` is an object (as revealed by * `Object.prototype.toString`). * * var selection = { name: 'Chai', serve: 'with spices' }; * assert.isObject(selection, 'tea selection is an object'); * * @name isObject * @param {Mixed} value * @param {String} message * @api public */ assert.isObject = function (val, msg) { new Assertion(val, msg).to.be.a('object'); }; /** * ### .isNotObject(value, [message]) * * Asserts that `value` is _not_ an object. * * var selection = 'chai' * assert.isObject(selection, 'tea selection is not an object'); * assert.isObject(null, 'null is not an object'); * * @name isNotObject * @param {Mixed} value * @param {String} message * @api public */ assert.isNotObject = function (val, msg) { new Assertion(val, msg).to.not.be.a('object'); }; /** * ### .isArray(value, [message]) * * Asserts that `value` is an array. * * var menu = [ 'green', 'chai', 'oolong' ]; * assert.isArray(menu, 'what kind of tea do we want?'); * * @name isArray * @param {Mixed} value * @param {String} message * @api public */ assert.isArray = function (val, msg) { new Assertion(val, msg).to.be.an('array'); }; /** * ### .isNotArray(value, [message]) * * Asserts that `value` is _not_ an array. * * var menu = 'green|chai|oolong'; * assert.isNotArray(menu, 'what kind of tea do we want?'); * * @name isNotArray * @param {Mixed} value * @param {String} message * @api public */ assert.isNotArray = function (val, msg) { new Assertion(val, msg).to.not.be.an('array'); }; /** * ### .isString(value, [message]) * * Asserts that `value` is a string. * * var teaOrder = 'chai'; * assert.isString(teaOrder, 'order placed'); * * @name isString * @param {Mixed} value * @param {String} message * @api public */ assert.isString = function (val, msg) { new Assertion(val, msg).to.be.a('string'); }; /** * ### .isNotString(value, [message]) * * Asserts that `value` is _not_ a string. * * var teaOrder = 4; * assert.isNotString(teaOrder, 'order placed'); * * @name isNotString * @param {Mixed} value * @param {String} message * @api public */ assert.isNotString = function (val, msg) { new Assertion(val, msg).to.not.be.a('string'); }; /** * ### .isNumber(value, [message]) * * Asserts that `value` is a number. * * var cups = 2; * assert.isNumber(cups, 'how many cups'); * * @name isNumber * @param {Number} value * @param {String} message * @api public */ assert.isNumber = function (val, msg) { new Assertion(val, msg).to.be.a('number'); }; /** * ### .isNotNumber(value, [message]) * * Asserts that `value` is _not_ a number. * * var cups = '2 cups please'; * assert.isNotNumber(cups, 'how many cups'); * * @name isNotNumber * @param {Mixed} value * @param {String} message * @api public */ assert.isNotNumber = function (val, msg) { new Assertion(val, msg).to.not.be.a('number'); }; /** * ### .isBoolean(value, [message]) * * Asserts that `value` is a boolean. * * var teaReady = true * , teaServed = false; * * assert.isBoolean(teaReady, 'is the tea ready'); * assert.isBoolean(teaServed, 'has tea been served'); * * @name isBoolean * @param {Mixed} value * @param {String} message * @api public */ assert.isBoolean = function (val, msg) { new Assertion(val, msg).to.be.a('boolean'); }; /** * ### .isNotBoolean(value, [message]) * * Asserts that `value` is _not_ a boolean. * * var teaReady = 'yep' * , teaServed = 'nope'; * * assert.isNotBoolean(teaReady, 'is the tea ready'); * assert.isNotBoolean(teaServed, 'has tea been served'); * * @name isNotBoolean * @param {Mixed} value * @param {String} message * @api public */ assert.isNotBoolean = function (val, msg) { new Assertion(val, msg).to.not.be.a('boolean'); }; /** * ### .typeOf(value, name, [message]) * * Asserts that `value`'s type is `name`, as determined by * `Object.prototype.toString`. * * assert.typeOf({ tea: 'chai' }, 'object', 'we have an object'); * assert.typeOf(['chai', 'jasmine'], 'array', 'we have an array'); * assert.typeOf('tea', 'string', 'we have a string'); * assert.typeOf(/tea/, 'regexp', 'we have a regular expression'); * assert.typeOf(null, 'null', 'we have a null'); * assert.typeOf(undefined, 'undefined', 'we have an undefined'); * * @name typeOf * @param {Mixed} value * @param {String} name * @param {String} message * @api public */ assert.typeOf = function (val, type, msg) { new Assertion(val, msg).to.be.a(type); }; /** * ### .notTypeOf(value, name, [message]) * * Asserts that `value`'s type is _not_ `name`, as determined by * `Object.prototype.toString`. * * assert.notTypeOf('tea', 'number', 'strings are not numbers'); * * @name notTypeOf * @param {Mixed} value * @param {String} typeof name * @param {String} message * @api public */ assert.notTypeOf = function (val, type, msg) { new Assertion(val, msg).to.not.be.a(type); }; /** * ### .instanceOf(object, constructor, [message]) * * Asserts that `value` is an instance of `constructor`. * * var Tea = function (name) { this.name = name; } * , chai = new Tea('chai'); * * assert.instanceOf(chai, Tea, 'chai is an instance of tea'); * * @name instanceOf * @param {Object} object * @param {Constructor} constructor * @param {String} message * @api public */ assert.instanceOf = function (val, type, msg) { new Assertion(val, msg).to.be.instanceOf(type); }; /** * ### .notInstanceOf(object, constructor, [message]) * * Asserts `value` is not an instance of `constructor`. * * var Tea = function (name) { this.name = name; } * , chai = new String('chai'); * * assert.notInstanceOf(chai, Tea, 'chai is not an instance of tea'); * * @name notInstanceOf * @param {Object} object * @param {Constructor} constructor * @param {String} message * @api public */ assert.notInstanceOf = function (val, type, msg) { new Assertion(val, msg).to.not.be.instanceOf(type); }; /** * ### .include(haystack, needle, [message]) * * Asserts that `haystack` includes `needle`. Works * for strings and arrays. * * assert.include('foobar', 'bar', 'foobar contains string "bar"'); * assert.include([ 1, 2, 3 ], 3, 'array contains value'); * * @name include * @param {Array|String} haystack * @param {Mixed} needle * @param {String} message * @api public */ assert.include = function (exp, inc, msg) { var obj = new Assertion(exp, msg); if (Array.isArray(exp)) { obj.to.include(inc); } else if ('string' === typeof exp) { obj.to.contain.string(inc); } }; /** * ### .match(value, regexp, [message]) * * Asserts that `value` matches the regular expression `regexp`. * * assert.match('foobar', /^foo/, 'regexp matches'); * * @name match * @param {Mixed} value * @param {RegExp} regexp * @param {String} message * @api public */ assert.match = function (exp, re, msg) { new Assertion(exp, msg).to.match(re); }; /** * ### .notMatch(value, regexp, [message]) * * Asserts that `value` does not match the regular expression `regexp`. * * assert.notMatch('foobar', /^foo/, 'regexp does not match'); * * @name notMatch * @param {Mixed} value * @param {RegExp} regexp * @param {String} message * @api public */ assert.notMatch = function (exp, re, msg) { new Assertion(exp, msg).to.not.match(re); }; /** * ### .property(object, property, [message]) * * Asserts that `object` has a property named by `property`. * * assert.property({ tea: { green: 'matcha' }}, 'tea'); * * @name property * @param {Object} object * @param {String} property * @param {String} message * @api public */ assert.property = function (obj, prop, msg) { new Assertion(obj, msg).to.have.property(prop); }; /** * ### .notProperty(object, property, [message]) * * Asserts that `object` does _not_ have a property named by `property`. * * assert.notProperty({ tea: { green: 'matcha' }}, 'coffee'); * * @name notProperty * @param {Object} object * @param {String} property * @param {String} message * @api public */ assert.notProperty = function (obj, prop, msg) { new Assertion(obj, msg).to.not.have.property(prop); }; /** * ### .deepProperty(object, property, [message]) * * Asserts that `object` has a property named by `property`, which can be a * string using dot- and bracket-notation for deep reference. * * assert.deepProperty({ tea: { green: 'matcha' }}, 'tea.green'); * * @name deepProperty * @param {Object} object * @param {String} property * @param {String} message * @api public */ assert.deepProperty = function (obj, prop, msg) { new Assertion(obj, msg).to.have.deep.property(prop); }; /** * ### .notDeepProperty(object, property, [message]) * * Asserts that `object` does _not_ have a property named by `property`, which * can be a string using dot- and bracket-notation for deep reference. * * assert.notDeepProperty({ tea: { green: 'matcha' }}, 'tea.oolong'); * * @name notDeepProperty * @param {Object} object * @param {String} property * @param {String} message * @api public */ assert.notDeepProperty = function (obj, prop, msg) { new Assertion(obj, msg).to.not.have.deep.property(prop); }; /** * ### .propertyVal(object, property, value, [message]) * * Asserts that `object` has a property named by `property` with value given * by `value`. * * assert.propertyVal({ tea: 'is good' }, 'tea', 'is good'); * * @name propertyVal * @param {Object} object * @param {String} property * @param {Mixed} value * @param {String} message * @api public */ assert.propertyVal = function (obj, prop, val, msg) { new Assertion(obj, msg).to.have.property(prop, val); }; /** * ### .propertyNotVal(object, property, value, [message]) * * Asserts that `object` has a property named by `property`, but with a value * different from that given by `value`. * * assert.propertyNotVal({ tea: 'is good' }, 'tea', 'is bad'); * * @name propertyNotVal * @param {Object} object * @param {String} property * @param {Mixed} value * @param {String} message * @api public */ assert.propertyNotVal = function (obj, prop, val, msg) { new Assertion(obj, msg).to.not.have.property(prop, val); }; /** * ### .deepPropertyVal(object, property, value, [message]) * * Asserts that `object` has a property named by `property` with value given * by `value`. `property` can use dot- and bracket-notation for deep * reference. * * assert.deepPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'matcha'); * * @name deepPropertyVal * @param {Object} object * @param {String} property * @param {Mixed} value * @param {String} message * @api public */ assert.deepPropertyVal = function (obj, prop, val, msg) { new Assertion(obj, msg).to.have.deep.property(prop, val); }; /** * ### .deepPropertyNotVal(object, property, value, [message]) * * Asserts that `object` has a property named by `property`, but with a value * different from that given by `value`. `property` can use dot- and * bracket-notation for deep reference. * * assert.deepPropertyNotVal({ tea: { green: 'matcha' }}, 'tea.green', 'konacha'); * * @name deepPropertyNotVal * @param {Object} object * @param {String} property * @param {Mixed} value * @param {String} message * @api public */ assert.deepPropertyNotVal = function (obj, prop, val, msg) { new Assertion(obj, msg).to.not.have.deep.property(prop, val); }; /** * ### .lengthOf(object, length, [message]) * * Asserts that `object` has a `length` property with the expected value. * * assert.lengthOf([1,2,3], 3, 'array has length of 3'); * assert.lengthOf('foobar', 5, 'string has length of 6'); * * @name lengthOf * @param {Mixed} object * @param {Number} length * @param {String} message * @api public */ assert.lengthOf = function (exp, len, msg) { new Assertion(exp, msg).to.have.length(len); }; /** * ### .throws(function, [constructor/regexp], [message]) * * Asserts that `function` will throw an error that is an instance of * `constructor`, or alternately that it will throw an error with message * matching `regexp`. * * assert.throw(fn, ReferenceError, 'function throws a reference error'); * * @name throws * @alias throw * @alias Throw * @param {Function} function * @param {ErrorConstructor} constructor * @param {RegExp} regexp * @param {String} message * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types * @api public */ assert.Throw = function (fn, type, msg) { if ('string' === typeof type) { msg = type; type = null; } new Assertion(fn, msg).to.Throw(type); }; /** * ### .doesNotThrow(function, [constructor/regexp], [message]) * * Asserts that `function` will _not_ throw an error that is an instance of * `constructor`, or alternately that it will not throw an error with message * matching `regexp`. * * assert.doesNotThrow(fn, Error, 'function does not throw'); * * @name doesNotThrow * @param {Function} function * @param {ErrorConstructor} constructor * @param {RegExp} regexp * @param {String} message * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types * @api public */ assert.doesNotThrow = function (fn, type, msg) { if ('string' === typeof type) { msg = type; type = null; } new Assertion(fn, msg).to.not.Throw(type); }; /** * ### .operator(val1, operator, val2, [message]) * * Compares two values using `operator`. * * assert.operator(1, '<', 2, 'everything is ok'); * assert.operator(1, '>', 2, 'this will fail'); * * @name operator * @param {Mixed} val1 * @param {String} operator * @param {Mixed} val2 * @param {String} message * @api public */ assert.operator = function (val, operator, val2, msg) { if (!~['==', '===', '>', '>=', '<', '<=', '!=', '!=='].indexOf(operator)) { throw new Error('Invalid operator "' + operator + '"'); } var test = new Assertion(eval(val + operator + val2), msg); test.assert( true === flag(test, 'object') , 'expected ' + util.inspect(val) + ' to be ' + operator + ' ' + util.inspect(val2) , 'expected ' + util.inspect(val) + ' to not be ' + operator + ' ' + util.inspect(val2) ); }; /** * ### .closeTo(actual, expected, delta, [message]) * * Asserts that the target is equal `expected`, to within a +/- `delta` range. * * assert.closeTo(1.5, 1, 0.5, 'numbers are close'); * * @name closeTo * @param {Number} actual * @param {Number} expected * @param {Number} delta * @param {String} message * @api public */ assert.closeTo = function (act, exp, delta, msg) { new Assertion(act, msg).to.be.closeTo(exp, delta); }; /*! * Undocumented / untested */ assert.ifError = function (val, msg) { new Assertion(val, msg).to.not.be.ok; }; /*! * Aliases. */ (function alias(name, as){ assert[as] = assert[name]; return alias; }) ('Throw', 'throw') ('Throw', 'throws'); }; }); // module: chai/interface/assert.js require.register("chai/interface/expect.js", function(module, exports, require){ /*! * chai * Copyright(c) 2011-2012 Jake Luer * MIT Licensed */ module.exports = function (chai, util) { chai.expect = function (val, message) { return new chai.Assertion(val, message); }; }; }); // module: chai/interface/expect.js require.register("chai/interface/should.js", function(module, exports, require){ /*! * chai * Copyright(c) 2011-2012 Jake Luer * MIT Licensed */ module.exports = function (chai, util) { var Assertion = chai.Assertion; function loadShould () { // modify Object.prototype to have `should` Object.defineProperty(Object.prototype, 'should', { set: function () {} , get: function(){ if (this instanceof String || this instanceof Number) { return new Assertion(this.constructor(this)); } else if (this instanceof Boolean) { return new Assertion(this == true); } return new Assertion(this); } , configurable: true }); var should = {}; should.equal = function (val1, val2) { new Assertion(val1).to.equal(val2); }; should.Throw = function (fn, errt, errs) { new Assertion(fn).to.Throw(errt, errs); }; should.exist = function (val) { new Assertion(val).to.exist; } // negation should.not = {} should.not.equal = function (val1, val2) { new Assertion(val1).to.not.equal(val2); }; should.not.Throw = function (fn, errt, errs) { new Assertion(fn).to.not.Throw(errt, errs); }; should.not.exist = function (val) { new Assertion(val).to.not.exist; } should['throw'] = should['Throw']; should.not['throw'] = should.not['Throw']; return should; }; chai.should = loadShould; chai.Should = loadShould; }; }); // module: chai/interface/should.js require.register("chai/utils/addChainableMethod.js", function(module, exports, require){ /*! * Chai - addChainingMethod utility * Copyright(c) 2012 Jake Luer * MIT Licensed */ /*! * Module dependencies */ var transferFlags = require('./transferFlags'); /** * ### addChainableMethod (ctx, name, method, chainingBehavior) * * Adds a method to an object, such that the method can also be chained. * * utils.addChainableMethod(chai.Assertion.prototype, 'foo', function (str) { * var obj = utils.flag(this, 'object'); * new chai.Assertion(obj).to.be.equal(str); * }); * * Can also be accessed directly from `chai.Assertion`. * * chai.Assertion.addChainableMethod('foo', fn, chainingBehavior); * * The result can then be used as both a method assertion, executing both `method` and * `chainingBehavior`, or as a language chain, which only executes `chainingBehavior`. * * expect(fooStr).to.be.foo('bar'); * expect(fooStr).to.be.foo.equal('foo'); * * @param {Object} ctx object to which the method is added * @param {String} name of method to add * @param {Function} method function to be used for `name`, when called * @param {Function} chainingBehavior function to be called every time the property is accessed * @name addChainableMethod * @api public */ module.exports = function (ctx, name, method, chainingBehavior) { if (typeof chainingBehavior !== 'function') chainingBehavior = function () { }; Object.defineProperty(ctx, name, { get: function () { chainingBehavior.call(this); var assert = function () { var result = method.apply(this, arguments); return result === undefined ? this : result; }; // Re-enumerate every time to better accomodate plugins. var asserterNames = Object.getOwnPropertyNames(ctx); asserterNames.forEach(function (asserterName) { var pd = Object.getOwnPropertyDescriptor(ctx, asserterName) , functionProtoPD = Object.getOwnPropertyDescriptor(Function.prototype, asserterName); // Avoid trying to overwrite things that we can't, like `length` and `arguments`. if (functionProtoPD && !functionProtoPD.configurable) return; if (asserterName === 'arguments') return; // @see chaijs/chai/issues/69 Object.defineProperty(assert, asserterName, pd); }); transferFlags(this, assert); return assert; } , configurable: true }); }; }); // module: chai/utils/addChainableMethod.js require.register("chai/utils/addMethod.js", function(module, exports, require){ /*! * Chai - addMethod utility * Copyright(c) 2012 Jake Luer * MIT Licensed */ /** * ### .addMethod (ctx, name, method) * * Adds a method to the prototype of an object. * * utils.addMethod(chai.Assertion.prototype, 'foo', function (str) { * var obj = utils.flag(this, 'object'); * new chai.Assertion(obj).to.be.equal(str); * }); * * Can also be accessed directly from `chai.Assertion`. * * chai.Assertion.addMethod('foo', fn); * * Then can be used as any other assertion. * * expect(fooStr).to.be.foo('bar'); * * @param {Object} ctx object to which the method is added * @param {String} name of method to add * @param {Function} method function to be used for name * @name addMethod * @api public */ module.exports = function (ctx, name, method) { ctx[name] = function () { var result = method.apply(this, arguments); return result === undefined ? this : result; }; }; }); // module: chai/utils/addMethod.js require.register("chai/utils/addProperty.js", function(module, exports, require){ /*! * Chai - addProperty utility * Copyright(c) 2012 Jake Luer * MIT Licensed */ /** * ### addProperty (ctx, name, getter) * * Adds a property to the prototype of an object. * * utils.addProperty(chai.Assertion.prototype, 'foo', function () { * var obj = utils.flag(this, 'object'); * new chai.Assertion(obj).to.be.instanceof(Foo); * }); * * Can also be accessed directly from `chai.Assertion`. * * chai.Assertion.addProperty('foo', fn); * * Then can be used as any other assertion. * * expect(myFoo).to.be.foo; * * @param {Object} ctx object to which the property is added * @param {String} name of property to add * @param {Function} getter function to be used for name * @name addProperty * @api public */ module.exports = function (ctx, name, getter) { Object.defineProperty(ctx, name, { get: function () { var result = getter.call(this); return result === undefined ? this : result; } , configurable: true }); }; }); // module: chai/utils/addProperty.js require.register("chai/utils/eql.js", function(module, exports, require){ // This is directly from Node.js assert // https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/assert.js module.exports = _deepEqual; // For browser implementation if (!Buffer) { var Buffer = { isBuffer: function () { return false; } }; } function _deepEqual(actual, expected) { // 7.1. All identical values are equivalent, as determined by ===. if (actual === expected) { return true; } else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { if (actual.length != expected.length) return false; for (var i = 0; i < actual.length; i++) { if (actual[i] !== expected[i]) return false; } return true; // 7.2. If the expected value is a Date object, the actual value is // equivalent if it is also a Date object that refers to the same time. } else if (actual instanceof Date && expected instanceof Date) { return actual.getTime() === expected.getTime(); // 7.3. Other pairs that do not both pass typeof value == 'object', // equivalence is determined by ==. } else if (typeof actual != 'object' && typeof expected != 'object') { return actual === expected; // 7.4. For all other Object pairs, including Array objects, equivalence is // determined by having the same number of owned properties (as verified // with Object.prototype.hasOwnProperty.call), the same set of keys // (although not necessarily the same order), equivalent values for every // corresponding key, and an identical 'prototype' property. Note: this // accounts for both named and indexed properties on Arrays. } else { return objEquiv(actual, expected); } } function isUndefinedOrNull(value) { return value === null || value === undefined; } function isArguments(object) { return Object.prototype.toString.call(object) == '[object Arguments]'; } function objEquiv(a, b) { if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) return false; // an identical 'prototype' property. if (a.prototype !== b.prototype) return false; //~~~I've managed to break Object.keys through screwy arguments passing. // Converting to array solves the problem. if (isArguments(a)) { if (!isArguments(b)) { return false; } a = pSlice.call(a); b = pSlice.call(b); return _deepEqual(a, b); } try { var ka = Object.keys(a), kb = Object.keys(b), key, i; } catch (e) {//happens when one is a string literal and the other isn't return false; } // having the same number of owned properties (keys incorporates // hasOwnProperty) if (ka.length != kb.length) return false; //the same set of keys (although not necessarily the same order), ka.sort(); kb.sort(); //~~~cheap key test for (i = ka.length - 1; i >= 0; i--) { if (ka[i] != kb[i]) return false; } //equivalent values for every corresponding key, and //~~~possibly expensive deep test for (i = ka.length - 1; i >= 0; i--) { key = ka[i]; if (!_deepEqual(a[key], b[key])) return false; } return true; } }); // module: chai/utils/eql.js require.register("chai/utils/flag.js", function(module, exports, require){ /*! * Chai - flag utility * Copyright(c) 2012 Jake Luer * MIT Licensed */ /** * ### flag(object ,key, [value]) * * Get or set a flag value on an object. If a * value is provided it will be set, else it will * return the currently set value or `undefined` if * the value is not set. * * utils.flag(this, 'foo', 'bar'); // setter * utils.flag(this, 'foo'); // getter, returns `bar` * * @param {Object} object (constructed Assertion * @param {String} key * @param {Mixed} value (optional) * @name flag * @api private */ module.exports = function (obj, key, value) { var flags = obj.__flags || (obj.__flags = Object.create(null)); if (arguments.length === 3) { flags[key] = value; } else { return flags[key]; } }; }); // module: chai/utils/flag.js require.register("chai/utils/getActual.js", function(module, exports, require){ /*! * Chai - getActual utility * Copyright(c) 2012 Jake Luer * MIT Licensed */ /** * # getActual(object, [actual]) * * Returns the `actual` value for an Assertion * * @param {Object} object (constructed Assertion) * @param {Arguments} chai.Assertion.prototype.assert arguments */ module.exports = function (obj, args) { var actual = args[4]; return 'undefined' !== actual ? actual : obj._obj; }; }); // module: chai/utils/getActual.js require.register("chai/utils/getMessage.js", function(module, exports, require){ /*! * Chai - message composition utility * Copyright(c) 2012 Jake Luer * MIT Licensed */ /*! * Module dependancies */ var flag = require('./flag') , getActual = require('./getActual') , inspect = require('./inspect') , objDisplay = require('./objDisplay'); /** * ### .getMessage(object, message, negateMessage) * * Construct the error message based on flags * and template tags. Template tags will return * a stringified inspection of the object referenced. * * Messsage template tags: * - `#{this}` current asserted object * - `#{act}` actual value * - `#{exp}` expected value * * @param {Object} object (constructed Assertion) * @param {Arguments} chai.Assertion.prototype.assert arguments * @name getMessage * @api public */ module.exports = function (obj, args) { var negate = flag(obj, 'negate') , val = flag(obj, 'object') , expected = args[3] , actual = getActual(obj, args) , msg = negate ? args[2] : args[1] , flagMsg = flag(obj, 'message'); msg = msg || ''; msg = msg .replace(/#{this}/g, objDisplay(val)) .replace(/#{act}/g, objDisplay(actual)) .replace(/#{exp}/g, objDisplay(expected)); return flagMsg ? flagMsg + ': ' + msg : msg; }; }); // module: chai/utils/getMessage.js require.register("chai/utils/getName.js", function(module, exports, require){ /*! * Chai - getName utility * Copyright(c) 2012 Jake Luer * MIT Licensed */ /** * # getName(func) * * Gets the name of a function, in a cross-browser way. * * @param {Function} a function (usually a constructor) */ module.exports = function (func) { if (func.name) return func.name; var match = /^\s?function ([^(]*)\(/.exec(func); return match && match[1] ? match[1] : ""; }; }); // module: chai/utils/getName.js require.register("chai/utils/getPathValue.js", function(module, exports, require){ /*! * Chai - getPathValue utility * Copyright(c) 2012 Jake Luer * @see https://github.com/logicalparadox/filtr * MIT Licensed */ /** * ### .getPathValue(path, object) * * This allows the retrieval of values in an * object given a string path. * * var obj = { * prop1: { * arr: ['a', 'b', 'c'] * , str: 'Hello' * } * , prop2: { * arr: [ { nested: 'Universe' } ] * , str: 'Hello again!' * } * } * * The following would be the results. * * getPathValue('prop1.str', obj); // Hello * getPathValue('prop1.att[2]', obj); // b * getPathValue('prop2.arr[0].nested', obj); // Universe * * @param {String} path * @param {Object} object * @returns {Object} value or `undefined` * @name getPathValue * @api public */ var getPathValue = module.exports = function (path, obj) { var parsed = parsePath(path); return _getPathValue(parsed, obj); }; /*! * ## parsePath(path) * * Helper function used to parse string object * paths. Use in conjunction with `_getPathValue`. * * var parsed = parsePath('myobject.property.subprop'); * * ### Paths: * * * Can be as near infinitely deep and nested * * Arrays are also valid using the formal `myobject.document[3].property`. * * @param {String} path * @returns {Object} parsed * @api private */ function parsePath (path) { var str = path.replace(/\[/g, '.[') , parts = str.match(/(\\\.|[^.]+?)+/g); return parts.map(function (value) { var re = /\[(\d+)\]$/ , mArr = re.exec(value) if (mArr) return { i: parseFloat(mArr[1]) }; else return { p: value }; }); }; /*! * ## _getPathValue(parsed, obj) * * Helper companion function for `.parsePath` that returns * the value located at the parsed address. * * var value = getPathValue(parsed, obj); * * @param {Object} parsed definition from `parsePath`. * @param {Object} object to search against * @returns {Object|Undefined} value * @api private */ function _getPathValue (parsed, obj) { var tmp = obj , res; for (var i = 0, l = parsed.length; i < l; i++) { var part = parsed[i]; if (tmp) { if ('undefined' !== typeof part.p) tmp = tmp[part.p]; else if ('undefined' !== typeof part.i) tmp = tmp[part.i]; if (i == (l - 1)) res = tmp; } else { res = undefined; } } return res; }; }); // module: chai/utils/getPathValue.js require.register("chai/utils/index.js", function(module, exports, require){ /*! * chai * Copyright(c) 2011 Jake Luer * MIT Licensed */ /*! * Main exports */ var exports = module.exports = {}; /*! * test utility */ exports.test = require('./test'); /*! * message utility */ exports.getMessage = require('./getMessage'); /*! * actual utility */ exports.getActual = require('./getActual'); /*! * Inspect util */ exports.inspect = require('./inspect'); /*! * Object Display util */ exports.objDisplay = require('./objDisplay'); /*! * Flag utility */ exports.flag = require('./flag'); /*! * Flag transferring utility */ exports.transferFlags = require('./transferFlags'); /*! * Deep equal utility */ exports.eql = require('./eql'); /*! * Deep path value */ exports.getPathValue = require('./getPathValue'); /*! * Function name */ exports.getName = require('./getName'); /*! * add Property */ exports.addProperty = require('./addProperty'); /*! * add Method */ exports.addMethod = require('./addMethod'); /*! * overwrite Property */ exports.overwriteProperty = require('./overwriteProperty'); /*! * overwrite Method */ exports.overwriteMethod = require('./overwriteMethod'); /*! * Add a chainable method */ exports.addChainableMethod = require('./addChainableMethod'); }); // module: chai/utils/index.js require.register("chai/utils/inspect.js", function(module, exports, require){ // This is (almost) directly from Node.js utils // https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/util.js var getName = require('./getName'); module.exports = inspect; /** * Echos the value of a value. Trys to print the value out * in the best way possible given the different types. * * @param {Object} obj The object to print out. * @param {Boolean} showHidden Flag that shows hidden (not enumerable) * properties of objects. * @param {Number} depth Depth in which to descend in object. Default is 2. * @param {Boolean} colors Flag to turn on ANSI escape codes to color the * output. Default is false (no coloring). */ function inspect(obj, showHidden, depth, colors) { var ctx = { showHidden: showHidden, seen: [], stylize: function (str) { return str; } }; return formatValue(ctx, obj, (typeof depth === 'undefined' ? 2 : depth)); } // https://gist.github.com/1044128/ var getOuterHTML = function(element) { if ('outerHTML' in element) return element.outerHTML; var ns = "http://www.w3.org/1999/xhtml"; var container = document.createElementNS(ns, '_'); var elemProto = (window.HTMLElement || window.Element).prototype; var xmlSerializer = new XMLSerializer(); var html; if (document.xmlVersion) { return xmlSerializer.serializeToString(element); } else { container.appendChild(element.cloneNode(false)); html = container.innerHTML.replace('><', '>' + element.innerHTML + '<'); container.innerHTML = ''; return html; } }; // Returns true if object is a DOM element. var isDOMElement = function (object) { if (typeof HTMLElement === 'object') { return object instanceof HTMLElement; } else { return object && typeof object === 'object' && object.nodeType === 1 && typeof object.nodeName === 'string'; } }; function formatValue(ctx, value, recurseTimes) { // Provide a hook for user-specified inspect functions. // Check that value is an object with an inspect function on it if (value && typeof value.inspect === 'function' && // Filter out the util module, it's inspect function is special value.inspect !== exports.inspect && // Also filter out any prototype objects using the circular check. !(value.constructor && value.constructor.prototype === value)) { return value.inspect(recurseTimes); } // Primitive types cannot have properties var primitive = formatPrimitive(ctx, value); if (primitive) { return primitive; } // If it's DOM elem, get outer HTML. if (isDOMElement(value)) { return getOuterHTML(value); } // Look up the keys of the object. var visibleKeys = Object.keys(value); var keys = ctx.showHidden ? Object.getOwnPropertyNames(value) : visibleKeys; // Some type of object without properties can be shortcutted. // In IE, errors have a single `stack` property, or if they are vanilla `Error`, // a `stack` plus `description` property; ignore those for consistency. if (keys.length === 0 || (isError(value) && ( (keys.length === 1 && keys[0] === 'stack') || (keys.length === 2 && keys[0] === 'description' && keys[1] === 'stack') ))) { if (typeof value === 'function') { var name = getName(value); var nameSuffix = name ? ': ' + name : ''; return ctx.stylize('[Function' + nameSuffix + ']', 'special'); } if (isRegExp(value)) { return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); } if (isDate(value)) { return ctx.stylize(Date.prototype.toUTCString.call(value), 'date'); } if (isError(value)) { return formatError(value); } } var base = '', array = false, braces = ['{', '}']; // Make Array say that they are Array if (isArray(value)) { array = true; braces = ['[', ']']; } // Make functions say that they are functions if (typeof value === 'function') { var name = getName(value); var nameSuffix = name ? ': ' + name : ''; base = ' [Function' + nameSuffix + ']'; } // Make RegExps say that they are RegExps if (isRegExp(value)) { base = ' ' + RegExp.prototype.toString.call(value); } // Make dates with properties first say the date if (isDate(value)) { base = ' ' + Date.prototype.toUTCString.call(value); } // Make error with message first say the error if (isError(value)) { return formatError(value); } if (keys.length === 0 && (!array || value.length == 0)) { return braces[0] + base + braces[1]; } if (recurseTimes < 0) { if (isRegExp(value)) { return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); } else { return ctx.stylize('[Object]', 'special'); } } ctx.seen.push(value); var output; if (array) { output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); } else { output = keys.map(function(key) { return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); }); } ctx.seen.pop(); return reduceToSingleString(output, base, braces); } function formatPrimitive(ctx, value) { switch (typeof value) { case 'undefined': return ctx.stylize('undefined', 'undefined'); case 'string': var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') .replace(/'/g, "\\'") .replace(/\\"/g, '"') + '\''; return ctx.stylize(simple, 'string'); case 'number': return ctx.stylize('' + value, 'number'); case 'boolean': return ctx.stylize('' + value, 'boolean'); } // For some reason typeof null is "object", so special case here. if (value === null) { return ctx.stylize('null', 'null'); } } function formatError(value) { return '[' + Error.prototype.toString.call(value) + ']'; } function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { var output = []; for (var i = 0, l = value.length; i < l; ++i) { if (Object.prototype.hasOwnProperty.call(value, String(i))) { output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, String(i), true)); } else { output.push(''); } } keys.forEach(function(key) { if (!key.match(/^\d+$/)) { output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, key, true)); } }); return output; } function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { var name, str; if (value.__lookupGetter__) { if (value.__lookupGetter__(key)) { if (value.__lookupSetter__(key)) { str = ctx.stylize('[Getter/Setter]', 'special'); } else { str = ctx.stylize('[Getter]', 'special'); } } else { if (value.__lookupSetter__(key)) { str = ctx.stylize('[Setter]', 'special'); } } } if (visibleKeys.indexOf(key) < 0) { name = '[' + key + ']'; } if (!str) { if (ctx.seen.indexOf(value[key]) < 0) { if (recurseTimes === null) { str = formatValue(ctx, value[key], null); } else { str = formatValue(ctx, value[key], recurseTimes - 1); } if (str.indexOf('\n') > -1) { if (array) { str = str.split('\n').map(function(line) { return ' ' + line; }).join('\n').substr(2); } else { str = '\n' + str.split('\n').map(function(line) { return ' ' + line; }).join('\n'); } } } else { str = ctx.stylize('[Circular]', 'special'); } } if (typeof name === 'undefined') { if (array && key.match(/^\d+$/)) { return str; } name = JSON.stringify('' + key); if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { name = name.substr(1, name.length - 2); name = ctx.stylize(name, 'name'); } else { name = name.replace(/'/g, "\\'") .replace(/\\"/g, '"') .replace(/(^"|"$)/g, "'"); name = ctx.stylize(name, 'string'); } } return name + ': ' + str; } function reduceToSingleString(output, base, braces) { var numLinesEst = 0; var length = output.reduce(function(prev, cur) { numLinesEst++; if (cur.indexOf('\n') >= 0) numLinesEst++; return prev + cur.length + 1; }, 0); if (length > 60) { return braces[0] + (base === '' ? '' : base + '\n ') + ' ' + output.join(',\n ') + ' ' + braces[1]; } return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; } function isArray(ar) { return Array.isArray(ar) || (typeof ar === 'object' && objectToString(ar) === '[object Array]'); } function isRegExp(re) { return typeof re === 'object' && objectToString(re) === '[object RegExp]'; } function isDate(d) { return typeof d === 'object' && objectToString(d) === '[object Date]'; } function isError(e) { return typeof e === 'object' && objectToString(e) === '[object Error]'; } function objectToString(o) { return Object.prototype.toString.call(o); } }); // module: chai/utils/inspect.js require.register("chai/utils/objDisplay.js", function(module, exports, require){ /*! * Chai - flag utility * Copyright(c) 2012 Jake Luer * MIT Licensed */ /*! * Module dependancies */ var inspect = require('./inspect'); /** * ### .objDisplay (object) * * Determines if an object or an array matches * criteria to be inspected in-line for error * messages or should be truncated. * * @param {Mixed} javascript object to inspect * @name objDisplay * @api public */ module.exports = function (obj) { var str = inspect(obj) , type = Object.prototype.toString.call(obj); if (str.length >= 40) { if (type === '[object Array]') { return '[ Array(' + obj.length + ') ]'; } else if (type === '[object Object]') { var keys = Object.keys(obj) , kstr = keys.length > 2 ? keys.splice(0, 2).join(', ') + ', ...' : keys.join(', '); return '{ Object (' + kstr + ') }'; } else { return str; } } else { return str; } }; }); // module: chai/utils/objDisplay.js require.register("chai/utils/overwriteMethod.js", function(module, exports, require){ /*! * Chai - overwriteMethod utility * Copyright(c) 2012 Jake Luer * MIT Licensed */ /** * ### overwriteMethod (ctx, name, fn) * * Overwites an already existing method and provides * access to previous function. Must return function * to be used for name. * * utils.overwriteMethod(chai.Assertion.prototype, 'equal', function (_super) { * return function (str) { * var obj = utils.flag(this, 'object'); * if (obj instanceof Foo) { * new chai.Assertion(obj.value).to.equal(str); * } else { * _super.apply(this, arguments); * } * } * }); * * Can also be accessed directly from `chai.Assertion`. * * chai.Assertion.overwriteMethod('foo', fn); * * Then can be used as any other assertion. * * expect(myFoo).to.equal('bar'); * * @param {Object} ctx object whose method is to be overwritten * @param {String} name of method to overwrite * @param {Function} method function that returns a function to be used for name * @name overwriteMethod * @api public */ module.exports = function (ctx, name, method) { var _method = ctx[name] , _super = function () { return this; }; if (_method && 'function' === typeof _method) _super = _method; ctx[name] = function () { var result = method(_super).apply(this, arguments); return result === undefined ? this : result; } }; }); // module: chai/utils/overwriteMethod.js require.register("chai/utils/overwriteProperty.js", function(module, exports, require){ /*! * Chai - overwriteProperty utility * Copyright(c) 2012 Jake Luer * MIT Licensed */ /** * ### overwriteProperty (ctx, name, fn) * * Overwites an already existing property getter and provides * access to previous value. Must return function to use as getter. * * utils.overwriteProperty(chai.Assertion.prototype, 'ok', function (_super) { * return function () { * var obj = utils.flag(this, 'object'); * if (obj instanceof Foo) { * new chai.Assertion(obj.name).to.equal('bar'); * } else { * _super.call(this); * } * } * }); * * * Can also be accessed directly from `chai.Assertion`. * * chai.Assertion.overwriteProperty('foo', fn); * * Then can be used as any other assertion. * * expect(myFoo).to.be.ok; * * @param {Object} ctx object whose property is to be overwritten * @param {String} name of property to overwrite * @param {Function} getter function that returns a getter function to be used for name * @name overwriteProperty * @api public */ module.exports = function (ctx, name, getter) { var _get = Object.getOwnPropertyDescriptor(ctx, name) , _super = function () {}; if (_get && 'function' === typeof _get.get) _super = _get.get Object.defineProperty(ctx, name, { get: function () { var result = getter(_super).call(this); return result === undefined ? this : result; } , configurable: true }); }; }); // module: chai/utils/overwriteProperty.js require.register("chai/utils/test.js", function(module, exports, require){ /*! * Chai - test utility * Copyright(c) 2012 Jake Luer * MIT Licensed */ /*! * Module dependancies */ var flag = require('./flag'); /** * # test(object, expression) * * Test and object for expression. * * @param {Object} object (constructed Assertion) * @param {Arguments} chai.Assertion.prototype.assert arguments */ module.exports = function (obj, args) { var negate = flag(obj, 'negate') , expr = args[0]; return negate ? !expr : expr; }; }); // module: chai/utils/test.js require.register("chai/utils/transferFlags.js", function(module, exports, require){ /*! * Chai - transferFlags utility * Copyright(c) 2012 Jake Luer * MIT Licensed */ /** * ### transferFlags(assertion, object, includeAll = true) * * Transfer all the flags for `assertion` to `object`. If * `includeAll` is set to `false`, then the base Chai * assertion flags (namely `object`, `ssfi`, and `message`) * will not be transferred. * * * var newAssertion = new Assertion(); * utils.transferFlags(assertion, newAssertion); * * var anotherAsseriton = new Assertion(myObj); * utils.transferFlags(assertion, anotherAssertion, false); * * @param {Assertion} assertion the assertion to transfer the flags from * @param {Object} object the object to transfer the flags too; usually a new assertion * @param {Boolean} includeAll * @name getAllFlags * @api private */ module.exports = function (assertion, object, includeAll) { var flags = assertion.__flags || (assertion.__flags = Object.create(null)); if (!object.__flags) { object.__flags = Object.create(null); } includeAll = arguments.length === 3 ? includeAll : true; for (var flag in flags) { if (includeAll || (flag !== 'object' && flag !== 'ssfi' && flag != 'message')) { object.__flags[flag] = flags[flag]; } } }; }); // module: chai/utils/transferFlags.js require.alias("./chai.js", "chai"); return require('chai'); }); ================================================ FILE: test/lib/expect.js ================================================ (function (global, module) { if ('undefined' == typeof module) { var module = { exports: {} } , exports = module.exports } /** * Exports. */ module.exports = expect; expect.Assertion = Assertion; /** * Exports version. */ expect.version = '0.1.2'; /** * Possible assertion flags. */ var flags = { not: ['to', 'be', 'have', 'include', 'only'] , to: ['be', 'have', 'include', 'only', 'not'] , only: ['have'] , have: ['own'] , be: ['an'] }; function expect (obj) { return new Assertion(obj); } /** * Constructor * * @api private */ function Assertion (obj, flag, parent) { this.obj = obj; this.flags = {}; if (undefined != parent) { this.flags[flag] = true; for (var i in parent.flags) { if (parent.flags.hasOwnProperty(i)) { this.flags[i] = true; } } } var $flags = flag ? flags[flag] : keys(flags) , self = this if ($flags) { for (var i = 0, l = $flags.length; i < l; i++) { // avoid recursion if (this.flags[$flags[i]]) continue; var name = $flags[i] , assertion = new Assertion(this.obj, name, this) if ('function' == typeof Assertion.prototype[name]) { // clone the function, make sure we dont touch the prot reference var old = this[name]; this[name] = function () { return old.apply(self, arguments); } for (var fn in Assertion.prototype) { if (Assertion.prototype.hasOwnProperty(fn) && fn != name) { this[name][fn] = bind(assertion[fn], assertion); } } } else { this[name] = assertion; } } } }; /** * Performs an assertion * * @api private */ Assertion.prototype.assert = function (truth, msg, error) { var msg = this.flags.not ? error : msg , ok = this.flags.not ? !truth : truth; if (!ok) { throw new Error(msg); } this.and = new Assertion(this.obj); }; /** * Check if the value is truthy * * @api public */ Assertion.prototype.ok = function () { this.assert( !!this.obj , 'expected ' + i(this.obj) + ' to be truthy' , 'expected ' + i(this.obj) + ' to be falsy'); }; /** * Assert that the function throws. * * @param {Function|RegExp} callback, or regexp to match error string against * @api public */ Assertion.prototype.throwError = Assertion.prototype.throwException = function (fn) { expect(this.obj).to.be.a('function'); var thrown = false , not = this.flags.not try { this.obj(); } catch (e) { if ('function' == typeof fn) { fn(e); } else if ('object' == typeof fn) { var subject = 'string' == typeof e ? e : e.message; if (not) { expect(subject).to.not.match(fn); } else { expect(subject).to.match(fn); } } thrown = true; } if ('object' == typeof fn && not) { // in the presence of a matcher, ensure the `not` only applies to // the matching. this.flags.not = false; } var name = this.obj.name || 'fn'; this.assert( thrown , 'expected ' + name + ' to throw an exception' , 'expected ' + name + ' not to throw an exception'); }; /** * Checks if the array is empty. * * @api public */ Assertion.prototype.empty = function () { var expectation; if ('object' == typeof this.obj && null !== this.obj && !isArray(this.obj)) { if ('number' == typeof this.obj.length) { expectation = !this.obj.length; } else { expectation = !keys(this.obj).length; } } else { if ('string' != typeof this.obj) { expect(this.obj).to.be.an('object'); } expect(this.obj).to.have.property('length'); expectation = !this.obj.length; } this.assert( expectation , 'expected ' + i(this.obj) + ' to be empty' , 'expected ' + i(this.obj) + ' to not be empty'); return this; }; /** * Checks if the obj exactly equals another. * * @api public */ Assertion.prototype.be = Assertion.prototype.equal = function (obj) { this.assert( obj === this.obj , 'expected ' + i(this.obj) + ' to equal ' + i(obj) , 'expected ' + i(this.obj) + ' to not equal ' + i(obj)); return this; }; /** * Checks if the obj sortof equals another. * * @api public */ Assertion.prototype.eql = function (obj) { this.assert( expect.eql(obj, this.obj) , 'expected ' + i(this.obj) + ' to sort of equal ' + i(obj) , 'expected ' + i(this.obj) + ' to sort of not equal ' + i(obj)); return this; }; /** * Assert within start to finish (inclusive). * * @param {Number} start * @param {Number} finish * @api public */ Assertion.prototype.within = function (start, finish) { var range = start + '..' + finish; this.assert( this.obj >= start && this.obj <= finish , 'expected ' + i(this.obj) + ' to be within ' + range , 'expected ' + i(this.obj) + ' to not be within ' + range); return this; }; /** * Assert typeof / instance of * * @api public */ Assertion.prototype.a = Assertion.prototype.an = function (type) { if ('string' == typeof type) { // proper english in error msg var n = /^[aeiou]/.test(type) ? 'n' : ''; // typeof with support for 'array' this.assert( 'array' == type ? isArray(this.obj) : 'object' == type ? 'object' == typeof this.obj && null !== this.obj : type == typeof this.obj , 'expected ' + i(this.obj) + ' to be a' + n + ' ' + type , 'expected ' + i(this.obj) + ' not to be a' + n + ' ' + type); } else { // instanceof var name = type.name || 'supplied constructor'; this.assert( this.obj instanceof type , 'expected ' + i(this.obj) + ' to be an instance of ' + name , 'expected ' + i(this.obj) + ' not to be an instance of ' + name); } return this; }; /** * Assert numeric value above _n_. * * @param {Number} n * @api public */ Assertion.prototype.greaterThan = Assertion.prototype.above = function (n) { this.assert( this.obj > n , 'expected ' + i(this.obj) + ' to be above ' + n , 'expected ' + i(this.obj) + ' to be below ' + n); return this; }; /** * Assert numeric value below _n_. * * @param {Number} n * @api public */ Assertion.prototype.lessThan = Assertion.prototype.below = function (n) { this.assert( this.obj < n , 'expected ' + i(this.obj) + ' to be below ' + n , 'expected ' + i(this.obj) + ' to be above ' + n); return this; }; /** * Assert string value matches _regexp_. * * @param {RegExp} regexp * @api public */ Assertion.prototype.match = function (regexp) { this.assert( regexp.exec(this.obj) , 'expected ' + i(this.obj) + ' to match ' + regexp , 'expected ' + i(this.obj) + ' not to match ' + regexp); return this; }; /** * Assert property "length" exists and has value of _n_. * * @param {Number} n * @api public */ Assertion.prototype.length = function (n) { expect(this.obj).to.have.property('length'); var len = this.obj.length; this.assert( n == len , 'expected ' + i(this.obj) + ' to have a length of ' + n + ' but got ' + len , 'expected ' + i(this.obj) + ' to not have a length of ' + len); return this; }; /** * Assert property _name_ exists, with optional _val_. * * @param {String} name * @param {Mixed} val * @api public */ Assertion.prototype.property = function (name, val) { if (this.flags.own) { this.assert( Object.prototype.hasOwnProperty.call(this.obj, name) , 'expected ' + i(this.obj) + ' to have own property ' + i(name) , 'expected ' + i(this.obj) + ' to not have own property ' + i(name)); return this; } if (this.flags.not && undefined !== val) { if (undefined === this.obj[name]) { throw new Error(i(this.obj) + ' has no property ' + i(name)); } } else { var hasProp; try { hasProp = name in this.obj } catch (e) { hasProp = undefined !== this.obj[name] } this.assert( hasProp , 'expected ' + i(this.obj) + ' to have a property ' + i(name) , 'expected ' + i(this.obj) + ' to not have a property ' + i(name)); } if (undefined !== val) { this.assert( val === this.obj[name] , 'expected ' + i(this.obj) + ' to have a property ' + i(name) + ' of ' + i(val) + ', but got ' + i(this.obj[name]) , 'expected ' + i(this.obj) + ' to not have a property ' + i(name) + ' of ' + i(val)); } this.obj = this.obj[name]; return this; }; /** * Assert that the array contains _obj_ or string contains _obj_. * * @param {Mixed} obj|string * @api public */ Assertion.prototype.string = Assertion.prototype.contain = function (obj) { if ('string' == typeof this.obj) { this.assert( ~this.obj.indexOf(obj) , 'expected ' + i(this.obj) + ' to contain ' + i(obj) , 'expected ' + i(this.obj) + ' to not contain ' + i(obj)); } else { this.assert( ~indexOf(this.obj, obj) , 'expected ' + i(this.obj) + ' to contain ' + i(obj) , 'expected ' + i(this.obj) + ' to not contain ' + i(obj)); } return this; }; /** * Assert exact keys or inclusion of keys by using * the `.own` modifier. * * @param {Array|String ...} keys * @api public */ Assertion.prototype.key = Assertion.prototype.keys = function ($keys) { var str , ok = true; $keys = isArray($keys) ? $keys : Array.prototype.slice.call(arguments); if (!$keys.length) throw new Error('keys required'); var actual = keys(this.obj) , len = $keys.length; // Inclusion ok = every($keys, function (key) { return ~indexOf(actual, key); }); // Strict if (!this.flags.not && this.flags.only) { ok = ok && $keys.length == actual.length; } // Key string if (len > 1) { $keys = map($keys, function (key) { return i(key); }); var last = $keys.pop(); str = $keys.join(', ') + ', and ' + last; } else { str = i($keys[0]); } // Form str = (len > 1 ? 'keys ' : 'key ') + str; // Have / include str = (!this.flags.only ? 'include ' : 'only have ') + str; // Assertion this.assert( ok , 'expected ' + i(this.obj) + ' to ' + str , 'expected ' + i(this.obj) + ' to not ' + str); return this; }; /** * Function bind implementation. */ function bind (fn, scope) { return function () { return fn.apply(scope, arguments); } } /** * Array every compatibility * * @see bit.ly/5Fq1N2 * @api public */ function every (arr, fn, thisObj) { var scope = thisObj || global; for (var i = 0, j = arr.length; i < j; ++i) { if (!fn.call(scope, arr[i], i, arr)) { return false; } } return true; }; /** * Array indexOf compatibility. * * @see bit.ly/a5Dxa2 * @api public */ function indexOf (arr, o, i) { if (Array.prototype.indexOf) { return Array.prototype.indexOf.call(arr, o, i); } if (arr.length === undefined) { return -1; } for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0 ; i < j && arr[i] !== o; i++); return j <= i ? -1 : i; }; /** * Inspects an object. * * @see taken from node.js `util` module (copyright Joyent, MIT license) * @api private */ function i (obj, showHidden, depth) { var seen = []; function stylize (str) { return str; }; function format (value, recurseTimes) { // Provide a hook for user-specified inspect functions. // Check that value is an object with an inspect function on it if (value && typeof value.inspect === 'function' && // Filter out the util module, it's inspect function is special value !== exports && // Also filter out any prototype objects using the circular check. !(value.constructor && value.constructor.prototype === value)) { return value.inspect(recurseTimes); } // Primitive types cannot have properties switch (typeof value) { case 'undefined': return stylize('undefined', 'undefined'); case 'string': var simple = '\'' + json.stringify(value).replace(/^"|"$/g, '') .replace(/'/g, "\\'") .replace(/\\"/g, '"') + '\''; return stylize(simple, 'string'); case 'number': return stylize('' + value, 'number'); case 'boolean': return stylize('' + value, 'boolean'); } // For some reason typeof null is "object", so special case here. if (value === null) { return stylize('null', 'null'); } // Look up the keys of the object. var visible_keys = keys(value); var $keys = showHidden ? Object.getOwnPropertyNames(value) : visible_keys; // Functions without properties can be shortcutted. if (typeof value === 'function' && $keys.length === 0) { if (isRegExp(value)) { return stylize('' + value, 'regexp'); } else { var name = value.name ? ': ' + value.name : ''; return stylize('[Function' + name + ']', 'special'); } } // Dates without properties can be shortcutted if (isDate(value) && $keys.length === 0) { return stylize(value.toUTCString(), 'date'); } var base, type, braces; // Determine the object type if (isArray(value)) { type = 'Array'; braces = ['[', ']']; } else { type = 'Object'; braces = ['{', '}']; } // Make functions say that they are functions if (typeof value === 'function') { var n = value.name ? ': ' + value.name : ''; base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']'; } else { base = ''; } // Make dates with properties first say the date if (isDate(value)) { base = ' ' + value.toUTCString(); } if ($keys.length === 0) { return braces[0] + base + braces[1]; } if (recurseTimes < 0) { if (isRegExp(value)) { return stylize('' + value, 'regexp'); } else { return stylize('[Object]', 'special'); } } seen.push(value); var output = map($keys, function (key) { var name, str; if (value.__lookupGetter__) { if (value.__lookupGetter__(key)) { if (value.__lookupSetter__(key)) { str = stylize('[Getter/Setter]', 'special'); } else { str = stylize('[Getter]', 'special'); } } else { if (value.__lookupSetter__(key)) { str = stylize('[Setter]', 'special'); } } } if (indexOf(visible_keys, key) < 0) { name = '[' + key + ']'; } if (!str) { if (indexOf(seen, value[key]) < 0) { if (recurseTimes === null) { str = format(value[key]); } else { str = format(value[key], recurseTimes - 1); } if (str.indexOf('\n') > -1) { if (isArray(value)) { str = map(str.split('\n'), function (line) { return ' ' + line; }).join('\n').substr(2); } else { str = '\n' + map(str.split('\n'), function (line) { return ' ' + line; }).join('\n'); } } } else { str = stylize('[Circular]', 'special'); } } if (typeof name === 'undefined') { if (type === 'Array' && key.match(/^\d+$/)) { return str; } name = json.stringify('' + key); if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { name = name.substr(1, name.length - 2); name = stylize(name, 'name'); } else { name = name.replace(/'/g, "\\'") .replace(/\\"/g, '"') .replace(/(^"|"$)/g, "'"); name = stylize(name, 'string'); } } return name + ': ' + str; }); seen.pop(); var numLinesEst = 0; var length = reduce(output, function (prev, cur) { numLinesEst++; if (indexOf(cur, '\n') >= 0) numLinesEst++; return prev + cur.length + 1; }, 0); if (length > 50) { output = braces[0] + (base === '' ? '' : base + '\n ') + ' ' + output.join(',\n ') + ' ' + braces[1]; } else { output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; } return output; } return format(obj, (typeof depth === 'undefined' ? 2 : depth)); }; function isArray (ar) { return Object.prototype.toString.call(ar) == '[object Array]'; }; function isRegExp(re) { var s = '' + re; return re instanceof RegExp || // easy case // duck-type for context-switching evalcx case typeof(re) === 'function' && re.constructor.name === 'RegExp' && re.compile && re.test && re.exec && s.match(/^\/.*\/[gim]{0,3}$/); }; function isDate(d) { if (d instanceof Date) return true; return false; }; function keys (obj) { if (Object.keys) { return Object.keys(obj); } var keys = []; for (var i in obj) { if (Object.prototype.hasOwnProperty.call(obj, i)) { keys.push(i); } } return keys; } function map (arr, mapper, that) { if (Array.prototype.map) { return Array.prototype.map.call(arr, mapper, that); } var other= new Array(arr.length); for (var i= 0, n = arr.length; i= 2) { var rv = arguments[1]; } else { do { if (i in this) { rv = this[i++]; break; } // if array contains no values, no initial value to return if (++i >= len) throw new TypeError(); } while (true); } for (; i < len; i++) { if (i in this) rv = fun.call(null, rv, this[i], i, this); } return rv; }; /** * Asserts deep equality * * @see taken from node.js `assert` module (copyright Joyent, MIT license) * @api private */ expect.eql = function eql (actual, expected) { // 7.1. All identical values are equivalent, as determined by ===. if (actual === expected) { return true; } else if ('undefined' != typeof Buffer && Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { if (actual.length != expected.length) return false; for (var i = 0; i < actual.length; i++) { if (actual[i] !== expected[i]) return false; } return true; // 7.2. If the expected value is a Date object, the actual value is // equivalent if it is also a Date object that refers to the same time. } else if (actual instanceof Date && expected instanceof Date) { return actual.getTime() === expected.getTime(); // 7.3. Other pairs that do not both pass typeof value == "object", // equivalence is determined by ==. } else if (typeof actual != 'object' && typeof expected != 'object') { return actual == expected; // 7.4. For all other Object pairs, including Array objects, equivalence is // determined by having the same number of owned properties (as verified // with Object.prototype.hasOwnProperty.call), the same set of keys // (although not necessarily the same order), equivalent values for every // corresponding key, and an identical "prototype" property. Note: this // accounts for both named and indexed properties on Arrays. } else { return objEquiv(actual, expected); } } function isUndefinedOrNull (value) { return value === null || value === undefined; } function isArguments (object) { return Object.prototype.toString.call(object) == '[object Arguments]'; } function objEquiv (a, b) { if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) return false; // an identical "prototype" property. if (a.prototype !== b.prototype) return false; //~~~I've managed to break Object.keys through screwy arguments passing. // Converting to array solves the problem. if (isArguments(a)) { if (!isArguments(b)) { return false; } a = pSlice.call(a); b = pSlice.call(b); return expect.eql(a, b); } try{ var ka = keys(a), kb = keys(b), key, i; } catch (e) {//happens when one is a string literal and the other isn't return false; } // having the same number of owned properties (keys incorporates hasOwnProperty) if (ka.length != kb.length) return false; //the same set of keys (although not necessarily the same order), ka.sort(); kb.sort(); //~~~cheap key test for (i = ka.length - 1; i >= 0; i--) { if (ka[i] != kb[i]) return false; } //equivalent values for every corresponding key, and //~~~possibly expensive deep test for (i = ka.length - 1; i >= 0; i--) { key = ka[i]; if (!expect.eql(a[key], b[key])) return false; } return true; } var json = (function () { "use strict"; if ('object' == typeof JSON && JSON.parse && JSON.stringify) { return { parse: nativeJSON.parse , stringify: nativeJSON.stringify } } var JSON = {}; function f(n) { // Format integers to have at least two digits. return n < 10 ? '0' + n : n; } function date(d, key) { return isFinite(d.valueOf()) ? d.getUTCFullYear() + '-' + f(d.getUTCMonth() + 1) + '-' + f(d.getUTCDate()) + 'T' + f(d.getUTCHours()) + ':' + f(d.getUTCMinutes()) + ':' + f(d.getUTCSeconds()) + 'Z' : null; }; var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, gap, indent, meta = { // table of character substitutions '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\' }, rep; function quote(string) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can safely slap some quotes around it. // Otherwise we must also replace the offending characters with safe escape // sequences. escapable.lastIndex = 0; return escapable.test(string) ? '"' + string.replace(escapable, function (a) { var c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; } function str(key, holder) { // Produce a string from holder[key]. var i, // The loop counter. k, // The member key. v, // The member value. length, mind = gap, partial, value = holder[key]; // If the value has a toJSON method, call it to obtain a replacement value. if (value instanceof Date) { value = date(key); } // If we were called with a replacer function, then call the replacer to // obtain a replacement value. if (typeof rep === 'function') { value = rep.call(holder, key, value); } // What happens next depends on the value's type. switch (typeof value) { case 'string': return quote(value); case 'number': // JSON numbers must be finite. Encode non-finite numbers as null. return isFinite(value) ? String(value) : 'null'; case 'boolean': case 'null': // If the value is a boolean or null, convert it to a string. Note: // typeof null does not produce 'null'. The case is included here in // the remote chance that this gets fixed someday. return String(value); // If the type is 'object', we might be dealing with an object or an array or // null. case 'object': // Due to a specification blunder in ECMAScript, typeof null is 'object', // so watch out for that case. if (!value) { return 'null'; } // Make an array to hold the partial results of stringifying this object value. gap += indent; partial = []; // Is the value an array? if (Object.prototype.toString.apply(value) === '[object Array]') { // The value is an array. Stringify every element. Use null as a placeholder // for non-JSON values. length = value.length; for (i = 0; i < length; i += 1) { partial[i] = str(i, value) || 'null'; } // Join all of the elements together, separated with commas, and wrap them in // brackets. v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']'; gap = mind; return v; } // If the replacer is an array, use it to select the members to be stringified. if (rep && typeof rep === 'object') { length = rep.length; for (i = 0; i < length; i += 1) { if (typeof rep[i] === 'string') { k = rep[i]; v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } else { // Otherwise, iterate through all of the keys in the object. for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } // Join all of the member texts together, separated with commas, // and wrap them in braces. v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}'; gap = mind; return v; } } // If the JSON object does not yet have a stringify method, give it one. JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional // space parameter, and returns a JSON text. The replacer can be a function // that can replace values, or an array of strings that will select the keys. // A default replacer method can be provided. Use of the space parameter can // produce text that is more easily readable. var i; gap = ''; indent = ''; // If the space parameter is a number, make an indent string containing that // many spaces. if (typeof space === 'number') { for (i = 0; i < space; i += 1) { indent += ' '; } // If the space parameter is a string, it will be used as the indent string. } else if (typeof space === 'string') { indent = space; } // If there is a replacer, it must be a function or an array. // Otherwise, throw an error. rep = replacer; if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number')) { throw new Error('JSON.stringify'); } // Make a fake root object containing our value under the key of ''. // Return the result of stringifying the value. return str('', {'': value}); }; // If the JSON object does not yet have a parse method, give it one. JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns // a JavaScript value if the text is a valid JSON text. var j; function walk(holder, key) { // The walk method is used to recursively walk the resulting structure so // that modifications can be made. var k, v, value = holder[key]; if (value && typeof value === 'object') { for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = walk(value, k); if (v !== undefined) { value[k] = v; } else { delete value[k]; } } } } return reviver.call(holder, key, value); } // Parsing happens in four stages. In the first stage, we replace certain // Unicode characters with escape sequences. JavaScript handles many characters // incorrectly, either silently deleting them, or treating them as line endings. text = String(text); cx.lastIndex = 0; if (cx.test(text)) { text = text.replace(cx, function (a) { return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }); } // In the second stage, we run the text against regular expressions that look // for non-JSON patterns. We are especially concerned with '()' and 'new' // because they can cause invocation, and '=' because it can cause mutation. // But just to be safe, we want to reject all unexpected forms. // We split the second stage into 4 regexp operations in order to work around // crippling inefficiencies in IE's and Safari's regexp engines. First we // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we // replace all simple value tokens with ']' characters. Third, we delete all // open brackets that follow a colon or comma or that begin the text. Finally, // we look to see that the remaining characters are only whitespace or ']' or // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. if (/^[\],:{}\s]*$/ .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { // In the third stage we use the eval function to compile the text into a // JavaScript structure. The '{' operator is subject to a syntactic ambiguity // in JavaScript: it can begin a block or an object literal. We wrap the text // in parens to eliminate the ambiguity. j = eval('(' + text + ')'); // In the optional fourth stage, we recursively walk the new structure, passing // each name/value pair to a reviver function for possible transformation. return typeof reviver === 'function' ? walk({'': j}, '') : j; } // If the text is not JSON parseable, then a SyntaxError is thrown. throw new SyntaxError('JSON.parse'); }; return JSON; })(); if ('undefined' != typeof window) { window.expect = module.exports; } })( this , 'undefined' != typeof module ? module : {} , 'undefined' != typeof exports ? exports : {} ); ================================================ FILE: test/lib/mocha/mocha.css ================================================ @charset "utf-8"; body { margin:0; } #mocha { font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; margin: 60px 50px; } #mocha ul, #mocha li { margin: 0; padding: 0; } #mocha ul { list-style: none; } #mocha h1, #mocha h2 { margin: 0; } #mocha h1 { margin-top: 15px; font-size: 1em; font-weight: 200; } #mocha h1 a { text-decoration: none; color: inherit; } #mocha h1 a:hover { text-decoration: underline; } #mocha .suite .suite h1 { margin-top: 0; font-size: .8em; } #mocha .hidden { display: none; } #mocha h2 { font-size: 12px; font-weight: normal; cursor: pointer; } #mocha .suite { margin-left: 15px; } #mocha .test { margin-left: 15px; overflow: hidden; } #mocha .test.pending:hover h2::after { content: '(pending)'; font-family: arial, sans-serif; } #mocha .test.pass.medium .duration { background: #C09853; } #mocha .test.pass.slow .duration { background: #B94A48; } #mocha .test.pass::before { content: '✓'; font-size: 12px; display: block; float: left; margin-right: 5px; color: #00d6b2; } #mocha .test.pass .duration { font-size: 9px; margin-left: 5px; padding: 2px 5px; color: white; -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); box-shadow: inset 0 1px 1px rgba(0,0,0,.2); -webkit-border-radius: 5px; -moz-border-radius: 5px; -ms-border-radius: 5px; -o-border-radius: 5px; border-radius: 5px; } #mocha .test.pass.fast .duration { display: none; } #mocha .test.pending { color: #0b97c4; } #mocha .test.pending::before { content: '◦'; color: #0b97c4; } #mocha .test.fail { color: #c00; } #mocha .test.fail pre { color: black; } #mocha .test.fail::before { content: '✖'; font-size: 12px; display: block; float: left; margin-right: 5px; color: #c00; } #mocha .test pre.error { color: #c00; max-height: 300px; overflow: auto; } #mocha .test pre { display: block; float: left; clear: left; font: 12px/1.5 monaco, monospace; margin: 5px; padding: 15px; border: 1px solid #eee; border-bottom-color: #ddd; -webkit-border-radius: 3px; -webkit-box-shadow: 0 1px 3px #eee; -moz-border-radius: 3px; -moz-box-shadow: 0 1px 3px #eee; border-radius: 3px; } #mocha .test h2 { position: relative; } #mocha .test a.replay { position: absolute; top: 3px; right: 0; text-decoration: none; vertical-align: middle; display: block; width: 15px; height: 15px; line-height: 15px; text-align: center; background: #eee; font-size: 15px; -moz-border-radius: 15px; border-radius: 15px; -webkit-transition: opacity 200ms; -moz-transition: opacity 200ms; transition: opacity 200ms; opacity: 0.3; color: #888; } #mocha .test:hover a.replay { opacity: 1; } #mocha-report.pass .test.fail { display: none; } #mocha-report.fail .test.pass { display: none; } #mocha-report.pending .test.pass, #mocha-report.pending .test.fail { display: none; } #mocha-report.pending .test.pass.pending { display: block; } #mocha-error { color: #c00; font-size: 1.5em; font-weight: 100; letter-spacing: 1px; } #mocha-stats { position: fixed; top: 15px; right: 10px; font-size: 12px; margin: 0; color: #888; z-index: 1; } #mocha-stats .progress { float: right; padding-top: 0; } #mocha-stats em { color: black; } #mocha-stats a { text-decoration: none; color: inherit; } #mocha-stats a:hover { border-bottom: 1px solid #eee; } #mocha-stats li { display: inline-block; margin: 0 5px; list-style: none; padding-top: 11px; } #mocha-stats canvas { width: 40px; height: 40px; } #mocha code .comment { color: #ddd } #mocha code .init { color: #2F6FAD } #mocha code .string { color: #5890AD } #mocha code .keyword { color: #8A6343 } #mocha code .number { color: #2F6FAD } @media screen and (max-device-width: 480px) { #mocha { margin: 60px 0px; } #mocha #stats { position: absolute; } } ================================================ FILE: test/lib/mocha/mocha.js ================================================ ;(function(){ // CommonJS require() function require(p){ var path = require.resolve(p) , mod = require.modules[path]; if (!mod) throw new Error('failed to require "' + p + '"'); if (!mod.exports) { mod.exports = {}; mod.call(mod.exports, mod, mod.exports, require.relative(path)); } return mod.exports; } require.modules = {}; require.resolve = function (path){ var orig = path , reg = path + '.js' , index = path + '/index.js'; return require.modules[reg] && reg || require.modules[index] && index || orig; }; require.register = function (path, fn){ require.modules[path] = fn; }; require.relative = function (parent) { return function(p){ if ('.' != p.charAt(0)) return require(p); var path = parent.split('/') , segs = p.split('/'); path.pop(); for (var i = 0; i < segs.length; i++) { var seg = segs[i]; if ('..' == seg) path.pop(); else if ('.' != seg) path.push(seg); } return require(path.join('/')); }; }; require.register("browser/debug.js", function(module, exports, require){ module.exports = function(type){ return function(){ } }; }); // module: browser/debug.js require.register("browser/diff.js", function(module, exports, require){ /* See LICENSE file for terms of use */ /* * Text diff implementation. * * This library supports the following APIS: * JsDiff.diffChars: Character by character diff * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace * JsDiff.diffLines: Line based diff * * JsDiff.diffCss: Diff targeted at CSS content * * These methods are based on the implementation proposed in * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 */ var JsDiff = (function() { /*jshint maxparams: 5*/ function clonePath(path) { return { newPos: path.newPos, components: path.components.slice(0) }; } function removeEmpty(array) { var ret = []; for (var i = 0; i < array.length; i++) { if (array[i]) { ret.push(array[i]); } } return ret; } function escapeHTML(s) { var n = s; n = n.replace(/&/g, '&'); n = n.replace(//g, '>'); n = n.replace(/"/g, '"'); return n; } var Diff = function(ignoreWhitespace) { this.ignoreWhitespace = ignoreWhitespace; }; Diff.prototype = { diff: function(oldString, newString) { // Handle the identity case (this is due to unrolling editLength == 0 if (newString === oldString) { return [{ value: newString }]; } if (!newString) { return [{ value: oldString, removed: true }]; } if (!oldString) { return [{ value: newString, added: true }]; } newString = this.tokenize(newString); oldString = this.tokenize(oldString); var newLen = newString.length, oldLen = oldString.length; var maxEditLength = newLen + oldLen; var bestPath = [{ newPos: -1, components: [] }]; // Seed editLength = 0 var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) { return bestPath[0].components; } for (var editLength = 1; editLength <= maxEditLength; editLength++) { for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) { var basePath; var addPath = bestPath[diagonalPath-1], removePath = bestPath[diagonalPath+1]; oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; if (addPath) { // No one else is going to attempt to use this value, clear it bestPath[diagonalPath-1] = undefined; } var canAdd = addPath && addPath.newPos+1 < newLen; var canRemove = removePath && 0 <= oldPos && oldPos < oldLen; if (!canAdd && !canRemove) { bestPath[diagonalPath] = undefined; continue; } // Select the diagonal that we want to branch from. We select the prior // path whose position in the new string is the farthest from the origin // and does not pass the bounds of the diff graph if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) { basePath = clonePath(removePath); this.pushComponent(basePath.components, oldString[oldPos], undefined, true); } else { basePath = clonePath(addPath); basePath.newPos++; this.pushComponent(basePath.components, newString[basePath.newPos], true, undefined); } var oldPos = this.extractCommon(basePath, newString, oldString, diagonalPath); if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) { return basePath.components; } else { bestPath[diagonalPath] = basePath; } } } }, pushComponent: function(components, value, added, removed) { var last = components[components.length-1]; if (last && last.added === added && last.removed === removed) { // We need to clone here as the component clone operation is just // as shallow array clone components[components.length-1] = {value: this.join(last.value, value), added: added, removed: removed }; } else { components.push({value: value, added: added, removed: removed }); } }, extractCommon: function(basePath, newString, oldString, diagonalPath) { var newLen = newString.length, oldLen = oldString.length, newPos = basePath.newPos, oldPos = newPos - diagonalPath; while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) { newPos++; oldPos++; this.pushComponent(basePath.components, newString[newPos], undefined, undefined); } basePath.newPos = newPos; return oldPos; }, equals: function(left, right) { var reWhitespace = /\S/; if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) { return true; } else { return left === right; } }, join: function(left, right) { return left + right; }, tokenize: function(value) { return value; } }; var CharDiff = new Diff(); var WordDiff = new Diff(true); var WordWithSpaceDiff = new Diff(); WordDiff.tokenize = WordWithSpaceDiff.tokenize = function(value) { return removeEmpty(value.split(/(\s+|\b)/)); }; var CssDiff = new Diff(true); CssDiff.tokenize = function(value) { return removeEmpty(value.split(/([{}:;,]|\s+)/)); }; var LineDiff = new Diff(); LineDiff.tokenize = function(value) { return value.split(/^/m); }; return { Diff: Diff, diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); }, diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); }, diffWordsWithSpace: function(oldStr, newStr) { return WordWithSpaceDiff.diff(oldStr, newStr); }, diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); }, diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); }, createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) { var ret = []; ret.push('Index: ' + fileName); ret.push('==================================================================='); ret.push('--- ' + fileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader)); ret.push('+++ ' + fileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader)); var diff = LineDiff.diff(oldStr, newStr); if (!diff[diff.length-1].value) { diff.pop(); // Remove trailing newline add } diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier function contextLines(lines) { return lines.map(function(entry) { return ' ' + entry; }); } function eofNL(curRange, i, current) { var last = diff[diff.length-2], isLast = i === diff.length-2, isLastOfType = i === diff.length-3 && (current.added !== last.added || current.removed !== last.removed); // Figure out if this is the last line for the given file and missing NL if (!/\n$/.test(current.value) && (isLast || isLastOfType)) { curRange.push('\\ No newline at end of file'); } } var oldRangeStart = 0, newRangeStart = 0, curRange = [], oldLine = 1, newLine = 1; for (var i = 0; i < diff.length; i++) { var current = diff[i], lines = current.lines || current.value.replace(/\n$/, '').split('\n'); current.lines = lines; if (current.added || current.removed) { if (!oldRangeStart) { var prev = diff[i-1]; oldRangeStart = oldLine; newRangeStart = newLine; if (prev) { curRange = contextLines(prev.lines.slice(-4)); oldRangeStart -= curRange.length; newRangeStart -= curRange.length; } } curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?'+':'-') + entry; })); eofNL(curRange, i, current); if (current.added) { newLine += lines.length; } else { oldLine += lines.length; } } else { if (oldRangeStart) { // Close out any changes that have been output (or join overlapping) if (lines.length <= 8 && i < diff.length-2) { // Overlapping curRange.push.apply(curRange, contextLines(lines)); } else { // end the range and output var contextSize = Math.min(lines.length, 4); ret.push( '@@ -' + oldRangeStart + ',' + (oldLine-oldRangeStart+contextSize) + ' +' + newRangeStart + ',' + (newLine-newRangeStart+contextSize) + ' @@'); ret.push.apply(ret, curRange); ret.push.apply(ret, contextLines(lines.slice(0, contextSize))); if (lines.length <= 4) { eofNL(ret, i, current); } oldRangeStart = 0; newRangeStart = 0; curRange = []; } } oldLine += lines.length; newLine += lines.length; } } return ret.join('\n') + '\n'; }, applyPatch: function(oldStr, uniDiff) { var diffstr = uniDiff.split('\n'); var diff = []; var remEOFNL = false, addEOFNL = false; for (var i = (diffstr[0][0]==='I'?4:0); i < diffstr.length; i++) { if(diffstr[i][0] === '@') { var meh = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/); diff.unshift({ start:meh[3], oldlength:meh[2], oldlines:[], newlength:meh[4], newlines:[] }); } else if(diffstr[i][0] === '+') { diff[0].newlines.push(diffstr[i].substr(1)); } else if(diffstr[i][0] === '-') { diff[0].oldlines.push(diffstr[i].substr(1)); } else if(diffstr[i][0] === ' ') { diff[0].newlines.push(diffstr[i].substr(1)); diff[0].oldlines.push(diffstr[i].substr(1)); } else if(diffstr[i][0] === '\\') { if (diffstr[i-1][0] === '+') { remEOFNL = true; } else if(diffstr[i-1][0] === '-') { addEOFNL = true; } } } var str = oldStr.split('\n'); for (var i = diff.length - 1; i >= 0; i--) { var d = diff[i]; for (var j = 0; j < d.oldlength; j++) { if(str[d.start-1+j] !== d.oldlines[j]) { return false; } } Array.prototype.splice.apply(str,[d.start-1,+d.oldlength].concat(d.newlines)); } if (remEOFNL) { while (!str[str.length-1]) { str.pop(); } } else if (addEOFNL) { str.push(''); } return str.join('\n'); }, convertChangesToXML: function(changes){ var ret = []; for ( var i = 0; i < changes.length; i++) { var change = changes[i]; if (change.added) { ret.push(''); } else if (change.removed) { ret.push(''); } ret.push(escapeHTML(change.value)); if (change.added) { ret.push(''); } else if (change.removed) { ret.push(''); } } return ret.join(''); }, // See: http://code.google.com/p/google-diff-match-patch/wiki/API convertChangesToDMP: function(changes){ var ret = [], change; for ( var i = 0; i < changes.length; i++) { change = changes[i]; ret.push([(change.added ? 1 : change.removed ? -1 : 0), change.value]); } return ret; } }; })(); if (typeof module !== 'undefined') { module.exports = JsDiff; } }); // module: browser/diff.js require.register("browser/events.js", function(module, exports, require){ /** * Module exports. */ exports.EventEmitter = EventEmitter; /** * Check if `obj` is an array. */ function isArray(obj) { return '[object Array]' == {}.toString.call(obj); } /** * Event emitter constructor. * * @api public */ function EventEmitter(){}; /** * Adds a listener. * * @api public */ EventEmitter.prototype.on = function (name, fn) { if (!this.$events) { this.$events = {}; } if (!this.$events[name]) { this.$events[name] = fn; } else if (isArray(this.$events[name])) { this.$events[name].push(fn); } else { this.$events[name] = [this.$events[name], fn]; } return this; }; EventEmitter.prototype.addListener = EventEmitter.prototype.on; /** * Adds a volatile listener. * * @api public */ EventEmitter.prototype.once = function (name, fn) { var self = this; function on () { self.removeListener(name, on); fn.apply(this, arguments); }; on.listener = fn; this.on(name, on); return this; }; /** * Removes a listener. * * @api public */ EventEmitter.prototype.removeListener = function (name, fn) { if (this.$events && this.$events[name]) { var list = this.$events[name]; if (isArray(list)) { var pos = -1; for (var i = 0, l = list.length; i < l; i++) { if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { pos = i; break; } } if (pos < 0) { return this; } list.splice(pos, 1); if (!list.length) { delete this.$events[name]; } } else if (list === fn || (list.listener && list.listener === fn)) { delete this.$events[name]; } } return this; }; /** * Removes all listeners for an event. * * @api public */ EventEmitter.prototype.removeAllListeners = function (name) { if (name === undefined) { this.$events = {}; return this; } if (this.$events && this.$events[name]) { this.$events[name] = null; } return this; }; /** * Gets all listeners for a certain event. * * @api public */ EventEmitter.prototype.listeners = function (name) { if (!this.$events) { this.$events = {}; } if (!this.$events[name]) { this.$events[name] = []; } if (!isArray(this.$events[name])) { this.$events[name] = [this.$events[name]]; } return this.$events[name]; }; /** * Emits an event. * * @api public */ EventEmitter.prototype.emit = function (name) { if (!this.$events) { return false; } var handler = this.$events[name]; if (!handler) { return false; } var args = [].slice.call(arguments, 1); if ('function' == typeof handler) { handler.apply(this, args); } else if (isArray(handler)) { var listeners = handler.slice(); for (var i = 0, l = listeners.length; i < l; i++) { listeners[i].apply(this, args); } } else { return false; } return true; }; }); // module: browser/events.js require.register("browser/fs.js", function(module, exports, require){ }); // module: browser/fs.js require.register("browser/path.js", function(module, exports, require){ }); // module: browser/path.js require.register("browser/progress.js", function(module, exports, require){ /** * Expose `Progress`. */ module.exports = Progress; /** * Initialize a new `Progress` indicator. */ function Progress() { this.percent = 0; this.size(0); this.fontSize(11); this.font('helvetica, arial, sans-serif'); } /** * Set progress size to `n`. * * @param {Number} n * @return {Progress} for chaining * @api public */ Progress.prototype.size = function(n){ this._size = n; return this; }; /** * Set text to `str`. * * @param {String} str * @return {Progress} for chaining * @api public */ Progress.prototype.text = function(str){ this._text = str; return this; }; /** * Set font size to `n`. * * @param {Number} n * @return {Progress} for chaining * @api public */ Progress.prototype.fontSize = function(n){ this._fontSize = n; return this; }; /** * Set font `family`. * * @param {String} family * @return {Progress} for chaining */ Progress.prototype.font = function(family){ this._font = family; return this; }; /** * Update percentage to `n`. * * @param {Number} n * @return {Progress} for chaining */ Progress.prototype.update = function(n){ this.percent = n; return this; }; /** * Draw on `ctx`. * * @param {CanvasRenderingContext2d} ctx * @return {Progress} for chaining */ Progress.prototype.draw = function(ctx){ var percent = Math.min(this.percent, 100) , size = this._size , half = size / 2 , x = half , y = half , rad = half - 1 , fontSize = this._fontSize; ctx.font = fontSize + 'px ' + this._font; var angle = Math.PI * 2 * (percent / 100); ctx.clearRect(0, 0, size, size); // outer circle ctx.strokeStyle = '#9f9f9f'; ctx.beginPath(); ctx.arc(x, y, rad, 0, angle, false); ctx.stroke(); // inner circle ctx.strokeStyle = '#eee'; ctx.beginPath(); ctx.arc(x, y, rad - 1, 0, angle, true); ctx.stroke(); // text var text = this._text || (percent | 0) + '%' , w = ctx.measureText(text).width; ctx.fillText( text , x - w / 2 + 1 , y + fontSize / 2 - 1); return this; }; }); // module: browser/progress.js require.register("browser/tty.js", function(module, exports, require){ exports.isatty = function(){ return true; }; exports.getWindowSize = function(){ if ('innerHeight' in global) { return [global.innerHeight, global.innerWidth]; } else { // In a Web Worker, the DOM Window is not available. return [640, 480]; } }; }); // module: browser/tty.js require.register("context.js", function(module, exports, require){ /** * Expose `Context`. */ module.exports = Context; /** * Initialize a new `Context`. * * @api private */ function Context(){} /** * Set or get the context `Runnable` to `runnable`. * * @param {Runnable} runnable * @return {Context} * @api private */ Context.prototype.runnable = function(runnable){ if (0 == arguments.length) return this._runnable; this.test = this._runnable = runnable; return this; }; /** * Set test timeout `ms`. * * @param {Number} ms * @return {Context} self * @api private */ Context.prototype.timeout = function(ms){ this.runnable().timeout(ms); return this; }; /** * Set test slowness threshold `ms`. * * @param {Number} ms * @return {Context} self * @api private */ Context.prototype.slow = function(ms){ this.runnable().slow(ms); return this; }; /** * Inspect the context void of `._runnable`. * * @return {String} * @api private */ Context.prototype.inspect = function(){ return JSON.stringify(this, function(key, val){ if ('_runnable' == key) return; if ('test' == key) return; return val; }, 2); }; }); // module: context.js require.register("hook.js", function(module, exports, require){ /** * Module dependencies. */ var Runnable = require('./runnable'); /** * Expose `Hook`. */ module.exports = Hook; /** * Initialize a new `Hook` with the given `title` and callback `fn`. * * @param {String} title * @param {Function} fn * @api private */ function Hook(title, fn) { Runnable.call(this, title, fn); this.type = 'hook'; } /** * Inherit from `Runnable.prototype`. */ function F(){}; F.prototype = Runnable.prototype; Hook.prototype = new F; Hook.prototype.constructor = Hook; /** * Get or set the test `err`. * * @param {Error} err * @return {Error} * @api public */ Hook.prototype.error = function(err){ if (0 == arguments.length) { var err = this._error; this._error = null; return err; } this._error = err; }; }); // module: hook.js require.register("interfaces/bdd.js", function(module, exports, require){ /** * Module dependencies. */ var Suite = require('../suite') , Test = require('../test') , utils = require('../utils'); /** * BDD-style interface: * * describe('Array', function(){ * describe('#indexOf()', function(){ * it('should return -1 when not present', function(){ * * }); * * it('should return the index when present', function(){ * * }); * }); * }); * */ module.exports = function(suite){ var suites = [suite]; suite.on('pre-require', function(context, file, mocha){ /** * Execute before running tests. */ context.before = function(fn){ suites[0].beforeAll(fn); }; /** * Execute after running tests. */ context.after = function(fn){ suites[0].afterAll(fn); }; /** * Execute before each test case. */ context.beforeEach = function(fn){ suites[0].beforeEach(fn); }; /** * Execute after each test case. */ context.afterEach = function(fn){ suites[0].afterEach(fn); }; /** * Describe a "suite" with the given `title` * and callback `fn` containing nested suites * and/or tests. */ context.describe = context.context = function(title, fn){ var suite = Suite.create(suites[0], title); suites.unshift(suite); fn.call(suite); suites.shift(); return suite; }; /** * Pending describe. */ context.xdescribe = context.xcontext = context.describe.skip = function(title, fn){ var suite = Suite.create(suites[0], title); suite.pending = true; suites.unshift(suite); fn.call(suite); suites.shift(); }; /** * Exclusive suite. */ context.describe.only = function(title, fn){ var suite = context.describe(title, fn); mocha.grep(suite.fullTitle()); return suite; }; /** * Describe a specification or test-case * with the given `title` and callback `fn` * acting as a thunk. */ context.it = context.specify = function(title, fn){ var suite = suites[0]; if (suite.pending) var fn = null; var test = new Test(title, fn); suite.addTest(test); return test; }; /** * Exclusive test-case. */ context.it.only = function(title, fn){ var test = context.it(title, fn); var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$'; mocha.grep(new RegExp(reString)); return test; }; /** * Pending test case. */ context.xit = context.xspecify = context.it.skip = function(title){ context.it(title); }; }); }; }); // module: interfaces/bdd.js require.register("interfaces/exports.js", function(module, exports, require){ /** * Module dependencies. */ var Suite = require('../suite') , Test = require('../test'); /** * TDD-style interface: * * exports.Array = { * '#indexOf()': { * 'should return -1 when the value is not present': function(){ * * }, * * 'should return the correct index when the value is present': function(){ * * } * } * }; * */ module.exports = function(suite){ var suites = [suite]; suite.on('require', visit); function visit(obj) { var suite; for (var key in obj) { if ('function' == typeof obj[key]) { var fn = obj[key]; switch (key) { case 'before': suites[0].beforeAll(fn); break; case 'after': suites[0].afterAll(fn); break; case 'beforeEach': suites[0].beforeEach(fn); break; case 'afterEach': suites[0].afterEach(fn); break; default: suites[0].addTest(new Test(key, fn)); } } else { var suite = Suite.create(suites[0], key); suites.unshift(suite); visit(obj[key]); suites.shift(); } } } }; }); // module: interfaces/exports.js require.register("interfaces/index.js", function(module, exports, require){ exports.bdd = require('./bdd'); exports.tdd = require('./tdd'); exports.qunit = require('./qunit'); exports.exports = require('./exports'); }); // module: interfaces/index.js require.register("interfaces/qunit.js", function(module, exports, require){ /** * Module dependencies. */ var Suite = require('../suite') , Test = require('../test') , utils = require('../utils'); /** * QUnit-style interface: * * suite('Array'); * * test('#length', function(){ * var arr = [1,2,3]; * ok(arr.length == 3); * }); * * test('#indexOf()', function(){ * var arr = [1,2,3]; * ok(arr.indexOf(1) == 0); * ok(arr.indexOf(2) == 1); * ok(arr.indexOf(3) == 2); * }); * * suite('String'); * * test('#length', function(){ * ok('foo'.length == 3); * }); * */ module.exports = function(suite){ var suites = [suite]; suite.on('pre-require', function(context, file, mocha){ /** * Execute before running tests. */ context.before = function(fn){ suites[0].beforeAll(fn); }; /** * Execute after running tests. */ context.after = function(fn){ suites[0].afterAll(fn); }; /** * Execute before each test case. */ context.beforeEach = function(fn){ suites[0].beforeEach(fn); }; /** * Execute after each test case. */ context.afterEach = function(fn){ suites[0].afterEach(fn); }; /** * Describe a "suite" with the given `title`. */ context.suite = function(title){ if (suites.length > 1) suites.shift(); var suite = Suite.create(suites[0], title); suites.unshift(suite); return suite; }; /** * Exclusive test-case. */ context.suite.only = function(title, fn){ var suite = context.suite(title, fn); mocha.grep(suite.fullTitle()); }; /** * Describe a specification or test-case * with the given `title` and callback `fn` * acting as a thunk. */ context.test = function(title, fn){ var test = new Test(title, fn); suites[0].addTest(test); return test; }; /** * Exclusive test-case. */ context.test.only = function(title, fn){ var test = context.test(title, fn); var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$'; mocha.grep(new RegExp(reString)); }; /** * Pending test case. */ context.test.skip = function(title){ context.test(title); }; }); }; }); // module: interfaces/qunit.js require.register("interfaces/tdd.js", function(module, exports, require){ /** * Module dependencies. */ var Suite = require('../suite') , Test = require('../test') , utils = require('../utils');; /** * TDD-style interface: * * suite('Array', function(){ * suite('#indexOf()', function(){ * suiteSetup(function(){ * * }); * * test('should return -1 when not present', function(){ * * }); * * test('should return the index when present', function(){ * * }); * * suiteTeardown(function(){ * * }); * }); * }); * */ module.exports = function(suite){ var suites = [suite]; suite.on('pre-require', function(context, file, mocha){ /** * Execute before each test case. */ context.setup = function(fn){ suites[0].beforeEach(fn); }; /** * Execute after each test case. */ context.teardown = function(fn){ suites[0].afterEach(fn); }; /** * Execute before the suite. */ context.suiteSetup = function(fn){ suites[0].beforeAll(fn); }; /** * Execute after the suite. */ context.suiteTeardown = function(fn){ suites[0].afterAll(fn); }; /** * Describe a "suite" with the given `title` * and callback `fn` containing nested suites * and/or tests. */ context.suite = function(title, fn){ var suite = Suite.create(suites[0], title); suites.unshift(suite); fn.call(suite); suites.shift(); return suite; }; /** * Pending suite. */ context.suite.skip = function(title, fn) { var suite = Suite.create(suites[0], title); suite.pending = true; suites.unshift(suite); fn.call(suite); suites.shift(); }; /** * Exclusive test-case. */ context.suite.only = function(title, fn){ var suite = context.suite(title, fn); mocha.grep(suite.fullTitle()); }; /** * Describe a specification or test-case * with the given `title` and callback `fn` * acting as a thunk. */ context.test = function(title, fn){ var suite = suites[0]; if (suite.pending) var fn = null; var test = new Test(title, fn); suite.addTest(test); return test; }; /** * Exclusive test-case. */ context.test.only = function(title, fn){ var test = context.test(title, fn); var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$'; mocha.grep(new RegExp(reString)); }; /** * Pending test case. */ context.test.skip = function(title){ context.test(title); }; }); }; }); // module: interfaces/tdd.js require.register("mocha.js", function(module, exports, require){ /*! * mocha * Copyright(c) 2011 TJ Holowaychuk * MIT Licensed */ /** * Module dependencies. */ var path = require('browser/path') , utils = require('./utils'); /** * Expose `Mocha`. */ exports = module.exports = Mocha; /** * Expose internals. */ exports.utils = utils; exports.interfaces = require('./interfaces'); exports.reporters = require('./reporters'); exports.Runnable = require('./runnable'); exports.Context = require('./context'); exports.Runner = require('./runner'); exports.Suite = require('./suite'); exports.Hook = require('./hook'); exports.Test = require('./test'); /** * Return image `name` path. * * @param {String} name * @return {String} * @api private */ function image(name) { return __dirname + '/../images/' + name + '.png'; } /** * Setup mocha with `options`. * * Options: * * - `ui` name "bdd", "tdd", "exports" etc * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` * - `globals` array of accepted globals * - `timeout` timeout in milliseconds * - `bail` bail on the first test failure * - `slow` milliseconds to wait before considering a test slow * - `ignoreLeaks` ignore global leaks * - `grep` string or regexp to filter tests with * * @param {Object} options * @api public */ function Mocha(options) { options = options || {}; this.files = []; this.options = options; this.grep(options.grep); this.suite = new exports.Suite('', new exports.Context); this.ui(options.ui); this.bail(options.bail); this.reporter(options.reporter); if (null != options.timeout) this.timeout(options.timeout); if (options.slow) this.slow(options.slow); } /** * Enable or disable bailing on the first failure. * * @param {Boolean} [bail] * @api public */ Mocha.prototype.bail = function(bail){ if (0 == arguments.length) bail = true; this.suite.bail(bail); return this; }; /** * Add test `file`. * * @param {String} file * @api public */ Mocha.prototype.addFile = function(file){ this.files.push(file); return this; }; /** * Set reporter to `reporter`, defaults to "dot". * * @param {String|Function} reporter name or constructor * @api public */ Mocha.prototype.reporter = function(reporter){ if ('function' == typeof reporter) { this._reporter = reporter; } else { reporter = reporter || 'dot'; try { this._reporter = require('./reporters/' + reporter); } catch (err) { this._reporter = require(reporter); } if (!this._reporter) throw new Error('invalid reporter "' + reporter + '"'); } return this; }; /** * Set test UI `name`, defaults to "bdd". * * @param {String} bdd * @api public */ Mocha.prototype.ui = function(name){ name = name || 'bdd'; this._ui = exports.interfaces[name]; if (!this._ui) throw new Error('invalid interface "' + name + '"'); this._ui = this._ui(this.suite); return this; }; /** * Load registered files. * * @api private */ Mocha.prototype.loadFiles = function(fn){ var self = this; var suite = this.suite; var pending = this.files.length; this.files.forEach(function(file){ file = path.resolve(file); suite.emit('pre-require', global, file, self); suite.emit('require', require(file), file, self); suite.emit('post-require', global, file, self); --pending || (fn && fn()); }); }; /** * Enable growl support. * * @api private */ Mocha.prototype._growl = function(runner, reporter) { var notify = require('growl'); runner.on('end', function(){ var stats = reporter.stats; if (stats.failures) { var msg = stats.failures + ' of ' + runner.total + ' tests failed'; notify(msg, { name: 'mocha', title: 'Failed', image: image('error') }); } else { notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { name: 'mocha' , title: 'Passed' , image: image('ok') }); } }); }; /** * Add regexp to grep, if `re` is a string it is escaped. * * @param {RegExp|String} re * @return {Mocha} * @api public */ Mocha.prototype.grep = function(re){ this.options.grep = 'string' == typeof re ? new RegExp(utils.escapeRegexp(re)) : re; return this; }; /** * Invert `.grep()` matches. * * @return {Mocha} * @api public */ Mocha.prototype.invert = function(){ this.options.invert = true; return this; }; /** * Ignore global leaks. * * @param {Boolean} ignore * @return {Mocha} * @api public */ Mocha.prototype.ignoreLeaks = function(ignore){ this.options.ignoreLeaks = !!ignore; return this; }; /** * Enable global leak checking. * * @return {Mocha} * @api public */ Mocha.prototype.checkLeaks = function(){ this.options.ignoreLeaks = false; return this; }; /** * Enable growl support. * * @return {Mocha} * @api public */ Mocha.prototype.growl = function(){ this.options.growl = true; return this; }; /** * Ignore `globals` array or string. * * @param {Array|String} globals * @return {Mocha} * @api public */ Mocha.prototype.globals = function(globals){ this.options.globals = (this.options.globals || []).concat(globals); return this; }; /** * Set the timeout in milliseconds. * * @param {Number} timeout * @return {Mocha} * @api public */ Mocha.prototype.timeout = function(timeout){ this.suite.timeout(timeout); return this; }; /** * Set slowness threshold in milliseconds. * * @param {Number} slow * @return {Mocha} * @api public */ Mocha.prototype.slow = function(slow){ this.suite.slow(slow); return this; }; /** * Makes all tests async (accepting a callback) * * @return {Mocha} * @api public */ Mocha.prototype.asyncOnly = function(){ this.options.asyncOnly = true; return this; }; /** * Run tests and invoke `fn()` when complete. * * @param {Function} fn * @return {Runner} * @api public */ Mocha.prototype.run = function(fn){ if (this.files.length) this.loadFiles(); var suite = this.suite; var options = this.options; var runner = new exports.Runner(suite); var reporter = new this._reporter(runner); runner.ignoreLeaks = false !== options.ignoreLeaks; runner.asyncOnly = options.asyncOnly; if (options.grep) runner.grep(options.grep, options.invert); if (options.globals) runner.globals(options.globals); if (options.growl) this._growl(runner, reporter); return runner.run(fn); }; }); // module: mocha.js require.register("ms.js", function(module, exports, require){ /** * Helpers. */ var s = 1000; var m = s * 60; var h = m * 60; var d = h * 24; var y = d * 365.25; /** * Parse or format the given `val`. * * Options: * * - `long` verbose formatting [false] * * @param {String|Number} val * @param {Object} options * @return {String|Number} * @api public */ module.exports = function(val, options){ options = options || {}; if ('string' == typeof val) return parse(val); return options.long ? long(val) : short(val); }; /** * Parse the given `str` and return milliseconds. * * @param {String} str * @return {Number} * @api private */ function parse(str) { var match = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str); if (!match) return; var n = parseFloat(match[1]); var type = (match[2] || 'ms').toLowerCase(); switch (type) { case 'years': case 'year': case 'y': return n * y; case 'days': case 'day': case 'd': return n * d; case 'hours': case 'hour': case 'h': return n * h; case 'minutes': case 'minute': case 'm': return n * m; case 'seconds': case 'second': case 's': return n * s; case 'ms': return n; } } /** * Short format for `ms`. * * @param {Number} ms * @return {String} * @api private */ function short(ms) { if (ms >= d) return Math.round(ms / d) + 'd'; if (ms >= h) return Math.round(ms / h) + 'h'; if (ms >= m) return Math.round(ms / m) + 'm'; if (ms >= s) return Math.round(ms / s) + 's'; return ms + 'ms'; } /** * Long format for `ms`. * * @param {Number} ms * @return {String} * @api private */ function long(ms) { return plural(ms, d, 'day') || plural(ms, h, 'hour') || plural(ms, m, 'minute') || plural(ms, s, 'second') || ms + ' ms'; } /** * Pluralization helper. */ function plural(ms, n, name) { if (ms < n) return; if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name; return Math.ceil(ms / n) + ' ' + name + 's'; } }); // module: ms.js require.register("reporters/base.js", function(module, exports, require){ /** * Module dependencies. */ var tty = require('browser/tty') , diff = require('browser/diff') , ms = require('../ms'); /** * Save timer references to avoid Sinon interfering (see GH-237). */ var Date = global.Date , setTimeout = global.setTimeout , setInterval = global.setInterval , clearTimeout = global.clearTimeout , clearInterval = global.clearInterval; /** * Check if both stdio streams are associated with a tty. */ var isatty = tty.isatty(1) && tty.isatty(2); /** * Expose `Base`. */ exports = module.exports = Base; /** * Enable coloring by default. */ exports.useColors = isatty || (process.env.MOCHA_COLORS !== undefined); /** * Inline diffs instead of +/- */ exports.inlineDiffs = false; /** * Default color map. */ exports.colors = { 'pass': 90 , 'fail': 31 , 'bright pass': 92 , 'bright fail': 91 , 'bright yellow': 93 , 'pending': 36 , 'suite': 0 , 'error title': 0 , 'error message': 31 , 'error stack': 90 , 'checkmark': 32 , 'fast': 90 , 'medium': 33 , 'slow': 31 , 'green': 32 , 'light': 90 , 'diff gutter': 90 , 'diff added': 42 , 'diff removed': 41 }; /** * Default symbol map. */ exports.symbols = { ok: '✓', err: '✖', dot: '․' }; // With node.js on Windows: use symbols available in terminal default fonts if ('win32' == process.platform) { exports.symbols.ok = '\u221A'; exports.symbols.err = '\u00D7'; exports.symbols.dot = '.'; } /** * Color `str` with the given `type`, * allowing colors to be disabled, * as well as user-defined color * schemes. * * @param {String} type * @param {String} str * @return {String} * @api private */ var color = exports.color = function(type, str) { if (!exports.useColors) return str; return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; }; /** * Expose term window size, with some * defaults for when stderr is not a tty. */ exports.window = { width: isatty ? process.stdout.getWindowSize ? process.stdout.getWindowSize(1)[0] : tty.getWindowSize()[1] : 75 }; /** * Expose some basic cursor interactions * that are common among reporters. */ exports.cursor = { hide: function(){ process.stdout.write('\u001b[?25l'); }, show: function(){ process.stdout.write('\u001b[?25h'); }, deleteLine: function(){ process.stdout.write('\u001b[2K'); }, beginningOfLine: function(){ process.stdout.write('\u001b[0G'); }, CR: function(){ exports.cursor.deleteLine(); exports.cursor.beginningOfLine(); } }; /** * Outut the given `failures` as a list. * * @param {Array} failures * @api public */ exports.list = function(failures){ console.error(); failures.forEach(function(test, i){ // format var fmt = color('error title', ' %s) %s:\n') + color('error message', ' %s') + color('error stack', '\n%s\n'); // msg var err = test.err , message = err.message || '' , stack = err.stack || message , index = stack.indexOf(message) + message.length , msg = stack.slice(0, index) , actual = err.actual , expected = err.expected , escape = true; // uncaught if (err.uncaught) { msg = 'Uncaught ' + msg; } // explicitly show diff if (err.showDiff && sameType(actual, expected)) { escape = false; err.actual = actual = stringify(actual); err.expected = expected = stringify(expected); } // actual / expected diff if ('string' == typeof actual && 'string' == typeof expected) { fmt = color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n'); if (exports.inlineDiffs) { msg = inlineDiff(err, escape); } else { msg = unifiedDiff(err, escape); } } // indent stack trace without msg stack = stack.slice(index ? index + 1 : index) .replace(/^/gm, ' '); console.error(fmt, (i + 1), test.fullTitle(), msg, stack); }); }; /** * Initialize a new `Base` reporter. * * All other reporters generally * inherit from this reporter, providing * stats such as test duration, number * of tests passed / failed etc. * * @param {Runner} runner * @api public */ function Base(runner) { var self = this , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } , failures = this.failures = []; if (!runner) return; this.runner = runner; runner.stats = stats; runner.on('start', function(){ stats.start = new Date; }); runner.on('suite', function(suite){ stats.suites = stats.suites || 0; suite.root || stats.suites++; }); runner.on('test end', function(test){ stats.tests = stats.tests || 0; stats.tests++; }); runner.on('pass', function(test){ stats.passes = stats.passes || 0; var medium = test.slow() / 2; test.speed = test.duration > test.slow() ? 'slow' : test.duration > medium ? 'medium' : 'fast'; stats.passes++; }); runner.on('fail', function(test, err){ stats.failures = stats.failures || 0; stats.failures++; test.err = err; failures.push(test); }); runner.on('end', function(){ stats.end = new Date; stats.duration = new Date - stats.start; }); runner.on('pending', function(){ stats.pending++; }); } /** * Output common epilogue used by many of * the bundled reporters. * * @api public */ Base.prototype.epilogue = function(){ var stats = this.stats; var tests; var fmt; console.log(); // passes fmt = color('bright pass', ' ') + color('green', ' %d passing') + color('light', ' (%s)'); console.log(fmt, stats.passes || 0, ms(stats.duration)); // pending if (stats.pending) { fmt = color('pending', ' ') + color('pending', ' %d pending'); console.log(fmt, stats.pending); } // failures if (stats.failures) { fmt = color('fail', ' %d failing'); console.error(fmt, stats.failures); Base.list(this.failures); console.error(); } console.log(); }; /** * Pad the given `str` to `len`. * * @param {String} str * @param {String} len * @return {String} * @api private */ function pad(str, len) { str = String(str); return Array(len - str.length + 1).join(' ') + str; } /** * Returns an inline diff between 2 strings with coloured ANSI output * * @param {Error} Error with actual/expected * @return {String} Diff * @api private */ function inlineDiff(err, escape) { var msg = errorDiff(err, 'WordsWithSpace', escape); // linenos var lines = msg.split('\n'); if (lines.length > 4) { var width = String(lines.length).length; msg = lines.map(function(str, i){ return pad(++i, width) + ' |' + ' ' + str; }).join('\n'); } // legend msg = '\n' + color('diff removed', 'actual') + ' ' + color('diff added', 'expected') + '\n\n' + msg + '\n'; // indent msg = msg.replace(/^/gm, ' '); return msg; } /** * Returns a unified diff between 2 strings * * @param {Error} Error with actual/expected * @return {String} Diff * @api private */ function unifiedDiff(err, escape) { var indent = ' '; function cleanUp(line) { if (escape) { line = escapeInvisibles(line); } if (line[0] === '+') return indent + colorLines('diff added', line); if (line[0] === '-') return indent + colorLines('diff removed', line); if (line.match(/\@\@/)) return null; if (line.match(/\\ No newline/)) return null; else return indent + line; } function notBlank(line) { return line != null; } msg = diff.createPatch('string', err.actual, err.expected); var lines = msg.split('\n').splice(4); return '\n ' + colorLines('diff added', '+ expected') + ' ' + colorLines('diff removed', '- actual') + '\n\n' + lines.map(cleanUp).filter(notBlank).join('\n'); } /** * Return a character diff for `err`. * * @param {Error} err * @return {String} * @api private */ function errorDiff(err, type, escape) { var actual = escape ? escapeInvisibles(err.actual) : err.actual; var expected = escape ? escapeInvisibles(err.expected) : err.expected; return diff['diff' + type](actual, expected).map(function(str){ if (str.added) return colorLines('diff added', str.value); if (str.removed) return colorLines('diff removed', str.value); return str.value; }).join(''); } /** * Returns a string with all invisible characters in plain text * * @param {String} line * @return {String} * @api private */ function escapeInvisibles(line) { return line.replace(/\t/g, '') .replace(/\r/g, '') .replace(/\n/g, '\n'); } /** * Color lines for `str`, using the color `name`. * * @param {String} name * @param {String} str * @return {String} * @api private */ function colorLines(name, str) { return str.split('\n').map(function(str){ return color(name, str); }).join('\n'); } /** * Stringify `obj`. * * @param {Mixed} obj * @return {String} * @api private */ function stringify(obj) { if (obj instanceof RegExp) return obj.toString(); return JSON.stringify(obj, null, 2); } /** * Check that a / b have the same type. * * @param {Object} a * @param {Object} b * @return {Boolean} * @api private */ function sameType(a, b) { a = Object.prototype.toString.call(a); b = Object.prototype.toString.call(b); return a == b; } }); // module: reporters/base.js require.register("reporters/doc.js", function(module, exports, require){ /** * Module dependencies. */ var Base = require('./base') , utils = require('../utils'); /** * Expose `Doc`. */ exports = module.exports = Doc; /** * Initialize a new `Doc` reporter. * * @param {Runner} runner * @api public */ function Doc(runner) { Base.call(this, runner); var self = this , stats = this.stats , total = runner.total , indents = 2; function indent() { return Array(indents).join(' '); } runner.on('suite', function(suite){ if (suite.root) return; ++indents; console.log('%s
', indent()); ++indents; console.log('%s

%s

', indent(), utils.escape(suite.title)); console.log('%s
', indent()); }); runner.on('suite end', function(suite){ if (suite.root) return; console.log('%s
', indent()); --indents; console.log('%s
', indent()); --indents; }); runner.on('pass', function(test){ console.log('%s
%s
', indent(), utils.escape(test.title)); var code = utils.escape(utils.clean(test.fn.toString())); console.log('%s
%s
', indent(), code); }); } }); // module: reporters/doc.js require.register("reporters/dot.js", function(module, exports, require){ /** * Module dependencies. */ var Base = require('./base') , color = Base.color; /** * Expose `Dot`. */ exports = module.exports = Dot; /** * Initialize a new `Dot` matrix test reporter. * * @param {Runner} runner * @api public */ function Dot(runner) { Base.call(this, runner); var self = this , stats = this.stats , width = Base.window.width * .75 | 0 , n = 0; runner.on('start', function(){ process.stdout.write('\n '); }); runner.on('pending', function(test){ process.stdout.write(color('pending', Base.symbols.dot)); }); runner.on('pass', function(test){ if (++n % width == 0) process.stdout.write('\n '); if ('slow' == test.speed) { process.stdout.write(color('bright yellow', Base.symbols.dot)); } else { process.stdout.write(color(test.speed, Base.symbols.dot)); } }); runner.on('fail', function(test, err){ if (++n % width == 0) process.stdout.write('\n '); process.stdout.write(color('fail', Base.symbols.dot)); }); runner.on('end', function(){ console.log(); self.epilogue(); }); } /** * Inherit from `Base.prototype`. */ function F(){}; F.prototype = Base.prototype; Dot.prototype = new F; Dot.prototype.constructor = Dot; }); // module: reporters/dot.js require.register("reporters/html-cov.js", function(module, exports, require){ /** * Module dependencies. */ var JSONCov = require('./json-cov') , fs = require('browser/fs'); /** * Expose `HTMLCov`. */ exports = module.exports = HTMLCov; /** * Initialize a new `JsCoverage` reporter. * * @param {Runner} runner * @api public */ function HTMLCov(runner) { var jade = require('jade') , file = __dirname + '/templates/coverage.jade' , str = fs.readFileSync(file, 'utf8') , fn = jade.compile(str, { filename: file }) , self = this; JSONCov.call(this, runner, false); runner.on('end', function(){ process.stdout.write(fn({ cov: self.cov , coverageClass: coverageClass })); }); } /** * Return coverage class for `n`. * * @return {String} * @api private */ function coverageClass(n) { if (n >= 75) return 'high'; if (n >= 50) return 'medium'; if (n >= 25) return 'low'; return 'terrible'; } }); // module: reporters/html-cov.js require.register("reporters/html.js", function(module, exports, require){ /** * Module dependencies. */ var Base = require('./base') , utils = require('../utils') , Progress = require('../browser/progress') , escape = utils.escape; /** * Save timer references to avoid Sinon interfering (see GH-237). */ var Date = global.Date , setTimeout = global.setTimeout , setInterval = global.setInterval , clearTimeout = global.clearTimeout , clearInterval = global.clearInterval; /** * Expose `Doc`. */ exports = module.exports = HTML; /** * Stats template. */ var statsTemplate = ''; /** * Initialize a new `Doc` reporter. * * @param {Runner} runner * @api public */ function HTML(runner, root) { Base.call(this, runner); var self = this , stats = this.stats , total = runner.total , stat = fragment(statsTemplate) , items = stat.getElementsByTagName('li') , passes = items[1].getElementsByTagName('em')[0] , passesLink = items[1].getElementsByTagName('a')[0] , failures = items[2].getElementsByTagName('em')[0] , failuresLink = items[2].getElementsByTagName('a')[0] , duration = items[3].getElementsByTagName('em')[0] , canvas = stat.getElementsByTagName('canvas')[0] , report = fragment('
    ') , stack = [report] , progress , ctx root = root || document.getElementById('mocha'); if (canvas.getContext) { var ratio = window.devicePixelRatio || 1; canvas.style.width = canvas.width; canvas.style.height = canvas.height; canvas.width *= ratio; canvas.height *= ratio; ctx = canvas.getContext('2d'); ctx.scale(ratio, ratio); progress = new Progress; } if (!root) return error('#mocha div missing, add it to your document'); // pass toggle on(passesLink, 'click', function(){ unhide(); var name = /pass/.test(report.className) ? '' : ' pass'; report.className = report.className.replace(/fail|pass/g, '') + name; if (report.className.trim()) hideSuitesWithout('test pass'); }); // failure toggle on(failuresLink, 'click', function(){ unhide(); var name = /fail/.test(report.className) ? '' : ' fail'; report.className = report.className.replace(/fail|pass/g, '') + name; if (report.className.trim()) hideSuitesWithout('test fail'); }); root.appendChild(stat); root.appendChild(report); if (progress) progress.size(40); runner.on('suite', function(suite){ if (suite.root) return; // suite var url = '?grep=' + encodeURIComponent(suite.fullTitle()); var el = fragment('
  • %s

  • ', url, escape(suite.title)); // container stack[0].appendChild(el); stack.unshift(document.createElement('ul')); el.appendChild(stack[0]); }); runner.on('suite end', function(suite){ if (suite.root) return; stack.shift(); }); runner.on('fail', function(test, err){ if ('hook' == test.type) runner.emit('test end', test); }); runner.on('test end', function(test){ // TODO: add to stats var percent = stats.tests / this.total * 100 | 0; if (progress) progress.update(percent).draw(ctx); // update stats var ms = new Date - stats.start; text(passes, stats.passes); text(failures, stats.failures); text(duration, (ms / 1000).toFixed(2)); // test if ('passed' == test.state) { var el = fragment('
  • %e%ems

  • ', test.speed, test.title, test.duration, encodeURIComponent(test.fullTitle())); } else if (test.pending) { var el = fragment('
  • %e

  • ', test.title); } else { var el = fragment('
  • %e

  • ', test.title, encodeURIComponent(test.fullTitle())); var str = test.err.stack || test.err.toString(); // FF / Opera do not add the message if (!~str.indexOf(test.err.message)) { str = test.err.message + '\n' + str; } // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we // check for the result of the stringifying. if ('[object Error]' == str) str = test.err.message; // Safari doesn't give you a stack. Let's at least provide a source line. if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; } el.appendChild(fragment('
    %e
    ', str)); } // toggle code // TODO: defer if (!test.pending) { var h2 = el.getElementsByTagName('h2')[0]; on(h2, 'click', function(){ pre.style.display = 'none' == pre.style.display ? 'block' : 'none'; }); var pre = fragment('
    %e
    ', utils.clean(test.fn.toString())); el.appendChild(pre); pre.style.display = 'none'; } // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack. if (stack[0]) stack[0].appendChild(el); }); } /** * Display error `msg`. */ function error(msg) { document.body.appendChild(fragment('
    %s
    ', msg)); } /** * Return a DOM fragment from `html`. */ function fragment(html) { var args = arguments , div = document.createElement('div') , i = 1; div.innerHTML = html.replace(/%([se])/g, function(_, type){ switch (type) { case 's': return String(args[i++]); case 'e': return escape(args[i++]); } }); return div.firstChild; } /** * Check for suites that do not have elements * with `classname`, and hide them. */ function hideSuitesWithout(classname) { var suites = document.getElementsByClassName('suite'); for (var i = 0; i < suites.length; i++) { var els = suites[i].getElementsByClassName(classname); if (0 == els.length) suites[i].className += ' hidden'; } } /** * Unhide .hidden suites. */ function unhide() { var els = document.getElementsByClassName('suite hidden'); for (var i = 0; i < els.length; ++i) { els[i].className = els[i].className.replace('suite hidden', 'suite'); } } /** * Set `el` text to `str`. */ function text(el, str) { if (el.textContent) { el.textContent = str; } else { el.innerText = str; } } /** * Listen on `event` with callback `fn`. */ function on(el, event, fn) { if (el.addEventListener) { el.addEventListener(event, fn, false); } else { el.attachEvent('on' + event, fn); } } }); // module: reporters/html.js require.register("reporters/index.js", function(module, exports, require){ exports.Base = require('./base'); exports.Dot = require('./dot'); exports.Doc = require('./doc'); exports.TAP = require('./tap'); exports.JSON = require('./json'); exports.HTML = require('./html'); exports.List = require('./list'); exports.Min = require('./min'); exports.Spec = require('./spec'); exports.Nyan = require('./nyan'); exports.XUnit = require('./xunit'); exports.Markdown = require('./markdown'); exports.Progress = require('./progress'); exports.Landing = require('./landing'); exports.JSONCov = require('./json-cov'); exports.HTMLCov = require('./html-cov'); exports.JSONStream = require('./json-stream'); }); // module: reporters/index.js require.register("reporters/json-cov.js", function(module, exports, require){ /** * Module dependencies. */ var Base = require('./base'); /** * Expose `JSONCov`. */ exports = module.exports = JSONCov; /** * Initialize a new `JsCoverage` reporter. * * @param {Runner} runner * @param {Boolean} output * @api public */ function JSONCov(runner, output) { var self = this , output = 1 == arguments.length ? true : output; Base.call(this, runner); var tests = [] , failures = [] , passes = []; runner.on('test end', function(test){ tests.push(test); }); runner.on('pass', function(test){ passes.push(test); }); runner.on('fail', function(test){ failures.push(test); }); runner.on('end', function(){ var cov = global._$jscoverage || {}; var result = self.cov = map(cov); result.stats = self.stats; result.tests = tests.map(clean); result.failures = failures.map(clean); result.passes = passes.map(clean); if (!output) return; process.stdout.write(JSON.stringify(result, null, 2 )); }); } /** * Map jscoverage data to a JSON structure * suitable for reporting. * * @param {Object} cov * @return {Object} * @api private */ function map(cov) { var ret = { instrumentation: 'node-jscoverage' , sloc: 0 , hits: 0 , misses: 0 , coverage: 0 , files: [] }; for (var filename in cov) { var data = coverage(filename, cov[filename]); ret.files.push(data); ret.hits += data.hits; ret.misses += data.misses; ret.sloc += data.sloc; } ret.files.sort(function(a, b) { return a.filename.localeCompare(b.filename); }); if (ret.sloc > 0) { ret.coverage = (ret.hits / ret.sloc) * 100; } return ret; }; /** * Map jscoverage data for a single source file * to a JSON structure suitable for reporting. * * @param {String} filename name of the source file * @param {Object} data jscoverage coverage data * @return {Object} * @api private */ function coverage(filename, data) { var ret = { filename: filename, coverage: 0, hits: 0, misses: 0, sloc: 0, source: {} }; data.source.forEach(function(line, num){ num++; if (data[num] === 0) { ret.misses++; ret.sloc++; } else if (data[num] !== undefined) { ret.hits++; ret.sloc++; } ret.source[num] = { source: line , coverage: data[num] === undefined ? '' : data[num] }; }); ret.coverage = ret.hits / ret.sloc * 100; return ret; } /** * Return a plain-object representation of `test` * free of cyclic properties etc. * * @param {Object} test * @return {Object} * @api private */ function clean(test) { return { title: test.title , fullTitle: test.fullTitle() , duration: test.duration } } }); // module: reporters/json-cov.js require.register("reporters/json-stream.js", function(module, exports, require){ /** * Module dependencies. */ var Base = require('./base') , color = Base.color; /** * Expose `List`. */ exports = module.exports = List; /** * Initialize a new `List` test reporter. * * @param {Runner} runner * @api public */ function List(runner) { Base.call(this, runner); var self = this , stats = this.stats , total = runner.total; runner.on('start', function(){ console.log(JSON.stringify(['start', { total: total }])); }); runner.on('pass', function(test){ console.log(JSON.stringify(['pass', clean(test)])); }); runner.on('fail', function(test, err){ console.log(JSON.stringify(['fail', clean(test)])); }); runner.on('end', function(){ process.stdout.write(JSON.stringify(['end', self.stats])); }); } /** * Return a plain-object representation of `test` * free of cyclic properties etc. * * @param {Object} test * @return {Object} * @api private */ function clean(test) { return { title: test.title , fullTitle: test.fullTitle() , duration: test.duration } } }); // module: reporters/json-stream.js require.register("reporters/json.js", function(module, exports, require){ /** * Module dependencies. */ var Base = require('./base') , cursor = Base.cursor , color = Base.color; /** * Expose `JSON`. */ exports = module.exports = JSONReporter; /** * Initialize a new `JSON` reporter. * * @param {Runner} runner * @api public */ function JSONReporter(runner) { var self = this; Base.call(this, runner); var tests = [] , failures = [] , passes = []; runner.on('test end', function(test){ tests.push(test); }); runner.on('pass', function(test){ passes.push(test); }); runner.on('fail', function(test){ failures.push(test); }); runner.on('end', function(){ var obj = { stats: self.stats , tests: tests.map(clean) , failures: failures.map(clean) , passes: passes.map(clean) }; process.stdout.write(JSON.stringify(obj, null, 2)); }); } /** * Return a plain-object representation of `test` * free of cyclic properties etc. * * @param {Object} test * @return {Object} * @api private */ function clean(test) { return { title: test.title , fullTitle: test.fullTitle() , duration: test.duration } } }); // module: reporters/json.js require.register("reporters/landing.js", function(module, exports, require){ /** * Module dependencies. */ var Base = require('./base') , cursor = Base.cursor , color = Base.color; /** * Expose `Landing`. */ exports = module.exports = Landing; /** * Airplane color. */ Base.colors.plane = 0; /** * Airplane crash color. */ Base.colors['plane crash'] = 31; /** * Runway color. */ Base.colors.runway = 90; /** * Initialize a new `Landing` reporter. * * @param {Runner} runner * @api public */ function Landing(runner) { Base.call(this, runner); var self = this , stats = this.stats , width = Base.window.width * .75 | 0 , total = runner.total , stream = process.stdout , plane = color('plane', '✈') , crashed = -1 , n = 0; function runway() { var buf = Array(width).join('-'); return ' ' + color('runway', buf); } runner.on('start', function(){ stream.write('\n '); cursor.hide(); }); runner.on('test end', function(test){ // check if the plane crashed var col = -1 == crashed ? width * ++n / total | 0 : crashed; // show the crash if ('failed' == test.state) { plane = color('plane crash', '✈'); crashed = col; } // render landing strip stream.write('\u001b[4F\n\n'); stream.write(runway()); stream.write('\n '); stream.write(color('runway', Array(col).join('⋅'))); stream.write(plane) stream.write(color('runway', Array(width - col).join('⋅') + '\n')); stream.write(runway()); stream.write('\u001b[0m'); }); runner.on('end', function(){ cursor.show(); console.log(); self.epilogue(); }); } /** * Inherit from `Base.prototype`. */ function F(){}; F.prototype = Base.prototype; Landing.prototype = new F; Landing.prototype.constructor = Landing; }); // module: reporters/landing.js require.register("reporters/list.js", function(module, exports, require){ /** * Module dependencies. */ var Base = require('./base') , cursor = Base.cursor , color = Base.color; /** * Expose `List`. */ exports = module.exports = List; /** * Initialize a new `List` test reporter. * * @param {Runner} runner * @api public */ function List(runner) { Base.call(this, runner); var self = this , stats = this.stats , n = 0; runner.on('start', function(){ console.log(); }); runner.on('test', function(test){ process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); }); runner.on('pending', function(test){ var fmt = color('checkmark', ' -') + color('pending', ' %s'); console.log(fmt, test.fullTitle()); }); runner.on('pass', function(test){ var fmt = color('checkmark', ' '+Base.symbols.dot) + color('pass', ' %s: ') + color(test.speed, '%dms'); cursor.CR(); console.log(fmt, test.fullTitle(), test.duration); }); runner.on('fail', function(test, err){ cursor.CR(); console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); }); runner.on('end', self.epilogue.bind(self)); } /** * Inherit from `Base.prototype`. */ function F(){}; F.prototype = Base.prototype; List.prototype = new F; List.prototype.constructor = List; }); // module: reporters/list.js require.register("reporters/markdown.js", function(module, exports, require){ /** * Module dependencies. */ var Base = require('./base') , utils = require('../utils'); /** * Expose `Markdown`. */ exports = module.exports = Markdown; /** * Initialize a new `Markdown` reporter. * * @param {Runner} runner * @api public */ function Markdown(runner) { Base.call(this, runner); var self = this , stats = this.stats , level = 0 , buf = ''; function title(str) { return Array(level).join('#') + ' ' + str; } function indent() { return Array(level).join(' '); } function mapTOC(suite, obj) { var ret = obj; obj = obj[suite.title] = obj[suite.title] || { suite: suite }; suite.suites.forEach(function(suite){ mapTOC(suite, obj); }); return ret; } function stringifyTOC(obj, level) { ++level; var buf = ''; var link; for (var key in obj) { if ('suite' == key) continue; if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; if (key) buf += Array(level).join(' ') + link; buf += stringifyTOC(obj[key], level); } --level; return buf; } function generateTOC(suite) { var obj = mapTOC(suite, {}); return stringifyTOC(obj, 0); } generateTOC(runner.suite); runner.on('suite', function(suite){ ++level; var slug = utils.slug(suite.fullTitle()); buf += '' + '\n'; buf += title(suite.title) + '\n'; }); runner.on('suite end', function(suite){ --level; }); runner.on('pass', function(test){ var code = utils.clean(test.fn.toString()); buf += test.title + '.\n'; buf += '\n```js\n'; buf += code + '\n'; buf += '```\n\n'; }); runner.on('end', function(){ process.stdout.write('# TOC\n'); process.stdout.write(generateTOC(runner.suite)); process.stdout.write(buf); }); } }); // module: reporters/markdown.js require.register("reporters/min.js", function(module, exports, require){ /** * Module dependencies. */ var Base = require('./base'); /** * Expose `Min`. */ exports = module.exports = Min; /** * Initialize a new `Min` minimal test reporter (best used with --watch). * * @param {Runner} runner * @api public */ function Min(runner) { Base.call(this, runner); runner.on('start', function(){ // clear screen process.stdout.write('\u001b[2J'); // set cursor position process.stdout.write('\u001b[1;3H'); }); runner.on('end', this.epilogue.bind(this)); } /** * Inherit from `Base.prototype`. */ function F(){}; F.prototype = Base.prototype; Min.prototype = new F; Min.prototype.constructor = Min; }); // module: reporters/min.js require.register("reporters/nyan.js", function(module, exports, require){ /** * Module dependencies. */ var Base = require('./base') , color = Base.color; /** * Expose `Dot`. */ exports = module.exports = NyanCat; /** * Initialize a new `Dot` matrix test reporter. * * @param {Runner} runner * @api public */ function NyanCat(runner) { Base.call(this, runner); var self = this , stats = this.stats , width = Base.window.width * .75 | 0 , rainbowColors = this.rainbowColors = self.generateColors() , colorIndex = this.colorIndex = 0 , numerOfLines = this.numberOfLines = 4 , trajectories = this.trajectories = [[], [], [], []] , nyanCatWidth = this.nyanCatWidth = 11 , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth) , scoreboardWidth = this.scoreboardWidth = 5 , tick = this.tick = 0 , n = 0; runner.on('start', function(){ Base.cursor.hide(); self.draw(); }); runner.on('pending', function(test){ self.draw(); }); runner.on('pass', function(test){ self.draw(); }); runner.on('fail', function(test, err){ self.draw(); }); runner.on('end', function(){ Base.cursor.show(); for (var i = 0; i < self.numberOfLines; i++) write('\n'); self.epilogue(); }); } /** * Draw the nyan cat * * @api private */ NyanCat.prototype.draw = function(){ this.appendRainbow(); this.drawScoreboard(); this.drawRainbow(); this.drawNyanCat(); this.tick = !this.tick; }; /** * Draw the "scoreboard" showing the number * of passes, failures and pending tests. * * @api private */ NyanCat.prototype.drawScoreboard = function(){ var stats = this.stats; var colors = Base.colors; function draw(color, n) { write(' '); write('\u001b[' + color + 'm' + n + '\u001b[0m'); write('\n'); } draw(colors.green, stats.passes); draw(colors.fail, stats.failures); draw(colors.pending, stats.pending); write('\n'); this.cursorUp(this.numberOfLines); }; /** * Append the rainbow. * * @api private */ NyanCat.prototype.appendRainbow = function(){ var segment = this.tick ? '_' : '-'; var rainbowified = this.rainbowify(segment); for (var index = 0; index < this.numberOfLines; index++) { var trajectory = this.trajectories[index]; if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift(); trajectory.push(rainbowified); } }; /** * Draw the rainbow. * * @api private */ NyanCat.prototype.drawRainbow = function(){ var self = this; this.trajectories.forEach(function(line, index) { write('\u001b[' + self.scoreboardWidth + 'C'); write(line.join('')); write('\n'); }); this.cursorUp(this.numberOfLines); }; /** * Draw the nyan cat * * @api private */ NyanCat.prototype.drawNyanCat = function() { var self = this; var startWidth = this.scoreboardWidth + this.trajectories[0].length; var color = '\u001b[' + startWidth + 'C'; var padding = ''; write(color); write('_,------,'); write('\n'); write(color); padding = self.tick ? ' ' : ' '; write('_|' + padding + '/\\_/\\ '); write('\n'); write(color); padding = self.tick ? '_' : '__'; var tail = self.tick ? '~' : '^'; var face; write(tail + '|' + padding + this.face() + ' '); write('\n'); write(color); padding = self.tick ? ' ' : ' '; write(padding + '"" "" '); write('\n'); this.cursorUp(this.numberOfLines); }; /** * Draw nyan cat face. * * @return {String} * @api private */ NyanCat.prototype.face = function() { var stats = this.stats; if (stats.failures) { return '( x .x)'; } else if (stats.pending) { return '( o .o)'; } else if(stats.passes) { return '( ^ .^)'; } else { return '( - .-)'; } } /** * Move cursor up `n`. * * @param {Number} n * @api private */ NyanCat.prototype.cursorUp = function(n) { write('\u001b[' + n + 'A'); }; /** * Move cursor down `n`. * * @param {Number} n * @api private */ NyanCat.prototype.cursorDown = function(n) { write('\u001b[' + n + 'B'); }; /** * Generate rainbow colors. * * @return {Array} * @api private */ NyanCat.prototype.generateColors = function(){ var colors = []; for (var i = 0; i < (6 * 7); i++) { var pi3 = Math.floor(Math.PI / 3); var n = (i * (1.0 / 6)); var r = Math.floor(3 * Math.sin(n) + 3); var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); colors.push(36 * r + 6 * g + b + 16); } return colors; }; /** * Apply rainbow to the given `str`. * * @param {String} str * @return {String} * @api private */ NyanCat.prototype.rainbowify = function(str){ var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; this.colorIndex += 1; return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; }; /** * Stdout helper. */ function write(string) { process.stdout.write(string); } /** * Inherit from `Base.prototype`. */ function F(){}; F.prototype = Base.prototype; NyanCat.prototype = new F; NyanCat.prototype.constructor = NyanCat; }); // module: reporters/nyan.js require.register("reporters/progress.js", function(module, exports, require){ /** * Module dependencies. */ var Base = require('./base') , cursor = Base.cursor , color = Base.color; /** * Expose `Progress`. */ exports = module.exports = Progress; /** * General progress bar color. */ Base.colors.progress = 90; /** * Initialize a new `Progress` bar test reporter. * * @param {Runner} runner * @param {Object} options * @api public */ function Progress(runner, options) { Base.call(this, runner); var self = this , options = options || {} , stats = this.stats , width = Base.window.width * .50 | 0 , total = runner.total , complete = 0 , max = Math.max; // default chars options.open = options.open || '['; options.complete = options.complete || '▬'; options.incomplete = options.incomplete || Base.symbols.dot; options.close = options.close || ']'; options.verbose = false; // tests started runner.on('start', function(){ console.log(); cursor.hide(); }); // tests complete runner.on('test end', function(){ complete++; var incomplete = total - complete , percent = complete / total , n = width * percent | 0 , i = width - n; cursor.CR(); process.stdout.write('\u001b[J'); process.stdout.write(color('progress', ' ' + options.open)); process.stdout.write(Array(n).join(options.complete)); process.stdout.write(Array(i).join(options.incomplete)); process.stdout.write(color('progress', options.close)); if (options.verbose) { process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); } }); // tests are complete, output some stats // and the failures if any runner.on('end', function(){ cursor.show(); console.log(); self.epilogue(); }); } /** * Inherit from `Base.prototype`. */ function F(){}; F.prototype = Base.prototype; Progress.prototype = new F; Progress.prototype.constructor = Progress; }); // module: reporters/progress.js require.register("reporters/spec.js", function(module, exports, require){ /** * Module dependencies. */ var Base = require('./base') , cursor = Base.cursor , color = Base.color; /** * Expose `Spec`. */ exports = module.exports = Spec; /** * Initialize a new `Spec` test reporter. * * @param {Runner} runner * @api public */ function Spec(runner) { Base.call(this, runner); var self = this , stats = this.stats , indents = 0 , n = 0; function indent() { return Array(indents).join(' ') } runner.on('start', function(){ console.log(); }); runner.on('suite', function(suite){ ++indents; console.log(color('suite', '%s%s'), indent(), suite.title); }); runner.on('suite end', function(suite){ --indents; if (1 == indents) console.log(); }); runner.on('test', function(test){ process.stdout.write(indent() + color('pass', ' ◦ ' + test.title + ': ')); }); runner.on('pending', function(test){ var fmt = indent() + color('pending', ' - %s'); console.log(fmt, test.title); }); runner.on('pass', function(test){ if ('fast' == test.speed) { var fmt = indent() + color('checkmark', ' ' + Base.symbols.ok) + color('pass', ' %s '); cursor.CR(); console.log(fmt, test.title); } else { var fmt = indent() + color('checkmark', ' ' + Base.symbols.ok) + color('pass', ' %s ') + color(test.speed, '(%dms)'); cursor.CR(); console.log(fmt, test.title, test.duration); } }); runner.on('fail', function(test, err){ cursor.CR(); console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); }); runner.on('end', self.epilogue.bind(self)); } /** * Inherit from `Base.prototype`. */ function F(){}; F.prototype = Base.prototype; Spec.prototype = new F; Spec.prototype.constructor = Spec; }); // module: reporters/spec.js require.register("reporters/tap.js", function(module, exports, require){ /** * Module dependencies. */ var Base = require('./base') , cursor = Base.cursor , color = Base.color; /** * Expose `TAP`. */ exports = module.exports = TAP; /** * Initialize a new `TAP` reporter. * * @param {Runner} runner * @api public */ function TAP(runner) { Base.call(this, runner); var self = this , stats = this.stats , n = 1 , passes = 0 , failures = 0; runner.on('start', function(){ var total = runner.grepTotal(runner.suite); console.log('%d..%d', 1, total); }); runner.on('test end', function(){ ++n; }); runner.on('pending', function(test){ console.log('ok %d %s # SKIP -', n, title(test)); }); runner.on('pass', function(test){ passes++; console.log('ok %d %s', n, title(test)); }); runner.on('fail', function(test, err){ failures++; console.log('not ok %d %s', n, title(test)); if (err.stack) console.log(err.stack.replace(/^/gm, ' ')); }); runner.on('end', function(){ console.log('# tests ' + (passes + failures)); console.log('# pass ' + passes); console.log('# fail ' + failures); }); } /** * Return a TAP-safe title of `test` * * @param {Object} test * @return {String} * @api private */ function title(test) { return test.fullTitle().replace(/#/g, ''); } }); // module: reporters/tap.js require.register("reporters/xunit.js", function(module, exports, require){ /** * Module dependencies. */ var Base = require('./base') , utils = require('../utils') , escape = utils.escape; /** * Save timer references to avoid Sinon interfering (see GH-237). */ var Date = global.Date , setTimeout = global.setTimeout , setInterval = global.setInterval , clearTimeout = global.clearTimeout , clearInterval = global.clearInterval; /** * Expose `XUnit`. */ exports = module.exports = XUnit; /** * Initialize a new `XUnit` reporter. * * @param {Runner} runner * @api public */ function XUnit(runner) { Base.call(this, runner); var stats = this.stats , tests = [] , self = this; runner.on('pass', function(test){ tests.push(test); }); runner.on('fail', function(test){ tests.push(test); }); runner.on('end', function(){ console.log(tag('testsuite', { name: 'Mocha Tests' , tests: stats.tests , failures: stats.failures , errors: stats.failures , skipped: stats.tests - stats.failures - stats.passes , timestamp: (new Date).toUTCString() , time: (stats.duration / 1000) || 0 }, false)); tests.forEach(test); console.log(''); }); } /** * Inherit from `Base.prototype`. */ function F(){}; F.prototype = Base.prototype; XUnit.prototype = new F; XUnit.prototype.constructor = XUnit; /** * Output tag for the given `test.` */ function test(test) { var attrs = { classname: test.parent.fullTitle() , name: test.title , time: test.duration / 1000 }; if ('failed' == test.state) { var err = test.err; attrs.message = escape(err.message); console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); } else if (test.pending) { console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); } else { console.log(tag('testcase', attrs, true) ); } } /** * HTML tag helper. */ function tag(name, attrs, close, content) { var end = close ? '/>' : '>' , pairs = [] , tag; for (var key in attrs) { pairs.push(key + '="' + escape(attrs[key]) + '"'); } tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; if (content) tag += content + ''; } }); // module: reporters/xunit.js require.register("runnable.js", function(module, exports, require){ /** * Module dependencies. */ var EventEmitter = require('browser/events').EventEmitter , debug = require('browser/debug')('mocha:runnable') , milliseconds = require('./ms'); /** * Save timer references to avoid Sinon interfering (see GH-237). */ var Date = global.Date , setTimeout = global.setTimeout , setInterval = global.setInterval , clearTimeout = global.clearTimeout , clearInterval = global.clearInterval; /** * Object#toString(). */ var toString = Object.prototype.toString; /** * Expose `Runnable`. */ module.exports = Runnable; /** * Initialize a new `Runnable` with the given `title` and callback `fn`. * * @param {String} title * @param {Function} fn * @api private */ function Runnable(title, fn) { this.title = title; this.fn = fn; this.async = fn && fn.length; this.sync = ! this.async; this._timeout = 2000; this._slow = 75; this.timedOut = false; } /** * Inherit from `EventEmitter.prototype`. */ function F(){}; F.prototype = EventEmitter.prototype; Runnable.prototype = new F; Runnable.prototype.constructor = Runnable; /** * Set & get timeout `ms`. * * @param {Number|String} ms * @return {Runnable|Number} ms or self * @api private */ Runnable.prototype.timeout = function(ms){ if (0 == arguments.length) return this._timeout; if ('string' == typeof ms) ms = milliseconds(ms); debug('timeout %d', ms); this._timeout = ms; if (this.timer) this.resetTimeout(); return this; }; /** * Set & get slow `ms`. * * @param {Number|String} ms * @return {Runnable|Number} ms or self * @api private */ Runnable.prototype.slow = function(ms){ if (0 === arguments.length) return this._slow; if ('string' == typeof ms) ms = milliseconds(ms); debug('timeout %d', ms); this._slow = ms; return this; }; /** * Return the full title generated by recursively * concatenating the parent's full title. * * @return {String} * @api public */ Runnable.prototype.fullTitle = function(){ return this.parent.fullTitle() + ' ' + this.title; }; /** * Clear the timeout. * * @api private */ Runnable.prototype.clearTimeout = function(){ clearTimeout(this.timer); }; /** * Inspect the runnable void of private properties. * * @return {String} * @api private */ Runnable.prototype.inspect = function(){ return JSON.stringify(this, function(key, val){ if ('_' == key[0]) return; if ('parent' == key) return '#'; if ('ctx' == key) return '#'; return val; }, 2); }; /** * Reset the timeout. * * @api private */ Runnable.prototype.resetTimeout = function(){ var self = this; var ms = this.timeout() || 1e9; this.clearTimeout(); this.timer = setTimeout(function(){ self.callback(new Error('timeout of ' + ms + 'ms exceeded')); self.timedOut = true; }, ms); }; /** * Run the test and invoke `fn(err)`. * * @param {Function} fn * @api private */ Runnable.prototype.run = function(fn){ var self = this , ms = this.timeout() , start = new Date , ctx = this.ctx , finished , emitted; if (ctx) ctx.runnable(this); // timeout if (this.async) { if (ms) { this.timer = setTimeout(function(){ done(new Error('timeout of ' + ms + 'ms exceeded')); self.timedOut = true; }, ms); } } // called multiple times function multiple(err) { if (emitted) return; emitted = true; self.emit('error', err || new Error('done() called multiple times')); } // finished function done(err) { if (self.timedOut) return; if (finished) return multiple(err); self.clearTimeout(); self.duration = new Date - start; finished = true; fn(err); } // for .resetTimeout() this.callback = done; // async if (this.async) { try { this.fn.call(ctx, function(err){ if (err instanceof Error || toString.call(err) === "[object Error]") return done(err); if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); done(); }); } catch (err) { done(err); } return; } if (this.asyncOnly) { return done(new Error('--async-only option in use without declaring `done()`')); } // sync try { if (!this.pending) this.fn.call(ctx); this.duration = new Date - start; fn(); } catch (err) { fn(err); } }; }); // module: runnable.js require.register("runner.js", function(module, exports, require){ /** * Module dependencies. */ var EventEmitter = require('browser/events').EventEmitter , debug = require('browser/debug')('mocha:runner') , Test = require('./test') , utils = require('./utils') , filter = utils.filter , keys = utils.keys; /** * Non-enumerable globals. */ var globals = [ 'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'XMLHttpRequest', 'Date' ]; /** * Expose `Runner`. */ module.exports = Runner; /** * Initialize a `Runner` for the given `suite`. * * Events: * * - `start` execution started * - `end` execution complete * - `suite` (suite) test suite execution started * - `suite end` (suite) all tests (and sub-suites) have finished * - `test` (test) test execution started * - `test end` (test) test completed * - `hook` (hook) hook execution started * - `hook end` (hook) hook complete * - `pass` (test) test passed * - `fail` (test, err) test failed * - `pending` (test) test pending * * @api public */ function Runner(suite) { var self = this; this._globals = []; this.suite = suite; this.total = suite.total(); this.failures = 0; this.on('test end', function(test){ self.checkGlobals(test); }); this.on('hook end', function(hook){ self.checkGlobals(hook); }); this.grep(/.*/); this.globals(this.globalProps().concat(['errno'])); } /** * Wrapper for setImmediate, process.nextTick, or browser polyfill. * * @param {Function} fn * @api private */ Runner.immediately = global.setImmediate || process.nextTick; /** * Inherit from `EventEmitter.prototype`. */ function F(){}; F.prototype = EventEmitter.prototype; Runner.prototype = new F; Runner.prototype.constructor = Runner; /** * Run tests with full titles matching `re`. Updates runner.total * with number of tests matched. * * @param {RegExp} re * @param {Boolean} invert * @return {Runner} for chaining * @api public */ Runner.prototype.grep = function(re, invert){ debug('grep %s', re); this._grep = re; this._invert = invert; this.total = this.grepTotal(this.suite); return this; }; /** * Returns the number of tests matching the grep search for the * given suite. * * @param {Suite} suite * @return {Number} * @api public */ Runner.prototype.grepTotal = function(suite) { var self = this; var total = 0; suite.eachTest(function(test){ var match = self._grep.test(test.fullTitle()); if (self._invert) match = !match; if (match) total++; }); return total; }; /** * Return a list of global properties. * * @return {Array} * @api private */ Runner.prototype.globalProps = function() { var props = utils.keys(global); // non-enumerables for (var i = 0; i < globals.length; ++i) { if (~utils.indexOf(props, globals[i])) continue; props.push(globals[i]); } return props; }; /** * Allow the given `arr` of globals. * * @param {Array} arr * @return {Runner} for chaining * @api public */ Runner.prototype.globals = function(arr){ if (0 == arguments.length) return this._globals; debug('globals %j', arr); utils.forEach(arr, function(arr){ this._globals.push(arr); }, this); return this; }; /** * Check for global variable leaks. * * @api private */ Runner.prototype.checkGlobals = function(test){ if (this.ignoreLeaks) return; var ok = this._globals; var globals = this.globalProps(); var isNode = process.kill; var leaks; // check length - 2 ('errno' and 'location' globals) if (isNode && 1 == ok.length - globals.length) return; else if (2 == ok.length - globals.length) return; if(this.prevGlobalsLength == globals.length) return; this.prevGlobalsLength = globals.length; leaks = filterLeaks(ok, globals); this._globals = this._globals.concat(leaks); if (leaks.length > 1) { this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); } else if (leaks.length) { this.fail(test, new Error('global leak detected: ' + leaks[0])); } }; /** * Fail the given `test`. * * @param {Test} test * @param {Error} err * @api private */ Runner.prototype.fail = function(test, err){ ++this.failures; test.state = 'failed'; if ('string' == typeof err) { err = new Error('the string "' + err + '" was thrown, throw an Error :)'); } this.emit('fail', test, err); }; /** * Fail the given `hook` with `err`. * * Hook failures (currently) hard-end due * to that fact that a failing hook will * surely cause subsequent tests to fail, * causing jumbled reporting. * * @param {Hook} hook * @param {Error} err * @api private */ Runner.prototype.failHook = function(hook, err){ this.fail(hook, err); this.emit('end'); }; /** * Run hook `name` callbacks and then invoke `fn()`. * * @param {String} name * @param {Function} function * @api private */ Runner.prototype.hook = function(name, fn){ var suite = this.suite , hooks = suite['_' + name] , self = this , timer; function next(i) { var hook = hooks[i]; if (!hook) return fn(); if (self.failures && suite.bail()) return fn(); self.currentRunnable = hook; hook.ctx.currentTest = self.test; self.emit('hook', hook); hook.on('error', function(err){ self.failHook(hook, err); }); hook.run(function(err){ hook.removeAllListeners('error'); var testError = hook.error(); if (testError) self.fail(self.test, testError); if (err) return self.failHook(hook, err); self.emit('hook end', hook); delete hook.ctx.currentTest; next(++i); }); } Runner.immediately(function(){ next(0); }); }; /** * Run hook `name` for the given array of `suites` * in order, and callback `fn(err)`. * * @param {String} name * @param {Array} suites * @param {Function} fn * @api private */ Runner.prototype.hooks = function(name, suites, fn){ var self = this , orig = this.suite; function next(suite) { self.suite = suite; if (!suite) { self.suite = orig; return fn(); } self.hook(name, function(err){ if (err) { self.suite = orig; return fn(err); } next(suites.pop()); }); } next(suites.pop()); }; /** * Run hooks from the top level down. * * @param {String} name * @param {Function} fn * @api private */ Runner.prototype.hookUp = function(name, fn){ var suites = [this.suite].concat(this.parents()).reverse(); this.hooks(name, suites, fn); }; /** * Run hooks from the bottom up. * * @param {String} name * @param {Function} fn * @api private */ Runner.prototype.hookDown = function(name, fn){ var suites = [this.suite].concat(this.parents()); this.hooks(name, suites, fn); }; /** * Return an array of parent Suites from * closest to furthest. * * @return {Array} * @api private */ Runner.prototype.parents = function(){ var suite = this.suite , suites = []; while (suite = suite.parent) suites.push(suite); return suites; }; /** * Run the current test and callback `fn(err)`. * * @param {Function} fn * @api private */ Runner.prototype.runTest = function(fn){ var test = this.test , self = this; if (this.asyncOnly) test.asyncOnly = true; try { test.on('error', function(err){ self.fail(test, err); }); test.run(fn); } catch (err) { fn(err); } }; /** * Run tests in the given `suite` and invoke * the callback `fn()` when complete. * * @param {Suite} suite * @param {Function} fn * @api private */ Runner.prototype.runTests = function(suite, fn){ var self = this , tests = suite.tests.slice() , test; function next(err) { // if we bail after first err if (self.failures && suite._bail) return fn(); // next test test = tests.shift(); // all done if (!test) return fn(); // grep var match = self._grep.test(test.fullTitle()); if (self._invert) match = !match; if (!match) return next(); // pending if (test.pending) { self.emit('pending', test); self.emit('test end', test); return next(); } // execute test and hook(s) self.emit('test', self.test = test); self.hookDown('beforeEach', function(){ self.currentRunnable = self.test; self.runTest(function(err){ test = self.test; if (err) { self.fail(test, err); self.emit('test end', test); return self.hookUp('afterEach', next); } test.state = 'passed'; self.emit('pass', test); self.emit('test end', test); self.hookUp('afterEach', next); }); }); } this.next = next; next(); }; /** * Run the given `suite` and invoke the * callback `fn()` when complete. * * @param {Suite} suite * @param {Function} fn * @api private */ Runner.prototype.runSuite = function(suite, fn){ var total = this.grepTotal(suite) , self = this , i = 0; debug('run suite %s', suite.fullTitle()); if (!total) return fn(); this.emit('suite', this.suite = suite); function next() { var curr = suite.suites[i++]; if (!curr) return done(); self.runSuite(curr, next); } function done() { self.suite = suite; self.hook('afterAll', function(){ self.emit('suite end', suite); fn(); }); } this.hook('beforeAll', function(){ self.runTests(suite, next); }); }; /** * Handle uncaught exceptions. * * @param {Error} err * @api private */ Runner.prototype.uncaught = function(err){ debug('uncaught exception %s', err.message); var runnable = this.currentRunnable; if (!runnable || 'failed' == runnable.state) return; runnable.clearTimeout(); err.uncaught = true; this.fail(runnable, err); // recover from test if ('test' == runnable.type) { this.emit('test end', runnable); this.hookUp('afterEach', this.next); return; } // bail on hooks this.emit('end'); }; /** * Run the root suite and invoke `fn(failures)` * on completion. * * @param {Function} fn * @return {Runner} for chaining * @api public */ Runner.prototype.run = function(fn){ var self = this , fn = fn || function(){}; function uncaught(err){ self.uncaught(err); } debug('start'); // callback this.on('end', function(){ debug('end'); process.removeListener('uncaughtException', uncaught); fn(self.failures); }); // run suites this.emit('start'); this.runSuite(this.suite, function(){ debug('finished running'); self.emit('end'); }); // uncaught exception process.on('uncaughtException', uncaught); return this; }; /** * Filter leaks with the given globals flagged as `ok`. * * @param {Array} ok * @param {Array} globals * @return {Array} * @api private */ function filterLeaks(ok, globals) { return filter(globals, function(key){ // Firefox and Chrome exposes iframes as index inside the window object if (/^d+/.test(key)) return false; // in firefox // if runner runs in an iframe, this iframe's window.getInterface method not init at first // it is assigned in some seconds if (global.navigator && /^getInterface/.test(key)) return false; // an iframe could be approached by window[iframeIndex] // in ie6,7,8 and opera, iframeIndex is enumerable, this could cause leak if (global.navigator && /^\d+/.test(key)) return false; // Opera and IE expose global variables for HTML element IDs (issue #243) if (/^mocha-/.test(key)) return false; var matched = filter(ok, function(ok){ if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]); return key == ok; }); return matched.length == 0 && (!global.navigator || 'onerror' !== key); }); } }); // module: runner.js require.register("suite.js", function(module, exports, require){ /** * Module dependencies. */ var EventEmitter = require('browser/events').EventEmitter , debug = require('browser/debug')('mocha:suite') , milliseconds = require('./ms') , utils = require('./utils') , Hook = require('./hook'); /** * Expose `Suite`. */ exports = module.exports = Suite; /** * Create a new `Suite` with the given `title` * and parent `Suite`. When a suite with the * same title is already present, that suite * is returned to provide nicer reporter * and more flexible meta-testing. * * @param {Suite} parent * @param {String} title * @return {Suite} * @api public */ exports.create = function(parent, title){ var suite = new Suite(title, parent.ctx); suite.parent = parent; if (parent.pending) suite.pending = true; title = suite.fullTitle(); parent.addSuite(suite); return suite; }; /** * Initialize a new `Suite` with the given * `title` and `ctx`. * * @param {String} title * @param {Context} ctx * @api private */ function Suite(title, ctx) { this.title = title; this.ctx = ctx; this.suites = []; this.tests = []; this.pending = false; this._beforeEach = []; this._beforeAll = []; this._afterEach = []; this._afterAll = []; this.root = !title; this._timeout = 2000; this._slow = 75; this._bail = false; } /** * Inherit from `EventEmitter.prototype`. */ function F(){}; F.prototype = EventEmitter.prototype; Suite.prototype = new F; Suite.prototype.constructor = Suite; /** * Return a clone of this `Suite`. * * @return {Suite} * @api private */ Suite.prototype.clone = function(){ var suite = new Suite(this.title); debug('clone'); suite.ctx = this.ctx; suite.timeout(this.timeout()); suite.slow(this.slow()); suite.bail(this.bail()); return suite; }; /** * Set timeout `ms` or short-hand such as "2s". * * @param {Number|String} ms * @return {Suite|Number} for chaining * @api private */ Suite.prototype.timeout = function(ms){ if (0 == arguments.length) return this._timeout; if ('string' == typeof ms) ms = milliseconds(ms); debug('timeout %d', ms); this._timeout = parseInt(ms, 10); return this; }; /** * Set slow `ms` or short-hand such as "2s". * * @param {Number|String} ms * @return {Suite|Number} for chaining * @api private */ Suite.prototype.slow = function(ms){ if (0 === arguments.length) return this._slow; if ('string' == typeof ms) ms = milliseconds(ms); debug('slow %d', ms); this._slow = ms; return this; }; /** * Sets whether to bail after first error. * * @parma {Boolean} bail * @return {Suite|Number} for chaining * @api private */ Suite.prototype.bail = function(bail){ if (0 == arguments.length) return this._bail; debug('bail %s', bail); this._bail = bail; return this; }; /** * Run `fn(test[, done])` before running tests. * * @param {Function} fn * @return {Suite} for chaining * @api private */ Suite.prototype.beforeAll = function(fn){ if (this.pending) return this; var hook = new Hook('"before all" hook', fn); hook.parent = this; hook.timeout(this.timeout()); hook.slow(this.slow()); hook.ctx = this.ctx; this._beforeAll.push(hook); this.emit('beforeAll', hook); return this; }; /** * Run `fn(test[, done])` after running tests. * * @param {Function} fn * @return {Suite} for chaining * @api private */ Suite.prototype.afterAll = function(fn){ if (this.pending) return this; var hook = new Hook('"after all" hook', fn); hook.parent = this; hook.timeout(this.timeout()); hook.slow(this.slow()); hook.ctx = this.ctx; this._afterAll.push(hook); this.emit('afterAll', hook); return this; }; /** * Run `fn(test[, done])` before each test case. * * @param {Function} fn * @return {Suite} for chaining * @api private */ Suite.prototype.beforeEach = function(fn){ if (this.pending) return this; var hook = new Hook('"before each" hook', fn); hook.parent = this; hook.timeout(this.timeout()); hook.slow(this.slow()); hook.ctx = this.ctx; this._beforeEach.push(hook); this.emit('beforeEach', hook); return this; }; /** * Run `fn(test[, done])` after each test case. * * @param {Function} fn * @return {Suite} for chaining * @api private */ Suite.prototype.afterEach = function(fn){ if (this.pending) return this; var hook = new Hook('"after each" hook', fn); hook.parent = this; hook.timeout(this.timeout()); hook.slow(this.slow()); hook.ctx = this.ctx; this._afterEach.push(hook); this.emit('afterEach', hook); return this; }; /** * Add a test `suite`. * * @param {Suite} suite * @return {Suite} for chaining * @api private */ Suite.prototype.addSuite = function(suite){ suite.parent = this; suite.timeout(this.timeout()); suite.slow(this.slow()); suite.bail(this.bail()); this.suites.push(suite); this.emit('suite', suite); return this; }; /** * Add a `test` to this suite. * * @param {Test} test * @return {Suite} for chaining * @api private */ Suite.prototype.addTest = function(test){ test.parent = this; test.timeout(this.timeout()); test.slow(this.slow()); test.ctx = this.ctx; this.tests.push(test); this.emit('test', test); return this; }; /** * Return the full title generated by recursively * concatenating the parent's full title. * * @return {String} * @api public */ Suite.prototype.fullTitle = function(){ if (this.parent) { var full = this.parent.fullTitle(); if (full) return full + ' ' + this.title; } return this.title; }; /** * Return the total number of tests. * * @return {Number} * @api public */ Suite.prototype.total = function(){ return utils.reduce(this.suites, function(sum, suite){ return sum + suite.total(); }, 0) + this.tests.length; }; /** * Iterates through each suite recursively to find * all tests. Applies a function in the format * `fn(test)`. * * @param {Function} fn * @return {Suite} * @api private */ Suite.prototype.eachTest = function(fn){ utils.forEach(this.tests, fn); utils.forEach(this.suites, function(suite){ suite.eachTest(fn); }); return this; }; }); // module: suite.js require.register("test.js", function(module, exports, require){ /** * Module dependencies. */ var Runnable = require('./runnable'); /** * Expose `Test`. */ module.exports = Test; /** * Initialize a new `Test` with the given `title` and callback `fn`. * * @param {String} title * @param {Function} fn * @api private */ function Test(title, fn) { Runnable.call(this, title, fn); this.pending = !fn; this.type = 'test'; } /** * Inherit from `Runnable.prototype`. */ function F(){}; F.prototype = Runnable.prototype; Test.prototype = new F; Test.prototype.constructor = Test; }); // module: test.js require.register("utils.js", function(module, exports, require){ /** * Module dependencies. */ var fs = require('browser/fs') , path = require('browser/path') , join = path.join , debug = require('browser/debug')('mocha:watch'); /** * Ignored directories. */ var ignore = ['node_modules', '.git']; /** * Escape special characters in the given string of html. * * @param {String} html * @return {String} * @api private */ exports.escape = function(html){ return String(html) .replace(/&/g, '&') .replace(/"/g, '"') .replace(//g, '>'); }; /** * Array#forEach (<=IE8) * * @param {Array} array * @param {Function} fn * @param {Object} scope * @api private */ exports.forEach = function(arr, fn, scope){ for (var i = 0, l = arr.length; i < l; i++) fn.call(scope, arr[i], i); }; /** * Array#indexOf (<=IE8) * * @parma {Array} arr * @param {Object} obj to find index of * @param {Number} start * @api private */ exports.indexOf = function(arr, obj, start){ for (var i = start || 0, l = arr.length; i < l; i++) { if (arr[i] === obj) return i; } return -1; }; /** * Array#reduce (<=IE8) * * @param {Array} array * @param {Function} fn * @param {Object} initial value * @api private */ exports.reduce = function(arr, fn, val){ var rval = val; for (var i = 0, l = arr.length; i < l; i++) { rval = fn(rval, arr[i], i, arr); } return rval; }; /** * Array#filter (<=IE8) * * @param {Array} array * @param {Function} fn * @api private */ exports.filter = function(arr, fn){ var ret = []; for (var i = 0, l = arr.length; i < l; i++) { var val = arr[i]; if (fn(val, i, arr)) ret.push(val); } return ret; }; /** * Object.keys (<=IE8) * * @param {Object} obj * @return {Array} keys * @api private */ exports.keys = Object.keys || function(obj) { var keys = [] , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 for (var key in obj) { if (has.call(obj, key)) { keys.push(key); } } return keys; }; /** * Watch the given `files` for changes * and invoke `fn(file)` on modification. * * @param {Array} files * @param {Function} fn * @api private */ exports.watch = function(files, fn){ var options = { interval: 100 }; files.forEach(function(file){ debug('file %s', file); fs.watchFile(file, options, function(curr, prev){ if (prev.mtime < curr.mtime) fn(file); }); }); }; /** * Ignored files. */ function ignored(path){ return !~ignore.indexOf(path); } /** * Lookup files in the given `dir`. * * @return {Array} * @api private */ exports.files = function(dir, ret){ ret = ret || []; fs.readdirSync(dir) .filter(ignored) .forEach(function(path){ path = join(dir, path); if (fs.statSync(path).isDirectory()) { exports.files(path, ret); } else if (path.match(/\.(js|coffee|litcoffee|coffee.md)$/)) { ret.push(path); } }); return ret; }; /** * Compute a slug from the given `str`. * * @param {String} str * @return {String} * @api private */ exports.slug = function(str){ return str .toLowerCase() .replace(/ +/g, '-') .replace(/[^-\w]/g, ''); }; /** * Strip the function definition from `str`, * and re-indent for pre whitespace. */ exports.clean = function(str) { str = str .replace(/^function *\(.*\) *{/, '') .replace(/\s+\}$/, ''); var whitespace = str.match(/^\n?(\s*)/)[1] , re = new RegExp('^' + whitespace, 'gm'); str = str.replace(re, ''); return exports.trim(str); }; /** * Escape regular expression characters in `str`. * * @param {String} str * @return {String} * @api private */ exports.escapeRegexp = function(str){ return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); }; /** * Trim the given `str`. * * @param {String} str * @return {String} * @api private */ exports.trim = function(str){ return str.replace(/^\s+|\s+$/g, ''); }; /** * Parse the given `qs`. * * @param {String} qs * @return {Object} * @api private */ exports.parseQuery = function(qs){ return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){ var i = pair.indexOf('=') , key = pair.slice(0, i) , val = pair.slice(++i); obj[key] = decodeURIComponent(val); return obj; }, {}); }; /** * Highlight the given string of `js`. * * @param {String} js * @return {String} * @api private */ function highlight(js) { return js .replace(//g, '>') .replace(/\/\/(.*)/gm, '//$1') .replace(/('.*?')/gm, '$1') .replace(/(\d+\.\d+)/gm, '$1') .replace(/(\d+)/gm, '$1') .replace(/\bnew *(\w+)/gm, 'new $1') .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '$1') } /** * Highlight the contents of tag `name`. * * @param {String} name * @api private */ exports.highlightTags = function(name) { var code = document.getElementsByTagName(name); for (var i = 0, len = code.length; i < len; ++i) { code[i].innerHTML = highlight(code[i].innerHTML); } }; }); // module: utils.js // The global object is "self" in Web Workers. global = (function() { return this; })(); /** * Save timer references to avoid Sinon interfering (see GH-237). */ var Date = global.Date; var setTimeout = global.setTimeout; var setInterval = global.setInterval; var clearTimeout = global.clearTimeout; var clearInterval = global.clearInterval; /** * Node shims. * * These are meant only to allow * mocha.js to run untouched, not * to allow running node code in * the browser. */ var process = {}; process.exit = function(status){}; process.stdout = {}; /** * Remove uncaughtException listener. */ process.removeListener = function(e){ if ('uncaughtException' == e) { global.onerror = function() {}; } }; /** * Implements uncaughtException listener. */ process.on = function(e, fn){ if ('uncaughtException' == e) { global.onerror = function(err, url, line){ fn(new Error(err + ' (' + url + ':' + line + ')')); }; } }; /** * Expose mocha. */ var Mocha = global.Mocha = require('mocha'), mocha = global.mocha = new Mocha({ reporter: 'html' }); var immediateQueue = [] , immediateTimeout; function timeslice() { var immediateStart = new Date().getTime(); while (immediateQueue.length && (new Date().getTime() - immediateStart) < 100) { immediateQueue.shift()(); } if (immediateQueue.length) { immediateTimeout = setTimeout(timeslice, 0); } else { immediateTimeout = null; } } /** * High-performance override of Runner.immediately. */ Mocha.Runner.immediately = function(callback) { immediateQueue.push(callback); if (!immediateTimeout) { immediateTimeout = setTimeout(timeslice, 0); } }; /** * Override ui to ensure that the ui functions are initialized. * Normally this would happen in Mocha.prototype.loadFiles. */ mocha.ui = function(ui){ Mocha.prototype.ui.call(this, ui); this.suite.emit('pre-require', global, null, this); return this; }; /** * Setup mocha with the given setting options. */ mocha.setup = function(opts){ if ('string' == typeof opts) opts = { ui: opts }; for (var opt in opts) this[opt](opts[opt]); return this; }; /** * Run mocha, returning the Runner. */ mocha.run = function(fn){ var options = mocha.options; mocha.globals('location'); var query = Mocha.utils.parseQuery(global.location.search || ''); if (query.grep) mocha.grep(query.grep); if (query.invert) mocha.invert(); return Mocha.prototype.run.call(mocha, function(){ // The DOM Document is not available in Web Workers. if (global.document) { Mocha.utils.highlightTags('code'); } if (fn) fn(); }); }; /** * Expose the process shim. */ Mocha.process = process; })(); ================================================ FILE: test/lib/sinon.js ================================================ /** * Sinon.JS 1.7.3, 2013/06/20 * * @author Christian Johansen (christian@cjohansen.no) * @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS * * (The BSD License) * * Copyright (c) 2010-2013, Christian Johansen, christian@cjohansen.no * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Christian Johansen nor the names of his contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ this.sinon = (function () { var buster = (function (setTimeout, B) { var isNode = typeof require == "function" && typeof module == "object"; var div = typeof document != "undefined" && document.createElement("div"); var F = function () {}; var buster = { bind: function bind(obj, methOrProp) { var method = typeof methOrProp == "string" ? obj[methOrProp] : methOrProp; var args = Array.prototype.slice.call(arguments, 2); return function () { var allArgs = args.concat(Array.prototype.slice.call(arguments)); return method.apply(obj, allArgs); }; }, partial: function partial(fn) { var args = [].slice.call(arguments, 1); return function () { return fn.apply(this, args.concat([].slice.call(arguments))); }; }, create: function create(object) { F.prototype = object; return new F(); }, extend: function extend(target) { if (!target) { return; } for (var i = 1, l = arguments.length, prop; i < l; ++i) { for (prop in arguments[i]) { target[prop] = arguments[i][prop]; } } return target; }, nextTick: function nextTick(callback) { if (typeof process != "undefined" && process.nextTick) { return process.nextTick(callback); } setTimeout(callback, 0); }, functionName: function functionName(func) { if (!func) return ""; if (func.displayName) return func.displayName; if (func.name) return func.name; var matches = func.toString().match(/function\s+([^\(]+)/m); return matches && matches[1] || ""; }, isNode: function isNode(obj) { if (!div) return false; try { obj.appendChild(div); obj.removeChild(div); } catch (e) { return false; } return true; }, isElement: function isElement(obj) { return obj && obj.nodeType === 1 && buster.isNode(obj); }, isArray: function isArray(arr) { return Object.prototype.toString.call(arr) == "[object Array]"; }, flatten: function flatten(arr) { var result = [], arr = arr || []; for (var i = 0, l = arr.length; i < l; ++i) { result = result.concat(buster.isArray(arr[i]) ? flatten(arr[i]) : arr[i]); } return result; }, each: function each(arr, callback) { for (var i = 0, l = arr.length; i < l; ++i) { callback(arr[i]); } }, map: function map(arr, callback) { var results = []; for (var i = 0, l = arr.length; i < l; ++i) { results.push(callback(arr[i])); } return results; }, parallel: function parallel(fns, callback) { function cb(err, res) { if (typeof callback == "function") { callback(err, res); callback = null; } } if (fns.length == 0) { return cb(null, []); } var remaining = fns.length, results = []; function makeDone(num) { return function done(err, result) { if (err) { return cb(err); } results[num] = result; if (--remaining == 0) { cb(null, results); } }; } for (var i = 0, l = fns.length; i < l; ++i) { fns[i](makeDone(i)); } }, series: function series(fns, callback) { function cb(err, res) { if (typeof callback == "function") { callback(err, res); } } var remaining = fns.slice(); var results = []; function callNext() { if (remaining.length == 0) return cb(null, results); var promise = remaining.shift()(next); if (promise && typeof promise.then == "function") { promise.then(buster.partial(next, null), next); } } function next(err, result) { if (err) return cb(err); results.push(result); callNext(); } callNext(); }, countdown: function countdown(num, done) { return function () { if (--num == 0) done(); }; } }; if (typeof process === "object" && typeof require === "function" && typeof module === "object") { var crypto = require("crypto"); var path = require("path"); buster.tmpFile = function (fileName) { var hashed = crypto.createHash("sha1"); hashed.update(fileName); var tmpfileName = hashed.digest("hex"); if (process.platform == "win32") { return path.join(process.env["TEMP"], tmpfileName); } else { return path.join("/tmp", tmpfileName); } }; } if (Array.prototype.some) { buster.some = function (arr, fn, thisp) { return arr.some(fn, thisp); }; } else { // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some buster.some = function (arr, fun, thisp) { if (arr == null) { throw new TypeError(); } arr = Object(arr); var len = arr.length >>> 0; if (typeof fun !== "function") { throw new TypeError(); } for (var i = 0; i < len; i++) { if (arr.hasOwnProperty(i) && fun.call(thisp, arr[i], i, arr)) { return true; } } return false; }; } if (Array.prototype.filter) { buster.filter = function (arr, fn, thisp) { return arr.filter(fn, thisp); }; } else { // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter buster.filter = function (fn, thisp) { if (this == null) { throw new TypeError(); } var t = Object(this); var len = t.length >>> 0; if (typeof fn != "function") { throw new TypeError(); } var res = []; for (var i = 0; i < len; i++) { if (i in t) { var val = t[i]; // in case fun mutates this if (fn.call(thisp, val, i, t)) { res.push(val); } } } return res; }; } if (isNode) { module.exports = buster; buster.eventEmitter = require("./buster-event-emitter"); Object.defineProperty(buster, "defineVersionGetter", { get: function () { return require("./define-version-getter"); } }); } return buster.extend(B || {}, buster); }(setTimeout, buster)); if (typeof buster === "undefined") { var buster = {}; } if (typeof module === "object" && typeof require === "function") { buster = require("buster-core"); } buster.format = buster.format || {}; buster.format.excludeConstructors = ["Object", /^.$/]; buster.format.quoteStrings = true; buster.format.ascii = (function () { var hasOwn = Object.prototype.hasOwnProperty; var specialObjects = []; if (typeof global != "undefined") { specialObjects.push({ obj: global, value: "[object global]" }); } if (typeof document != "undefined") { specialObjects.push({ obj: document, value: "[object HTMLDocument]" }); } if (typeof window != "undefined") { specialObjects.push({ obj: window, value: "[object Window]" }); } function keys(object) { var k = Object.keys && Object.keys(object) || []; if (k.length == 0) { for (var prop in object) { if (hasOwn.call(object, prop)) { k.push(prop); } } } return k.sort(); } function isCircular(object, objects) { if (typeof object != "object") { return false; } for (var i = 0, l = objects.length; i < l; ++i) { if (objects[i] === object) { return true; } } return false; } function ascii(object, processed, indent) { if (typeof object == "string") { var quote = typeof this.quoteStrings != "boolean" || this.quoteStrings; return processed || quote ? '"' + object + '"' : object; } if (typeof object == "function" && !(object instanceof RegExp)) { return ascii.func(object); } processed = processed || []; if (isCircular(object, processed)) { return "[Circular]"; } if (Object.prototype.toString.call(object) == "[object Array]") { return ascii.array.call(this, object, processed); } if (!object) { return "" + object; } if (buster.isElement(object)) { return ascii.element(object); } if (typeof object.toString == "function" && object.toString !== Object.prototype.toString) { return object.toString(); } for (var i = 0, l = specialObjects.length; i < l; i++) { if (object === specialObjects[i].obj) { return specialObjects[i].value; } } return ascii.object.call(this, object, processed, indent); } ascii.func = function (func) { return "function " + buster.functionName(func) + "() {}"; }; ascii.array = function (array, processed) { processed = processed || []; processed.push(array); var pieces = []; for (var i = 0, l = array.length; i < l; ++i) { pieces.push(ascii.call(this, array[i], processed)); } return "[" + pieces.join(", ") + "]"; }; ascii.object = function (object, processed, indent) { processed = processed || []; processed.push(object); indent = indent || 0; var pieces = [], properties = keys(object), prop, str, obj; var is = ""; var length = 3; for (var i = 0, l = indent; i < l; ++i) { is += " "; } for (i = 0, l = properties.length; i < l; ++i) { prop = properties[i]; obj = object[prop]; if (isCircular(obj, processed)) { str = "[Circular]"; } else { str = ascii.call(this, obj, processed, indent + 2); } str = (/\s/.test(prop) ? '"' + prop + '"' : prop) + ": " + str; length += str.length; pieces.push(str); } var cons = ascii.constructorName.call(this, object); var prefix = cons ? "[" + cons + "] " : "" return (length + indent) > 80 ? prefix + "{\n " + is + pieces.join(",\n " + is) + "\n" + is + "}" : prefix + "{ " + pieces.join(", ") + " }"; }; ascii.element = function (element) { var tagName = element.tagName.toLowerCase(); var attrs = element.attributes, attribute, pairs = [], attrName; for (var i = 0, l = attrs.length; i < l; ++i) { attribute = attrs.item(i); attrName = attribute.nodeName.toLowerCase().replace("html:", ""); if (attrName == "contenteditable" && attribute.nodeValue == "inherit") { continue; } if (!!attribute.nodeValue) { pairs.push(attrName + "=\"" + attribute.nodeValue + "\""); } } var formatted = "<" + tagName + (pairs.length > 0 ? " " : ""); var content = element.innerHTML; if (content.length > 20) { content = content.substr(0, 20) + "[...]"; } var res = formatted + pairs.join(" ") + ">" + content + ""; return res.replace(/ contentEditable="inherit"/, ""); }; ascii.constructorName = function (object) { var name = buster.functionName(object && object.constructor); var excludes = this.excludeConstructors || buster.format.excludeConstructors || []; for (var i = 0, l = excludes.length; i < l; ++i) { if (typeof excludes[i] == "string" && excludes[i] == name) { return ""; } else if (excludes[i].test && excludes[i].test(name)) { return ""; } } return name; }; return ascii; }()); if (typeof module != "undefined") { module.exports = buster.format; } /*jslint eqeqeq: false, onevar: false, forin: true, nomen: false, regexp: false, plusplus: false*/ /*global module, require, __dirname, document*/ /** * Sinon core utilities. For internal use only. * * @author Christian Johansen (christian@cjohansen.no) * @license BSD * * Copyright (c) 2010-2013 Christian Johansen */ var sinon = (function (buster) { var div = typeof document != "undefined" && document.createElement("div"); var hasOwn = Object.prototype.hasOwnProperty; function isDOMNode(obj) { var success = false; try { obj.appendChild(div); success = div.parentNode == obj; } catch (e) { return false; } finally { try { obj.removeChild(div); } catch (e) { // Remove failed, not much we can do about that } } return success; } function isElement(obj) { return div && obj && obj.nodeType === 1 && isDOMNode(obj); } function isFunction(obj) { return typeof obj === "function" || !!(obj && obj.constructor && obj.call && obj.apply); } function mirrorProperties(target, source) { for (var prop in source) { if (!hasOwn.call(target, prop)) { target[prop] = source[prop]; } } } function isRestorable (obj) { return typeof obj === "function" && typeof obj.restore === "function" && obj.restore.sinon; } var sinon = { wrapMethod: function wrapMethod(object, property, method) { if (!object) { throw new TypeError("Should wrap property of object"); } if (typeof method != "function") { throw new TypeError("Method wrapper should be function"); } var wrappedMethod = object[property]; if (!isFunction(wrappedMethod)) { throw new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " + property + " as function"); } if (wrappedMethod.restore && wrappedMethod.restore.sinon) { throw new TypeError("Attempted to wrap " + property + " which is already wrapped"); } if (wrappedMethod.calledBefore) { var verb = !!wrappedMethod.returns ? "stubbed" : "spied on"; throw new TypeError("Attempted to wrap " + property + " which is already " + verb); } // IE 8 does not support hasOwnProperty on the window object. var owned = hasOwn.call(object, property); object[property] = method; method.displayName = property; method.restore = function () { // For prototype properties try to reset by delete first. // If this fails (ex: localStorage on mobile safari) then force a reset // via direct assignment. if (!owned) { delete object[property]; } if (object[property] === method) { object[property] = wrappedMethod; } }; method.restore.sinon = true; mirrorProperties(method, wrappedMethod); return method; }, extend: function extend(target) { for (var i = 1, l = arguments.length; i < l; i += 1) { for (var prop in arguments[i]) { if (arguments[i].hasOwnProperty(prop)) { target[prop] = arguments[i][prop]; } // DONT ENUM bug, only care about toString if (arguments[i].hasOwnProperty("toString") && arguments[i].toString != target.toString) { target.toString = arguments[i].toString; } } } return target; }, create: function create(proto) { var F = function () {}; F.prototype = proto; return new F(); }, deepEqual: function deepEqual(a, b) { if (sinon.match && sinon.match.isMatcher(a)) { return a.test(b); } if (typeof a != "object" || typeof b != "object") { return a === b; } if (isElement(a) || isElement(b)) { return a === b; } if (a === b) { return true; } if ((a === null && b !== null) || (a !== null && b === null)) { return false; } var aString = Object.prototype.toString.call(a); if (aString != Object.prototype.toString.call(b)) { return false; } if (aString == "[object Array]") { if (a.length !== b.length) { return false; } for (var i = 0, l = a.length; i < l; i += 1) { if (!deepEqual(a[i], b[i])) { return false; } } return true; } if (aString == "[object Date]") { return a.valueOf() === b.valueOf(); } var prop, aLength = 0, bLength = 0; for (prop in a) { aLength += 1; if (!deepEqual(a[prop], b[prop])) { return false; } } for (prop in b) { bLength += 1; } return aLength == bLength; }, functionName: function functionName(func) { var name = func.displayName || func.name; // Use function decomposition as a last resort to get function // name. Does not rely on function decomposition to work - if it // doesn't debugging will be slightly less informative // (i.e. toString will say 'spy' rather than 'myFunc'). if (!name) { var matches = func.toString().match(/function ([^\s\(]+)/); name = matches && matches[1]; } return name; }, functionToString: function toString() { if (this.getCall && this.callCount) { var thisValue, prop, i = this.callCount; while (i--) { thisValue = this.getCall(i).thisValue; for (prop in thisValue) { if (thisValue[prop] === this) { return prop; } } } } return this.displayName || "sinon fake"; }, getConfig: function (custom) { var config = {}; custom = custom || {}; var defaults = sinon.defaultConfig; for (var prop in defaults) { if (defaults.hasOwnProperty(prop)) { config[prop] = custom.hasOwnProperty(prop) ? custom[prop] : defaults[prop]; } } return config; }, format: function (val) { return "" + val; }, defaultConfig: { injectIntoThis: true, injectInto: null, properties: ["spy", "stub", "mock", "clock", "server", "requests"], useFakeTimers: true, useFakeServer: true }, timesInWords: function timesInWords(count) { return count == 1 && "once" || count == 2 && "twice" || count == 3 && "thrice" || (count || 0) + " times"; }, calledInOrder: function (spies) { for (var i = 1, l = spies.length; i < l; i++) { if (!spies[i - 1].calledBefore(spies[i]) || !spies[i].called) { return false; } } return true; }, orderByFirstCall: function (spies) { return spies.sort(function (a, b) { // uuid, won't ever be equal var aCall = a.getCall(0); var bCall = b.getCall(0); var aId = aCall && aCall.callId || -1; var bId = bCall && bCall.callId || -1; return aId < bId ? -1 : 1; }); }, log: function () {}, logError: function (label, err) { var msg = label + " threw exception: " sinon.log(msg + "[" + err.name + "] " + err.message); if (err.stack) { sinon.log(err.stack); } setTimeout(function () { err.message = msg + err.message; throw err; }, 0); }, typeOf: function (value) { if (value === null) { return "null"; } else if (value === undefined) { return "undefined"; } var string = Object.prototype.toString.call(value); return string.substring(8, string.length - 1).toLowerCase(); }, createStubInstance: function (constructor) { if (typeof constructor !== "function") { throw new TypeError("The constructor should be a function."); } return sinon.stub(sinon.create(constructor.prototype)); }, restore: function (object) { if (object !== null && typeof object === "object") { for (var prop in object) { if (isRestorable(object[prop])) { object[prop].restore(); } } } else if (isRestorable(object)) { object.restore(); } } }; var isNode = typeof module == "object" && typeof require == "function"; if (isNode) { try { buster = { format: require("buster-format") }; } catch (e) {} module.exports = sinon; module.exports.spy = require("./sinon/spy"); module.exports.stub = require("./sinon/stub"); module.exports.mock = require("./sinon/mock"); module.exports.collection = require("./sinon/collection"); module.exports.assert = require("./sinon/assert"); module.exports.sandbox = require("./sinon/sandbox"); module.exports.test = require("./sinon/test"); module.exports.testCase = require("./sinon/test_case"); module.exports.assert = require("./sinon/assert"); module.exports.match = require("./sinon/match"); } if (buster) { var formatter = sinon.create(buster.format); formatter.quoteStrings = false; sinon.format = function () { return formatter.ascii.apply(formatter, arguments); }; } else if (isNode) { try { var util = require("util"); sinon.format = function (value) { return typeof value == "object" && value.toString === Object.prototype.toString ? util.inspect(value) : value; }; } catch (e) { /* Node, but no util module - would be very old, but better safe than sorry */ } } return sinon; }(typeof buster == "object" && buster)); /* @depend ../sinon.js */ /*jslint eqeqeq: false, onevar: false, plusplus: false*/ /*global module, require, sinon*/ /** * Match functions * * @author Maximilian Antoni (mail@maxantoni.de) * @license BSD * * Copyright (c) 2012 Maximilian Antoni */ (function (sinon) { var commonJSModule = typeof module == "object" && typeof require == "function"; if (!sinon && commonJSModule) { sinon = require("../sinon"); } if (!sinon) { return; } function assertType(value, type, name) { var actual = sinon.typeOf(value); if (actual !== type) { throw new TypeError("Expected type of " + name + " to be " + type + ", but was " + actual); } } var matcher = { toString: function () { return this.message; } }; function isMatcher(object) { return matcher.isPrototypeOf(object); } function matchObject(expectation, actual) { if (actual === null || actual === undefined) { return false; } for (var key in expectation) { if (expectation.hasOwnProperty(key)) { var exp = expectation[key]; var act = actual[key]; if (match.isMatcher(exp)) { if (!exp.test(act)) { return false; } } else if (sinon.typeOf(exp) === "object") { if (!matchObject(exp, act)) { return false; } } else if (!sinon.deepEqual(exp, act)) { return false; } } } return true; } matcher.or = function (m2) { if (!isMatcher(m2)) { throw new TypeError("Matcher expected"); } var m1 = this; var or = sinon.create(matcher); or.test = function (actual) { return m1.test(actual) || m2.test(actual); }; or.message = m1.message + ".or(" + m2.message + ")"; return or; }; matcher.and = function (m2) { if (!isMatcher(m2)) { throw new TypeError("Matcher expected"); } var m1 = this; var and = sinon.create(matcher); and.test = function (actual) { return m1.test(actual) && m2.test(actual); }; and.message = m1.message + ".and(" + m2.message + ")"; return and; }; var match = function (expectation, message) { var m = sinon.create(matcher); var type = sinon.typeOf(expectation); switch (type) { case "object": if (typeof expectation.test === "function") { m.test = function (actual) { return expectation.test(actual) === true; }; m.message = "match(" + sinon.functionName(expectation.test) + ")"; return m; } var str = []; for (var key in expectation) { if (expectation.hasOwnProperty(key)) { str.push(key + ": " + expectation[key]); } } m.test = function (actual) { return matchObject(expectation, actual); }; m.message = "match(" + str.join(", ") + ")"; break; case "number": m.test = function (actual) { return expectation == actual; }; break; case "string": m.test = function (actual) { if (typeof actual !== "string") { return false; } return actual.indexOf(expectation) !== -1; }; m.message = "match(\"" + expectation + "\")"; break; case "regexp": m.test = function (actual) { if (typeof actual !== "string") { return false; } return expectation.test(actual); }; break; case "function": m.test = expectation; if (message) { m.message = message; } else { m.message = "match(" + sinon.functionName(expectation) + ")"; } break; default: m.test = function (actual) { return sinon.deepEqual(expectation, actual); }; } if (!m.message) { m.message = "match(" + expectation + ")"; } return m; }; match.isMatcher = isMatcher; match.any = match(function () { return true; }, "any"); match.defined = match(function (actual) { return actual !== null && actual !== undefined; }, "defined"); match.truthy = match(function (actual) { return !!actual; }, "truthy"); match.falsy = match(function (actual) { return !actual; }, "falsy"); match.same = function (expectation) { return match(function (actual) { return expectation === actual; }, "same(" + expectation + ")"); }; match.typeOf = function (type) { assertType(type, "string", "type"); return match(function (actual) { return sinon.typeOf(actual) === type; }, "typeOf(\"" + type + "\")"); }; match.instanceOf = function (type) { assertType(type, "function", "type"); return match(function (actual) { return actual instanceof type; }, "instanceOf(" + sinon.functionName(type) + ")"); }; function createPropertyMatcher(propertyTest, messagePrefix) { return function (property, value) { assertType(property, "string", "property"); var onlyProperty = arguments.length === 1; var message = messagePrefix + "(\"" + property + "\""; if (!onlyProperty) { message += ", " + value; } message += ")"; return match(function (actual) { if (actual === undefined || actual === null || !propertyTest(actual, property)) { return false; } return onlyProperty || sinon.deepEqual(value, actual[property]); }, message); }; } match.has = createPropertyMatcher(function (actual, property) { if (typeof actual === "object") { return property in actual; } return actual[property] !== undefined; }, "has"); match.hasOwn = createPropertyMatcher(function (actual, property) { return actual.hasOwnProperty(property); }, "hasOwn"); match.bool = match.typeOf("boolean"); match.number = match.typeOf("number"); match.string = match.typeOf("string"); match.object = match.typeOf("object"); match.func = match.typeOf("function"); match.array = match.typeOf("array"); match.regexp = match.typeOf("regexp"); match.date = match.typeOf("date"); if (commonJSModule) { module.exports = match; } else { sinon.match = match; } }(typeof sinon == "object" && sinon || null)); /** * @depend ../sinon.js * @depend match.js */ /*jslint eqeqeq: false, onevar: false, plusplus: false*/ /*global module, require, sinon*/ /** * Spy calls * * @author Christian Johansen (christian@cjohansen.no) * @author Maximilian Antoni (mail@maxantoni.de) * @license BSD * * Copyright (c) 2010-2013 Christian Johansen * Copyright (c) 2013 Maximilian Antoni */ var commonJSModule = typeof module == "object" && typeof require == "function"; if (!this.sinon && commonJSModule) { var sinon = require("../sinon"); } (function (sinon) { function throwYieldError(proxy, text, args) { var msg = sinon.functionName(proxy) + text; if (args.length) { msg += " Received [" + slice.call(args).join(", ") + "]"; } throw new Error(msg); } var slice = Array.prototype.slice; var callProto = { calledOn: function calledOn(thisValue) { if (sinon.match && sinon.match.isMatcher(thisValue)) { return thisValue.test(this.thisValue); } return this.thisValue === thisValue; }, calledWith: function calledWith() { for (var i = 0, l = arguments.length; i < l; i += 1) { if (!sinon.deepEqual(arguments[i], this.args[i])) { return false; } } return true; }, calledWithMatch: function calledWithMatch() { for (var i = 0, l = arguments.length; i < l; i += 1) { var actual = this.args[i]; var expectation = arguments[i]; if (!sinon.match || !sinon.match(expectation).test(actual)) { return false; } } return true; }, calledWithExactly: function calledWithExactly() { return arguments.length == this.args.length && this.calledWith.apply(this, arguments); }, notCalledWith: function notCalledWith() { return !this.calledWith.apply(this, arguments); }, notCalledWithMatch: function notCalledWithMatch() { return !this.calledWithMatch.apply(this, arguments); }, returned: function returned(value) { return sinon.deepEqual(value, this.returnValue); }, threw: function threw(error) { if (typeof error === "undefined" || !this.exception) { return !!this.exception; } return this.exception === error || this.exception.name === error; }, calledWithNew: function calledWithNew(thisValue) { return this.thisValue instanceof this.proxy; }, calledBefore: function (other) { return this.callId < other.callId; }, calledAfter: function (other) { return this.callId > other.callId; }, callArg: function (pos) { this.args[pos](); }, callArgOn: function (pos, thisValue) { this.args[pos].apply(thisValue); }, callArgWith: function (pos) { this.callArgOnWith.apply(this, [pos, null].concat(slice.call(arguments, 1))); }, callArgOnWith: function (pos, thisValue) { var args = slice.call(arguments, 2); this.args[pos].apply(thisValue, args); }, "yield": function () { this.yieldOn.apply(this, [null].concat(slice.call(arguments, 0))); }, yieldOn: function (thisValue) { var args = this.args; for (var i = 0, l = args.length; i < l; ++i) { if (typeof args[i] === "function") { args[i].apply(thisValue, slice.call(arguments, 1)); return; } } throwYieldError(this.proxy, " cannot yield since no callback was passed.", args); }, yieldTo: function (prop) { this.yieldToOn.apply(this, [prop, null].concat(slice.call(arguments, 1))); }, yieldToOn: function (prop, thisValue) { var args = this.args; for (var i = 0, l = args.length; i < l; ++i) { if (args[i] && typeof args[i][prop] === "function") { args[i][prop].apply(thisValue, slice.call(arguments, 2)); return; } } throwYieldError(this.proxy, " cannot yield to '" + prop + "' since no callback was passed.", args); }, toString: function () { var callStr = this.proxy.toString() + "("; var args = []; for (var i = 0, l = this.args.length; i < l; ++i) { args.push(sinon.format(this.args[i])); } callStr = callStr + args.join(", ") + ")"; if (typeof this.returnValue != "undefined") { callStr += " => " + sinon.format(this.returnValue); } if (this.exception) { callStr += " !" + this.exception.name; if (this.exception.message) { callStr += "(" + this.exception.message + ")"; } } return callStr; } }; callProto.invokeCallback = callProto.yield; function createSpyCall(spy, thisValue, args, returnValue, exception, id) { if (typeof id !== "number") { throw new TypeError("Call id is not a number"); } var proxyCall = sinon.create(callProto); proxyCall.proxy = spy; proxyCall.thisValue = thisValue; proxyCall.args = args; proxyCall.returnValue = returnValue; proxyCall.exception = exception; proxyCall.callId = id; return proxyCall; }; createSpyCall.toString = callProto.toString; // used by mocks sinon.spyCall = createSpyCall; }(typeof sinon == "object" && sinon || null)); /** * @depend ../sinon.js */ /*jslint eqeqeq: false, onevar: false, plusplus: false*/ /*global module, require, sinon*/ /** * Spy functions * * @author Christian Johansen (christian@cjohansen.no) * @license BSD * * Copyright (c) 2010-2013 Christian Johansen */ (function (sinon) { var commonJSModule = typeof module == "object" && typeof require == "function"; var push = Array.prototype.push; var slice = Array.prototype.slice; var callId = 0; function spy(object, property) { if (!property && typeof object == "function") { return spy.create(object); } if (!object && !property) { return spy.create(function () { }); } var method = object[property]; return sinon.wrapMethod(object, property, spy.create(method)); } function matchingFake(fakes, args, strict) { if (!fakes) { return; } var alen = args.length; for (var i = 0, l = fakes.length; i < l; i++) { if (fakes[i].matches(args, strict)) { return fakes[i]; } } } function incrementCallCount() { this.called = true; this.callCount += 1; this.notCalled = false; this.calledOnce = this.callCount == 1; this.calledTwice = this.callCount == 2; this.calledThrice = this.callCount == 3; } function createCallProperties() { this.firstCall = this.getCall(0); this.secondCall = this.getCall(1); this.thirdCall = this.getCall(2); this.lastCall = this.getCall(this.callCount - 1); } var vars = "a,b,c,d,e,f,g,h,i,j,k,l"; function createProxy(func) { // Retain the function length: var p; if (func.length) { eval("p = (function proxy(" + vars.substring(0, func.length * 2 - 1) + ") { return p.invoke(func, this, slice.call(arguments)); });"); } else { p = function proxy() { return p.invoke(func, this, slice.call(arguments)); }; } return p; } var uuid = 0; // Public API var spyApi = { reset: function () { this.called = false; this.notCalled = true; this.calledOnce = false; this.calledTwice = false; this.calledThrice = false; this.callCount = 0; this.firstCall = null; this.secondCall = null; this.thirdCall = null; this.lastCall = null; this.args = []; this.returnValues = []; this.thisValues = []; this.exceptions = []; this.callIds = []; if (this.fakes) { for (var i = 0; i < this.fakes.length; i++) { this.fakes[i].reset(); } } }, create: function create(func) { var name; if (typeof func != "function") { func = function () { }; } else { name = sinon.functionName(func); } var proxy = createProxy(func); sinon.extend(proxy, spy); delete proxy.create; sinon.extend(proxy, func); proxy.reset(); proxy.prototype = func.prototype; proxy.displayName = name || "spy"; proxy.toString = sinon.functionToString; proxy._create = sinon.spy.create; proxy.id = "spy#" + uuid++; return proxy; }, invoke: function invoke(func, thisValue, args) { var matching = matchingFake(this.fakes, args); var exception, returnValue; incrementCallCount.call(this); push.call(this.thisValues, thisValue); push.call(this.args, args); push.call(this.callIds, callId++); try { if (matching) { returnValue = matching.invoke(func, thisValue, args); } else { returnValue = (this.func || func).apply(thisValue, args); } } catch (e) { push.call(this.returnValues, undefined); exception = e; throw e; } finally { push.call(this.exceptions, exception); } push.call(this.returnValues, returnValue); createCallProperties.call(this); return returnValue; }, getCall: function getCall(i) { if (i < 0 || i >= this.callCount) { return null; } return sinon.spyCall(this, this.thisValues[i], this.args[i], this.returnValues[i], this.exceptions[i], this.callIds[i]); }, calledBefore: function calledBefore(spyFn) { if (!this.called) { return false; } if (!spyFn.called) { return true; } return this.callIds[0] < spyFn.callIds[spyFn.callIds.length - 1]; }, calledAfter: function calledAfter(spyFn) { if (!this.called || !spyFn.called) { return false; } return this.callIds[this.callCount - 1] > spyFn.callIds[spyFn.callCount - 1]; }, withArgs: function () { var args = slice.call(arguments); if (this.fakes) { var match = matchingFake(this.fakes, args, true); if (match) { return match; } } else { this.fakes = []; } var original = this; var fake = this._create(); fake.matchingAguments = args; push.call(this.fakes, fake); fake.withArgs = function () { return original.withArgs.apply(original, arguments); }; for (var i = 0; i < this.args.length; i++) { if (fake.matches(this.args[i])) { incrementCallCount.call(fake); push.call(fake.thisValues, this.thisValues[i]); push.call(fake.args, this.args[i]); push.call(fake.returnValues, this.returnValues[i]); push.call(fake.exceptions, this.exceptions[i]); push.call(fake.callIds, this.callIds[i]); } } createCallProperties.call(fake); return fake; }, matches: function (args, strict) { var margs = this.matchingAguments; if (margs.length <= args.length && sinon.deepEqual(margs, args.slice(0, margs.length))) { return !strict || margs.length == args.length; } }, printf: function (format) { var spy = this; var args = slice.call(arguments, 1); var formatter; return (format || "").replace(/%(.)/g, function (match, specifyer) { formatter = spyApi.formatters[specifyer]; if (typeof formatter == "function") { return formatter.call(null, spy, args); } else if (!isNaN(parseInt(specifyer), 10)) { return sinon.format(args[specifyer - 1]); } return "%" + specifyer; }); } }; function delegateToCalls(method, matchAny, actual, notCalled) { spyApi[method] = function () { if (!this.called) { if (notCalled) { return notCalled.apply(this, arguments); } return false; } var currentCall; var matches = 0; for (var i = 0, l = this.callCount; i < l; i += 1) { currentCall = this.getCall(i); if (currentCall[actual || method].apply(currentCall, arguments)) { matches += 1; if (matchAny) { return true; } } } return matches === this.callCount; }; } delegateToCalls("calledOn", true); delegateToCalls("alwaysCalledOn", false, "calledOn"); delegateToCalls("calledWith", true); delegateToCalls("calledWithMatch", true); delegateToCalls("alwaysCalledWith", false, "calledWith"); delegateToCalls("alwaysCalledWithMatch", false, "calledWithMatch"); delegateToCalls("calledWithExactly", true); delegateToCalls("alwaysCalledWithExactly", false, "calledWithExactly"); delegateToCalls("neverCalledWith", false, "notCalledWith", function () { return true; }); delegateToCalls("neverCalledWithMatch", false, "notCalledWithMatch", function () { return true; }); delegateToCalls("threw", true); delegateToCalls("alwaysThrew", false, "threw"); delegateToCalls("returned", true); delegateToCalls("alwaysReturned", false, "returned"); delegateToCalls("calledWithNew", true); delegateToCalls("alwaysCalledWithNew", false, "calledWithNew"); delegateToCalls("callArg", false, "callArgWith", function () { throw new Error(this.toString() + " cannot call arg since it was not yet invoked."); }); spyApi.callArgWith = spyApi.callArg; delegateToCalls("callArgOn", false, "callArgOnWith", function () { throw new Error(this.toString() + " cannot call arg since it was not yet invoked."); }); spyApi.callArgOnWith = spyApi.callArgOn; delegateToCalls("yield", false, "yield", function () { throw new Error(this.toString() + " cannot yield since it was not yet invoked."); }); // "invokeCallback" is an alias for "yield" since "yield" is invalid in strict mode. spyApi.invokeCallback = spyApi.yield; delegateToCalls("yieldOn", false, "yieldOn", function () { throw new Error(this.toString() + " cannot yield since it was not yet invoked."); }); delegateToCalls("yieldTo", false, "yieldTo", function (property) { throw new Error(this.toString() + " cannot yield to '" + property + "' since it was not yet invoked."); }); delegateToCalls("yieldToOn", false, "yieldToOn", function (property) { throw new Error(this.toString() + " cannot yield to '" + property + "' since it was not yet invoked."); }); spyApi.formatters = { "c": function (spy) { return sinon.timesInWords(spy.callCount); }, "n": function (spy) { return spy.toString(); }, "C": function (spy) { var calls = []; for (var i = 0, l = spy.callCount; i < l; ++i) { var stringifiedCall = " " + spy.getCall(i).toString(); if (/\n/.test(calls[i - 1])) { stringifiedCall = "\n" + stringifiedCall; } push.call(calls, stringifiedCall); } return calls.length > 0 ? "\n" + calls.join("\n") : ""; }, "t": function (spy) { var objects = []; for (var i = 0, l = spy.callCount; i < l; ++i) { push.call(objects, sinon.format(spy.thisValues[i])); } return objects.join(", "); }, "*": function (spy, args) { var formatted = []; for (var i = 0, l = args.length; i < l; ++i) { push.call(formatted, sinon.format(args[i])); } return formatted.join(", "); } }; sinon.extend(spy, spyApi); spy.spyCall = sinon.spyCall; if (commonJSModule) { module.exports = spy; } else { sinon.spy = spy; } }(typeof sinon == "object" && sinon || null)); /** * @depend ../sinon.js * @depend spy.js */ /*jslint eqeqeq: false, onevar: false*/ /*global module, require, sinon*/ /** * Stub functions * * @author Christian Johansen (christian@cjohansen.no) * @license BSD * * Copyright (c) 2010-2013 Christian Johansen */ (function (sinon) { var commonJSModule = typeof module == "object" && typeof require == "function"; if (!sinon && commonJSModule) { sinon = require("../sinon"); } if (!sinon) { return; } function stub(object, property, func) { if (!!func && typeof func != "function") { throw new TypeError("Custom stub should be function"); } var wrapper; if (func) { wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func; } else { wrapper = stub.create(); } if (!object && !property) { return sinon.stub.create(); } if (!property && !!object && typeof object == "object") { for (var prop in object) { if (typeof object[prop] === "function") { stub(object, prop); } } return object; } return sinon.wrapMethod(object, property, wrapper); } function getChangingValue(stub, property) { var index = stub.callCount - 1; var values = stub[property]; var prop = index in values ? values[index] : values[values.length - 1]; stub[property + "Last"] = prop; return prop; } function getCallback(stub, args) { var callArgAt = getChangingValue(stub, "callArgAts"); if (callArgAt < 0) { var callArgProp = getChangingValue(stub, "callArgProps"); for (var i = 0, l = args.length; i < l; ++i) { if (!callArgProp && typeof args[i] == "function") { return args[i]; } if (callArgProp && args[i] && typeof args[i][callArgProp] == "function") { return args[i][callArgProp]; } } return null; } return args[callArgAt]; } var join = Array.prototype.join; function getCallbackError(stub, func, args) { if (stub.callArgAtsLast < 0) { var msg; if (stub.callArgPropsLast) { msg = sinon.functionName(stub) + " expected to yield to '" + stub.callArgPropsLast + "', but no object with such a property was passed." } else { msg = sinon.functionName(stub) + " expected to yield, but no callback was passed." } if (args.length > 0) { msg += " Received [" + join.call(args, ", ") + "]"; } return msg; } return "argument at index " + stub.callArgAtsLast + " is not a function: " + func; } var nextTick = (function () { if (typeof process === "object" && typeof process.nextTick === "function") { return process.nextTick; } else if (typeof setImmediate === "function") { return setImmediate; } else { return function (callback) { setTimeout(callback, 0); }; } })(); function callCallback(stub, args) { if (stub.callArgAts.length > 0) { var func = getCallback(stub, args); if (typeof func != "function") { throw new TypeError(getCallbackError(stub, func, args)); } var callbackArguments = getChangingValue(stub, "callbackArguments"); var callbackContext = getChangingValue(stub, "callbackContexts"); if (stub.callbackAsync) { nextTick(function() { func.apply(callbackContext, callbackArguments); }); } else { func.apply(callbackContext, callbackArguments); } } } var uuid = 0; sinon.extend(stub, (function () { var slice = Array.prototype.slice, proto; function throwsException(error, message) { if (typeof error == "string") { this.exception = new Error(message || ""); this.exception.name = error; } else if (!error) { this.exception = new Error("Error"); } else { this.exception = error; } return this; } proto = { create: function create() { var functionStub = function () { callCallback(functionStub, arguments); if (functionStub.exception) { throw functionStub.exception; } else if (typeof functionStub.returnArgAt == 'number') { return arguments[functionStub.returnArgAt]; } else if (functionStub.returnThis) { return this; } return functionStub.returnValue; }; functionStub.id = "stub#" + uuid++; var orig = functionStub; functionStub = sinon.spy.create(functionStub); functionStub.func = orig; functionStub.callArgAts = []; functionStub.callbackArguments = []; functionStub.callbackContexts = []; functionStub.callArgProps = []; sinon.extend(functionStub, stub); functionStub._create = sinon.stub.create; functionStub.displayName = "stub"; functionStub.toString = sinon.functionToString; return functionStub; }, resetBehavior: function () { var i; this.callArgAts = []; this.callbackArguments = []; this.callbackContexts = []; this.callArgProps = []; delete this.returnValue; delete this.returnArgAt; this.returnThis = false; if (this.fakes) { for (i = 0; i < this.fakes.length; i++) { this.fakes[i].resetBehavior(); } } }, returns: function returns(value) { this.returnValue = value; return this; }, returnsArg: function returnsArg(pos) { if (typeof pos != "number") { throw new TypeError("argument index is not number"); } this.returnArgAt = pos; return this; }, returnsThis: function returnsThis() { this.returnThis = true; return this; }, "throws": throwsException, throwsException: throwsException, callsArg: function callsArg(pos) { if (typeof pos != "number") { throw new TypeError("argument index is not number"); } this.callArgAts.push(pos); this.callbackArguments.push([]); this.callbackContexts.push(undefined); this.callArgProps.push(undefined); return this; }, callsArgOn: function callsArgOn(pos, context) { if (typeof pos != "number") { throw new TypeError("argument index is not number"); } if (typeof context != "object") { throw new TypeError("argument context is not an object"); } this.callArgAts.push(pos); this.callbackArguments.push([]); this.callbackContexts.push(context); this.callArgProps.push(undefined); return this; }, callsArgWith: function callsArgWith(pos) { if (typeof pos != "number") { throw new TypeError("argument index is not number"); } this.callArgAts.push(pos); this.callbackArguments.push(slice.call(arguments, 1)); this.callbackContexts.push(undefined); this.callArgProps.push(undefined); return this; }, callsArgOnWith: function callsArgWith(pos, context) { if (typeof pos != "number") { throw new TypeError("argument index is not number"); } if (typeof context != "object") { throw new TypeError("argument context is not an object"); } this.callArgAts.push(pos); this.callbackArguments.push(slice.call(arguments, 2)); this.callbackContexts.push(context); this.callArgProps.push(undefined); return this; }, yields: function () { this.callArgAts.push(-1); this.callbackArguments.push(slice.call(arguments, 0)); this.callbackContexts.push(undefined); this.callArgProps.push(undefined); return this; }, yieldsOn: function (context) { if (typeof context != "object") { throw new TypeError("argument context is not an object"); } this.callArgAts.push(-1); this.callbackArguments.push(slice.call(arguments, 1)); this.callbackContexts.push(context); this.callArgProps.push(undefined); return this; }, yieldsTo: function (prop) { this.callArgAts.push(-1); this.callbackArguments.push(slice.call(arguments, 1)); this.callbackContexts.push(undefined); this.callArgProps.push(prop); return this; }, yieldsToOn: function (prop, context) { if (typeof context != "object") { throw new TypeError("argument context is not an object"); } this.callArgAts.push(-1); this.callbackArguments.push(slice.call(arguments, 2)); this.callbackContexts.push(context); this.callArgProps.push(prop); return this; } }; // create asynchronous versions of callsArg* and yields* methods for (var method in proto) { // need to avoid creating anotherasync versions of the newly added async methods if (proto.hasOwnProperty(method) && method.match(/^(callsArg|yields|thenYields$)/) && !method.match(/Async/)) { proto[method + 'Async'] = (function (syncFnName) { return function () { this.callbackAsync = true; return this[syncFnName].apply(this, arguments); }; })(method); } } return proto; }())); if (commonJSModule) { module.exports = stub; } else { sinon.stub = stub; } }(typeof sinon == "object" && sinon || null)); /** * @depend ../sinon.js * @depend stub.js */ /*jslint eqeqeq: false, onevar: false, nomen: false*/ /*global module, require, sinon*/ /** * Mock functions. * * @author Christian Johansen (christian@cjohansen.no) * @license BSD * * Copyright (c) 2010-2013 Christian Johansen */ (function (sinon) { var commonJSModule = typeof module == "object" && typeof require == "function"; var push = [].push; if (!sinon && commonJSModule) { sinon = require("../sinon"); } if (!sinon) { return; } function mock(object) { if (!object) { return sinon.expectation.create("Anonymous mock"); } return mock.create(object); } sinon.mock = mock; sinon.extend(mock, (function () { function each(collection, callback) { if (!collection) { return; } for (var i = 0, l = collection.length; i < l; i += 1) { callback(collection[i]); } } return { create: function create(object) { if (!object) { throw new TypeError("object is null"); } var mockObject = sinon.extend({}, mock); mockObject.object = object; delete mockObject.create; return mockObject; }, expects: function expects(method) { if (!method) { throw new TypeError("method is falsy"); } if (!this.expectations) { this.expectations = {}; this.proxies = []; } if (!this.expectations[method]) { this.expectations[method] = []; var mockObject = this; sinon.wrapMethod(this.object, method, function () { return mockObject.invokeMethod(method, this, arguments); }); push.call(this.proxies, method); } var expectation = sinon.expectation.create(method); push.call(this.expectations[method], expectation); return expectation; }, restore: function restore() { var object = this.object; each(this.proxies, function (proxy) { if (typeof object[proxy].restore == "function") { object[proxy].restore(); } }); }, verify: function verify() { var expectations = this.expectations || {}; var messages = [], met = []; each(this.proxies, function (proxy) { each(expectations[proxy], function (expectation) { if (!expectation.met()) { push.call(messages, expectation.toString()); } else { push.call(met, expectation.toString()); } }); }); this.restore(); if (messages.length > 0) { sinon.expectation.fail(messages.concat(met).join("\n")); } else { sinon.expectation.pass(messages.concat(met).join("\n")); } return true; }, invokeMethod: function invokeMethod(method, thisValue, args) { var expectations = this.expectations && this.expectations[method]; var length = expectations && expectations.length || 0, i; for (i = 0; i < length; i += 1) { if (!expectations[i].met() && expectations[i].allowsCall(thisValue, args)) { return expectations[i].apply(thisValue, args); } } var messages = [], available, exhausted = 0; for (i = 0; i < length; i += 1) { if (expectations[i].allowsCall(thisValue, args)) { available = available || expectations[i]; } else { exhausted += 1; } push.call(messages, " " + expectations[i].toString()); } if (exhausted === 0) { return available.apply(thisValue, args); } messages.unshift("Unexpected call: " + sinon.spyCall.toString.call({ proxy: method, args: args })); sinon.expectation.fail(messages.join("\n")); } }; }())); var times = sinon.timesInWords; sinon.expectation = (function () { var slice = Array.prototype.slice; var _invoke = sinon.spy.invoke; function callCountInWords(callCount) { if (callCount == 0) { return "never called"; } else { return "called " + times(callCount); } } function expectedCallCountInWords(expectation) { var min = expectation.minCalls; var max = expectation.maxCalls; if (typeof min == "number" && typeof max == "number") { var str = times(min); if (min != max) { str = "at least " + str + " and at most " + times(max); } return str; } if (typeof min == "number") { return "at least " + times(min); } return "at most " + times(max); } function receivedMinCalls(expectation) { var hasMinLimit = typeof expectation.minCalls == "number"; return !hasMinLimit || expectation.callCount >= expectation.minCalls; } function receivedMaxCalls(expectation) { if (typeof expectation.maxCalls != "number") { return false; } return expectation.callCount == expectation.maxCalls; } return { minCalls: 1, maxCalls: 1, create: function create(methodName) { var expectation = sinon.extend(sinon.stub.create(), sinon.expectation); delete expectation.create; expectation.method = methodName; return expectation; }, invoke: function invoke(func, thisValue, args) { this.verifyCallAllowed(thisValue, args); return _invoke.apply(this, arguments); }, atLeast: function atLeast(num) { if (typeof num != "number") { throw new TypeError("'" + num + "' is not number"); } if (!this.limitsSet) { this.maxCalls = null; this.limitsSet = true; } this.minCalls = num; return this; }, atMost: function atMost(num) { if (typeof num != "number") { throw new TypeError("'" + num + "' is not number"); } if (!this.limitsSet) { this.minCalls = null; this.limitsSet = true; } this.maxCalls = num; return this; }, never: function never() { return this.exactly(0); }, once: function once() { return this.exactly(1); }, twice: function twice() { return this.exactly(2); }, thrice: function thrice() { return this.exactly(3); }, exactly: function exactly(num) { if (typeof num != "number") { throw new TypeError("'" + num + "' is not a number"); } this.atLeast(num); return this.atMost(num); }, met: function met() { return !this.failed && receivedMinCalls(this); }, verifyCallAllowed: function verifyCallAllowed(thisValue, args) { if (receivedMaxCalls(this)) { this.failed = true; sinon.expectation.fail(this.method + " already called " + times(this.maxCalls)); } if ("expectedThis" in this && this.expectedThis !== thisValue) { sinon.expectation.fail(this.method + " called with " + thisValue + " as thisValue, expected " + this.expectedThis); } if (!("expectedArguments" in this)) { return; } if (!args) { sinon.expectation.fail(this.method + " received no arguments, expected " + sinon.format(this.expectedArguments)); } if (args.length < this.expectedArguments.length) { sinon.expectation.fail(this.method + " received too few arguments (" + sinon.format(args) + "), expected " + sinon.format(this.expectedArguments)); } if (this.expectsExactArgCount && args.length != this.expectedArguments.length) { sinon.expectation.fail(this.method + " received too many arguments (" + sinon.format(args) + "), expected " + sinon.format(this.expectedArguments)); } for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) { if (!sinon.deepEqual(this.expectedArguments[i], args[i])) { sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) + ", expected " + sinon.format(this.expectedArguments)); } } }, allowsCall: function allowsCall(thisValue, args) { if (this.met() && receivedMaxCalls(this)) { return false; } if ("expectedThis" in this && this.expectedThis !== thisValue) { return false; } if (!("expectedArguments" in this)) { return true; } args = args || []; if (args.length < this.expectedArguments.length) { return false; } if (this.expectsExactArgCount && args.length != this.expectedArguments.length) { return false; } for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) { if (!sinon.deepEqual(this.expectedArguments[i], args[i])) { return false; } } return true; }, withArgs: function withArgs() { this.expectedArguments = slice.call(arguments); return this; }, withExactArgs: function withExactArgs() { this.withArgs.apply(this, arguments); this.expectsExactArgCount = true; return this; }, on: function on(thisValue) { this.expectedThis = thisValue; return this; }, toString: function () { var args = (this.expectedArguments || []).slice(); if (!this.expectsExactArgCount) { push.call(args, "[...]"); } var callStr = sinon.spyCall.toString.call({ proxy: this.method || "anonymous mock expectation", args: args }); var message = callStr.replace(", [...", "[, ...") + " " + expectedCallCountInWords(this); if (this.met()) { return "Expectation met: " + message; } return "Expected " + message + " (" + callCountInWords(this.callCount) + ")"; }, verify: function verify() { if (!this.met()) { sinon.expectation.fail(this.toString()); } else { sinon.expectation.pass(this.toString()); } return true; }, pass: function(message) { sinon.assert.pass(message); }, fail: function (message) { var exception = new Error(message); exception.name = "ExpectationError"; throw exception; } }; }()); if (commonJSModule) { module.exports = mock; } else { sinon.mock = mock; } }(typeof sinon == "object" && sinon || null)); /** * @depend ../sinon.js * @depend stub.js * @depend mock.js */ /*jslint eqeqeq: false, onevar: false, forin: true*/ /*global module, require, sinon*/ /** * Collections of stubs, spies and mocks. * * @author Christian Johansen (christian@cjohansen.no) * @license BSD * * Copyright (c) 2010-2013 Christian Johansen */ (function (sinon) { var commonJSModule = typeof module == "object" && typeof require == "function"; var push = [].push; var hasOwnProperty = Object.prototype.hasOwnProperty; if (!sinon && commonJSModule) { sinon = require("../sinon"); } if (!sinon) { return; } function getFakes(fakeCollection) { if (!fakeCollection.fakes) { fakeCollection.fakes = []; } return fakeCollection.fakes; } function each(fakeCollection, method) { var fakes = getFakes(fakeCollection); for (var i = 0, l = fakes.length; i < l; i += 1) { if (typeof fakes[i][method] == "function") { fakes[i][method](); } } } function compact(fakeCollection) { var fakes = getFakes(fakeCollection); var i = 0; while (i < fakes.length) { fakes.splice(i, 1); } } var collection = { verify: function resolve() { each(this, "verify"); }, restore: function restore() { each(this, "restore"); compact(this); }, verifyAndRestore: function verifyAndRestore() { var exception; try { this.verify(); } catch (e) { exception = e; } this.restore(); if (exception) { throw exception; } }, add: function add(fake) { push.call(getFakes(this), fake); return fake; }, spy: function spy() { return this.add(sinon.spy.apply(sinon, arguments)); }, stub: function stub(object, property, value) { if (property) { var original = object[property]; if (typeof original != "function") { if (!hasOwnProperty.call(object, property)) { throw new TypeError("Cannot stub non-existent own property " + property); } object[property] = value; return this.add({ restore: function () { object[property] = original; } }); } } if (!property && !!object && typeof object == "object") { var stubbedObj = sinon.stub.apply(sinon, arguments); for (var prop in stubbedObj) { if (typeof stubbedObj[prop] === "function") { this.add(stubbedObj[prop]); } } return stubbedObj; } return this.add(sinon.stub.apply(sinon, arguments)); }, mock: function mock() { return this.add(sinon.mock.apply(sinon, arguments)); }, inject: function inject(obj) { var col = this; obj.spy = function () { return col.spy.apply(col, arguments); }; obj.stub = function () { return col.stub.apply(col, arguments); }; obj.mock = function () { return col.mock.apply(col, arguments); }; return obj; } }; if (commonJSModule) { module.exports = collection; } else { sinon.collection = collection; } }(typeof sinon == "object" && sinon || null)); /*jslint eqeqeq: false, plusplus: false, evil: true, onevar: false, browser: true, forin: false*/ /*global module, require, window*/ /** * Fake timer API * setTimeout * setInterval * clearTimeout * clearInterval * tick * reset * Date * * Inspired by jsUnitMockTimeOut from JsUnit * * @author Christian Johansen (christian@cjohansen.no) * @license BSD * * Copyright (c) 2010-2013 Christian Johansen */ if (typeof sinon == "undefined") { var sinon = {}; } (function (global) { var id = 1; function addTimer(args, recurring) { if (args.length === 0) { throw new Error("Function requires at least 1 parameter"); } var toId = id++; var delay = args[1] || 0; if (!this.timeouts) { this.timeouts = {}; } this.timeouts[toId] = { id: toId, func: args[0], callAt: this.now + delay, invokeArgs: Array.prototype.slice.call(args, 2) }; if (recurring === true) { this.timeouts[toId].interval = delay; } return toId; } function parseTime(str) { if (!str) { return 0; } var strings = str.split(":"); var l = strings.length, i = l; var ms = 0, parsed; if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) { throw new Error("tick only understands numbers and 'h:m:s'"); } while (i--) { parsed = parseInt(strings[i], 10); if (parsed >= 60) { throw new Error("Invalid time " + str); } ms += parsed * Math.pow(60, (l - i - 1)); } return ms * 1000; } function createObject(object) { var newObject; if (Object.create) { newObject = Object.create(object); } else { var F = function () {}; F.prototype = object; newObject = new F(); } newObject.Date.clock = newObject; return newObject; } sinon.clock = { now: 0, create: function create(now) { var clock = createObject(this); if (typeof now == "number") { clock.now = now; } if (!!now && typeof now == "object") { throw new TypeError("now should be milliseconds since UNIX epoch"); } return clock; }, setTimeout: function setTimeout(callback, timeout) { return addTimer.call(this, arguments, false); }, clearTimeout: function clearTimeout(timerId) { if (!this.timeouts) { this.timeouts = []; } if (timerId in this.timeouts) { delete this.timeouts[timerId]; } }, setInterval: function setInterval(callback, timeout) { return addTimer.call(this, arguments, true); }, clearInterval: function clearInterval(timerId) { this.clearTimeout(timerId); }, tick: function tick(ms) { ms = typeof ms == "number" ? ms : parseTime(ms); var tickFrom = this.now, tickTo = this.now + ms, previous = this.now; var timer = this.firstTimerInRange(tickFrom, tickTo); var firstException; while (timer && tickFrom <= tickTo) { if (this.timeouts[timer.id]) { tickFrom = this.now = timer.callAt; try { this.callTimer(timer); } catch (e) { firstException = firstException || e; } } timer = this.firstTimerInRange(previous, tickTo); previous = tickFrom; } this.now = tickTo; if (firstException) { throw firstException; } return this.now; }, firstTimerInRange: function (from, to) { var timer, smallest, originalTimer; for (var id in this.timeouts) { if (this.timeouts.hasOwnProperty(id)) { if (this.timeouts[id].callAt < from || this.timeouts[id].callAt > to) { continue; } if (!smallest || this.timeouts[id].callAt < smallest) { originalTimer = this.timeouts[id]; smallest = this.timeouts[id].callAt; timer = { func: this.timeouts[id].func, callAt: this.timeouts[id].callAt, interval: this.timeouts[id].interval, id: this.timeouts[id].id, invokeArgs: this.timeouts[id].invokeArgs }; } } } return timer || null; }, callTimer: function (timer) { if (typeof timer.interval == "number") { this.timeouts[timer.id].callAt += timer.interval; } else { delete this.timeouts[timer.id]; } try { if (typeof timer.func == "function") { timer.func.apply(null, timer.invokeArgs); } else { eval(timer.func); } } catch (e) { var exception = e; } if (!this.timeouts[timer.id]) { if (exception) { throw exception; } return; } if (exception) { throw exception; } }, reset: function reset() { this.timeouts = {}; }, Date: (function () { var NativeDate = Date; function ClockDate(year, month, date, hour, minute, second, ms) { // Defensive and verbose to avoid potential harm in passing // explicit undefined when user does not pass argument switch (arguments.length) { case 0: return new NativeDate(ClockDate.clock.now); case 1: return new NativeDate(year); case 2: return new NativeDate(year, month); case 3: return new NativeDate(year, month, date); case 4: return new NativeDate(year, month, date, hour); case 5: return new NativeDate(year, month, date, hour, minute); case 6: return new NativeDate(year, month, date, hour, minute, second); default: return new NativeDate(year, month, date, hour, minute, second, ms); } } return mirrorDateProperties(ClockDate, NativeDate); }()) }; function mirrorDateProperties(target, source) { if (source.now) { target.now = function now() { return target.clock.now; }; } else { delete target.now; } if (source.toSource) { target.toSource = function toSource() { return source.toSource(); }; } else { delete target.toSource; } target.toString = function toString() { return source.toString(); }; target.prototype = source.prototype; target.parse = source.parse; target.UTC = source.UTC; target.prototype.toUTCString = source.prototype.toUTCString; return target; } var methods = ["Date", "setTimeout", "setInterval", "clearTimeout", "clearInterval"]; function restore() { var method; for (var i = 0, l = this.methods.length; i < l; i++) { method = this.methods[i]; if (global[method].hadOwnProperty) { global[method] = this["_" + method]; } else { delete global[method]; } } // Prevent multiple executions which will completely remove these props this.methods = []; } function stubGlobal(method, clock) { clock[method].hadOwnProperty = Object.prototype.hasOwnProperty.call(global, method); clock["_" + method] = global[method]; if (method == "Date") { var date = mirrorDateProperties(clock[method], global[method]); global[method] = date; } else { global[method] = function () { return clock[method].apply(clock, arguments); }; for (var prop in clock[method]) { if (clock[method].hasOwnProperty(prop)) { global[method][prop] = clock[method][prop]; } } } global[method].clock = clock; } sinon.useFakeTimers = function useFakeTimers(now) { var clock = sinon.clock.create(now); clock.restore = restore; clock.methods = Array.prototype.slice.call(arguments, typeof now == "number" ? 1 : 0); if (clock.methods.length === 0) { clock.methods = methods; } for (var i = 0, l = clock.methods.length; i < l; i++) { stubGlobal(clock.methods[i], clock); } return clock; }; }(typeof global != "undefined" && typeof global !== "function" ? global : this)); sinon.timers = { setTimeout: setTimeout, clearTimeout: clearTimeout, setInterval: setInterval, clearInterval: clearInterval, Date: Date }; if (typeof module == "object" && typeof require == "function") { module.exports = sinon; } /*jslint eqeqeq: false, onevar: false*/ /*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/ /** * Minimal Event interface implementation * * Original implementation by Sven Fuchs: https://gist.github.com/995028 * Modifications and tests by Christian Johansen. * * @author Sven Fuchs (svenfuchs@artweb-design.de) * @author Christian Johansen (christian@cjohansen.no) * @license BSD * * Copyright (c) 2011 Sven Fuchs, Christian Johansen */ if (typeof sinon == "undefined") { this.sinon = {}; } (function () { var push = [].push; sinon.Event = function Event(type, bubbles, cancelable, target) { this.initEvent(type, bubbles, cancelable, target); }; sinon.Event.prototype = { initEvent: function(type, bubbles, cancelable, target) { this.type = type; this.bubbles = bubbles; this.cancelable = cancelable; this.target = target; }, stopPropagation: function () {}, preventDefault: function () { this.defaultPrevented = true; } }; sinon.EventTarget = { addEventListener: function addEventListener(event, listener, useCapture) { this.eventListeners = this.eventListeners || {}; this.eventListeners[event] = this.eventListeners[event] || []; push.call(this.eventListeners[event], listener); }, removeEventListener: function removeEventListener(event, listener, useCapture) { var listeners = this.eventListeners && this.eventListeners[event] || []; for (var i = 0, l = listeners.length; i < l; ++i) { if (listeners[i] == listener) { return listeners.splice(i, 1); } } }, dispatchEvent: function dispatchEvent(event) { var type = event.type; var listeners = this.eventListeners && this.eventListeners[type] || []; for (var i = 0; i < listeners.length; i++) { if (typeof listeners[i] == "function") { listeners[i].call(this, event); } else { listeners[i].handleEvent(event); } } return !!event.defaultPrevented; } }; }()); /** * @depend ../../sinon.js * @depend event.js */ /*jslint eqeqeq: false, onevar: false*/ /*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/ /** * Fake XMLHttpRequest object * * @author Christian Johansen (christian@cjohansen.no) * @license BSD * * Copyright (c) 2010-2013 Christian Johansen */ if (typeof sinon == "undefined") { this.sinon = {}; } sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; // wrapper for global (function(global) { var xhr = sinon.xhr; xhr.GlobalXMLHttpRequest = global.XMLHttpRequest; xhr.GlobalActiveXObject = global.ActiveXObject; xhr.supportsActiveX = typeof xhr.GlobalActiveXObject != "undefined"; xhr.supportsXHR = typeof xhr.GlobalXMLHttpRequest != "undefined"; xhr.workingXHR = xhr.supportsXHR ? xhr.GlobalXMLHttpRequest : xhr.supportsActiveX ? function() { return new xhr.GlobalActiveXObject("MSXML2.XMLHTTP.3.0") } : false; /*jsl:ignore*/ var unsafeHeaders = { "Accept-Charset": true, "Accept-Encoding": true, "Connection": true, "Content-Length": true, "Cookie": true, "Cookie2": true, "Content-Transfer-Encoding": true, "Date": true, "Expect": true, "Host": true, "Keep-Alive": true, "Referer": true, "TE": true, "Trailer": true, "Transfer-Encoding": true, "Upgrade": true, "User-Agent": true, "Via": true }; /*jsl:end*/ function FakeXMLHttpRequest() { this.readyState = FakeXMLHttpRequest.UNSENT; this.requestHeaders = {}; this.requestBody = null; this.status = 0; this.statusText = ""; var xhr = this; var events = ["loadstart", "load", "abort", "loadend"]; function addEventListener(eventName) { xhr.addEventListener(eventName, function (event) { var listener = xhr["on" + eventName]; if (listener && typeof listener == "function") { listener(event); } }); } for (var i = events.length - 1; i >= 0; i--) { addEventListener(events[i]); } if (typeof FakeXMLHttpRequest.onCreate == "function") { FakeXMLHttpRequest.onCreate(this); } } function verifyState(xhr) { if (xhr.readyState !== FakeXMLHttpRequest.OPENED) { throw new Error("INVALID_STATE_ERR"); } if (xhr.sendFlag) { throw new Error("INVALID_STATE_ERR"); } } // filtering to enable a white-list version of Sinon FakeXhr, // where whitelisted requests are passed through to real XHR function each(collection, callback) { if (!collection) return; for (var i = 0, l = collection.length; i < l; i += 1) { callback(collection[i]); } } function some(collection, callback) { for (var index = 0; index < collection.length; index++) { if(callback(collection[index]) === true) return true; }; return false; } // largest arity in XHR is 5 - XHR#open var apply = function(obj,method,args) { switch(args.length) { case 0: return obj[method](); case 1: return obj[method](args[0]); case 2: return obj[method](args[0],args[1]); case 3: return obj[method](args[0],args[1],args[2]); case 4: return obj[method](args[0],args[1],args[2],args[3]); case 5: return obj[method](args[0],args[1],args[2],args[3],args[4]); }; }; FakeXMLHttpRequest.filters = []; FakeXMLHttpRequest.addFilter = function(fn) { this.filters.push(fn) }; var IE6Re = /MSIE 6/; FakeXMLHttpRequest.defake = function(fakeXhr,xhrArgs) { var xhr = new sinon.xhr.workingXHR(); each(["open","setRequestHeader","send","abort","getResponseHeader", "getAllResponseHeaders","addEventListener","overrideMimeType","removeEventListener"], function(method) { fakeXhr[method] = function() { return apply(xhr,method,arguments); }; }); var copyAttrs = function(args) { each(args, function(attr) { try { fakeXhr[attr] = xhr[attr] } catch(e) { if(!IE6Re.test(navigator.userAgent)) throw e; } }); }; var stateChange = function() { fakeXhr.readyState = xhr.readyState; if(xhr.readyState >= FakeXMLHttpRequest.HEADERS_RECEIVED) { copyAttrs(["status","statusText"]); } if(xhr.readyState >= FakeXMLHttpRequest.LOADING) { copyAttrs(["responseText"]); } if(xhr.readyState === FakeXMLHttpRequest.DONE) { copyAttrs(["responseXML"]); } if(fakeXhr.onreadystatechange) fakeXhr.onreadystatechange.call(fakeXhr); }; if(xhr.addEventListener) { for(var event in fakeXhr.eventListeners) { if(fakeXhr.eventListeners.hasOwnProperty(event)) { each(fakeXhr.eventListeners[event],function(handler) { xhr.addEventListener(event, handler); }); } } xhr.addEventListener("readystatechange",stateChange); } else { xhr.onreadystatechange = stateChange; } apply(xhr,"open",xhrArgs); }; FakeXMLHttpRequest.useFilters = false; function verifyRequestSent(xhr) { if (xhr.readyState == FakeXMLHttpRequest.DONE) { throw new Error("Request done"); } } function verifyHeadersReceived(xhr) { if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) { throw new Error("No headers received"); } } function verifyResponseBodyType(body) { if (typeof body != "string") { var error = new Error("Attempted to respond to fake XMLHttpRequest with " + body + ", which is not a string."); error.name = "InvalidBodyException"; throw error; } } sinon.extend(FakeXMLHttpRequest.prototype, sinon.EventTarget, { async: true, open: function open(method, url, async, username, password) { this.method = method; this.url = url; this.async = typeof async == "boolean" ? async : true; this.username = username; this.password = password; this.responseText = null; this.responseXML = null; this.requestHeaders = {}; this.sendFlag = false; if(sinon.FakeXMLHttpRequest.useFilters === true) { var xhrArgs = arguments; var defake = some(FakeXMLHttpRequest.filters,function(filter) { return filter.apply(this,xhrArgs) }); if (defake) { return sinon.FakeXMLHttpRequest.defake(this,arguments); } } this.readyStateChange(FakeXMLHttpRequest.OPENED); }, readyStateChange: function readyStateChange(state) { this.readyState = state; if (typeof this.onreadystatechange == "function") { try { this.onreadystatechange(); } catch (e) { sinon.logError("Fake XHR onreadystatechange handler", e); } } this.dispatchEvent(new sinon.Event("readystatechange")); switch (this.readyState) { case FakeXMLHttpRequest.DONE: this.dispatchEvent(new sinon.Event("load", false, false, this)); this.dispatchEvent(new sinon.Event("loadend", false, false, this)); break; } }, setRequestHeader: function setRequestHeader(header, value) { verifyState(this); if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) { throw new Error("Refused to set unsafe header \"" + header + "\""); } if (this.requestHeaders[header]) { this.requestHeaders[header] += "," + value; } else { this.requestHeaders[header] = value; } }, // Helps testing setResponseHeaders: function setResponseHeaders(headers) { this.responseHeaders = {}; for (var header in headers) { if (headers.hasOwnProperty(header)) { this.responseHeaders[header] = headers[header]; } } if (this.async) { this.readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED); } else { this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED; } }, // Currently treats ALL data as a DOMString (i.e. no Document) send: function send(data) { verifyState(this); if (!/^(get|head)$/i.test(this.method)) { if (this.requestHeaders["Content-Type"]) { var value = this.requestHeaders["Content-Type"].split(";"); this.requestHeaders["Content-Type"] = value[0] + ";charset=utf-8"; } else { this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8"; } this.requestBody = data; } this.errorFlag = false; this.sendFlag = this.async; this.readyStateChange(FakeXMLHttpRequest.OPENED); if (typeof this.onSend == "function") { this.onSend(this); } this.dispatchEvent(new sinon.Event("loadstart", false, false, this)); }, abort: function abort() { this.aborted = true; this.responseText = null; this.errorFlag = true; this.requestHeaders = {}; if (this.readyState > sinon.FakeXMLHttpRequest.UNSENT && this.sendFlag) { this.readyStateChange(sinon.FakeXMLHttpRequest.DONE); this.sendFlag = false; } this.readyState = sinon.FakeXMLHttpRequest.UNSENT; this.dispatchEvent(new sinon.Event("abort", false, false, this)); if (typeof this.onerror === "function") { this.onerror(); } }, getResponseHeader: function getResponseHeader(header) { if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { return null; } if (/^Set-Cookie2?$/i.test(header)) { return null; } header = header.toLowerCase(); for (var h in this.responseHeaders) { if (h.toLowerCase() == header) { return this.responseHeaders[h]; } } return null; }, getAllResponseHeaders: function getAllResponseHeaders() { if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { return ""; } var headers = ""; for (var header in this.responseHeaders) { if (this.responseHeaders.hasOwnProperty(header) && !/^Set-Cookie2?$/i.test(header)) { headers += header + ": " + this.responseHeaders[header] + "\r\n"; } } return headers; }, setResponseBody: function setResponseBody(body) { verifyRequestSent(this); verifyHeadersReceived(this); verifyResponseBodyType(body); var chunkSize = this.chunkSize || 10; var index = 0; this.responseText = ""; do { if (this.async) { this.readyStateChange(FakeXMLHttpRequest.LOADING); } this.responseText += body.substring(index, index + chunkSize); index += chunkSize; } while (index < body.length); var type = this.getResponseHeader("Content-Type"); if (this.responseText && (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) { try { this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText); } catch (e) { // Unable to parse XML - no biggie } } if (this.async) { this.readyStateChange(FakeXMLHttpRequest.DONE); } else { this.readyState = FakeXMLHttpRequest.DONE; } }, respond: function respond(status, headers, body) { this.setResponseHeaders(headers || {}); this.status = typeof status == "number" ? status : 200; this.statusText = FakeXMLHttpRequest.statusCodes[this.status]; this.setResponseBody(body || ""); if (typeof this.onload === "function"){ this.onload(); } } }); sinon.extend(FakeXMLHttpRequest, { UNSENT: 0, OPENED: 1, HEADERS_RECEIVED: 2, LOADING: 3, DONE: 4 }); // Borrowed from JSpec FakeXMLHttpRequest.parseXML = function parseXML(text) { var xmlDoc; if (typeof DOMParser != "undefined") { var parser = new DOMParser(); xmlDoc = parser.parseFromString(text, "text/xml"); } else { xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); xmlDoc.async = "false"; xmlDoc.loadXML(text); } return xmlDoc; }; FakeXMLHttpRequest.statusCodes = { 100: "Continue", 101: "Switching Protocols", 200: "OK", 201: "Created", 202: "Accepted", 203: "Non-Authoritative Information", 204: "No Content", 205: "Reset Content", 206: "Partial Content", 300: "Multiple Choice", 301: "Moved Permanently", 302: "Found", 303: "See Other", 304: "Not Modified", 305: "Use Proxy", 307: "Temporary Redirect", 400: "Bad Request", 401: "Unauthorized", 402: "Payment Required", 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", 406: "Not Acceptable", 407: "Proxy Authentication Required", 408: "Request Timeout", 409: "Conflict", 410: "Gone", 411: "Length Required", 412: "Precondition Failed", 413: "Request Entity Too Large", 414: "Request-URI Too Long", 415: "Unsupported Media Type", 416: "Requested Range Not Satisfiable", 417: "Expectation Failed", 422: "Unprocessable Entity", 500: "Internal Server Error", 501: "Not Implemented", 502: "Bad Gateway", 503: "Service Unavailable", 504: "Gateway Timeout", 505: "HTTP Version Not Supported" }; sinon.useFakeXMLHttpRequest = function () { sinon.FakeXMLHttpRequest.restore = function restore(keepOnCreate) { if (xhr.supportsXHR) { global.XMLHttpRequest = xhr.GlobalXMLHttpRequest; } if (xhr.supportsActiveX) { global.ActiveXObject = xhr.GlobalActiveXObject; } delete sinon.FakeXMLHttpRequest.restore; if (keepOnCreate !== true) { delete sinon.FakeXMLHttpRequest.onCreate; } }; if (xhr.supportsXHR) { global.XMLHttpRequest = sinon.FakeXMLHttpRequest; } if (xhr.supportsActiveX) { global.ActiveXObject = function ActiveXObject(objId) { if (objId == "Microsoft.XMLHTTP" || /^Msxml2\.XMLHTTP/i.test(objId)) { return new sinon.FakeXMLHttpRequest(); } return new xhr.GlobalActiveXObject(objId); }; } return sinon.FakeXMLHttpRequest; }; sinon.FakeXMLHttpRequest = FakeXMLHttpRequest; })(this); if (typeof module == "object" && typeof require == "function") { module.exports = sinon; } /** * @depend fake_xml_http_request.js */ /*jslint eqeqeq: false, onevar: false, regexp: false, plusplus: false*/ /*global module, require, window*/ /** * The Sinon "server" mimics a web server that receives requests from * sinon.FakeXMLHttpRequest and provides an API to respond to those requests, * both synchronously and asynchronously. To respond synchronuously, canned * answers have to be provided upfront. * * @author Christian Johansen (christian@cjohansen.no) * @license BSD * * Copyright (c) 2010-2013 Christian Johansen */ if (typeof sinon == "undefined") { var sinon = {}; } sinon.fakeServer = (function () { var push = [].push; function F() {} function create(proto) { F.prototype = proto; return new F(); } function responseArray(handler) { var response = handler; if (Object.prototype.toString.call(handler) != "[object Array]") { response = [200, {}, handler]; } if (typeof response[2] != "string") { throw new TypeError("Fake server response body should be string, but was " + typeof response[2]); } return response; } var wloc = typeof window !== "undefined" ? window.location : {}; var rCurrLoc = new RegExp("^" + wloc.protocol + "//" + wloc.host); function matchOne(response, reqMethod, reqUrl) { var rmeth = response.method; var matchMethod = !rmeth || rmeth.toLowerCase() == reqMethod.toLowerCase(); var url = response.url; var matchUrl = !url || url == reqUrl || (typeof url.test == "function" && url.test(reqUrl)); return matchMethod && matchUrl; } function match(response, request) { var requestMethod = this.getHTTPMethod(request); var requestUrl = request.url; if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) { requestUrl = requestUrl.replace(rCurrLoc, ""); } if (matchOne(response, this.getHTTPMethod(request), requestUrl)) { if (typeof response.response == "function") { var ru = response.url; var args = [request].concat(!ru ? [] : requestUrl.match(ru).slice(1)); return response.response.apply(response, args); } return true; } return false; } function log(response, request) { var str; str = "Request:\n" + sinon.format(request) + "\n\n"; str += "Response:\n" + sinon.format(response) + "\n\n"; sinon.log(str); } return { create: function () { var server = create(this); this.xhr = sinon.useFakeXMLHttpRequest(); server.requests = []; this.xhr.onCreate = function (xhrObj) { server.addRequest(xhrObj); }; return server; }, addRequest: function addRequest(xhrObj) { var server = this; push.call(this.requests, xhrObj); xhrObj.onSend = function () { server.handleRequest(this); }; if (this.autoRespond && !this.responding) { setTimeout(function () { server.responding = false; server.respond(); }, this.autoRespondAfter || 10); this.responding = true; } }, getHTTPMethod: function getHTTPMethod(request) { if (this.fakeHTTPMethods && /post/i.test(request.method)) { var matches = (request.requestBody || "").match(/_method=([^\b;]+)/); return !!matches ? matches[1] : request.method; } return request.method; }, handleRequest: function handleRequest(xhr) { if (xhr.async) { if (!this.queue) { this.queue = []; } push.call(this.queue, xhr); } else { this.processRequest(xhr); } }, respondWith: function respondWith(method, url, body) { if (arguments.length == 1 && typeof method != "function") { this.response = responseArray(method); return; } if (!this.responses) { this.responses = []; } if (arguments.length == 1) { body = method; url = method = null; } if (arguments.length == 2) { body = url; url = method; method = null; } push.call(this.responses, { method: method, url: url, response: typeof body == "function" ? body : responseArray(body) }); }, respond: function respond() { if (arguments.length > 0) this.respondWith.apply(this, arguments); var queue = this.queue || []; var request; while(request = queue.shift()) { this.processRequest(request); } }, processRequest: function processRequest(request) { try { if (request.aborted) { return; } var response = this.response || [404, {}, ""]; if (this.responses) { for (var i = 0, l = this.responses.length; i < l; i++) { if (match.call(this, this.responses[i], request)) { response = this.responses[i].response; break; } } } if (request.readyState != 4) { log(response, request); request.respond(response[0], response[1], response[2]); } } catch (e) { sinon.logError("Fake server request processing", e); } }, restore: function restore() { return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments); } }; }()); if (typeof module == "object" && typeof require == "function") { module.exports = sinon; } /** * @depend fake_server.js * @depend fake_timers.js */ /*jslint browser: true, eqeqeq: false, onevar: false*/ /*global sinon*/ /** * Add-on for sinon.fakeServer that automatically handles a fake timer along with * the FakeXMLHttpRequest. The direct inspiration for this add-on is jQuery * 1.3.x, which does not use xhr object's onreadystatehandler at all - instead, * it polls the object for completion with setInterval. Dispite the direct * motivation, there is nothing jQuery-specific in this file, so it can be used * in any environment where the ajax implementation depends on setInterval or * setTimeout. * * @author Christian Johansen (christian@cjohansen.no) * @license BSD * * Copyright (c) 2010-2013 Christian Johansen */ (function () { function Server() {} Server.prototype = sinon.fakeServer; sinon.fakeServerWithClock = new Server(); sinon.fakeServerWithClock.addRequest = function addRequest(xhr) { if (xhr.async) { if (typeof setTimeout.clock == "object") { this.clock = setTimeout.clock; } else { this.clock = sinon.useFakeTimers(); this.resetClock = true; } if (!this.longestTimeout) { var clockSetTimeout = this.clock.setTimeout; var clockSetInterval = this.clock.setInterval; var server = this; this.clock.setTimeout = function (fn, timeout) { server.longestTimeout = Math.max(timeout, server.longestTimeout || 0); return clockSetTimeout.apply(this, arguments); }; this.clock.setInterval = function (fn, timeout) { server.longestTimeout = Math.max(timeout, server.longestTimeout || 0); return clockSetInterval.apply(this, arguments); }; } } return sinon.fakeServer.addRequest.call(this, xhr); }; sinon.fakeServerWithClock.respond = function respond() { var returnVal = sinon.fakeServer.respond.apply(this, arguments); if (this.clock) { this.clock.tick(this.longestTimeout || 0); this.longestTimeout = 0; if (this.resetClock) { this.clock.restore(); this.resetClock = false; } } return returnVal; }; sinon.fakeServerWithClock.restore = function restore() { if (this.clock) { this.clock.restore(); } return sinon.fakeServer.restore.apply(this, arguments); }; }()); /** * @depend ../sinon.js * @depend collection.js * @depend util/fake_timers.js * @depend util/fake_server_with_clock.js */ /*jslint eqeqeq: false, onevar: false, plusplus: false*/ /*global require, module*/ /** * Manages fake collections as well as fake utilities such as Sinon's * timers and fake XHR implementation in one convenient object. * * @author Christian Johansen (christian@cjohansen.no) * @license BSD * * Copyright (c) 2010-2013 Christian Johansen */ if (typeof module == "object" && typeof require == "function") { var sinon = require("../sinon"); sinon.extend(sinon, require("./util/fake_timers")); } (function () { var push = [].push; function exposeValue(sandbox, config, key, value) { if (!value) { return; } if (config.injectInto) { config.injectInto[key] = value; } else { push.call(sandbox.args, value); } } function prepareSandboxFromConfig(config) { var sandbox = sinon.create(sinon.sandbox); if (config.useFakeServer) { if (typeof config.useFakeServer == "object") { sandbox.serverPrototype = config.useFakeServer; } sandbox.useFakeServer(); } if (config.useFakeTimers) { if (typeof config.useFakeTimers == "object") { sandbox.useFakeTimers.apply(sandbox, config.useFakeTimers); } else { sandbox.useFakeTimers(); } } return sandbox; } sinon.sandbox = sinon.extend(sinon.create(sinon.collection), { useFakeTimers: function useFakeTimers() { this.clock = sinon.useFakeTimers.apply(sinon, arguments); return this.add(this.clock); }, serverPrototype: sinon.fakeServer, useFakeServer: function useFakeServer() { var proto = this.serverPrototype || sinon.fakeServer; if (!proto || !proto.create) { return null; } this.server = proto.create(); return this.add(this.server); }, inject: function (obj) { sinon.collection.inject.call(this, obj); if (this.clock) { obj.clock = this.clock; } if (this.server) { obj.server = this.server; obj.requests = this.server.requests; } return obj; }, create: function (config) { if (!config) { return sinon.create(sinon.sandbox); } var sandbox = prepareSandboxFromConfig(config); sandbox.args = sandbox.args || []; var prop, value, exposed = sandbox.inject({}); if (config.properties) { for (var i = 0, l = config.properties.length; i < l; i++) { prop = config.properties[i]; value = exposed[prop] || prop == "sandbox" && sandbox; exposeValue(sandbox, config, prop, value); } } else { exposeValue(sandbox, config, "sandbox", value); } return sandbox; } }); sinon.sandbox.useFakeXMLHttpRequest = sinon.sandbox.useFakeServer; if (typeof module == "object" && typeof require == "function") { module.exports = sinon.sandbox; } }()); /** * @depend ../sinon.js * @depend stub.js * @depend mock.js * @depend sandbox.js */ /*jslint eqeqeq: false, onevar: false, forin: true, plusplus: false*/ /*global module, require, sinon*/ /** * Test function, sandboxes fakes * * @author Christian Johansen (christian@cjohansen.no) * @license BSD * * Copyright (c) 2010-2013 Christian Johansen */ (function (sinon) { var commonJSModule = typeof module == "object" && typeof require == "function"; if (!sinon && commonJSModule) { sinon = require("../sinon"); } if (!sinon) { return; } function test(callback) { var type = typeof callback; if (type != "function") { throw new TypeError("sinon.test needs to wrap a test function, got " + type); } return function () { var config = sinon.getConfig(sinon.config); config.injectInto = config.injectIntoThis && this || config.injectInto; var sandbox = sinon.sandbox.create(config); var exception, result; var args = Array.prototype.slice.call(arguments).concat(sandbox.args); try { result = callback.apply(this, args); } catch (e) { exception = e; } if (typeof exception !== "undefined") { sandbox.restore(); throw exception; } else { sandbox.verifyAndRestore(); } return result; }; } test.config = { injectIntoThis: true, injectInto: null, properties: ["spy", "stub", "mock", "clock", "server", "requests"], useFakeTimers: true, useFakeServer: true }; if (commonJSModule) { module.exports = test; } else { sinon.test = test; } }(typeof sinon == "object" && sinon || null)); /** * @depend ../sinon.js * @depend test.js */ /*jslint eqeqeq: false, onevar: false, eqeqeq: false*/ /*global module, require, sinon*/ /** * Test case, sandboxes all test functions * * @author Christian Johansen (christian@cjohansen.no) * @license BSD * * Copyright (c) 2010-2013 Christian Johansen */ (function (sinon) { var commonJSModule = typeof module == "object" && typeof require == "function"; if (!sinon && commonJSModule) { sinon = require("../sinon"); } if (!sinon || !Object.prototype.hasOwnProperty) { return; } function createTest(property, setUp, tearDown) { return function () { if (setUp) { setUp.apply(this, arguments); } var exception, result; try { result = property.apply(this, arguments); } catch (e) { exception = e; } if (tearDown) { tearDown.apply(this, arguments); } if (exception) { throw exception; } return result; }; } function testCase(tests, prefix) { /*jsl:ignore*/ if (!tests || typeof tests != "object") { throw new TypeError("sinon.testCase needs an object with test functions"); } /*jsl:end*/ prefix = prefix || "test"; var rPrefix = new RegExp("^" + prefix); var methods = {}, testName, property, method; var setUp = tests.setUp; var tearDown = tests.tearDown; for (testName in tests) { if (tests.hasOwnProperty(testName)) { property = tests[testName]; if (/^(setUp|tearDown)$/.test(testName)) { continue; } if (typeof property == "function" && rPrefix.test(testName)) { method = property; if (setUp || tearDown) { method = createTest(property, setUp, tearDown); } methods[testName] = sinon.test(method); } else { methods[testName] = tests[testName]; } } } return methods; } if (commonJSModule) { module.exports = testCase; } else { sinon.testCase = testCase; } }(typeof sinon == "object" && sinon || null)); /** * @depend ../sinon.js * @depend stub.js */ /*jslint eqeqeq: false, onevar: false, nomen: false, plusplus: false*/ /*global module, require, sinon*/ /** * Assertions matching the test spy retrieval interface. * * @author Christian Johansen (christian@cjohansen.no) * @license BSD * * Copyright (c) 2010-2013 Christian Johansen */ (function (sinon, global) { var commonJSModule = typeof module == "object" && typeof require == "function"; var slice = Array.prototype.slice; var assert; if (!sinon && commonJSModule) { sinon = require("../sinon"); } if (!sinon) { return; } function verifyIsStub() { var method; for (var i = 0, l = arguments.length; i < l; ++i) { method = arguments[i]; if (!method) { assert.fail("fake is not a spy"); } if (typeof method != "function") { assert.fail(method + " is not a function"); } if (typeof method.getCall != "function") { assert.fail(method + " is not stubbed"); } } } function failAssertion(object, msg) { object = object || global; var failMethod = object.fail || assert.fail; failMethod.call(object, msg); } function mirrorPropAsAssertion(name, method, message) { if (arguments.length == 2) { message = method; method = name; } assert[name] = function (fake) { verifyIsStub(fake); var args = slice.call(arguments, 1); var failed = false; if (typeof method == "function") { failed = !method(fake); } else { failed = typeof fake[method] == "function" ? !fake[method].apply(fake, args) : !fake[method]; } if (failed) { failAssertion(this, fake.printf.apply(fake, [message].concat(args))); } else { assert.pass(name); } }; } function exposedName(prefix, prop) { return !prefix || /^fail/.test(prop) ? prop : prefix + prop.slice(0, 1).toUpperCase() + prop.slice(1); }; assert = { failException: "AssertError", fail: function fail(message) { var error = new Error(message); error.name = this.failException || assert.failException; throw error; }, pass: function pass(assertion) {}, callOrder: function assertCallOrder() { verifyIsStub.apply(null, arguments); var expected = "", actual = ""; if (!sinon.calledInOrder(arguments)) { try { expected = [].join.call(arguments, ", "); var calls = slice.call(arguments); var i = calls.length; while (i) { if (!calls[--i].called) { calls.splice(i, 1); } } actual = sinon.orderByFirstCall(calls).join(", "); } catch (e) { // If this fails, we'll just fall back to the blank string } failAssertion(this, "expected " + expected + " to be " + "called in order but were called as " + actual); } else { assert.pass("callOrder"); } }, callCount: function assertCallCount(method, count) { verifyIsStub(method); if (method.callCount != count) { var msg = "expected %n to be called " + sinon.timesInWords(count) + " but was called %c%C"; failAssertion(this, method.printf(msg)); } else { assert.pass("callCount"); } }, expose: function expose(target, options) { if (!target) { throw new TypeError("target is null or undefined"); } var o = options || {}; var prefix = typeof o.prefix == "undefined" && "assert" || o.prefix; var includeFail = typeof o.includeFail == "undefined" || !!o.includeFail; for (var method in this) { if (method != "export" && (includeFail || !/^(fail)/.test(method))) { target[exposedName(prefix, method)] = this[method]; } } return target; } }; mirrorPropAsAssertion("called", "expected %n to have been called at least once but was never called"); mirrorPropAsAssertion("notCalled", function (spy) { return !spy.called; }, "expected %n to not have been called but was called %c%C"); mirrorPropAsAssertion("calledOnce", "expected %n to be called once but was called %c%C"); mirrorPropAsAssertion("calledTwice", "expected %n to be called twice but was called %c%C"); mirrorPropAsAssertion("calledThrice", "expected %n to be called thrice but was called %c%C"); mirrorPropAsAssertion("calledOn", "expected %n to be called with %1 as this but was called with %t"); mirrorPropAsAssertion("alwaysCalledOn", "expected %n to always be called with %1 as this but was called with %t"); mirrorPropAsAssertion("calledWithNew", "expected %n to be called with new"); mirrorPropAsAssertion("alwaysCalledWithNew", "expected %n to always be called with new"); mirrorPropAsAssertion("calledWith", "expected %n to be called with arguments %*%C"); mirrorPropAsAssertion("calledWithMatch", "expected %n to be called with match %*%C"); mirrorPropAsAssertion("alwaysCalledWith", "expected %n to always be called with arguments %*%C"); mirrorPropAsAssertion("alwaysCalledWithMatch", "expected %n to always be called with match %*%C"); mirrorPropAsAssertion("calledWithExactly", "expected %n to be called with exact arguments %*%C"); mirrorPropAsAssertion("alwaysCalledWithExactly", "expected %n to always be called with exact arguments %*%C"); mirrorPropAsAssertion("neverCalledWith", "expected %n to never be called with arguments %*%C"); mirrorPropAsAssertion("neverCalledWithMatch", "expected %n to never be called with match %*%C"); mirrorPropAsAssertion("threw", "%n did not throw exception%C"); mirrorPropAsAssertion("alwaysThrew", "%n did not always throw exception%C"); if (commonJSModule) { module.exports = assert; } else { sinon.assert = assert; } }(typeof sinon == "object" && sinon || null, typeof window != "undefined" ? window : (typeof self != "undefined") ? self : global)); return sinon;}.call(typeof window != 'undefined' && window || {})); ================================================ FILE: test/runner/mocha.js ================================================ (function() { var runner = mocha.run(); if(window.PHANTOMJS) { runner.on('test', function(test) { sendMessage('testStart', test.title); }); runner.on('test end', function(test) { sendMessage('testDone', test.title, test.state); }); runner.on('suite', function(suite) { sendMessage('suiteStart', suite.title); }); runner.on('suite end', function(suite) { if (suite.root) return; sendMessage('suiteDone', suite.title); }); runner.on('fail', function(test, err) { sendMessage('testFail', test.title, err); }); runner.on('end', function() { var output = { failed : this.failures, passed : this.total - this.failures, total : this.total }; sendMessage('done', output.failed,output.passed, output.total); }); function sendMessage() { var args = [].slice.call(arguments); alert(JSON.stringify(args)); } } })(); ================================================ FILE: test/spec/LargeLocalStorageTest.js ================================================ (function(lls) { Q.longStackSupport = true; Q.onerror = function(err) { console.log(err); throw err; }; var storage = new lls({ size: 10 * 1024 * 1024, name: 'lls-test' // forceProvider: 'WebSQL' // force a desired provider. }); // for debug // window.storage = storage; function getAttachment(a, cb) { var xhr = new XMLHttpRequest(), blob; xhr.open("GET", a, true); var is_safari = navigator.userAgent.indexOf("Safari") > -1; if (is_safari) { xhr.responseType = "arraybuffer"; } else { xhr.responseType = "blob"; } xhr.addEventListener("load", function () { if (xhr.status === 200) { if (is_safari) { blob = new Blob([xhr.response], {type: 'image/jpeg'}); } else { blob = xhr.response; } cb(blob); } }, false); xhr.send(); } describe('LargeLocalStorage', function() { it('Allows string contents to be set and read', function(done) { storage.setContents("testFile", "contents").then(function() { return storage.getContents("testFile"); }).then(function(contents) { expect(contents).to.equal("contents"); }).done(done); }); it('Allows js objects to be set and read', function(done) { var jsondoc = { a: 1, b: 2, c: {a: true} }; storage.setContents("testfile2", jsondoc, {json:true}).then(function() { return storage.getContents("testfile2", {json:true}); }).then(function(contents) { expect(jsondoc).to.eql(contents); }).done(done); }); it('Allows items to be deleted', function(done) { storage.setContents("testfile3", "contents").then(function() { return storage.rm("testfile3"); }).then(function() { return storage.getContents("testfile3"); }).then(function(contents) { expect(contents).to.equal(undefined); }).done(done); }); it('Allows attachments to be written, read', function(done) { getAttachment("elephant.jpg", function(blob) { storage.setContents("testfile4", "file...").then(function() { return storage.setAttachment("testfile4", "ele", blob); }).then(function() { return storage.getAttachment("testfile4", "ele"); }).then(function(attach) { expect(attach instanceof Blob).to.equal(true); }).done(done); }); }); // Apparently these tests are being run sequentially... // so taking advantage of that. it('Allows us to get attachments as urls', function(done) { storage.getAttachmentURL("testfile4", "ele").then(function(url) { // urls are pretty opaque since they could be from // filesystem api, indexeddb, or websql // meaning there isn't much we can do to verify them // besides ensure that they are strings. expect(typeof url === 'string').to.equal(true); $(document.body).append(''); }).done(done); }); it('Allows attachments to be deleted', function(done) { storage.rmAttachment("testfile4", "ele").then(function() { // .done will throw any errors and fail the test for us if // something went wrong. }).done(done); }); it('Removes all attachments when removing a file', function(done) { getAttachment("pie.jpg", function(blob) { storage.setContents("testfile5", "fileo").then(function() { return storage.setAttachment("testfile5", "pie", blob); }).then(function() { return storage.setAttachment("testfile5", "pie2", blob); }).then(function() { return storage.rm("testfile5"); }).then(function() { return storage.getAttachment("testfile5", "pie"); }).then(function(val) { expect(val).to.equal(undefined); storage.getAttachment("testfile5", "pie2") .then(function(a) { expect(a).to.equal(undefined); }).done(done); }).done(); }); }); it('Allows one to revoke attachment urls', function() { storage.revokeAttachmentURL(''); }); it('Allows all attachments to be gotten in one shot', function(done) { var c = countdown(2, continuation); getAttachment("pie.jpg", function(pie) { c(pie); }); getAttachment("elephant.jpg", function(ele) { c(ele); }); function continuation(blob1, blob2) { Q.all([ storage.setAttachment("testfile6", "blob1", blob1), storage.setAttachment("testfile6", "blob2", blob2) ]).then(function() { return storage.getAllAttachments("testfile6"); }).then(function(attachments) { expect(attachments.length).to.equal(2); expect(attachments[0].docKey).to.equal('testfile6'); expect(attachments[1].docKey).to.equal('testfile6'); expect(attachments[0].attachKey.indexOf('blob')).to.equal(0); expect(attachments[1].attachKey.indexOf('blob')).to.equal(0); }).done(done); } }); it('Allows all attachment urls to be gotten in one shot', function(done) { storage.getAllAttachmentURLs('testfile6').then(function(urls) { expect(urls.length).to.equal(2); urls.forEach(function(url) { $(document.body).append(''); }); }).done(done); }); it('Allows us to ls the attachments on a document', function(done) { storage.ls('testfile6').then(function(listing) { expect(listing.length).to.equal(2); expect(listing[0] == 'blob1' || listing[0] == 'blob2').to.equal(true); expect(listing[1] == 'blob1' || listing[1] == 'blob2').to.equal(true); }).done(done); }); // TODO: create a new db to test on so this isn't // broken when updating other tests it('Allows us to ls for all docs', function(done) { storage.ls().then(function(listing) { expect(listing.indexOf('testfile4')).to.not.equal(-1); expect(listing.indexOf('testFile')).to.not.equal(-1); expect(listing.indexOf('testfile2')).to.not.equal(-1); expect(listing.length).to.equal(3); }).done(done); }); it('Allows us to clear out the entire storage', function(done) { storage.clear().then(function() { var scb = countdown(2, function(value) { if (value != undefined) throw new Error('Files were not removed.'); done(); }); var ecb = function(err) { throw new Error('getting missing documents should not return an error'); }; storage.getContents('testfile4').then(scb, ecb); storage.getContents('testfile2').then(scb, ecb); }).done(); }); describe('Data Migration', function() { it('Allows us to copy data when the implementation changes', function(done) { var available = lls.availableProviders; if (available.length >= 2) testDataMigration(done, available); else done(); }); }); }); function testDataMigration(done, availableProviders) { var fromStorage = new lls({ name: 'lls-migration-test', forceProvider: availableProviders[0] }); var toStorage; var test1doc = 'Allo Allo'; var test2doc = 'Ello Ello'; var test1a1txt = '123asd'; var test1a2txt = 'sdfsdfsdf'; var test1a1 = new Blob([test1a1txt], {type: 'text/plain'}); var test1a2 = new Blob([test1a2txt], {type: 'text/plain'}); fromStorage.initialized.then(function() { return fromStorage.setContents('test1', test1doc); }).then(function() { return fromStorage.setContents('test2', test2doc); }).then(function() { return fromStorage.setAttachment('test1', 'a1', test1a1); }).then(function() { return fromStorage.setAttachment('test1', 'a2', test1a2); }).then(function() { var deferred = Q.defer(); toStorage = new lls({ name: 'lls-migration-test', forceProvider: availableProviders[1], migrate: lls.copyOldData, migrationComplete: function(err) { deferred.resolve(); } }); console.log('Migrating to: ' + availableProviders[1] + ' From: ' + availableProviders[0]); return deferred.promise; }).then(function() { return toStorage.getContents('test1'); }).then(function(content) { expect(content).to.eql(test1doc); return toStorage.getContents('test2'); }).then(function(content) { expect(content).to.eql(test2doc); return toStorage.getAttachment('test1', 'a1'); }).then(function(attachment) { var deferred = Q.defer(); var r = new FileReader(); r.addEventListener("loadend", function() { expect(r.result).to.eql(test1a1txt); toStorage.getAttachment('test1', 'a2').then(deferred.resolve, deferred.reject) }); r.readAsText(attachment); return deferred.promise; }).then(function(attachment) { var r = new FileReader(); r.addEventListener("loadend", function() { console.log(r.result); expect(r.result).to.eql(test1a2txt); Q.all([fromStorage.clear(), toStorage.clear()]).done(function() {done();}); }); console.log('Attach: ' + attachment); r.readAsText(attachment); }).done(); } function getAvailableImplementations() { var deferred = Q.defer(); var available = []; var potentialProviders = Object.keys(lls._providers); var latch = countdown(potentialProviders.length, function() { deferred.resolve(available); }); potentialProviders.forEach(function(potentialProvider) { lls._providers[potentialProvider].init({name: 'lls-test-avail'}).then(function() { available.push(potentialProvider); latch(); }, function() { latch(); }) }); return deferred.promise; } storage.initialized.then(function() { storage.clear().then(function() { window.runMocha(); }).catch(function(err) { console.log(err); }); }, function(err) { console.log(err); alert('Could not initialize storage. Did you not authorize it? ' + err); }); })(LargeLocalStorage); ================================================ FILE: test/spec/URLCacheTest.js ================================================ (function(lls) { function fail(err) { console.log(err); expect(true).to.equal(false); } var blob = new Blob(['

    worthless

    '], {type: 'text/html'}); var storage = new lls({name: 'lls-urlcache-test', size: 10 * 1024 * 1024}); LargeLocalStorage.contrib.URLCache.addTo(storage); var cacheObj = storage.pipe.getHandler('URLCache').cache; // for debug // window.cacheObj = cacheObj; // window.storage = storage; // TODO: spy on LargeLocalStorage to ensure that // revokeAttachmentURL is being called. // And also spy to make sure piped methods are receiving their calls. function loadTests() { describe('URLCache', function() { it('Caches getAttachmentURL operations', function(done) { storage.setAttachment('doc', 'attach', blob) .then(function() { console.log('Getting attach url'); return storage.getAttachmentURL('doc', 'attach'); }) .then(function(url) { console.log('Comparison'); expect(url).to.equal(cacheObj.main.doc.attach); expect(cacheObj.reverse[url]).to.eql({ docKey: 'doc', attachKey: 'attach' }); }).done(done); }); it('Removes the URL from the cache when updating the attachment', function(done) { storage.setAttachment('doc', 'attach', blob) .then(function() { expect(cacheObj.main.doc.attach).to.equal(undefined); expect(cacheObj.reverse).to.eql({}); }).done(done); }); it('Removes the URL from the cache when removing the attachment', function(done) { var theUrl; storage.getAttachmentURL('doc', 'attach').then(function(url) { expect(url).to.equal(cacheObj.main.doc.attach); theUrl = url; return storage.rmAttachment('doc', 'attach'); }).then(function() { expect(cacheObj.main.doc.attach).to.equal(undefined); expect(cacheObj.reverse[theUrl]).to.equal(undefined); }).done(done); }); it('Removes the URL from the cache when removing the attachment via removing the host document', function(done) { storage.setAttachment('doc2', 'attach', blob) .then(function() { return storage.rm('doc2'); }).then(function() { expect(cacheObj.main.doc2).to.equal(undefined); expect(cacheObj.reverse).to.eql({}); }).done(done); }); it('Removes the URL from the cache when revoking the URL', function(done) { storage.setAttachment('doc3', 'attach', blob) .then(function() { return storage.getAttachmentURL('doc3', 'attach'); }).then(function(url) { expect(url).to.equal(cacheObj.main.doc3.attach); expect(cacheObj.reverse[url]).to.eql({ docKey: 'doc3', attachKey: 'attach' }); storage.revokeAttachmentURL(url); expect(cacheObj.main.doc3.attach).to.equal(undefined); expect(cacheObj.reverse).to.eql({}); }).done(done); }); it('Removes all URLs when emptying the database', function(done) { Q.all([storage.setAttachment('doc4', 'attach', blob), storage.setAttachment('doc5', 'attach', blob)]) .then(function() { return storage.clear(); }).then(function() { expect(cacheObj.reverse).to.eql({}); expect(cacheObj.main).to.eql({}); }).done(done); }); }); } loadTests(); storage.initialized.then(function() { window.runMocha(); }); })(LargeLocalStorage);