added unit testing, and started implementing unit tests...phew
This commit is contained in:
+18
@@ -0,0 +1,18 @@
|
||||
|
||||
1.4.1 / 2018-06-03
|
||||
==================
|
||||
|
||||
* Fix issue where no parsererror is returned on invalid XML (#55)
|
||||
|
||||
1.4.0 / 2018-06-01
|
||||
==================
|
||||
|
||||
* Throw an explicit error if the header value is not a string (#53)
|
||||
* Pass responseType for defaked XHR (#45)
|
||||
* Correctly handle case where fake XHR returns invalid XML (#50)
|
||||
|
||||
|
||||
|
||||
Older releases need descriptions
|
||||
================================
|
||||
Feel free to help out with a PR!
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
(The BSD License)
|
||||
|
||||
Copyright (c) 2010-2017, 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.
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
# nise (偽) [](https://www.npmjs.com/package/nise) [](https://travis-ci.org/sinonjs/nise)
|
||||
|
||||
fake XHR and Server
|
||||
|
||||
Documentation: http://sinonjs.github.io/nise/
|
||||
|
||||
## Backers
|
||||
|
||||
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/sinon#backer)]
|
||||
|
||||
<a href="https://opencollective.com/sinon/backer/0/website" target="_blank"><img src="https://opencollective.com/sinon/backer/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/1/website" target="_blank"><img src="https://opencollective.com/sinon/backer/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/2/website" target="_blank"><img src="https://opencollective.com/sinon/backer/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/3/website" target="_blank"><img src="https://opencollective.com/sinon/backer/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/4/website" target="_blank"><img src="https://opencollective.com/sinon/backer/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/5/website" target="_blank"><img src="https://opencollective.com/sinon/backer/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/6/website" target="_blank"><img src="https://opencollective.com/sinon/backer/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/7/website" target="_blank"><img src="https://opencollective.com/sinon/backer/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/8/website" target="_blank"><img src="https://opencollective.com/sinon/backer/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/9/website" target="_blank"><img src="https://opencollective.com/sinon/backer/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/10/website" target="_blank"><img src="https://opencollective.com/sinon/backer/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/11/website" target="_blank"><img src="https://opencollective.com/sinon/backer/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/12/website" target="_blank"><img src="https://opencollective.com/sinon/backer/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/13/website" target="_blank"><img src="https://opencollective.com/sinon/backer/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/14/website" target="_blank"><img src="https://opencollective.com/sinon/backer/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/15/website" target="_blank"><img src="https://opencollective.com/sinon/backer/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/16/website" target="_blank"><img src="https://opencollective.com/sinon/backer/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/17/website" target="_blank"><img src="https://opencollective.com/sinon/backer/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/18/website" target="_blank"><img src="https://opencollective.com/sinon/backer/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/19/website" target="_blank"><img src="https://opencollective.com/sinon/backer/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/20/website" target="_blank"><img src="https://opencollective.com/sinon/backer/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/21/website" target="_blank"><img src="https://opencollective.com/sinon/backer/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/22/website" target="_blank"><img src="https://opencollective.com/sinon/backer/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/23/website" target="_blank"><img src="https://opencollective.com/sinon/backer/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/24/website" target="_blank"><img src="https://opencollective.com/sinon/backer/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/25/website" target="_blank"><img src="https://opencollective.com/sinon/backer/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/26/website" target="_blank"><img src="https://opencollective.com/sinon/backer/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/27/website" target="_blank"><img src="https://opencollective.com/sinon/backer/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/28/website" target="_blank"><img src="https://opencollective.com/sinon/backer/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/backer/29/website" target="_blank"><img src="https://opencollective.com/sinon/backer/29/avatar.svg"></a>
|
||||
|
||||
|
||||
## Sponsors
|
||||
|
||||
Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor](https://opencollective.com/sinon#sponsor)]
|
||||
|
||||
<a href="https://opencollective.com/sinon/sponsor/0/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/1/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/2/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/3/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/4/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/5/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/6/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/7/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/8/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/9/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/10/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/11/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/12/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/13/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/14/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/15/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/16/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/17/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/18/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/19/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/20/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/21/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/22/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/23/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/24/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/25/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/26/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/27/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/28/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/sinon/sponsor/29/website" target="_blank"><img src="https://opencollective.com/sinon/sponsor/29/avatar.svg"></a>
|
||||
|
||||
## Licence
|
||||
|
||||
nise was released under [BSD-3](LICENSE)
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
"use strict";
|
||||
|
||||
// cache a reference to setTimeout, so that our reference won't be stubbed out
|
||||
// when using fake timers and errors will still get logged
|
||||
// https://github.com/cjohansen/Sinon.JS/issues/381
|
||||
var realSetTimeout = setTimeout;
|
||||
|
||||
function configureLogger(config) {
|
||||
config = config || {};
|
||||
// Function which prints errors.
|
||||
if (!config.hasOwnProperty("logger")) {
|
||||
config.logger = function () { };
|
||||
}
|
||||
// When set to true, any errors logged will be thrown immediately;
|
||||
// If set to false, the errors will be thrown in separate execution frame.
|
||||
if (!config.hasOwnProperty("useImmediateExceptions")) {
|
||||
config.useImmediateExceptions = true;
|
||||
}
|
||||
// wrap realSetTimeout with something we can stub in tests
|
||||
if (!config.hasOwnProperty("setTimeout")) {
|
||||
config.setTimeout = realSetTimeout;
|
||||
}
|
||||
|
||||
return function logError(label, e) {
|
||||
var msg = label + " threw exception: ";
|
||||
var err = { name: e.name || label, message: e.message || e.toString(), stack: e.stack };
|
||||
|
||||
function throwLoggedError() {
|
||||
err.message = msg + err.message;
|
||||
throw err;
|
||||
}
|
||||
|
||||
config.logger(msg + "[" + err.name + "] " + err.message);
|
||||
|
||||
if (err.stack) {
|
||||
config.logger(err.stack);
|
||||
}
|
||||
|
||||
if (config.useImmediateExceptions) {
|
||||
throwLoggedError();
|
||||
} else {
|
||||
config.setTimeout(throwLoggedError, 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = configureLogger;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
"use strict";
|
||||
|
||||
var Event = require("./event");
|
||||
|
||||
function CustomEvent(type, customData, target) {
|
||||
this.initEvent(type, false, false, target);
|
||||
this.detail = customData.detail || null;
|
||||
}
|
||||
|
||||
CustomEvent.prototype = new Event();
|
||||
|
||||
CustomEvent.prototype.constructor = CustomEvent;
|
||||
|
||||
module.exports = CustomEvent;
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
"use strict";
|
||||
|
||||
function flattenOptions(options) {
|
||||
if (options !== Object(options)) {
|
||||
return {
|
||||
capture: Boolean(options),
|
||||
once: false,
|
||||
passive: false
|
||||
};
|
||||
}
|
||||
return {
|
||||
capture: Boolean(options.capture),
|
||||
once: Boolean(options.once),
|
||||
passive: Boolean(options.passive)
|
||||
};
|
||||
}
|
||||
function not(fn) {
|
||||
return function () {
|
||||
return !fn.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
function hasListenerFilter(listener, capture) {
|
||||
return function (listenerSpec) {
|
||||
return listenerSpec.capture === capture
|
||||
&& listenerSpec.listener === listener;
|
||||
};
|
||||
}
|
||||
|
||||
var EventTarget = {
|
||||
// https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener
|
||||
addEventListener: function addEventListener(event, listener, providedOptions) {
|
||||
// 3. Let capture, passive, and once be the result of flattening more options.
|
||||
// Flatten property before executing step 2,
|
||||
// feture detection is usually based on registering handler with options object,
|
||||
// that has getter defined
|
||||
// addEventListener("load", () => {}, {
|
||||
// get once() { supportsOnce = true; }
|
||||
// });
|
||||
var options = flattenOptions(providedOptions);
|
||||
|
||||
// 2. If callback is null, then return.
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.eventListeners = this.eventListeners || {};
|
||||
this.eventListeners[event] = this.eventListeners[event] || [];
|
||||
|
||||
// 4. If context object’s associated list of event listener
|
||||
// does not contain an event listener whose type is type,
|
||||
// callback is callback, and capture is capture, then append
|
||||
// a new event listener to it, whose type is type, callback is
|
||||
// callback, capture is capture, passive is passive, and once is once.
|
||||
if (!this.eventListeners[event].some(hasListenerFilter(listener, options.capture))) {
|
||||
this.eventListeners[event].push({
|
||||
listener: listener,
|
||||
capture: options.capture,
|
||||
once: options.once
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener
|
||||
removeEventListener: function removeEventListener(event, listener, providedOptions) {
|
||||
if (!this.eventListeners || !this.eventListeners[event]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Let capture be the result of flattening options.
|
||||
var options = flattenOptions(providedOptions);
|
||||
|
||||
// 3. If there is an event listener in the associated list of
|
||||
// event listeners whose type is type, callback is callback,
|
||||
// and capture is capture, then set that event listener’s
|
||||
// removed to true and remove it from the associated list of event listeners.
|
||||
this.eventListeners[event] = this.eventListeners[event]
|
||||
.filter(not(hasListenerFilter(listener, options.capture)));
|
||||
},
|
||||
|
||||
dispatchEvent: function dispatchEvent(event) {
|
||||
if (!this.eventListeners || !this.eventListeners[event.type]) {
|
||||
return Boolean(event.defaultPrevented);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var type = event.type;
|
||||
var listeners = self.eventListeners[type];
|
||||
|
||||
// Remove listeners, that should be dispatched once
|
||||
// before running dispatch loop to avoid nested dispatch issues
|
||||
self.eventListeners[type] = listeners.filter(function (listenerSpec) {
|
||||
return !listenerSpec.once;
|
||||
});
|
||||
listeners.forEach(function (listenerSpec) {
|
||||
var listener = listenerSpec.listener;
|
||||
if (typeof listener === "function") {
|
||||
listener.call(self, event);
|
||||
} else {
|
||||
listener.handleEvent(event);
|
||||
}
|
||||
});
|
||||
|
||||
return Boolean(event.defaultPrevented);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = EventTarget;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
"use strict";
|
||||
|
||||
function Event(type, bubbles, cancelable, target) {
|
||||
this.initEvent(type, bubbles, cancelable, target);
|
||||
}
|
||||
|
||||
Event.prototype = {
|
||||
initEvent: function (type, bubbles, cancelable, target) {
|
||||
this.type = type;
|
||||
this.bubbles = bubbles;
|
||||
this.cancelable = cancelable;
|
||||
this.target = target;
|
||||
this.currentTarget = target;
|
||||
},
|
||||
|
||||
stopPropagation: function () {},
|
||||
|
||||
preventDefault: function () {
|
||||
this.defaultPrevented = true;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Event;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
Event: require("./event"),
|
||||
ProgressEvent: require("./progress-event"),
|
||||
CustomEvent: require("./custom-event"),
|
||||
EventTarget: require("./event-target")
|
||||
};
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
|
||||
var Event = require("./event");
|
||||
|
||||
function ProgressEvent(type, progressEventRaw, target) {
|
||||
this.initEvent(type, false, false, target);
|
||||
this.loaded = typeof progressEventRaw.loaded === "number" ? progressEventRaw.loaded : null;
|
||||
this.total = typeof progressEventRaw.total === "number" ? progressEventRaw.total : null;
|
||||
this.lengthComputable = !!progressEventRaw.total;
|
||||
}
|
||||
|
||||
ProgressEvent.prototype = new Event();
|
||||
|
||||
ProgressEvent.prototype.constructor = ProgressEvent;
|
||||
|
||||
module.exports = ProgressEvent;
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
"use strict";
|
||||
|
||||
var lolex = require("lolex");
|
||||
var fakeServer = require("./index");
|
||||
|
||||
function Server() {}
|
||||
Server.prototype = fakeServer;
|
||||
|
||||
var fakeServerWithClock = new Server();
|
||||
|
||||
fakeServerWithClock.addRequest = function addRequest(xhr) {
|
||||
if (xhr.async) {
|
||||
if (typeof setTimeout.clock === "object") {
|
||||
this.clock = setTimeout.clock;
|
||||
} else {
|
||||
this.clock = lolex.install();
|
||||
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 fakeServer.addRequest.call(this, xhr);
|
||||
};
|
||||
|
||||
fakeServerWithClock.respond = function respond() {
|
||||
var returnVal = fakeServer.respond.apply(this, arguments);
|
||||
|
||||
if (this.clock) {
|
||||
this.clock.tick(this.longestTimeout || 0);
|
||||
this.longestTimeout = 0;
|
||||
|
||||
if (this.resetClock) {
|
||||
this.clock.uninstall();
|
||||
this.resetClock = false;
|
||||
}
|
||||
}
|
||||
|
||||
return returnVal;
|
||||
};
|
||||
|
||||
fakeServerWithClock.restore = function restore() {
|
||||
if (this.clock) {
|
||||
this.clock.uninstall();
|
||||
}
|
||||
|
||||
return fakeServer.restore.apply(this, arguments);
|
||||
};
|
||||
|
||||
module.exports = fakeServerWithClock;
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
"use strict";
|
||||
|
||||
var formatio = require("@sinonjs/formatio");
|
||||
|
||||
var formatter = formatio.configure({
|
||||
quoteStrings: false,
|
||||
limitChildrenCount: 250
|
||||
});
|
||||
|
||||
module.exports = function format() {
|
||||
return formatter.ascii.apply(formatter, arguments);
|
||||
};
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
"use strict";
|
||||
|
||||
var fakeXhr = require("../fake-xhr");
|
||||
var push = [].push;
|
||||
var format = require("./format");
|
||||
var configureLogError = require("../configure-logger");
|
||||
var pathToRegexp = require("path-to-regexp");
|
||||
|
||||
var supportsArrayBuffer = typeof ArrayBuffer !== "undefined";
|
||||
|
||||
function responseArray(handler) {
|
||||
var response = handler;
|
||||
|
||||
if (Object.prototype.toString.call(handler) !== "[object Array]") {
|
||||
response = [200, {}, handler];
|
||||
}
|
||||
|
||||
if (typeof response[2] !== "string") {
|
||||
if (!supportsArrayBuffer) {
|
||||
throw new TypeError("Fake server response body should be a string, but was " +
|
||||
typeof response[2]);
|
||||
}
|
||||
else if (!(response[2] instanceof ArrayBuffer)) {
|
||||
throw new TypeError("Fake server response body should be a string or ArrayBuffer, but was " +
|
||||
typeof response[2]);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
function getDefaultWindowLocation() {
|
||||
return { "host": "localhost", "protocol": "http" };
|
||||
}
|
||||
|
||||
function getWindowLocation() {
|
||||
if (typeof window === "undefined") {
|
||||
// Fallback
|
||||
return getDefaultWindowLocation();
|
||||
}
|
||||
|
||||
if (typeof window.location !== "undefined") {
|
||||
// Browsers place location on window
|
||||
return window.location;
|
||||
}
|
||||
|
||||
if ((typeof window.window !== "undefined") && (typeof window.window.location !== "undefined")) {
|
||||
// React Native on Android places location on window.window
|
||||
return window.window.location;
|
||||
}
|
||||
|
||||
return getDefaultWindowLocation();
|
||||
}
|
||||
|
||||
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 wloc = getWindowLocation();
|
||||
|
||||
var rCurrLoc = new RegExp("^" + wloc.protocol + "//" + wloc.host);
|
||||
|
||||
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 && typeof ru.exec === "function" ? ru.exec(requestUrl).slice(1) : []);
|
||||
return response.response.apply(response, args);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function incrementRequestCount() {
|
||||
var count = ++this.requestCount;
|
||||
|
||||
this.requested = true;
|
||||
|
||||
this.requestedOnce = count === 1;
|
||||
this.requestedTwice = count === 2;
|
||||
this.requestedThrice = count === 3;
|
||||
|
||||
this.firstRequest = this.getRequest(0);
|
||||
this.secondRequest = this.getRequest(1);
|
||||
this.thirdRequest = this.getRequest(2);
|
||||
|
||||
this.lastRequest = this.getRequest(count - 1);
|
||||
}
|
||||
|
||||
var fakeServer = {
|
||||
create: function (config) {
|
||||
var server = Object.create(this);
|
||||
server.configure(config);
|
||||
this.xhr = fakeXhr.useFakeXMLHttpRequest();
|
||||
server.requests = [];
|
||||
server.requestCount = 0;
|
||||
server.queue = [];
|
||||
server.responses = [];
|
||||
|
||||
|
||||
this.xhr.onCreate = function (xhrObj) {
|
||||
xhrObj.unsafeHeadersEnabled = function () {
|
||||
return !(server.unsafeHeadersEnabled === false);
|
||||
};
|
||||
server.addRequest(xhrObj);
|
||||
};
|
||||
|
||||
return server;
|
||||
},
|
||||
|
||||
configure: function (config) {
|
||||
var self = this;
|
||||
var whitelist = {
|
||||
"autoRespond": true,
|
||||
"autoRespondAfter": true,
|
||||
"respondImmediately": true,
|
||||
"fakeHTTPMethods": true,
|
||||
"logger": true,
|
||||
"unsafeHeadersEnabled": true
|
||||
};
|
||||
|
||||
config = config || {};
|
||||
|
||||
Object.keys(config).forEach(function (setting) {
|
||||
if (setting in whitelist) {
|
||||
self[setting] = config[setting];
|
||||
}
|
||||
});
|
||||
|
||||
self.logError = configureLogError(config);
|
||||
},
|
||||
|
||||
addRequest: function addRequest(xhrObj) {
|
||||
var server = this;
|
||||
push.call(this.requests, xhrObj);
|
||||
|
||||
incrementRequestCount.call(this);
|
||||
|
||||
xhrObj.onSend = function () {
|
||||
server.handleRequest(this);
|
||||
|
||||
if (server.respondImmediately) {
|
||||
server.respond();
|
||||
} else if (server.autoRespond && !server.responding) {
|
||||
setTimeout(function () {
|
||||
server.responding = false;
|
||||
server.respond();
|
||||
}, server.autoRespondAfter || 10);
|
||||
|
||||
server.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) {
|
||||
push.call(this.queue, xhr);
|
||||
} else {
|
||||
this.processRequest(xhr);
|
||||
}
|
||||
},
|
||||
|
||||
logger: function () {
|
||||
// no-op; override via configure()
|
||||
},
|
||||
|
||||
logError: configureLogError({}),
|
||||
|
||||
log: function log(response, request) {
|
||||
var str;
|
||||
|
||||
str = "Request:\n" + format(request) + "\n\n";
|
||||
str += "Response:\n" + format(response) + "\n\n";
|
||||
|
||||
if (typeof this.logger === "function") {
|
||||
this.logger(str);
|
||||
}
|
||||
},
|
||||
|
||||
respondWith: function respondWith(method, url, body) {
|
||||
if (arguments.length === 1 && typeof method !== "function") {
|
||||
this.response = responseArray(method);
|
||||
return;
|
||||
}
|
||||
|
||||
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: typeof url === "string" && url !== "" ? pathToRegexp(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 requests = queue.splice(0, queue.length);
|
||||
var self = this;
|
||||
|
||||
requests.forEach(function (request) {
|
||||
self.processRequest(request);
|
||||
});
|
||||
},
|
||||
|
||||
processRequest: function processRequest(request) {
|
||||
try {
|
||||
if (request.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
var response = this.response || [404, {}, ""];
|
||||
|
||||
if (this.responses) {
|
||||
for (var l = this.responses.length, i = l - 1; i >= 0; i--) {
|
||||
if (match.call(this, this.responses[i], request)) {
|
||||
response = this.responses[i].response;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (request.readyState !== 4) {
|
||||
this.log(response, request);
|
||||
|
||||
request.respond(response[0], response[1], response[2]);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logError("Fake server request processing", e);
|
||||
}
|
||||
},
|
||||
|
||||
restore: function restore() {
|
||||
return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments);
|
||||
},
|
||||
|
||||
getRequest: function getRequest(index) {
|
||||
return this.requests[index] || null;
|
||||
},
|
||||
|
||||
reset: function reset() {
|
||||
this.resetBehavior();
|
||||
this.resetHistory();
|
||||
},
|
||||
|
||||
resetBehavior: function resetBehavior() {
|
||||
this.responses.length = this.queue.length = 0;
|
||||
},
|
||||
|
||||
resetHistory: function resetHistory() {
|
||||
this.requests.length = this.requestCount = 0;
|
||||
|
||||
this.requestedOnce = this.requestedTwice = this.requestedThrice = this.requested = false;
|
||||
|
||||
this.firstRequest = this.secondRequest = this.thirdRequest = this.lastRequest = null;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = fakeServer;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/*global Blob */
|
||||
"use strict";
|
||||
|
||||
exports.isSupported = (function () {
|
||||
try {
|
||||
return !!new Blob();
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}());
|
||||
+777
@@ -0,0 +1,777 @@
|
||||
"use strict";
|
||||
|
||||
var TextEncoder = require("@sinonjs/text-encoding").TextEncoder;
|
||||
|
||||
var configureLogError = require("../configure-logger");
|
||||
var sinonEvent = require("../event");
|
||||
var extend = require("just-extend");
|
||||
|
||||
function getWorkingXHR(globalScope) {
|
||||
var supportsXHR = typeof globalScope.XMLHttpRequest !== "undefined";
|
||||
if (supportsXHR) {
|
||||
return globalScope.XMLHttpRequest;
|
||||
}
|
||||
|
||||
var supportsActiveX = typeof globalScope.ActiveXObject !== "undefined";
|
||||
if (supportsActiveX) {
|
||||
return function () {
|
||||
return new globalScope.ActiveXObject("MSXML2.XMLHTTP.3.0");
|
||||
};
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var supportsProgress = typeof ProgressEvent !== "undefined";
|
||||
var supportsCustomEvent = typeof CustomEvent !== "undefined";
|
||||
var supportsFormData = typeof FormData !== "undefined";
|
||||
var supportsArrayBuffer = typeof ArrayBuffer !== "undefined";
|
||||
var supportsBlob = require("./blob").isSupported;
|
||||
var isReactNative = global.navigator && global.navigator.product === "ReactNative";
|
||||
var sinonXhr = { XMLHttpRequest: global.XMLHttpRequest };
|
||||
sinonXhr.GlobalXMLHttpRequest = global.XMLHttpRequest;
|
||||
sinonXhr.GlobalActiveXObject = global.ActiveXObject;
|
||||
sinonXhr.supportsActiveX = typeof sinonXhr.GlobalActiveXObject !== "undefined";
|
||||
sinonXhr.supportsXHR = typeof sinonXhr.GlobalXMLHttpRequest !== "undefined";
|
||||
sinonXhr.workingXHR = getWorkingXHR(global);
|
||||
sinonXhr.supportsTimeout =
|
||||
(sinonXhr.supportsXHR && "timeout" in (new sinonXhr.GlobalXMLHttpRequest()));
|
||||
sinonXhr.supportsCORS = isReactNative ||
|
||||
(sinonXhr.supportsXHR && "withCredentials" in (new sinonXhr.GlobalXMLHttpRequest()));
|
||||
|
||||
// Ref: https://fetch.spec.whatwg.org/#forbidden-header-name
|
||||
var unsafeHeaders = {
|
||||
"Accept-Charset": true,
|
||||
"Access-Control-Request-Headers": true,
|
||||
"Access-Control-Request-Method": true,
|
||||
"Accept-Encoding": true,
|
||||
"Connection": true,
|
||||
"Content-Length": true,
|
||||
"Cookie": true,
|
||||
"Cookie2": true,
|
||||
"Content-Transfer-Encoding": true,
|
||||
"Date": true,
|
||||
"DNT": true,
|
||||
"Expect": true,
|
||||
"Host": true,
|
||||
"Keep-Alive": true,
|
||||
"Origin": true,
|
||||
"Referer": true,
|
||||
"TE": true,
|
||||
"Trailer": true,
|
||||
"Transfer-Encoding": true,
|
||||
"Upgrade": true,
|
||||
"User-Agent": true,
|
||||
"Via": true
|
||||
};
|
||||
|
||||
|
||||
function EventTargetHandler() {
|
||||
var self = this;
|
||||
var events = ["loadstart", "progress", "abort", "error", "load", "timeout", "loadend"];
|
||||
|
||||
function addEventListener(eventName) {
|
||||
self.addEventListener(eventName, function (event) {
|
||||
var listener = self["on" + eventName];
|
||||
|
||||
if (listener && typeof listener === "function") {
|
||||
listener.call(this, event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
events.forEach(addEventListener);
|
||||
}
|
||||
|
||||
EventTargetHandler.prototype = sinonEvent.EventTarget;
|
||||
|
||||
// Note that for FakeXMLHttpRequest to work pre ES5
|
||||
// we lose some of the alignment with the spec.
|
||||
// To ensure as close a match as possible,
|
||||
// set responseType before calling open, send or respond;
|
||||
function FakeXMLHttpRequest(config) {
|
||||
EventTargetHandler.call(this);
|
||||
this.readyState = FakeXMLHttpRequest.UNSENT;
|
||||
this.requestHeaders = {};
|
||||
this.requestBody = null;
|
||||
this.status = 0;
|
||||
this.statusText = "";
|
||||
this.upload = new EventTargetHandler();
|
||||
this.responseType = "";
|
||||
this.response = "";
|
||||
this.logError = configureLogError(config);
|
||||
|
||||
if (sinonXhr.supportsTimeout) {
|
||||
this.timeout = 0;
|
||||
}
|
||||
|
||||
if (sinonXhr.supportsCORS) {
|
||||
this.withCredentials = false;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeHeaderValue(value) {
|
||||
// Ref: https://fetch.spec.whatwg.org/#http-whitespace-bytes
|
||||
/*eslint no-control-regex: "off"*/
|
||||
return value.replace(/^[\x09\x0A\x0D\x20]+|[\x09\x0A\x0D\x20]+$/g, "");
|
||||
}
|
||||
|
||||
function getHeader(headers, header) {
|
||||
var foundHeader = Object.keys(headers).filter(function (h) {
|
||||
return h.toLowerCase() === header.toLowerCase();
|
||||
});
|
||||
|
||||
return foundHeader[0] || null;
|
||||
}
|
||||
|
||||
function excludeSetCookie2Header(header) {
|
||||
return !/^Set-Cookie2?$/i.test(header);
|
||||
}
|
||||
|
||||
// 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]);
|
||||
default: throw new Error("Unhandled case");
|
||||
}
|
||||
};
|
||||
|
||||
FakeXMLHttpRequest.filters = [];
|
||||
FakeXMLHttpRequest.addFilter = function addFilter(fn) {
|
||||
this.filters.push(fn);
|
||||
};
|
||||
FakeXMLHttpRequest.defake = function defake(fakeXhr, xhrArgs) {
|
||||
var xhr = new sinonXhr.workingXHR(); // eslint-disable-line new-cap
|
||||
|
||||
[
|
||||
"open",
|
||||
"setRequestHeader",
|
||||
"abort",
|
||||
"getResponseHeader",
|
||||
"getAllResponseHeaders",
|
||||
"addEventListener",
|
||||
"overrideMimeType",
|
||||
"removeEventListener"
|
||||
].forEach(function (method) {
|
||||
fakeXhr[method] = function () {
|
||||
return apply(xhr, method, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
fakeXhr.send = function () {
|
||||
// Ref: https://xhr.spec.whatwg.org/#the-responsetype-attribute
|
||||
if (xhr.responseType !== fakeXhr.responseType) {
|
||||
xhr.responseType = fakeXhr.responseType;
|
||||
}
|
||||
return apply(xhr, "send", arguments);
|
||||
};
|
||||
|
||||
var copyAttrs = function (args) {
|
||||
args.forEach(function (attr) {
|
||||
fakeXhr[attr] = xhr[attr];
|
||||
});
|
||||
};
|
||||
|
||||
var stateChangeStart = function () {
|
||||
fakeXhr.readyState = xhr.readyState;
|
||||
if (xhr.readyState >= FakeXMLHttpRequest.HEADERS_RECEIVED) {
|
||||
copyAttrs(["status", "statusText"]);
|
||||
}
|
||||
if (xhr.readyState >= FakeXMLHttpRequest.LOADING) {
|
||||
copyAttrs(["response"]);
|
||||
if (xhr.responseType === "" || xhr.responseType === "text") {
|
||||
copyAttrs(["responseText"]);
|
||||
}
|
||||
}
|
||||
if (
|
||||
xhr.readyState === FakeXMLHttpRequest.DONE &&
|
||||
(xhr.responseType === "" || xhr.responseType === "document")
|
||||
) {
|
||||
copyAttrs(["responseXML"]);
|
||||
}
|
||||
};
|
||||
|
||||
var stateChangeEnd = function () {
|
||||
if (fakeXhr.onreadystatechange) {
|
||||
fakeXhr.onreadystatechange.call(fakeXhr, { target: fakeXhr, currentTarget: fakeXhr });
|
||||
}
|
||||
};
|
||||
|
||||
var stateChange = function stateChange() {
|
||||
stateChangeStart();
|
||||
stateChangeEnd();
|
||||
};
|
||||
|
||||
if (xhr.addEventListener) {
|
||||
xhr.addEventListener("readystatechange", stateChangeStart);
|
||||
|
||||
Object.keys(fakeXhr.eventListeners).forEach(function (event) {
|
||||
/*eslint-disable no-loop-func*/
|
||||
fakeXhr.eventListeners[event].forEach(function (handler) {
|
||||
xhr.addEventListener(event, handler.listener, {
|
||||
capture: handler.capture,
|
||||
once: handler.once
|
||||
});
|
||||
});
|
||||
/*eslint-enable no-loop-func*/
|
||||
});
|
||||
|
||||
xhr.addEventListener("readystatechange", stateChangeEnd);
|
||||
} else {
|
||||
xhr.onreadystatechange = stateChange;
|
||||
}
|
||||
apply(xhr, "open", xhrArgs);
|
||||
};
|
||||
FakeXMLHttpRequest.useFilters = false;
|
||||
|
||||
function verifyRequestOpened(xhr) {
|
||||
if (xhr.readyState !== FakeXMLHttpRequest.OPENED) {
|
||||
throw new Error("INVALID_STATE_ERR - " + xhr.readyState);
|
||||
}
|
||||
}
|
||||
|
||||
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, responseType) {
|
||||
var error = null;
|
||||
var isString = typeof body === "string";
|
||||
|
||||
if (responseType === "arraybuffer") {
|
||||
|
||||
if (!isString && !(body instanceof ArrayBuffer)) {
|
||||
error = new Error("Attempted to respond to fake XMLHttpRequest with " +
|
||||
body + ", which is not a string or ArrayBuffer.");
|
||||
error.name = "InvalidBodyException";
|
||||
}
|
||||
}
|
||||
else if (!isString) {
|
||||
error = new Error("Attempted to respond to fake XMLHttpRequest with " +
|
||||
body + ", which is not a string.");
|
||||
error.name = "InvalidBodyException";
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function convertToArrayBuffer(body, encoding) {
|
||||
if (body instanceof ArrayBuffer) {
|
||||
return body;
|
||||
}
|
||||
|
||||
return new TextEncoder(encoding || "utf-8").encode(body).buffer;
|
||||
}
|
||||
|
||||
function isXmlContentType(contentType) {
|
||||
return !contentType || /(text\/xml)|(application\/xml)|(\+xml)/.test(contentType);
|
||||
}
|
||||
|
||||
function convertResponseBody(responseType, contentType, body) {
|
||||
if (responseType === "" || responseType === "text") {
|
||||
return body;
|
||||
} else if (supportsArrayBuffer && responseType === "arraybuffer") {
|
||||
return convertToArrayBuffer(body);
|
||||
} else if (responseType === "json") {
|
||||
try {
|
||||
return JSON.parse(body);
|
||||
} catch (e) {
|
||||
// Return parsing failure as null
|
||||
return null;
|
||||
}
|
||||
} else if (supportsBlob && responseType === "blob") {
|
||||
var blobOptions = {};
|
||||
if (contentType) {
|
||||
blobOptions.type = contentType;
|
||||
}
|
||||
return new Blob([convertToArrayBuffer(body)], blobOptions);
|
||||
} else if (responseType === "document") {
|
||||
if (isXmlContentType(contentType)) {
|
||||
return FakeXMLHttpRequest.parseXML(body);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
throw new Error("Invalid responseType " + responseType);
|
||||
}
|
||||
|
||||
function clearResponse(xhr) {
|
||||
if (xhr.responseType === "" || xhr.responseType === "text") {
|
||||
xhr.response = xhr.responseText = "";
|
||||
} else {
|
||||
xhr.response = xhr.responseText = null;
|
||||
}
|
||||
xhr.responseXML = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Steps to follow when there is an error, according to:
|
||||
* https://xhr.spec.whatwg.org/#request-error-steps
|
||||
*/
|
||||
function requestErrorSteps(xhr) {
|
||||
clearResponse(xhr);
|
||||
xhr.errorFlag = true;
|
||||
xhr.requestHeaders = {};
|
||||
xhr.responseHeaders = {};
|
||||
|
||||
if (xhr.readyState !== FakeXMLHttpRequest.UNSENT && xhr.sendFlag
|
||||
&& xhr.readyState !== FakeXMLHttpRequest.DONE) {
|
||||
xhr.readyStateChange(FakeXMLHttpRequest.DONE);
|
||||
xhr.sendFlag = false;
|
||||
}
|
||||
}
|
||||
|
||||
FakeXMLHttpRequest.parseXML = function parseXML(text) {
|
||||
// Treat empty string as parsing failure
|
||||
if (text !== "") {
|
||||
try {
|
||||
if (typeof DOMParser !== "undefined") {
|
||||
var parser = new DOMParser();
|
||||
var parsererrorNS = "";
|
||||
|
||||
try {
|
||||
var parsererrors = parser
|
||||
.parseFromString("INVALID", "text/xml")
|
||||
.getElementsByTagName("parsererror");
|
||||
if (parsererrors.length) {
|
||||
parsererrorNS = parsererrors[0].namespaceURI;
|
||||
}
|
||||
} catch (e) {
|
||||
// passing invalid XML makes IE11 throw
|
||||
// so no namespace needs to be determined
|
||||
}
|
||||
|
||||
var result;
|
||||
try {
|
||||
result = parser.parseFromString(text, "text/xml");
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.getElementsByTagNameNS(parsererrorNS, "parsererror").length
|
||||
? null : result;
|
||||
}
|
||||
var xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM");
|
||||
xmlDoc.async = "false";
|
||||
xmlDoc.loadXML(text);
|
||||
return xmlDoc.parseError.errorCode !== 0
|
||||
? null : xmlDoc;
|
||||
} catch (e) {
|
||||
// Unable to parse XML - no biggie
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
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",
|
||||
207: "Multi-Status",
|
||||
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"
|
||||
};
|
||||
|
||||
extend(FakeXMLHttpRequest.prototype, sinonEvent.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;
|
||||
clearResponse(this);
|
||||
this.requestHeaders = {};
|
||||
this.sendFlag = false;
|
||||
|
||||
if (FakeXMLHttpRequest.useFilters === true) {
|
||||
var xhrArgs = arguments;
|
||||
var defake = FakeXMLHttpRequest.filters.some(function (filter) {
|
||||
return filter.apply(this, xhrArgs);
|
||||
});
|
||||
if (defake) {
|
||||
FakeXMLHttpRequest.defake(this, arguments);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.readyStateChange(FakeXMLHttpRequest.OPENED);
|
||||
},
|
||||
|
||||
readyStateChange: function readyStateChange(state) {
|
||||
this.readyState = state;
|
||||
|
||||
var readyStateChangeEvent = new sinonEvent.Event("readystatechange", false, false, this);
|
||||
var event, progress;
|
||||
|
||||
if (typeof this.onreadystatechange === "function") {
|
||||
try {
|
||||
this.onreadystatechange(readyStateChangeEvent);
|
||||
} catch (e) {
|
||||
this.logError("Fake XHR onreadystatechange handler", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.readyState === FakeXMLHttpRequest.DONE) {
|
||||
if (this.timedOut || this.aborted || this.status === 0) {
|
||||
progress = {loaded: 0, total: 0};
|
||||
event = (this.timedOut && "timeout") || (this.aborted && "abort") || "error";
|
||||
} else {
|
||||
progress = {loaded: 100, total: 100};
|
||||
event = "load";
|
||||
}
|
||||
|
||||
if (supportsProgress) {
|
||||
this.upload.dispatchEvent(new sinonEvent.ProgressEvent("progress", progress, this));
|
||||
this.upload.dispatchEvent(new sinonEvent.ProgressEvent(event, progress, this));
|
||||
this.upload.dispatchEvent(new sinonEvent.ProgressEvent("loadend", progress, this));
|
||||
}
|
||||
|
||||
this.dispatchEvent(new sinonEvent.ProgressEvent("progress", progress, this));
|
||||
this.dispatchEvent(new sinonEvent.ProgressEvent(event, progress, this));
|
||||
this.dispatchEvent(new sinonEvent.ProgressEvent("loadend", progress, this));
|
||||
}
|
||||
|
||||
this.dispatchEvent(readyStateChangeEvent);
|
||||
},
|
||||
|
||||
// Ref https://xhr.spec.whatwg.org/#the-setrequestheader()-method
|
||||
setRequestHeader: function setRequestHeader(header, value) {
|
||||
if (typeof value !== "string") {
|
||||
throw new TypeError("By RFC7230, section 3.2.4, header values should be strings. Got " + typeof value);
|
||||
}
|
||||
verifyState(this);
|
||||
|
||||
var checkUnsafeHeaders = true;
|
||||
if (typeof this.unsafeHeadersEnabled === "function") {
|
||||
checkUnsafeHeaders = this.unsafeHeadersEnabled();
|
||||
}
|
||||
|
||||
if (checkUnsafeHeaders && (getHeader(unsafeHeaders, header) !== null || /^(Sec-|Proxy-)/i.test(header))) {
|
||||
throw new Error("Refused to set unsafe header \"" + header + "\"");
|
||||
}
|
||||
|
||||
value = normalizeHeaderValue(value);
|
||||
|
||||
var existingHeader = getHeader(this.requestHeaders, header);
|
||||
if (existingHeader) {
|
||||
this.requestHeaders[existingHeader] += ", " + value;
|
||||
} else {
|
||||
this.requestHeaders[header] = value;
|
||||
}
|
||||
},
|
||||
|
||||
setStatus: function setStatus(status) {
|
||||
var sanitizedStatus = typeof status === "number" ? status : 200;
|
||||
|
||||
verifyRequestOpened(this);
|
||||
this.status = sanitizedStatus;
|
||||
this.statusText = FakeXMLHttpRequest.statusCodes[sanitizedStatus];
|
||||
},
|
||||
|
||||
// Helps testing
|
||||
setResponseHeaders: function setResponseHeaders(headers) {
|
||||
verifyRequestOpened(this);
|
||||
|
||||
var responseHeaders = this.responseHeaders = {};
|
||||
|
||||
Object.keys(headers).forEach(function (header) {
|
||||
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 (!/^(head)$/i.test(this.method)) {
|
||||
var contentType = getHeader(this.requestHeaders, "Content-Type");
|
||||
if (this.requestHeaders[contentType]) {
|
||||
var value = this.requestHeaders[contentType].split(";");
|
||||
this.requestHeaders[contentType] = value[0] + ";charset=utf-8";
|
||||
} else if (supportsFormData && !(data instanceof FormData)) {
|
||||
this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8";
|
||||
}
|
||||
|
||||
this.requestBody = data;
|
||||
}
|
||||
|
||||
this.errorFlag = false;
|
||||
this.sendFlag = this.async;
|
||||
clearResponse(this);
|
||||
this.readyStateChange(FakeXMLHttpRequest.OPENED);
|
||||
|
||||
if (typeof this.onSend === "function") {
|
||||
this.onSend(this);
|
||||
}
|
||||
|
||||
// Only listen if setInterval and Date are a stubbed.
|
||||
if (sinonXhr.supportsTimeout && typeof setInterval.clock === "object" && typeof Date.clock === "object") {
|
||||
var initiatedTime = Date.now();
|
||||
var self = this;
|
||||
|
||||
// Listen to any possible tick by fake timers and check to see if timeout has
|
||||
// been exceeded. It's important to note that timeout can be changed while a request
|
||||
// is in flight, so we must check anytime the end user forces a clock tick to make
|
||||
// sure timeout hasn't changed.
|
||||
// https://xhr.spec.whatwg.org/#dfnReturnLink-2
|
||||
var clearIntervalId = setInterval(function () {
|
||||
// Check if the readyState has been reset or is done. If this is the case, there
|
||||
// should be no timeout. This will also prevent aborted requests and
|
||||
// fakeServerWithClock from triggering unnecessary responses.
|
||||
if (self.readyState === FakeXMLHttpRequest.UNSENT
|
||||
|| self.readyState === FakeXMLHttpRequest.DONE) {
|
||||
clearInterval(clearIntervalId);
|
||||
} else if (typeof self.timeout === "number" && self.timeout > 0) {
|
||||
if (Date.now() >= (initiatedTime + self.timeout)) {
|
||||
self.triggerTimeout();
|
||||
clearInterval(clearIntervalId);
|
||||
}
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
|
||||
this.dispatchEvent(new sinonEvent.Event("loadstart", false, false, this));
|
||||
},
|
||||
|
||||
abort: function abort() {
|
||||
this.aborted = true;
|
||||
requestErrorSteps(this);
|
||||
this.readyState = FakeXMLHttpRequest.UNSENT;
|
||||
},
|
||||
|
||||
error: function () {
|
||||
clearResponse(this);
|
||||
this.errorFlag = true;
|
||||
this.requestHeaders = {};
|
||||
this.responseHeaders = {};
|
||||
|
||||
this.readyStateChange(FakeXMLHttpRequest.DONE);
|
||||
},
|
||||
|
||||
triggerTimeout: function triggerTimeout() {
|
||||
if (sinonXhr.supportsTimeout) {
|
||||
this.timedOut = true;
|
||||
requestErrorSteps(this);
|
||||
}
|
||||
},
|
||||
|
||||
getResponseHeader: function getResponseHeader(header) {
|
||||
if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (/^Set-Cookie2?$/i.test(header)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
header = getHeader(this.responseHeaders, header);
|
||||
|
||||
return this.responseHeaders[header] || null;
|
||||
},
|
||||
|
||||
getAllResponseHeaders: function getAllResponseHeaders() {
|
||||
if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
|
||||
return "";
|
||||
}
|
||||
|
||||
var responseHeaders = this.responseHeaders;
|
||||
var headers = Object.keys(responseHeaders)
|
||||
.filter(excludeSetCookie2Header)
|
||||
.reduce(function (prev, header) {
|
||||
var value = responseHeaders[header];
|
||||
|
||||
return prev + (header + ": " + value + "\r\n");
|
||||
}, "");
|
||||
|
||||
return headers;
|
||||
},
|
||||
|
||||
setResponseBody: function setResponseBody(body) {
|
||||
verifyRequestSent(this);
|
||||
verifyHeadersReceived(this);
|
||||
verifyResponseBodyType(body, this.responseType);
|
||||
var contentType = this.overriddenMimeType || this.getResponseHeader("Content-Type");
|
||||
|
||||
var isTextResponse = this.responseType === "" || this.responseType === "text";
|
||||
clearResponse(this);
|
||||
if (this.async) {
|
||||
var chunkSize = this.chunkSize || 10;
|
||||
var index = 0;
|
||||
|
||||
do {
|
||||
this.readyStateChange(FakeXMLHttpRequest.LOADING);
|
||||
|
||||
if (isTextResponse) {
|
||||
this.responseText = this.response += body.substring(index, index + chunkSize);
|
||||
}
|
||||
index += chunkSize;
|
||||
} while (index < body.length);
|
||||
}
|
||||
|
||||
this.response = convertResponseBody(this.responseType, contentType, body);
|
||||
if (isTextResponse) {
|
||||
this.responseText = this.response;
|
||||
}
|
||||
|
||||
if (this.responseType === "document") {
|
||||
this.responseXML = this.response;
|
||||
} else if (this.responseType === "" && isXmlContentType(contentType)) {
|
||||
this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText);
|
||||
}
|
||||
this.readyStateChange(FakeXMLHttpRequest.DONE);
|
||||
},
|
||||
|
||||
respond: function respond(status, headers, body) {
|
||||
this.setStatus(status);
|
||||
this.setResponseHeaders(headers || {});
|
||||
this.setResponseBody(body || "");
|
||||
},
|
||||
|
||||
uploadProgress: function uploadProgress(progressEventRaw) {
|
||||
if (supportsProgress) {
|
||||
this.upload.dispatchEvent(new sinonEvent.ProgressEvent("progress", progressEventRaw, this.upload));
|
||||
}
|
||||
},
|
||||
|
||||
downloadProgress: function downloadProgress(progressEventRaw) {
|
||||
if (supportsProgress) {
|
||||
this.dispatchEvent(new sinonEvent.ProgressEvent("progress", progressEventRaw, this));
|
||||
}
|
||||
},
|
||||
|
||||
uploadError: function uploadError(error) {
|
||||
if (supportsCustomEvent) {
|
||||
this.upload.dispatchEvent(new sinonEvent.CustomEvent("error", {detail: error}));
|
||||
}
|
||||
},
|
||||
|
||||
overrideMimeType: function overrideMimeType(type) {
|
||||
if (this.readyState >= FakeXMLHttpRequest.LOADING) {
|
||||
throw new Error("INVALID_STATE_ERR");
|
||||
}
|
||||
this.overriddenMimeType = type;
|
||||
}
|
||||
});
|
||||
|
||||
var states = {
|
||||
UNSENT: 0,
|
||||
OPENED: 1,
|
||||
HEADERS_RECEIVED: 2,
|
||||
LOADING: 3,
|
||||
DONE: 4
|
||||
};
|
||||
|
||||
extend(FakeXMLHttpRequest, states);
|
||||
extend(FakeXMLHttpRequest.prototype, states);
|
||||
|
||||
function useFakeXMLHttpRequest() {
|
||||
FakeXMLHttpRequest.restore = function restore(keepOnCreate) {
|
||||
if (sinonXhr.supportsXHR) {
|
||||
global.XMLHttpRequest = sinonXhr.GlobalXMLHttpRequest;
|
||||
}
|
||||
|
||||
if (sinonXhr.supportsActiveX) {
|
||||
global.ActiveXObject = sinonXhr.GlobalActiveXObject;
|
||||
}
|
||||
|
||||
delete FakeXMLHttpRequest.restore;
|
||||
|
||||
if (keepOnCreate !== true) {
|
||||
delete FakeXMLHttpRequest.onCreate;
|
||||
}
|
||||
};
|
||||
if (sinonXhr.supportsXHR) {
|
||||
global.XMLHttpRequest = FakeXMLHttpRequest;
|
||||
}
|
||||
|
||||
if (sinonXhr.supportsActiveX) {
|
||||
global.ActiveXObject = function ActiveXObject(objId) {
|
||||
if (objId === "Microsoft.XMLHTTP" || /^Msxml2\.XMLHTTP/i.test(objId)) {
|
||||
|
||||
return new FakeXMLHttpRequest();
|
||||
}
|
||||
|
||||
return new sinonXhr.GlobalActiveXObject(objId);
|
||||
};
|
||||
}
|
||||
|
||||
return FakeXMLHttpRequest;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
xhr: sinonXhr,
|
||||
FakeXMLHttpRequest: FakeXMLHttpRequest,
|
||||
useFakeXMLHttpRequest: useFakeXMLHttpRequest
|
||||
};
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
fakeServer: require("./fake-server"),
|
||||
fakeServerWithClock: require("./fake-server/fake-server-with-clock"),
|
||||
fakeXhr: require("./fake-xhr")
|
||||
};
|
||||
+8848
File diff suppressed because one or more lines are too long
+54
@@ -0,0 +1,54 @@
|
||||
|
||||
# isarray
|
||||
|
||||
`Array#isArray` for older browsers.
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
var isArray = require('isarray');
|
||||
|
||||
console.log(isArray([])); // => true
|
||||
console.log(isArray({})); // => false
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
With [npm](http://npmjs.org) do
|
||||
|
||||
```bash
|
||||
$ npm install isarray
|
||||
```
|
||||
|
||||
Then bundle for the browser with
|
||||
[browserify](https://github.com/substack/browserify).
|
||||
|
||||
With [component](http://component.io) do
|
||||
|
||||
```bash
|
||||
$ component install juliangruber/isarray
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
(MIT)
|
||||
|
||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
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.
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
|
||||
/**
|
||||
* Require the given path.
|
||||
*
|
||||
* @param {String} path
|
||||
* @return {Object} exports
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function require(path, parent, orig) {
|
||||
var resolved = require.resolve(path);
|
||||
|
||||
// lookup failed
|
||||
if (null == resolved) {
|
||||
orig = orig || path;
|
||||
parent = parent || 'root';
|
||||
var err = new Error('Failed to require "' + orig + '" from "' + parent + '"');
|
||||
err.path = orig;
|
||||
err.parent = parent;
|
||||
err.require = true;
|
||||
throw err;
|
||||
}
|
||||
|
||||
var module = require.modules[resolved];
|
||||
|
||||
// perform real require()
|
||||
// by invoking the module's
|
||||
// registered function
|
||||
if (!module.exports) {
|
||||
module.exports = {};
|
||||
module.client = module.component = true;
|
||||
module.call(this, module.exports, require.relative(resolved), module);
|
||||
}
|
||||
|
||||
return module.exports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registered modules.
|
||||
*/
|
||||
|
||||
require.modules = {};
|
||||
|
||||
/**
|
||||
* Registered aliases.
|
||||
*/
|
||||
|
||||
require.aliases = {};
|
||||
|
||||
/**
|
||||
* Resolve `path`.
|
||||
*
|
||||
* Lookup:
|
||||
*
|
||||
* - PATH/index.js
|
||||
* - PATH.js
|
||||
* - PATH
|
||||
*
|
||||
* @param {String} path
|
||||
* @return {String} path or null
|
||||
* @api private
|
||||
*/
|
||||
|
||||
require.resolve = function(path) {
|
||||
if (path.charAt(0) === '/') path = path.slice(1);
|
||||
var index = path + '/index.js';
|
||||
|
||||
var paths = [
|
||||
path,
|
||||
path + '.js',
|
||||
path + '.json',
|
||||
path + '/index.js',
|
||||
path + '/index.json'
|
||||
];
|
||||
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
var path = paths[i];
|
||||
if (require.modules.hasOwnProperty(path)) return path;
|
||||
}
|
||||
|
||||
if (require.aliases.hasOwnProperty(index)) {
|
||||
return require.aliases[index];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize `path` relative to the current path.
|
||||
*
|
||||
* @param {String} curr
|
||||
* @param {String} path
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
require.normalize = function(curr, path) {
|
||||
var segs = [];
|
||||
|
||||
if ('.' != path.charAt(0)) return path;
|
||||
|
||||
curr = curr.split('/');
|
||||
path = path.split('/');
|
||||
|
||||
for (var i = 0; i < path.length; ++i) {
|
||||
if ('..' == path[i]) {
|
||||
curr.pop();
|
||||
} else if ('.' != path[i] && '' != path[i]) {
|
||||
segs.push(path[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return curr.concat(segs).join('/');
|
||||
};
|
||||
|
||||
/**
|
||||
* Register module at `path` with callback `definition`.
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {Function} definition
|
||||
* @api private
|
||||
*/
|
||||
|
||||
require.register = function(path, definition) {
|
||||
require.modules[path] = definition;
|
||||
};
|
||||
|
||||
/**
|
||||
* Alias a module definition.
|
||||
*
|
||||
* @param {String} from
|
||||
* @param {String} to
|
||||
* @api private
|
||||
*/
|
||||
|
||||
require.alias = function(from, to) {
|
||||
if (!require.modules.hasOwnProperty(from)) {
|
||||
throw new Error('Failed to alias "' + from + '", it does not exist');
|
||||
}
|
||||
require.aliases[to] = from;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a require function relative to the `parent` path.
|
||||
*
|
||||
* @param {String} parent
|
||||
* @return {Function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
require.relative = function(parent) {
|
||||
var p = require.normalize(parent, '..');
|
||||
|
||||
/**
|
||||
* lastIndexOf helper.
|
||||
*/
|
||||
|
||||
function lastIndexOf(arr, obj) {
|
||||
var i = arr.length;
|
||||
while (i--) {
|
||||
if (arr[i] === obj) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The relative require() itself.
|
||||
*/
|
||||
|
||||
function localRequire(path) {
|
||||
var resolved = localRequire.resolve(path);
|
||||
return require(resolved, parent, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve relative to the parent.
|
||||
*/
|
||||
|
||||
localRequire.resolve = function(path) {
|
||||
var c = path.charAt(0);
|
||||
if ('/' == c) return path.slice(1);
|
||||
if ('.' == c) return require.normalize(p, path);
|
||||
|
||||
// resolve deps by returning
|
||||
// the dep in the nearest "deps"
|
||||
// directory
|
||||
var segs = parent.split('/');
|
||||
var i = lastIndexOf(segs, 'deps') + 1;
|
||||
if (!i) i = 0;
|
||||
path = segs.slice(0, i + 1).join('/') + '/deps/' + path;
|
||||
return path;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if module is defined at `path`.
|
||||
*/
|
||||
|
||||
localRequire.exists = function(path) {
|
||||
return require.modules.hasOwnProperty(localRequire.resolve(path));
|
||||
};
|
||||
|
||||
return localRequire;
|
||||
};
|
||||
require.register("isarray/index.js", function(exports, require, module){
|
||||
module.exports = Array.isArray || function (arr) {
|
||||
return Object.prototype.toString.call(arr) == '[object Array]';
|
||||
};
|
||||
|
||||
});
|
||||
require.alias("isarray/index.js", "isarray/index.js");
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name" : "isarray",
|
||||
"description" : "Array#isArray for older browsers",
|
||||
"version" : "0.0.1",
|
||||
"repository" : "juliangruber/isarray",
|
||||
"homepage": "https://github.com/juliangruber/isarray",
|
||||
"main" : "index.js",
|
||||
"scripts" : [
|
||||
"index.js"
|
||||
],
|
||||
"dependencies" : {},
|
||||
"keywords": ["browser","isarray","array"],
|
||||
"author": {
|
||||
"name": "Julian Gruber",
|
||||
"email": "mail@juliangruber.com",
|
||||
"url": "http://juliangruber.com"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
module.exports = Array.isArray || function (arr) {
|
||||
return Object.prototype.toString.call(arr) == '[object Array]';
|
||||
};
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"_from": "isarray@0.0.1",
|
||||
"_id": "isarray@0.0.1",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
|
||||
"_location": "/nise/isarray",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "version",
|
||||
"registry": true,
|
||||
"raw": "isarray@0.0.1",
|
||||
"name": "isarray",
|
||||
"escapedName": "isarray",
|
||||
"rawSpec": "0.0.1",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "0.0.1"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/nise/path-to-regexp"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"_shasum": "8a18acfca9a8f4177e09abfc6038939b05d1eedf",
|
||||
"_spec": "isarray@0.0.1",
|
||||
"_where": "/Users/josh.burman/Projects/braid/node_modules/nise/node_modules/path-to-regexp",
|
||||
"author": {
|
||||
"name": "Julian Gruber",
|
||||
"email": "mail@juliangruber.com",
|
||||
"url": "http://juliangruber.com"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/juliangruber/isarray/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"dependencies": {},
|
||||
"deprecated": false,
|
||||
"description": "Array#isArray for older browsers",
|
||||
"devDependencies": {
|
||||
"tap": "*"
|
||||
},
|
||||
"homepage": "https://github.com/juliangruber/isarray",
|
||||
"keywords": [
|
||||
"browser",
|
||||
"isarray",
|
||||
"array"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"name": "isarray",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/juliangruber/isarray.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "tap test/*.js"
|
||||
},
|
||||
"version": "0.0.1"
|
||||
}
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
v2.7.5 / 2018-09-19
|
||||
==================
|
||||
|
||||
* fix: handle floating point in hrtime (#210)
|
||||
* fix: reset high resolution timer on clock.reset (#209)
|
||||
* Add an error when creating a clock with no Date object (#205)
|
||||
|
||||
v2.7.4 / 2018-09-05
|
||||
==================
|
||||
|
||||
* performance.mark related fixes for failing Safari, IE 10 and IE 11 tests
|
||||
|
||||
v2.7.3 / 2018-09-05
|
||||
==================
|
||||
|
||||
* Fix for #200: TypeError on performance.mark
|
||||
|
||||
v2.7.2 / 2018-09-04
|
||||
==================
|
||||
|
||||
* fix(setInterval): parse `timeout` arg to integer (#202)
|
||||
* Upgrade insecure dependencies with npm audit fix
|
||||
|
||||
v2.7.1 / 2018-07-06
|
||||
==================
|
||||
* Fix performance replacement on iOS 9.3
|
||||
|
||||
v2.7.0 / 2018-05-25
|
||||
==================
|
||||
|
||||
* reset clock to start
|
||||
* check Performance exists before touching it
|
||||
|
||||
v2.6.0 / 2018-05-16
|
||||
==================
|
||||
|
||||
* Fix `reset` and document it publicly Clear microtick jobs and set now to 0 in reset (#179)
|
||||
* Access Date on `_global` (#178)
|
||||
|
||||
v2.5.0 / 2018-05-13
|
||||
==================
|
||||
|
||||
* feat: respect loopLimit in runMicrotasks (#172)
|
||||
* assign performance as a property, not as a function
|
||||
|
||||
v2.4.2 / 2018-05-11
|
||||
===================
|
||||
* Upgrade Mochify to v5.6 (#162) fixed #170
|
||||
* Access `Performance` via `_global` (#168)
|
||||
|
||||
v2.4.1 / 2018-05-08
|
||||
==================
|
||||
|
||||
* fix: handle negative infinity timeout (#165)
|
||||
|
||||
v2.4.0 / 2018-05-08
|
||||
==================
|
||||
|
||||
* Add `withGlobal` export
|
||||
* expose runMicrotasks
|
||||
* Fix that performance.mark is undefined after timer install
|
||||
|
||||
v2.3.2 / 2018-01-29
|
||||
==================
|
||||
|
||||
* Add files section to package.json to avoid unnecessary package bloat #154
|
||||
* Add missing functions in default `toFake` #150
|
||||
|
||||
v2.3.1 / 2017-11-22
|
||||
==================
|
||||
|
||||
* bugfix for a setTimeout() or setSystemTime() within a nextTick() call. (#145)
|
||||
|
||||
v2.3.0 / 2017-11-08
|
||||
==================
|
||||
|
||||
* Stops leak of (request|cancel)AnimationFrame into global scope. (#143)
|
||||
* return timers on uninstall
|
||||
|
||||
v2.2.0 / 2017-11-07
|
||||
==================
|
||||
|
||||
* Add support for requestAnimationFrame
|
||||
* fix negative timeout bug
|
||||
|
||||
v2.1.3 / 2017-10-03
|
||||
==================
|
||||
|
||||
* add module entry point (#133)
|
||||
|
||||
v2.1.2 / 2017-07-25
|
||||
==================
|
||||
|
||||
* - does not fake process.nextTick by default - added .idea folder to .gitignore - fixed documentation - added clock teardowns in tests
|
||||
* overflowing the timer correctly (issue #67)
|
||||
|
||||
v2.1.1 / 2017-07-19
|
||||
==================
|
||||
|
||||
* support passing parameters in nextTick (fixes #122)
|
||||
|
||||
v2.1.0 / 2017-07-18
|
||||
==================
|
||||
|
||||
* Throw error on incorrect install use (#112)
|
||||
* Add support for process.nextTick
|
||||
* lolex can now attach itself to the system timers and automatically ad… (#102)
|
||||
* update hrtime when an interval ticks
|
||||
|
||||
v2.0.0 / 2017-07-13
|
||||
==================
|
||||
|
||||
* New install() signature
|
||||
* Add support for performance.now (#106)
|
||||
* Fix issue with tick(): setSystemClock then throw
|
||||
* Update old dependencies
|
||||
* Added support to automatically increment time (#85)
|
||||
* Changed internal uninstall method signature
|
||||
|
||||
v1.6.0 / 2017-02-25
|
||||
===================
|
||||
|
||||
* Use common Sinon.JS eslint config
|
||||
* Allow install to be called with date object
|
||||
* Remove wrapper function
|
||||
* Fixed typo in clock.runAll error
|
||||
|
||||
v1.5.2 / 2016-11-10
|
||||
===================
|
||||
|
||||
* Upgrade mocha to latest
|
||||
* Only overwrite globals when running in IE
|
||||
|
||||
1.5.1 / 2016-07-26
|
||||
==================
|
||||
|
||||
* Fix setInterval() behavior with string times
|
||||
* Incorporate test from PR #65
|
||||
* Fix issue #59: context object required 'process'
|
||||
* fixed a case where runAll was called and there are no timers (#70)
|
||||
* Correct the clear{Interval|Timeout|Immediate} error message when calling `set*` for a different type of timer.
|
||||
* Lots of minor changes to tooling and the build process
|
||||
|
||||
v1.5.0 / 2016-05-18
|
||||
===================
|
||||
|
||||
* 1.5.0
|
||||
* Check for existence of `process` before using it
|
||||
* Run to last existing timer
|
||||
* Add runAll method to run timers until empty
|
||||
* Turn off Sauce Labs tests for pull requests
|
||||
* Add tests demonstrating that a fake Date could be created with one argument as a String since this string is in a format recognized by the Date.parse() method.
|
||||
* Run test-cloud on Travis
|
||||
* Add process.hrtime()
|
||||
* Add bithound badge to Readme.md
|
||||
* Make Travis also run tests in node 4.2
|
||||
* Update jslint, referee, sinon, browserify, mocha, mochify
|
||||
* Rename src/lolex.js to src/lolex-src.js to avoid bithound ignoring it
|
||||
* Add .bithoundrc
|
||||
|
||||
v1.4.0 / 2015-12-11
|
||||
===================
|
||||
|
||||
* 1.4.0
|
||||
* Remove BASH syntax in lint script
|
||||
* correct test descriptions to match the tests
|
||||
* correct parseTime() error message so it matches behavior
|
||||
* don't run test-cloud as part of npm test
|
||||
* doc: full API reference
|
||||
* doc: update 'Running tests' section
|
||||
* doc: update 'Faking the native timers' section
|
||||
* doc: remove requestAnimationFrame
|
||||
* Implement clock.next()
|
||||
* Run lint in CI
|
||||
* Fix jslint errors
|
||||
|
||||
v1.3.2 / 2015-09-22
|
||||
===================
|
||||
|
||||
* 1.3.2
|
||||
* Fix for breaking shimmed setImmediate
|
||||
|
||||
v1.3.1 / 2015-08-20
|
||||
===================
|
||||
|
||||
* Remove error whos reason is no longer accurate
|
||||
|
||||
v1.3.0 / 2015-08-19
|
||||
===================
|
||||
|
||||
* 1.3.0
|
||||
* Throw exception on wrong use of clearXYZ()
|
||||
* Fix for Sinon.JS issue #808 :add setSystemTime() function
|
||||
* Fix for Sinon.JS issue #766: clearTimeout() no longer clears Immediate/Interval and vice versa
|
||||
* Update Readme.md to point to LICENSE file
|
||||
* Fix error in readme about running tests
|
||||
* Fix for warning about SPDX license format on npm install
|
||||
|
||||
v1.2.2 / 2015-07-22
|
||||
===================
|
||||
|
||||
* 1.2.2
|
||||
* Fixing lint mistake
|
||||
* Update travis to use node@0.12
|
||||
* Fix complaint about missing fake setImmediate
|
||||
* Use license in package.json
|
||||
|
||||
v1.2.1 / 2015-01-06
|
||||
===================
|
||||
|
||||
* New build
|
||||
* Dodge JSLint...
|
||||
* Up version
|
||||
* Proper fix for writable globals in IE
|
||||
* Make timers writable in old IEs
|
||||
|
||||
v1.2.0 / 2014-12-12
|
||||
===================
|
||||
|
||||
* 1.2.0
|
||||
* Fix Sinon.JS issue 624
|
||||
* Lint the test files also
|
||||
* Add .jslintrc
|
||||
* Delay setImmediate if it is during tick call
|
||||
* Add test case
|
||||
* Test behaviour of hasOwnProperty beforehand
|
||||
* Compare now() with delta
|
||||
* Use undefined for defined predicate
|
||||
* Put setImmediate in toFake list
|
||||
* Capture clock instance for uninstall
|
||||
* Restore commented out tests
|
||||
* Add JSLint verification to test
|
||||
* Configure Travis to run tests in node 0.10.x
|
||||
* Add .editorconfig
|
||||
* Fail when faking Date but not setTimeout/setInterval
|
||||
|
||||
v1.1.10 / 2014-11-14
|
||||
====================
|
||||
|
||||
* 1.1.0 Fixes setImmediate problems
|
||||
* Rely on `timer` initialization to null
|
||||
* Timer assembly occurs at addTimer callsites
|
||||
* Sort immediate timers before non-immediate
|
||||
* Add createdAt to timers
|
||||
* Sort timers by multiple criteria, not just callAt
|
||||
* Refactor firstTimerInRange
|
||||
* Rename `timeouts` property to `timers`
|
||||
* addTimer is options-driven
|
||||
|
||||
v1.0.0 / 2014-11-12
|
||||
===================
|
||||
|
||||
* Add built file for browsers
|
||||
* Fix URL
|
||||
* Don't run tests that require global.__proto__ on IE 9 and IE 10
|
||||
* Add "bundle" script to create standalone UMD bundle with browserify
|
||||
* Float with new test framework versions
|
||||
* Remove redundant module prefix
|
||||
* Let Browserify set "global" for us
|
||||
* Change test framework from Buster to Mocha and Mochify
|
||||
* Make timer functions independent on `this`
|
||||
* Change APIs according to Readme
|
||||
* Change clock-creating interface
|
||||
* Change Github paths
|
||||
* Basically working extraction from Sinon.JS
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
Copyright (c) 2010-2014, 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:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. 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.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its 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.
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
# Lolex [](https://travis-ci.org/sinonjs/lolex)
|
||||
|
||||
JavaScript implementation of the timer APIs; `setTimeout`, `clearTimeout`, `setImmediate`, `clearImmediate`, `setInterval`, `clearInterval`, `requestAnimationFrame`, and `cancelAnimationFrame`, along with a clock instance that controls the flow of time. Lolex also provides a `Date` implementation that gets its time from the clock.
|
||||
|
||||
In addition in browser environment lolex provides a `performance` implementation that gets its time from the clock. In Node environments lolex provides a `nextTick` implementation that is synchronized with the clock - and a `process.hrtime` shim that works with the clock.
|
||||
|
||||
Lolex can be used to simulate passing time in automated tests and other
|
||||
situations where you want the scheduling semantics, but don't want to actually
|
||||
wait (however, from version 2.0 lolex supports those of you who would like to wait too).
|
||||
|
||||
Lolex is extracted from [Sinon.JS](https://github.com/sinonjs/sinon.js).
|
||||
|
||||
## Installation
|
||||
|
||||
Lolex can be used in both Node and browser environments. Installation is as easy as
|
||||
|
||||
```sh
|
||||
npm install lolex
|
||||
```
|
||||
|
||||
If you want to use Lolex in a browser you can use [the pre-built
|
||||
version](https://github.com/sinonjs/lolex/blob/master/lolex.js) available in the repo
|
||||
and the npm package. Using npm you only need to reference `./node_modules/lolex/lolex.js` in your `<script>` tags.
|
||||
|
||||
You are always free to [build it yourself](https://github.com/sinonjs/lolex/blob/53ea4d9b9e5bcff53cc7c9755dc9aa340368cf1c/package.json#L22), of course.
|
||||
|
||||
## Usage
|
||||
|
||||
To use lolex, create a new clock, schedule events on it using the timer
|
||||
functions and pass time using the `tick` method.
|
||||
|
||||
```js
|
||||
// In the browser distribution, a global `lolex` is already available
|
||||
var lolex = require("lolex");
|
||||
var clock = lolex.createClock();
|
||||
|
||||
clock.setTimeout(function () {
|
||||
console.log("The poblano is a mild chili pepper originating in the state of Puebla, Mexico.");
|
||||
}, 15);
|
||||
|
||||
// ...
|
||||
|
||||
clock.tick(15);
|
||||
```
|
||||
|
||||
Upon executing the last line, an interesting fact about the
|
||||
[Poblano](http://en.wikipedia.org/wiki/Poblano) will be printed synchronously to
|
||||
the screen. If you want to simulate asynchronous behavior, you have to use your
|
||||
imagination when calling the various functions.
|
||||
|
||||
The `next`, `runAll`, `runToFrame`, and `runToLast` methods are available to advance the clock. See the
|
||||
API Reference for more details.
|
||||
|
||||
### Faking the native timers
|
||||
|
||||
When using lolex to test timers, you will most likely want to replace the native
|
||||
timers such that calling `setTimeout` actually schedules a callback with your
|
||||
clock instance, not the browser's internals.
|
||||
|
||||
Calling `install` with no arguments achieves this. You can call `uninstall`
|
||||
later to restore things as they were again.
|
||||
|
||||
```js
|
||||
// In the browser distribution, a global `lolex` is already available
|
||||
var lolex = require("lolex");
|
||||
|
||||
var clock = lolex.install();
|
||||
// Equivalent to
|
||||
// var clock = lolex.install(typeof global !== "undefined" ? global : window);
|
||||
|
||||
setTimeout(fn, 15); // Schedules with clock.setTimeout
|
||||
|
||||
clock.uninstall();
|
||||
// setTimeout is restored to the native implementation
|
||||
```
|
||||
|
||||
To hijack timers in another context pass it to the `install` method.
|
||||
|
||||
```js
|
||||
var lolex = require("lolex");
|
||||
var context = {
|
||||
setTimeout: setTimeout // By default context.setTimeout uses the global setTimeout
|
||||
}
|
||||
var clock = lolex.install({target: context});
|
||||
|
||||
context.setTimeout(fn, 15); // Schedules with clock.setTimeout
|
||||
|
||||
clock.uninstall();
|
||||
// context.setTimeout is restored to the original implementation
|
||||
```
|
||||
|
||||
Usually you want to install the timers onto the global object, so call `install`
|
||||
without arguments.
|
||||
|
||||
#### Automatically incrementing mocked time
|
||||
Since version 2.0 Lolex supports the possibility to attach the faked timers
|
||||
to any change in the real system time. This basically means you no longer need
|
||||
to `tick()` the clock in a situation where you won't know **when** to call `tick()`.
|
||||
|
||||
Please note that this is achieved using the original setImmediate() API at a certain
|
||||
configurable interval `config.advanceTimeDelta` (default: 20ms). Meaning time would
|
||||
be incremented every 20ms, not in real time.
|
||||
|
||||
An example would be:
|
||||
|
||||
```js
|
||||
var lolex = require("lolex");
|
||||
var clock = lolex.install({shouldAdvanceTime: true, advanceTimeDelta: 40});
|
||||
|
||||
setTimeout(() => {
|
||||
console.log('this just timed out'); //executed after 40ms
|
||||
}, 30);
|
||||
|
||||
setImmediate(() => {
|
||||
console.log('not so immediate'); //executed after 40ms
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
console.log('this timed out after'); //executed after 80ms
|
||||
clock.uninstall();
|
||||
}, 50);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `var clock = lolex.createClock([now[, loopLimit]])`
|
||||
|
||||
Creates a clock. The default
|
||||
[epoch](https://en.wikipedia.org/wiki/Epoch_%28reference_date%29) is `0`.
|
||||
|
||||
The `now` argument may be a number (in milliseconds) or a Date object.
|
||||
|
||||
The `loopLimit` argument sets the maximum number of timers that will be run when calling `runAll()` before assuming that we have an infinite loop and throwing an error. The default is `1000`.
|
||||
|
||||
### `var clock = lolex.install([config])`
|
||||
Installs lolex using the specified config (otherwise with epoch `0` on the global scope). The following configuration options are available
|
||||
|
||||
Parameter | Type | Default | Description
|
||||
--------- | ---- | ------- | ------------
|
||||
`config.target`| Object | global | installs lolex onto the specified target context
|
||||
`config.now` | Number/Date | 0 | installs lolex with the specified unix epoch
|
||||
`config.toFake` | String[] | ["setTimeout", "clearTimeout", "setImmediate", "clearImmediate","setInterval", "clearInterval", "Date", "requestAnimationFrame", "cancelAnimationFrame", "hrtime"] | an array with explicit function names to hijack. *When not set, lolex will automatically fake all methods **except** `nextTick`* e.g., `lolex.install({ toFake: ["setTimeout","nextTick"]})` will fake only `setTimeout` and `nextTick`
|
||||
`config.loopLimit` | Number | 1000 | the maximum number of timers that will be run when calling runAll()
|
||||
`config.shouldAdvanceTime` | Boolean | false | tells lolex to increment mocked time automatically based on the real system time shift (e.g. the mocked time will be incremented by 20ms for every 20ms change in the real system time)
|
||||
`config.advanceTimeDelta` | Number | 20 | relevant only when using with `shouldAdvanceTime: true`. increment mocked time by `advanceTimeDelta` ms every `advanceTimeDelta` ms change in the real system time.
|
||||
|
||||
### `var id = clock.setTimeout(callback, timeout)`
|
||||
|
||||
Schedules the callback to be fired once `timeout` milliseconds have ticked by.
|
||||
|
||||
In Node.js `setTimeout` returns a timer object. Lolex will do the same, however
|
||||
its `ref()` and `unref()` methods have no effect.
|
||||
|
||||
In browsers a timer ID is returned.
|
||||
|
||||
### `clock.clearTimeout(id)`
|
||||
|
||||
Clears the timer given the ID or timer object, as long as it was created using
|
||||
`setTimeout`.
|
||||
|
||||
### `var id = clock.setInterval(callback, timeout)`
|
||||
|
||||
Schedules the callback to be fired every time `timeout` milliseconds have ticked
|
||||
by.
|
||||
|
||||
In Node.js `setInterval` returns a timer object. Lolex will do the same, however
|
||||
its `ref()` and `unref()` methods have no effect.
|
||||
|
||||
In browsers a timer ID is returned.
|
||||
|
||||
### `clock.clearInterval(id)`
|
||||
|
||||
Clears the timer given the ID or timer object, as long as it was created using
|
||||
`setInterval`.
|
||||
|
||||
### `var id = clock.setImmediate(callback)`
|
||||
|
||||
Schedules the callback to be fired once `0` milliseconds have ticked by. Note
|
||||
that you'll still have to call `clock.tick()` for the callback to fire. If
|
||||
called during a tick the callback won't fire until `1` millisecond has ticked
|
||||
by.
|
||||
|
||||
In Node.js `setImmediate` returns a timer object. Lolex will do the same,
|
||||
however its `ref()` and `unref()` methods have no effect.
|
||||
|
||||
In browsers a timer ID is returned.
|
||||
|
||||
### `clock.clearImmediate(id)`
|
||||
|
||||
Clears the timer given the ID or timer object, as long as it was created using
|
||||
`setImmediate`.
|
||||
|
||||
### `clock.requestAnimationFrame(callback)`
|
||||
|
||||
Schedules the callback to be fired on the next animation frame, which runs every
|
||||
16 ticks. Returns an `id` which can be used to cancel the callback. This is
|
||||
available in both browser & node environments.
|
||||
|
||||
### `clock.cancelAnimationFrame(id)`
|
||||
|
||||
Cancels the callback scheduled by the provided id.
|
||||
|
||||
### `clock.hrtime(prevTime?)`
|
||||
Only available in Node.js, mimicks process.hrtime().
|
||||
|
||||
### `clock.nextTick(callback)`
|
||||
|
||||
Only available in Node.js, mimics `process.nextTick` to enable completely synchronous testing flows.
|
||||
|
||||
### `clock.performance.now()`
|
||||
Only available in browser environments, mimicks performance.now().
|
||||
|
||||
|
||||
### `clock.tick(time)`
|
||||
|
||||
Advance the clock, firing callbacks if necessary. `time` may be the number of
|
||||
milliseconds to advance the clock by or a human-readable string. Valid string
|
||||
formats are `"08"` for eight seconds, `"01:00"` for one minute and `"02:34:10"`
|
||||
for two hours, 34 minutes and ten seconds.
|
||||
|
||||
`time` may be negative, which causes the clock to change but won't fire any
|
||||
callbacks.
|
||||
|
||||
### `clock.next()`
|
||||
|
||||
Advances the clock to the the moment of the first scheduled timer, firing it.
|
||||
|
||||
### `clock.reset()`
|
||||
|
||||
Removes all timers and ticks without firing them, and sets `now` to `config.now`
|
||||
that was provided to `lolex.install` or to `0` if `config.now` was not provided.
|
||||
Useful to reset the state of the clock without having to `uninstall` and `install` it.
|
||||
|
||||
### `clock.runAll()`
|
||||
|
||||
This runs all pending timers until there are none remaining. If new timers are added while it is executing they will be run as well.
|
||||
|
||||
This makes it easier to run asynchronous tests to completion without worrying about the number of timers they use, or the delays in those timers.
|
||||
|
||||
It runs a maximum of `loopLimit` times after which it assumes there is an infinite loop of timers and throws an error.
|
||||
|
||||
### `clock.runMicrotasks()`
|
||||
|
||||
This runs all pending microtasks scheduled with `nextTick` but none of the timers and is mostly useful for libraries using lolex underneath and for running `nextTick` items without any timers.
|
||||
|
||||
### `clock.runToFrame()`
|
||||
|
||||
Advances the clock to the next frame, firing all scheduled animation frame callbacks,
|
||||
if any, for that frame as well as any other timers scheduled along the way.
|
||||
|
||||
### `clock.runToLast()`
|
||||
|
||||
This takes note of the last scheduled timer when it is run, and advances the
|
||||
clock to that time firing callbacks as necessary.
|
||||
|
||||
If new timers are added while it is executing they will be run only if they
|
||||
would occur before this time.
|
||||
|
||||
This is useful when you want to run a test to completion, but the test recursively
|
||||
sets timers that would cause `runAll` to trigger an infinite loop warning.
|
||||
|
||||
### `clock.setSystemTime([now])`
|
||||
|
||||
This simulates a user changing the system clock while your program is running.
|
||||
It affects the current time but it does not in itself cause e.g. timers to fire;
|
||||
they will fire exactly as they would have done without the call to
|
||||
setSystemTime().
|
||||
|
||||
### `clock.uninstall()`
|
||||
|
||||
Restores the original methods on the `target` that was passed to
|
||||
`lolex.install`, or the native timers if no `target` was given.
|
||||
|
||||
### `Date`
|
||||
|
||||
Implements the `Date` object but using the clock to provide the correct time.
|
||||
|
||||
### `Performance`
|
||||
|
||||
Implements the `now` method of the [`Performance`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) object but using the clock to provide the correct time. Only available in environments that support the Performance object (browsers mostly).
|
||||
|
||||
### `lolex.withGlobal`
|
||||
|
||||
In order to support creating clocks based on separate or sandboxed environments (such as JSDOM), Lolex exports a factory method which takes single argument `global`, which it inspects to figure out what to mock and what features to support. When invoking this function with a global, you will get back an object with `timers`, `createClock` and `install` - same as the regular Lolex exports only based on the passed in global instead of the global environment.
|
||||
|
||||
## Running tests
|
||||
|
||||
Lolex has a comprehensive test suite. If you're thinking of contributing bug
|
||||
fixes or suggesting new features, you need to make sure you have not broken any
|
||||
tests. You are also expected to add tests for any new behavior.
|
||||
|
||||
### On node:
|
||||
|
||||
```sh
|
||||
npm test
|
||||
```
|
||||
|
||||
Or, if you prefer more verbose output:
|
||||
|
||||
```
|
||||
$(npm bin)/mocha ./test/lolex-test.js
|
||||
```
|
||||
|
||||
### In the browser
|
||||
|
||||
[Mochify](https://github.com/mantoni/mochify.js) is used to run the tests in
|
||||
PhantomJS. Make sure you have `phantomjs` installed. Then:
|
||||
|
||||
```sh
|
||||
npm test-headless
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
BSD 3-clause "New" or "Revised" License (see LICENSE file)
|
||||
+869
@@ -0,0 +1,869 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.lolex = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
(function (global){
|
||||
"use strict";
|
||||
|
||||
function withGlobal(_global) {
|
||||
var userAgent = _global.navigator && _global.navigator.userAgent;
|
||||
var isRunningInIE = userAgent && userAgent.indexOf("MSIE ") > -1;
|
||||
var maxTimeout = Math.pow(2, 31) - 1; //see https://heycam.github.io/webidl/#abstract-opdef-converttoint
|
||||
|
||||
// Make properties writable in IE, as per
|
||||
// http://www.adequatelygood.com/Replacing-setTimeout-Globally.html
|
||||
if (isRunningInIE) {
|
||||
_global.setTimeout = _global.setTimeout;
|
||||
_global.clearTimeout = _global.clearTimeout;
|
||||
_global.setInterval = _global.setInterval;
|
||||
_global.clearInterval = _global.clearInterval;
|
||||
_global.Date = _global.Date;
|
||||
}
|
||||
|
||||
// setImmediate is not a standard function
|
||||
// avoid adding the prop to the window object if not present
|
||||
if (_global.setImmediate !== undefined) {
|
||||
_global.setImmediate = _global.setImmediate;
|
||||
_global.clearImmediate = _global.clearImmediate;
|
||||
}
|
||||
|
||||
// node expects setTimeout/setInterval to return a fn object w/ .ref()/.unref()
|
||||
// browsers, a number.
|
||||
// see https://github.com/cjohansen/Sinon.JS/pull/436
|
||||
|
||||
var NOOP = function () { return undefined; };
|
||||
var timeoutResult = _global.setTimeout(NOOP, 0);
|
||||
var addTimerReturnsObject = typeof timeoutResult === "object";
|
||||
var hrtimePresent = (_global.process && typeof _global.process.hrtime === "function");
|
||||
var nextTickPresent = (_global.process && typeof _global.process.nextTick === "function");
|
||||
var performancePresent = (_global.performance && typeof _global.performance.now === "function");
|
||||
var hasPerformancePrototype = (_global.Performance && (typeof _global.Performance).match(/^(function|object)$/));
|
||||
var requestAnimationFramePresent = (
|
||||
_global.requestAnimationFrame && typeof _global.requestAnimationFrame === "function"
|
||||
);
|
||||
var cancelAnimationFramePresent = (
|
||||
_global.cancelAnimationFrame && typeof _global.cancelAnimationFrame === "function"
|
||||
);
|
||||
|
||||
_global.clearTimeout(timeoutResult);
|
||||
|
||||
var NativeDate = _global.Date;
|
||||
var uniqueTimerId = 1;
|
||||
|
||||
function isNumberFinite(num) {
|
||||
if (Number.isFinite) {
|
||||
return Number.isFinite(num);
|
||||
}
|
||||
|
||||
if (typeof num !== "number") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isFinite(num);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse strings like "01:10:00" (meaning 1 hour, 10 minutes, 0 seconds) into
|
||||
* number of milliseconds. This is used to support human-readable strings passed
|
||||
* to clock.tick()
|
||||
*/
|
||||
function parseTime(str) {
|
||||
if (!str) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var strings = str.split(":");
|
||||
var l = strings.length;
|
||||
var i = l;
|
||||
var ms = 0;
|
||||
var parsed;
|
||||
|
||||
if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) {
|
||||
throw new Error("tick only understands numbers, 'm:s' and 'h:m:s'. Each part must be two digits");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Floor function that also works for negative numbers
|
||||
*/
|
||||
function fixedFloor(n) {
|
||||
return (n >= 0 ? Math.floor(n) : Math.ceil(n));
|
||||
}
|
||||
|
||||
/**
|
||||
* % operator that also works for negative numbers
|
||||
*/
|
||||
function fixedModulo(n, m) {
|
||||
return Math.round(((n % m) + m) % m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to grok the `now` parameter to createClock.
|
||||
* @param epoch {Date|number} the system time
|
||||
*/
|
||||
function getEpoch(epoch) {
|
||||
if (!epoch) { return 0; }
|
||||
if (typeof epoch.getTime === "function") { return epoch.getTime(); }
|
||||
if (typeof epoch === "number") { return epoch; }
|
||||
throw new TypeError("now should be milliseconds since UNIX epoch");
|
||||
}
|
||||
|
||||
function inRange(from, to, timer) {
|
||||
return timer && timer.callAt >= from && timer.callAt <= to;
|
||||
}
|
||||
|
||||
function mirrorDateProperties(target, source) {
|
||||
var prop;
|
||||
for (prop in source) {
|
||||
if (source.hasOwnProperty(prop)) {
|
||||
target[prop] = source[prop];
|
||||
}
|
||||
}
|
||||
|
||||
// set special now implementation
|
||||
if (source.now) {
|
||||
target.now = function now() {
|
||||
return target.clock.now;
|
||||
};
|
||||
} else {
|
||||
delete target.now;
|
||||
}
|
||||
|
||||
// set special toSource implementation
|
||||
if (source.toSource) {
|
||||
target.toSource = function toSource() {
|
||||
return source.toSource();
|
||||
};
|
||||
} else {
|
||||
delete target.toSource;
|
||||
}
|
||||
|
||||
// set special toString implementation
|
||||
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;
|
||||
}
|
||||
|
||||
function createDate() {
|
||||
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 enqueueJob(clock, job) {
|
||||
// enqueues a microtick-deferred task - ecma262/#sec-enqueuejob
|
||||
if (!clock.jobs) {
|
||||
clock.jobs = [];
|
||||
}
|
||||
clock.jobs.push(job);
|
||||
}
|
||||
|
||||
function runJobs(clock) {
|
||||
// runs all microtick-deferred tasks - ecma262/#sec-runjobs
|
||||
if (!clock.jobs) {
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < clock.jobs.length; i++) {
|
||||
var job = clock.jobs[i];
|
||||
job.func.apply(null, job.args);
|
||||
if (clock.loopLimit && i > clock.loopLimit) {
|
||||
throw new Error("Aborting after running " + clock.loopLimit + " timers, assuming an infinite loop!");
|
||||
}
|
||||
}
|
||||
clock.jobs = [];
|
||||
}
|
||||
|
||||
function addTimer(clock, timer) {
|
||||
if (timer.func === undefined) {
|
||||
throw new Error("Callback must be provided to timer calls");
|
||||
}
|
||||
|
||||
timer.type = timer.immediate ? "Immediate" : "Timeout";
|
||||
|
||||
if (timer.hasOwnProperty("delay")) {
|
||||
if (!isNumberFinite(timer.delay)) {
|
||||
timer.delay = 0;
|
||||
}
|
||||
timer.delay = timer.delay > maxTimeout ? 1 : timer.delay;
|
||||
timer.delay = Math.max(0, timer.delay);
|
||||
}
|
||||
|
||||
if (timer.hasOwnProperty("interval")) {
|
||||
timer.type = "Interval";
|
||||
timer.interval = timer.interval > maxTimeout ? 1 : timer.interval;
|
||||
}
|
||||
|
||||
if (timer.hasOwnProperty("animation")) {
|
||||
timer.type = "AnimationFrame";
|
||||
timer.animation = true;
|
||||
}
|
||||
|
||||
if (!clock.timers) {
|
||||
clock.timers = {};
|
||||
}
|
||||
|
||||
timer.id = uniqueTimerId++;
|
||||
timer.createdAt = clock.now;
|
||||
timer.callAt = clock.now + (parseInt(timer.delay) || (clock.duringTick ? 1 : 0));
|
||||
|
||||
clock.timers[timer.id] = timer;
|
||||
|
||||
if (addTimerReturnsObject) {
|
||||
var res = {
|
||||
id: timer.id,
|
||||
ref: function () { return res; },
|
||||
unref: function () { return res; },
|
||||
refresh: function () { return res; }
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
return timer.id;
|
||||
}
|
||||
|
||||
|
||||
/* eslint consistent-return: "off" */
|
||||
function compareTimers(a, b) {
|
||||
// Sort first by absolute timing
|
||||
if (a.callAt < b.callAt) {
|
||||
return -1;
|
||||
}
|
||||
if (a.callAt > b.callAt) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Sort next by immediate, immediate timers take precedence
|
||||
if (a.immediate && !b.immediate) {
|
||||
return -1;
|
||||
}
|
||||
if (!a.immediate && b.immediate) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Sort next by creation time, earlier-created timers take precedence
|
||||
if (a.createdAt < b.createdAt) {
|
||||
return -1;
|
||||
}
|
||||
if (a.createdAt > b.createdAt) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Sort next by id, lower-id timers take precedence
|
||||
if (a.id < b.id) {
|
||||
return -1;
|
||||
}
|
||||
if (a.id > b.id) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// As timer ids are unique, no fallback `0` is necessary
|
||||
}
|
||||
|
||||
function firstTimerInRange(clock, from, to) {
|
||||
var timers = clock.timers;
|
||||
var timer = null;
|
||||
var id, isInRange;
|
||||
|
||||
for (id in timers) {
|
||||
if (timers.hasOwnProperty(id)) {
|
||||
isInRange = inRange(from, to, timers[id]);
|
||||
|
||||
if (isInRange && (!timer || compareTimers(timer, timers[id]) === 1)) {
|
||||
timer = timers[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return timer;
|
||||
}
|
||||
|
||||
function firstTimer(clock) {
|
||||
var timers = clock.timers;
|
||||
var timer = null;
|
||||
var id;
|
||||
|
||||
for (id in timers) {
|
||||
if (timers.hasOwnProperty(id)) {
|
||||
if (!timer || compareTimers(timer, timers[id]) === 1) {
|
||||
timer = timers[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return timer;
|
||||
}
|
||||
|
||||
function lastTimer(clock) {
|
||||
var timers = clock.timers;
|
||||
var timer = null;
|
||||
var id;
|
||||
|
||||
for (id in timers) {
|
||||
if (timers.hasOwnProperty(id)) {
|
||||
if (!timer || compareTimers(timer, timers[id]) === -1) {
|
||||
timer = timers[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return timer;
|
||||
}
|
||||
|
||||
function callTimer(clock, timer) {
|
||||
if (typeof timer.interval === "number") {
|
||||
clock.timers[timer.id].callAt += timer.interval;
|
||||
} else {
|
||||
delete clock.timers[timer.id];
|
||||
}
|
||||
|
||||
if (typeof timer.func === "function") {
|
||||
timer.func.apply(null, timer.args);
|
||||
} else {
|
||||
/* eslint no-eval: "off" */
|
||||
eval(timer.func);
|
||||
}
|
||||
}
|
||||
|
||||
function clearTimer(clock, timerId, ttype) {
|
||||
if (!timerId) {
|
||||
// null appears to be allowed in most browsers, and appears to be
|
||||
// relied upon by some libraries, like Bootstrap carousel
|
||||
return;
|
||||
}
|
||||
|
||||
if (!clock.timers) {
|
||||
clock.timers = {};
|
||||
}
|
||||
|
||||
// in Node, timerId is an object with .ref()/.unref(), and
|
||||
// its .id field is the actual timer id.
|
||||
if (typeof timerId === "object") {
|
||||
timerId = timerId.id;
|
||||
}
|
||||
|
||||
if (clock.timers.hasOwnProperty(timerId)) {
|
||||
// check that the ID matches a timer of the correct type
|
||||
var timer = clock.timers[timerId];
|
||||
if (timer.type === ttype) {
|
||||
delete clock.timers[timerId];
|
||||
} else {
|
||||
var clear = ttype === "AnimationFrame" ? "cancelAnimationFrame" : "clear" + ttype;
|
||||
var schedule = timer.type === "AnimationFrame" ? "requestAnimationFrame" : "set" + timer.type;
|
||||
throw new Error("Cannot clear timer: timer created with " + schedule
|
||||
+ "() but cleared with " + clear + "()");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function uninstall(clock, target, config) {
|
||||
var method,
|
||||
i,
|
||||
l;
|
||||
var installedHrTime = "_hrtime";
|
||||
var installedNextTick = "_nextTick";
|
||||
|
||||
for (i = 0, l = clock.methods.length; i < l; i++) {
|
||||
method = clock.methods[i];
|
||||
if (method === "hrtime" && target.process) {
|
||||
target.process.hrtime = clock[installedHrTime];
|
||||
} else if (method === "nextTick" && target.process) {
|
||||
target.process.nextTick = clock[installedNextTick];
|
||||
} else if (method === "performance") {
|
||||
target[method] = clock["_" + method];
|
||||
} else {
|
||||
if (target[method] && target[method].hadOwnProperty) {
|
||||
target[method] = clock["_" + method];
|
||||
if (method === "clearInterval" && config.shouldAdvanceTime === true) {
|
||||
target[method](clock.attachedInterval);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
delete target[method];
|
||||
} catch (ignore) { /* eslint empty-block: "off" */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent multiple executions which will completely remove these props
|
||||
clock.methods = [];
|
||||
|
||||
// return pending timers, to enable checking what timers remained on uninstall
|
||||
if (!clock.timers) {
|
||||
return [];
|
||||
}
|
||||
return Object.keys(clock.timers).map(function mapper(key) {
|
||||
return clock.timers[key];
|
||||
});
|
||||
}
|
||||
|
||||
function hijackMethod(target, method, clock) {
|
||||
var prop;
|
||||
clock[method].hadOwnProperty = Object.prototype.hasOwnProperty.call(target, method);
|
||||
clock["_" + method] = target[method];
|
||||
|
||||
if (method === "Date") {
|
||||
var date = mirrorDateProperties(clock[method], target[method]);
|
||||
target[method] = date;
|
||||
} else if (method === "performance") {
|
||||
target[method] = clock[method];
|
||||
} else {
|
||||
target[method] = function () {
|
||||
return clock[method].apply(clock, arguments);
|
||||
};
|
||||
|
||||
for (prop in clock[method]) {
|
||||
if (clock[method].hasOwnProperty(prop)) {
|
||||
target[method][prop] = clock[method][prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
target[method].clock = clock;
|
||||
}
|
||||
|
||||
function doIntervalTick(clock, advanceTimeDelta) {
|
||||
clock.tick(advanceTimeDelta);
|
||||
}
|
||||
|
||||
var timers = {
|
||||
setTimeout: _global.setTimeout,
|
||||
clearTimeout: _global.clearTimeout,
|
||||
setImmediate: _global.setImmediate,
|
||||
clearImmediate: _global.clearImmediate,
|
||||
setInterval: _global.setInterval,
|
||||
clearInterval: _global.clearInterval,
|
||||
Date: _global.Date
|
||||
};
|
||||
|
||||
if (hrtimePresent) {
|
||||
timers.hrtime = _global.process.hrtime;
|
||||
}
|
||||
|
||||
if (nextTickPresent) {
|
||||
timers.nextTick = _global.process.nextTick;
|
||||
}
|
||||
|
||||
if (performancePresent) {
|
||||
timers.performance = _global.performance;
|
||||
}
|
||||
|
||||
if (requestAnimationFramePresent) {
|
||||
timers.requestAnimationFrame = _global.requestAnimationFrame;
|
||||
}
|
||||
|
||||
if (cancelAnimationFramePresent) {
|
||||
timers.cancelAnimationFrame = _global.cancelAnimationFrame;
|
||||
}
|
||||
|
||||
var keys = Object.keys || function (obj) {
|
||||
var ks = [];
|
||||
var key;
|
||||
|
||||
for (key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
ks.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
return ks;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param start {Date|number} the system time
|
||||
* @param loopLimit {number} maximum number of timers that will be run when calling runAll()
|
||||
*/
|
||||
function createClock(start, loopLimit) {
|
||||
start = start || 0;
|
||||
loopLimit = loopLimit || 1000;
|
||||
|
||||
if (NativeDate === undefined) {
|
||||
throw new Error("The global scope doesn't have a `Date` object"
|
||||
+ " (see https://github.com/sinonjs/sinon/issues/1852#issuecomment-419622780)");
|
||||
}
|
||||
|
||||
var clock = {
|
||||
now: getEpoch(start),
|
||||
hrNow: 0,
|
||||
timeouts: {},
|
||||
Date: createDate(),
|
||||
loopLimit: loopLimit
|
||||
};
|
||||
|
||||
clock.Date.clock = clock;
|
||||
|
||||
function getTimeToNextFrame() {
|
||||
return 16 - ((clock.now - start) % 16);
|
||||
}
|
||||
|
||||
clock.setTimeout = function setTimeout(func, timeout) {
|
||||
return addTimer(clock, {
|
||||
func: func,
|
||||
args: Array.prototype.slice.call(arguments, 2),
|
||||
delay: timeout
|
||||
});
|
||||
};
|
||||
|
||||
clock.clearTimeout = function clearTimeout(timerId) {
|
||||
return clearTimer(clock, timerId, "Timeout");
|
||||
};
|
||||
clock.nextTick = function nextTick(func) {
|
||||
return enqueueJob(clock, {
|
||||
func: func,
|
||||
args: Array.prototype.slice.call(arguments, 1)
|
||||
});
|
||||
};
|
||||
clock.setInterval = function setInterval(func, timeout) {
|
||||
timeout = parseInt(timeout, 10);
|
||||
return addTimer(clock, {
|
||||
func: func,
|
||||
args: Array.prototype.slice.call(arguments, 2),
|
||||
delay: timeout,
|
||||
interval: timeout
|
||||
});
|
||||
};
|
||||
|
||||
clock.clearInterval = function clearInterval(timerId) {
|
||||
return clearTimer(clock, timerId, "Interval");
|
||||
};
|
||||
|
||||
clock.setImmediate = function setImmediate(func) {
|
||||
return addTimer(clock, {
|
||||
func: func,
|
||||
args: Array.prototype.slice.call(arguments, 1),
|
||||
immediate: true
|
||||
});
|
||||
};
|
||||
|
||||
clock.clearImmediate = function clearImmediate(timerId) {
|
||||
return clearTimer(clock, timerId, "Immediate");
|
||||
};
|
||||
|
||||
clock.requestAnimationFrame = function requestAnimationFrame(func) {
|
||||
var result = addTimer(clock, {
|
||||
func: func,
|
||||
delay: getTimeToNextFrame(),
|
||||
args: [clock.now + getTimeToNextFrame()],
|
||||
animation: true
|
||||
});
|
||||
|
||||
return result.id || result;
|
||||
};
|
||||
|
||||
clock.cancelAnimationFrame = function cancelAnimationFrame(timerId) {
|
||||
return clearTimer(clock, timerId, "AnimationFrame");
|
||||
};
|
||||
|
||||
function updateHrTime(newNow) {
|
||||
clock.hrNow += (newNow - clock.now);
|
||||
}
|
||||
|
||||
clock.runMicrotasks = function runMicrotasks() {
|
||||
runJobs(clock);
|
||||
};
|
||||
|
||||
clock.tick = function tick(ms) {
|
||||
ms = typeof ms === "number" ? ms : parseTime(ms);
|
||||
var tickFrom = clock.now;
|
||||
var tickTo = clock.now + ms;
|
||||
var previous = clock.now;
|
||||
var timer, firstException, oldNow;
|
||||
|
||||
clock.duringTick = true;
|
||||
|
||||
// perform process.nextTick()s
|
||||
oldNow = clock.now;
|
||||
runJobs(clock);
|
||||
if (oldNow !== clock.now) {
|
||||
// compensate for any setSystemTime() call during process.nextTick() callback
|
||||
tickFrom += clock.now - oldNow;
|
||||
tickTo += clock.now - oldNow;
|
||||
}
|
||||
|
||||
// perform each timer in the requested range
|
||||
timer = firstTimerInRange(clock, tickFrom, tickTo);
|
||||
while (timer && tickFrom <= tickTo) {
|
||||
if (clock.timers[timer.id]) {
|
||||
updateHrTime(timer.callAt);
|
||||
tickFrom = timer.callAt;
|
||||
clock.now = timer.callAt;
|
||||
oldNow = clock.now;
|
||||
try {
|
||||
runJobs(clock);
|
||||
callTimer(clock, timer);
|
||||
} catch (e) {
|
||||
firstException = firstException || e;
|
||||
}
|
||||
|
||||
// compensate for any setSystemTime() call during timer callback
|
||||
if (oldNow !== clock.now) {
|
||||
tickFrom += clock.now - oldNow;
|
||||
tickTo += clock.now - oldNow;
|
||||
previous += clock.now - oldNow;
|
||||
}
|
||||
}
|
||||
|
||||
timer = firstTimerInRange(clock, previous, tickTo);
|
||||
previous = tickFrom;
|
||||
}
|
||||
|
||||
// perform process.nextTick()s again
|
||||
oldNow = clock.now;
|
||||
runJobs(clock);
|
||||
if (oldNow !== clock.now) {
|
||||
// compensate for any setSystemTime() call during process.nextTick() callback
|
||||
tickFrom += clock.now - oldNow;
|
||||
tickTo += clock.now - oldNow;
|
||||
}
|
||||
clock.duringTick = false;
|
||||
|
||||
// corner case: during runJobs, new timers were scheduled which could be in the range [clock.now, tickTo]
|
||||
timer = firstTimerInRange(clock, tickFrom, tickTo);
|
||||
if (timer) {
|
||||
try {
|
||||
clock.tick(tickTo - clock.now); // do it all again - for the remainder of the requested range
|
||||
} catch (e) {
|
||||
firstException = firstException || e;
|
||||
}
|
||||
} else {
|
||||
// no timers remaining in the requested range: move the clock all the way to the end
|
||||
updateHrTime(tickTo);
|
||||
clock.now = tickTo;
|
||||
}
|
||||
if (firstException) {
|
||||
throw firstException;
|
||||
}
|
||||
return clock.now;
|
||||
};
|
||||
|
||||
clock.next = function next() {
|
||||
runJobs(clock);
|
||||
var timer = firstTimer(clock);
|
||||
if (!timer) {
|
||||
return clock.now;
|
||||
}
|
||||
|
||||
clock.duringTick = true;
|
||||
try {
|
||||
updateHrTime(timer.callAt);
|
||||
clock.now = timer.callAt;
|
||||
callTimer(clock, timer);
|
||||
runJobs(clock);
|
||||
return clock.now;
|
||||
} finally {
|
||||
clock.duringTick = false;
|
||||
}
|
||||
};
|
||||
|
||||
clock.runAll = function runAll() {
|
||||
var numTimers, i;
|
||||
runJobs(clock);
|
||||
for (i = 0; i < clock.loopLimit; i++) {
|
||||
if (!clock.timers) {
|
||||
return clock.now;
|
||||
}
|
||||
|
||||
numTimers = keys(clock.timers).length;
|
||||
if (numTimers === 0) {
|
||||
return clock.now;
|
||||
}
|
||||
|
||||
clock.next();
|
||||
}
|
||||
|
||||
throw new Error("Aborting after running " + clock.loopLimit + " timers, assuming an infinite loop!");
|
||||
};
|
||||
|
||||
clock.runToFrame = function runToFrame() {
|
||||
return clock.tick(getTimeToNextFrame());
|
||||
};
|
||||
|
||||
clock.runToLast = function runToLast() {
|
||||
var timer = lastTimer(clock);
|
||||
if (!timer) {
|
||||
runJobs(clock);
|
||||
return clock.now;
|
||||
}
|
||||
|
||||
return clock.tick(timer.callAt - clock.now);
|
||||
};
|
||||
|
||||
clock.reset = function reset() {
|
||||
clock.timers = {};
|
||||
clock.jobs = [];
|
||||
clock.now = getEpoch(start);
|
||||
clock.hrNow = 0;
|
||||
};
|
||||
|
||||
clock.setSystemTime = function setSystemTime(systemTime) {
|
||||
// determine time difference
|
||||
var newNow = getEpoch(systemTime);
|
||||
var difference = newNow - clock.now;
|
||||
var id, timer;
|
||||
|
||||
// update 'system clock'
|
||||
clock.now = newNow;
|
||||
|
||||
// update timers and intervals to keep them stable
|
||||
for (id in clock.timers) {
|
||||
if (clock.timers.hasOwnProperty(id)) {
|
||||
timer = clock.timers[id];
|
||||
timer.createdAt += difference;
|
||||
timer.callAt += difference;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (performancePresent) {
|
||||
clock.performance = Object.create(null);
|
||||
|
||||
if (hasPerformancePrototype) {
|
||||
var proto = _global.Performance.prototype;
|
||||
|
||||
Object
|
||||
.getOwnPropertyNames(proto)
|
||||
.forEach(function (name) {
|
||||
clock.performance[name] = NOOP;
|
||||
});
|
||||
}
|
||||
|
||||
clock.performance.now = function lolexNow() {
|
||||
return clock.hrNow;
|
||||
};
|
||||
}
|
||||
|
||||
if (hrtimePresent) {
|
||||
clock.hrtime = function (prev) {
|
||||
if (Array.isArray(prev)) {
|
||||
var oldSecs = (prev[0] + prev[1] / 1e9);
|
||||
var newSecs = (clock.hrNow / 1000);
|
||||
var difference = (newSecs - oldSecs);
|
||||
var secs = fixedFloor(difference);
|
||||
var nanosecs = fixedModulo(difference * 1e9, 1e9);
|
||||
return [
|
||||
secs,
|
||||
nanosecs
|
||||
];
|
||||
}
|
||||
return [
|
||||
fixedFloor(clock.hrNow / 1000),
|
||||
fixedModulo(clock.hrNow * 1e6, 1e9)
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
return clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param config {Object} optional config
|
||||
* @param config.target {Object} the target to install timers in (default `window`)
|
||||
* @param config.now {number|Date} a number (in milliseconds) or a Date object (default epoch)
|
||||
* @param config.toFake {string[]} names of the methods that should be faked.
|
||||
* @param config.loopLimit {number} the maximum number of timers that will be run when calling runAll()
|
||||
* @param config.shouldAdvanceTime {Boolean} tells lolex to increment mocked time automatically (default false)
|
||||
* @param config.advanceTimeDelta {Number} increment mocked time every <<advanceTimeDelta>> ms (default: 20ms)
|
||||
*/
|
||||
function install(config) {
|
||||
if ( arguments.length > 1 || config instanceof Date || Array.isArray(config) || typeof config === "number") {
|
||||
throw new TypeError("lolex.install called with " + String(config) +
|
||||
" lolex 2.0+ requires an object parameter - see https://github.com/sinonjs/lolex");
|
||||
}
|
||||
config = typeof config !== "undefined" ? config : {};
|
||||
config.shouldAdvanceTime = config.shouldAdvanceTime || false;
|
||||
config.advanceTimeDelta = config.advanceTimeDelta || 20;
|
||||
|
||||
var i, l;
|
||||
var target = config.target || _global;
|
||||
var clock = createClock(config.now, config.loopLimit);
|
||||
|
||||
clock.uninstall = function () {
|
||||
return uninstall(clock, target, config);
|
||||
};
|
||||
|
||||
clock.methods = config.toFake || [];
|
||||
|
||||
if (clock.methods.length === 0) {
|
||||
// do not fake nextTick by default - GitHub#126
|
||||
clock.methods = keys(timers).filter(function (key) {return key !== "nextTick";});
|
||||
}
|
||||
|
||||
for (i = 0, l = clock.methods.length; i < l; i++) {
|
||||
if (clock.methods[i] === "hrtime") {
|
||||
if (target.process && typeof target.process.hrtime === "function") {
|
||||
hijackMethod(target.process, clock.methods[i], clock);
|
||||
}
|
||||
} else if (clock.methods[i] === "nextTick") {
|
||||
if (target.process && typeof target.process.nextTick === "function") {
|
||||
hijackMethod(target.process, clock.methods[i], clock);
|
||||
}
|
||||
} else {
|
||||
if (clock.methods[i] === "setInterval" && config.shouldAdvanceTime === true) {
|
||||
var intervalTick = doIntervalTick.bind(null, clock, config.advanceTimeDelta);
|
||||
var intervalId = target[clock.methods[i]](
|
||||
intervalTick,
|
||||
config.advanceTimeDelta);
|
||||
clock.attachedInterval = intervalId;
|
||||
}
|
||||
hijackMethod(target, clock.methods[i], clock);
|
||||
}
|
||||
}
|
||||
|
||||
return clock;
|
||||
}
|
||||
|
||||
return {
|
||||
timers: timers,
|
||||
createClock: createClock,
|
||||
install: install,
|
||||
withGlobal: withGlobal
|
||||
};
|
||||
}
|
||||
|
||||
var defaultImplementation = withGlobal(global || window);
|
||||
|
||||
exports.timers = defaultImplementation.timers;
|
||||
exports.createClock = defaultImplementation.createClock;
|
||||
exports.install = defaultImplementation.install;
|
||||
exports.withGlobal = withGlobal;
|
||||
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"_from": "lolex@^2.3.2",
|
||||
"_id": "lolex@2.7.5",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==",
|
||||
"_location": "/nise/lolex",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "lolex@^2.3.2",
|
||||
"name": "lolex",
|
||||
"escapedName": "lolex",
|
||||
"rawSpec": "^2.3.2",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "^2.3.2"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/nise"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz",
|
||||
"_shasum": "113001d56bfc7e02d56e36291cc5c413d1aa0733",
|
||||
"_spec": "lolex@^2.3.2",
|
||||
"_where": "/Users/josh.burman/Projects/braid/node_modules/nise",
|
||||
"author": {
|
||||
"name": "Christian Johansen"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "http://github.com/sinonjs/lolex/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"dependencies": {},
|
||||
"deprecated": false,
|
||||
"description": "Fake JavaScript timers",
|
||||
"devDependencies": {
|
||||
"@sinonjs/referee-sinon": "4.1.0",
|
||||
"browserify": "16.2.0",
|
||||
"eslint": "4.19.1",
|
||||
"eslint-config-sinon": "1.0.3",
|
||||
"eslint-plugin-ie11": "1.0.0",
|
||||
"eslint-plugin-mocha": "4.11.0",
|
||||
"husky": "0.14.3",
|
||||
"jsdom": "11.10.0",
|
||||
"lint-staged": "7.0.4",
|
||||
"mocha": "5.1.0",
|
||||
"mochify": "5.8.0",
|
||||
"npm-run-all": "4.1.2"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "eslint-config-sinon",
|
||||
"plugins": [
|
||||
"ie11"
|
||||
],
|
||||
"rules": {
|
||||
"ie11/no-collection-args": [
|
||||
"error"
|
||||
],
|
||||
"ie11/no-for-in-const": [
|
||||
"error"
|
||||
],
|
||||
"ie11/no-loop-func": [
|
||||
"warn"
|
||||
],
|
||||
"ie11/no-weak-collections": [
|
||||
"error"
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"src/",
|
||||
"lolex.js"
|
||||
],
|
||||
"homepage": "http://github.com/sinonjs/lolex",
|
||||
"license": "BSD-3-Clause",
|
||||
"lint-staged": {
|
||||
"*.js": "eslint"
|
||||
},
|
||||
"main": "./src/lolex-src.js",
|
||||
"module": "./lolex.js",
|
||||
"name": "lolex",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+ssh://git@github.com/sinonjs/lolex.git"
|
||||
},
|
||||
"scripts": {
|
||||
"bundle": "browserify -s lolex -o lolex.js src/lolex-src.js",
|
||||
"lint": "eslint .",
|
||||
"precommit": "run-p lint test-node",
|
||||
"prepublishOnly": "npm run bundle",
|
||||
"test": "npm run lint && npm run test-node && npm run test-headless",
|
||||
"test-cloud": "mochify --wd",
|
||||
"test-headless": "mochify",
|
||||
"test-node": "mocha test/ integration-test/ -R dot --check-leaks"
|
||||
},
|
||||
"version": "2.7.5"
|
||||
}
|
||||
+863
@@ -0,0 +1,863 @@
|
||||
"use strict";
|
||||
|
||||
function withGlobal(_global) {
|
||||
var userAgent = _global.navigator && _global.navigator.userAgent;
|
||||
var isRunningInIE = userAgent && userAgent.indexOf("MSIE ") > -1;
|
||||
var maxTimeout = Math.pow(2, 31) - 1; //see https://heycam.github.io/webidl/#abstract-opdef-converttoint
|
||||
|
||||
// Make properties writable in IE, as per
|
||||
// http://www.adequatelygood.com/Replacing-setTimeout-Globally.html
|
||||
if (isRunningInIE) {
|
||||
_global.setTimeout = _global.setTimeout;
|
||||
_global.clearTimeout = _global.clearTimeout;
|
||||
_global.setInterval = _global.setInterval;
|
||||
_global.clearInterval = _global.clearInterval;
|
||||
_global.Date = _global.Date;
|
||||
}
|
||||
|
||||
// setImmediate is not a standard function
|
||||
// avoid adding the prop to the window object if not present
|
||||
if (_global.setImmediate !== undefined) {
|
||||
_global.setImmediate = _global.setImmediate;
|
||||
_global.clearImmediate = _global.clearImmediate;
|
||||
}
|
||||
|
||||
// node expects setTimeout/setInterval to return a fn object w/ .ref()/.unref()
|
||||
// browsers, a number.
|
||||
// see https://github.com/cjohansen/Sinon.JS/pull/436
|
||||
|
||||
var NOOP = function () { return undefined; };
|
||||
var timeoutResult = _global.setTimeout(NOOP, 0);
|
||||
var addTimerReturnsObject = typeof timeoutResult === "object";
|
||||
var hrtimePresent = (_global.process && typeof _global.process.hrtime === "function");
|
||||
var nextTickPresent = (_global.process && typeof _global.process.nextTick === "function");
|
||||
var performancePresent = (_global.performance && typeof _global.performance.now === "function");
|
||||
var hasPerformancePrototype = (_global.Performance && (typeof _global.Performance).match(/^(function|object)$/));
|
||||
var requestAnimationFramePresent = (
|
||||
_global.requestAnimationFrame && typeof _global.requestAnimationFrame === "function"
|
||||
);
|
||||
var cancelAnimationFramePresent = (
|
||||
_global.cancelAnimationFrame && typeof _global.cancelAnimationFrame === "function"
|
||||
);
|
||||
|
||||
_global.clearTimeout(timeoutResult);
|
||||
|
||||
var NativeDate = _global.Date;
|
||||
var uniqueTimerId = 1;
|
||||
|
||||
function isNumberFinite(num) {
|
||||
if (Number.isFinite) {
|
||||
return Number.isFinite(num);
|
||||
}
|
||||
|
||||
if (typeof num !== "number") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isFinite(num);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse strings like "01:10:00" (meaning 1 hour, 10 minutes, 0 seconds) into
|
||||
* number of milliseconds. This is used to support human-readable strings passed
|
||||
* to clock.tick()
|
||||
*/
|
||||
function parseTime(str) {
|
||||
if (!str) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var strings = str.split(":");
|
||||
var l = strings.length;
|
||||
var i = l;
|
||||
var ms = 0;
|
||||
var parsed;
|
||||
|
||||
if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) {
|
||||
throw new Error("tick only understands numbers, 'm:s' and 'h:m:s'. Each part must be two digits");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Floor function that also works for negative numbers
|
||||
*/
|
||||
function fixedFloor(n) {
|
||||
return (n >= 0 ? Math.floor(n) : Math.ceil(n));
|
||||
}
|
||||
|
||||
/**
|
||||
* % operator that also works for negative numbers
|
||||
*/
|
||||
function fixedModulo(n, m) {
|
||||
return Math.round(((n % m) + m) % m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to grok the `now` parameter to createClock.
|
||||
* @param epoch {Date|number} the system time
|
||||
*/
|
||||
function getEpoch(epoch) {
|
||||
if (!epoch) { return 0; }
|
||||
if (typeof epoch.getTime === "function") { return epoch.getTime(); }
|
||||
if (typeof epoch === "number") { return epoch; }
|
||||
throw new TypeError("now should be milliseconds since UNIX epoch");
|
||||
}
|
||||
|
||||
function inRange(from, to, timer) {
|
||||
return timer && timer.callAt >= from && timer.callAt <= to;
|
||||
}
|
||||
|
||||
function mirrorDateProperties(target, source) {
|
||||
var prop;
|
||||
for (prop in source) {
|
||||
if (source.hasOwnProperty(prop)) {
|
||||
target[prop] = source[prop];
|
||||
}
|
||||
}
|
||||
|
||||
// set special now implementation
|
||||
if (source.now) {
|
||||
target.now = function now() {
|
||||
return target.clock.now;
|
||||
};
|
||||
} else {
|
||||
delete target.now;
|
||||
}
|
||||
|
||||
// set special toSource implementation
|
||||
if (source.toSource) {
|
||||
target.toSource = function toSource() {
|
||||
return source.toSource();
|
||||
};
|
||||
} else {
|
||||
delete target.toSource;
|
||||
}
|
||||
|
||||
// set special toString implementation
|
||||
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;
|
||||
}
|
||||
|
||||
function createDate() {
|
||||
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 enqueueJob(clock, job) {
|
||||
// enqueues a microtick-deferred task - ecma262/#sec-enqueuejob
|
||||
if (!clock.jobs) {
|
||||
clock.jobs = [];
|
||||
}
|
||||
clock.jobs.push(job);
|
||||
}
|
||||
|
||||
function runJobs(clock) {
|
||||
// runs all microtick-deferred tasks - ecma262/#sec-runjobs
|
||||
if (!clock.jobs) {
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < clock.jobs.length; i++) {
|
||||
var job = clock.jobs[i];
|
||||
job.func.apply(null, job.args);
|
||||
if (clock.loopLimit && i > clock.loopLimit) {
|
||||
throw new Error("Aborting after running " + clock.loopLimit + " timers, assuming an infinite loop!");
|
||||
}
|
||||
}
|
||||
clock.jobs = [];
|
||||
}
|
||||
|
||||
function addTimer(clock, timer) {
|
||||
if (timer.func === undefined) {
|
||||
throw new Error("Callback must be provided to timer calls");
|
||||
}
|
||||
|
||||
timer.type = timer.immediate ? "Immediate" : "Timeout";
|
||||
|
||||
if (timer.hasOwnProperty("delay")) {
|
||||
if (!isNumberFinite(timer.delay)) {
|
||||
timer.delay = 0;
|
||||
}
|
||||
timer.delay = timer.delay > maxTimeout ? 1 : timer.delay;
|
||||
timer.delay = Math.max(0, timer.delay);
|
||||
}
|
||||
|
||||
if (timer.hasOwnProperty("interval")) {
|
||||
timer.type = "Interval";
|
||||
timer.interval = timer.interval > maxTimeout ? 1 : timer.interval;
|
||||
}
|
||||
|
||||
if (timer.hasOwnProperty("animation")) {
|
||||
timer.type = "AnimationFrame";
|
||||
timer.animation = true;
|
||||
}
|
||||
|
||||
if (!clock.timers) {
|
||||
clock.timers = {};
|
||||
}
|
||||
|
||||
timer.id = uniqueTimerId++;
|
||||
timer.createdAt = clock.now;
|
||||
timer.callAt = clock.now + (parseInt(timer.delay) || (clock.duringTick ? 1 : 0));
|
||||
|
||||
clock.timers[timer.id] = timer;
|
||||
|
||||
if (addTimerReturnsObject) {
|
||||
var res = {
|
||||
id: timer.id,
|
||||
ref: function () { return res; },
|
||||
unref: function () { return res; },
|
||||
refresh: function () { return res; }
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
return timer.id;
|
||||
}
|
||||
|
||||
|
||||
/* eslint consistent-return: "off" */
|
||||
function compareTimers(a, b) {
|
||||
// Sort first by absolute timing
|
||||
if (a.callAt < b.callAt) {
|
||||
return -1;
|
||||
}
|
||||
if (a.callAt > b.callAt) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Sort next by immediate, immediate timers take precedence
|
||||
if (a.immediate && !b.immediate) {
|
||||
return -1;
|
||||
}
|
||||
if (!a.immediate && b.immediate) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Sort next by creation time, earlier-created timers take precedence
|
||||
if (a.createdAt < b.createdAt) {
|
||||
return -1;
|
||||
}
|
||||
if (a.createdAt > b.createdAt) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Sort next by id, lower-id timers take precedence
|
||||
if (a.id < b.id) {
|
||||
return -1;
|
||||
}
|
||||
if (a.id > b.id) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// As timer ids are unique, no fallback `0` is necessary
|
||||
}
|
||||
|
||||
function firstTimerInRange(clock, from, to) {
|
||||
var timers = clock.timers;
|
||||
var timer = null;
|
||||
var id, isInRange;
|
||||
|
||||
for (id in timers) {
|
||||
if (timers.hasOwnProperty(id)) {
|
||||
isInRange = inRange(from, to, timers[id]);
|
||||
|
||||
if (isInRange && (!timer || compareTimers(timer, timers[id]) === 1)) {
|
||||
timer = timers[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return timer;
|
||||
}
|
||||
|
||||
function firstTimer(clock) {
|
||||
var timers = clock.timers;
|
||||
var timer = null;
|
||||
var id;
|
||||
|
||||
for (id in timers) {
|
||||
if (timers.hasOwnProperty(id)) {
|
||||
if (!timer || compareTimers(timer, timers[id]) === 1) {
|
||||
timer = timers[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return timer;
|
||||
}
|
||||
|
||||
function lastTimer(clock) {
|
||||
var timers = clock.timers;
|
||||
var timer = null;
|
||||
var id;
|
||||
|
||||
for (id in timers) {
|
||||
if (timers.hasOwnProperty(id)) {
|
||||
if (!timer || compareTimers(timer, timers[id]) === -1) {
|
||||
timer = timers[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return timer;
|
||||
}
|
||||
|
||||
function callTimer(clock, timer) {
|
||||
if (typeof timer.interval === "number") {
|
||||
clock.timers[timer.id].callAt += timer.interval;
|
||||
} else {
|
||||
delete clock.timers[timer.id];
|
||||
}
|
||||
|
||||
if (typeof timer.func === "function") {
|
||||
timer.func.apply(null, timer.args);
|
||||
} else {
|
||||
/* eslint no-eval: "off" */
|
||||
eval(timer.func);
|
||||
}
|
||||
}
|
||||
|
||||
function clearTimer(clock, timerId, ttype) {
|
||||
if (!timerId) {
|
||||
// null appears to be allowed in most browsers, and appears to be
|
||||
// relied upon by some libraries, like Bootstrap carousel
|
||||
return;
|
||||
}
|
||||
|
||||
if (!clock.timers) {
|
||||
clock.timers = {};
|
||||
}
|
||||
|
||||
// in Node, timerId is an object with .ref()/.unref(), and
|
||||
// its .id field is the actual timer id.
|
||||
if (typeof timerId === "object") {
|
||||
timerId = timerId.id;
|
||||
}
|
||||
|
||||
if (clock.timers.hasOwnProperty(timerId)) {
|
||||
// check that the ID matches a timer of the correct type
|
||||
var timer = clock.timers[timerId];
|
||||
if (timer.type === ttype) {
|
||||
delete clock.timers[timerId];
|
||||
} else {
|
||||
var clear = ttype === "AnimationFrame" ? "cancelAnimationFrame" : "clear" + ttype;
|
||||
var schedule = timer.type === "AnimationFrame" ? "requestAnimationFrame" : "set" + timer.type;
|
||||
throw new Error("Cannot clear timer: timer created with " + schedule
|
||||
+ "() but cleared with " + clear + "()");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function uninstall(clock, target, config) {
|
||||
var method,
|
||||
i,
|
||||
l;
|
||||
var installedHrTime = "_hrtime";
|
||||
var installedNextTick = "_nextTick";
|
||||
|
||||
for (i = 0, l = clock.methods.length; i < l; i++) {
|
||||
method = clock.methods[i];
|
||||
if (method === "hrtime" && target.process) {
|
||||
target.process.hrtime = clock[installedHrTime];
|
||||
} else if (method === "nextTick" && target.process) {
|
||||
target.process.nextTick = clock[installedNextTick];
|
||||
} else if (method === "performance") {
|
||||
target[method] = clock["_" + method];
|
||||
} else {
|
||||
if (target[method] && target[method].hadOwnProperty) {
|
||||
target[method] = clock["_" + method];
|
||||
if (method === "clearInterval" && config.shouldAdvanceTime === true) {
|
||||
target[method](clock.attachedInterval);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
delete target[method];
|
||||
} catch (ignore) { /* eslint empty-block: "off" */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent multiple executions which will completely remove these props
|
||||
clock.methods = [];
|
||||
|
||||
// return pending timers, to enable checking what timers remained on uninstall
|
||||
if (!clock.timers) {
|
||||
return [];
|
||||
}
|
||||
return Object.keys(clock.timers).map(function mapper(key) {
|
||||
return clock.timers[key];
|
||||
});
|
||||
}
|
||||
|
||||
function hijackMethod(target, method, clock) {
|
||||
var prop;
|
||||
clock[method].hadOwnProperty = Object.prototype.hasOwnProperty.call(target, method);
|
||||
clock["_" + method] = target[method];
|
||||
|
||||
if (method === "Date") {
|
||||
var date = mirrorDateProperties(clock[method], target[method]);
|
||||
target[method] = date;
|
||||
} else if (method === "performance") {
|
||||
target[method] = clock[method];
|
||||
} else {
|
||||
target[method] = function () {
|
||||
return clock[method].apply(clock, arguments);
|
||||
};
|
||||
|
||||
for (prop in clock[method]) {
|
||||
if (clock[method].hasOwnProperty(prop)) {
|
||||
target[method][prop] = clock[method][prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
target[method].clock = clock;
|
||||
}
|
||||
|
||||
function doIntervalTick(clock, advanceTimeDelta) {
|
||||
clock.tick(advanceTimeDelta);
|
||||
}
|
||||
|
||||
var timers = {
|
||||
setTimeout: _global.setTimeout,
|
||||
clearTimeout: _global.clearTimeout,
|
||||
setImmediate: _global.setImmediate,
|
||||
clearImmediate: _global.clearImmediate,
|
||||
setInterval: _global.setInterval,
|
||||
clearInterval: _global.clearInterval,
|
||||
Date: _global.Date
|
||||
};
|
||||
|
||||
if (hrtimePresent) {
|
||||
timers.hrtime = _global.process.hrtime;
|
||||
}
|
||||
|
||||
if (nextTickPresent) {
|
||||
timers.nextTick = _global.process.nextTick;
|
||||
}
|
||||
|
||||
if (performancePresent) {
|
||||
timers.performance = _global.performance;
|
||||
}
|
||||
|
||||
if (requestAnimationFramePresent) {
|
||||
timers.requestAnimationFrame = _global.requestAnimationFrame;
|
||||
}
|
||||
|
||||
if (cancelAnimationFramePresent) {
|
||||
timers.cancelAnimationFrame = _global.cancelAnimationFrame;
|
||||
}
|
||||
|
||||
var keys = Object.keys || function (obj) {
|
||||
var ks = [];
|
||||
var key;
|
||||
|
||||
for (key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
ks.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
return ks;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param start {Date|number} the system time
|
||||
* @param loopLimit {number} maximum number of timers that will be run when calling runAll()
|
||||
*/
|
||||
function createClock(start, loopLimit) {
|
||||
start = start || 0;
|
||||
loopLimit = loopLimit || 1000;
|
||||
|
||||
if (NativeDate === undefined) {
|
||||
throw new Error("The global scope doesn't have a `Date` object"
|
||||
+ " (see https://github.com/sinonjs/sinon/issues/1852#issuecomment-419622780)");
|
||||
}
|
||||
|
||||
var clock = {
|
||||
now: getEpoch(start),
|
||||
hrNow: 0,
|
||||
timeouts: {},
|
||||
Date: createDate(),
|
||||
loopLimit: loopLimit
|
||||
};
|
||||
|
||||
clock.Date.clock = clock;
|
||||
|
||||
function getTimeToNextFrame() {
|
||||
return 16 - ((clock.now - start) % 16);
|
||||
}
|
||||
|
||||
clock.setTimeout = function setTimeout(func, timeout) {
|
||||
return addTimer(clock, {
|
||||
func: func,
|
||||
args: Array.prototype.slice.call(arguments, 2),
|
||||
delay: timeout
|
||||
});
|
||||
};
|
||||
|
||||
clock.clearTimeout = function clearTimeout(timerId) {
|
||||
return clearTimer(clock, timerId, "Timeout");
|
||||
};
|
||||
clock.nextTick = function nextTick(func) {
|
||||
return enqueueJob(clock, {
|
||||
func: func,
|
||||
args: Array.prototype.slice.call(arguments, 1)
|
||||
});
|
||||
};
|
||||
clock.setInterval = function setInterval(func, timeout) {
|
||||
timeout = parseInt(timeout, 10);
|
||||
return addTimer(clock, {
|
||||
func: func,
|
||||
args: Array.prototype.slice.call(arguments, 2),
|
||||
delay: timeout,
|
||||
interval: timeout
|
||||
});
|
||||
};
|
||||
|
||||
clock.clearInterval = function clearInterval(timerId) {
|
||||
return clearTimer(clock, timerId, "Interval");
|
||||
};
|
||||
|
||||
clock.setImmediate = function setImmediate(func) {
|
||||
return addTimer(clock, {
|
||||
func: func,
|
||||
args: Array.prototype.slice.call(arguments, 1),
|
||||
immediate: true
|
||||
});
|
||||
};
|
||||
|
||||
clock.clearImmediate = function clearImmediate(timerId) {
|
||||
return clearTimer(clock, timerId, "Immediate");
|
||||
};
|
||||
|
||||
clock.requestAnimationFrame = function requestAnimationFrame(func) {
|
||||
var result = addTimer(clock, {
|
||||
func: func,
|
||||
delay: getTimeToNextFrame(),
|
||||
args: [clock.now + getTimeToNextFrame()],
|
||||
animation: true
|
||||
});
|
||||
|
||||
return result.id || result;
|
||||
};
|
||||
|
||||
clock.cancelAnimationFrame = function cancelAnimationFrame(timerId) {
|
||||
return clearTimer(clock, timerId, "AnimationFrame");
|
||||
};
|
||||
|
||||
function updateHrTime(newNow) {
|
||||
clock.hrNow += (newNow - clock.now);
|
||||
}
|
||||
|
||||
clock.runMicrotasks = function runMicrotasks() {
|
||||
runJobs(clock);
|
||||
};
|
||||
|
||||
clock.tick = function tick(ms) {
|
||||
ms = typeof ms === "number" ? ms : parseTime(ms);
|
||||
var tickFrom = clock.now;
|
||||
var tickTo = clock.now + ms;
|
||||
var previous = clock.now;
|
||||
var timer, firstException, oldNow;
|
||||
|
||||
clock.duringTick = true;
|
||||
|
||||
// perform process.nextTick()s
|
||||
oldNow = clock.now;
|
||||
runJobs(clock);
|
||||
if (oldNow !== clock.now) {
|
||||
// compensate for any setSystemTime() call during process.nextTick() callback
|
||||
tickFrom += clock.now - oldNow;
|
||||
tickTo += clock.now - oldNow;
|
||||
}
|
||||
|
||||
// perform each timer in the requested range
|
||||
timer = firstTimerInRange(clock, tickFrom, tickTo);
|
||||
while (timer && tickFrom <= tickTo) {
|
||||
if (clock.timers[timer.id]) {
|
||||
updateHrTime(timer.callAt);
|
||||
tickFrom = timer.callAt;
|
||||
clock.now = timer.callAt;
|
||||
oldNow = clock.now;
|
||||
try {
|
||||
runJobs(clock);
|
||||
callTimer(clock, timer);
|
||||
} catch (e) {
|
||||
firstException = firstException || e;
|
||||
}
|
||||
|
||||
// compensate for any setSystemTime() call during timer callback
|
||||
if (oldNow !== clock.now) {
|
||||
tickFrom += clock.now - oldNow;
|
||||
tickTo += clock.now - oldNow;
|
||||
previous += clock.now - oldNow;
|
||||
}
|
||||
}
|
||||
|
||||
timer = firstTimerInRange(clock, previous, tickTo);
|
||||
previous = tickFrom;
|
||||
}
|
||||
|
||||
// perform process.nextTick()s again
|
||||
oldNow = clock.now;
|
||||
runJobs(clock);
|
||||
if (oldNow !== clock.now) {
|
||||
// compensate for any setSystemTime() call during process.nextTick() callback
|
||||
tickFrom += clock.now - oldNow;
|
||||
tickTo += clock.now - oldNow;
|
||||
}
|
||||
clock.duringTick = false;
|
||||
|
||||
// corner case: during runJobs, new timers were scheduled which could be in the range [clock.now, tickTo]
|
||||
timer = firstTimerInRange(clock, tickFrom, tickTo);
|
||||
if (timer) {
|
||||
try {
|
||||
clock.tick(tickTo - clock.now); // do it all again - for the remainder of the requested range
|
||||
} catch (e) {
|
||||
firstException = firstException || e;
|
||||
}
|
||||
} else {
|
||||
// no timers remaining in the requested range: move the clock all the way to the end
|
||||
updateHrTime(tickTo);
|
||||
clock.now = tickTo;
|
||||
}
|
||||
if (firstException) {
|
||||
throw firstException;
|
||||
}
|
||||
return clock.now;
|
||||
};
|
||||
|
||||
clock.next = function next() {
|
||||
runJobs(clock);
|
||||
var timer = firstTimer(clock);
|
||||
if (!timer) {
|
||||
return clock.now;
|
||||
}
|
||||
|
||||
clock.duringTick = true;
|
||||
try {
|
||||
updateHrTime(timer.callAt);
|
||||
clock.now = timer.callAt;
|
||||
callTimer(clock, timer);
|
||||
runJobs(clock);
|
||||
return clock.now;
|
||||
} finally {
|
||||
clock.duringTick = false;
|
||||
}
|
||||
};
|
||||
|
||||
clock.runAll = function runAll() {
|
||||
var numTimers, i;
|
||||
runJobs(clock);
|
||||
for (i = 0; i < clock.loopLimit; i++) {
|
||||
if (!clock.timers) {
|
||||
return clock.now;
|
||||
}
|
||||
|
||||
numTimers = keys(clock.timers).length;
|
||||
if (numTimers === 0) {
|
||||
return clock.now;
|
||||
}
|
||||
|
||||
clock.next();
|
||||
}
|
||||
|
||||
throw new Error("Aborting after running " + clock.loopLimit + " timers, assuming an infinite loop!");
|
||||
};
|
||||
|
||||
clock.runToFrame = function runToFrame() {
|
||||
return clock.tick(getTimeToNextFrame());
|
||||
};
|
||||
|
||||
clock.runToLast = function runToLast() {
|
||||
var timer = lastTimer(clock);
|
||||
if (!timer) {
|
||||
runJobs(clock);
|
||||
return clock.now;
|
||||
}
|
||||
|
||||
return clock.tick(timer.callAt - clock.now);
|
||||
};
|
||||
|
||||
clock.reset = function reset() {
|
||||
clock.timers = {};
|
||||
clock.jobs = [];
|
||||
clock.now = getEpoch(start);
|
||||
clock.hrNow = 0;
|
||||
};
|
||||
|
||||
clock.setSystemTime = function setSystemTime(systemTime) {
|
||||
// determine time difference
|
||||
var newNow = getEpoch(systemTime);
|
||||
var difference = newNow - clock.now;
|
||||
var id, timer;
|
||||
|
||||
// update 'system clock'
|
||||
clock.now = newNow;
|
||||
|
||||
// update timers and intervals to keep them stable
|
||||
for (id in clock.timers) {
|
||||
if (clock.timers.hasOwnProperty(id)) {
|
||||
timer = clock.timers[id];
|
||||
timer.createdAt += difference;
|
||||
timer.callAt += difference;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (performancePresent) {
|
||||
clock.performance = Object.create(null);
|
||||
|
||||
if (hasPerformancePrototype) {
|
||||
var proto = _global.Performance.prototype;
|
||||
|
||||
Object
|
||||
.getOwnPropertyNames(proto)
|
||||
.forEach(function (name) {
|
||||
clock.performance[name] = NOOP;
|
||||
});
|
||||
}
|
||||
|
||||
clock.performance.now = function lolexNow() {
|
||||
return clock.hrNow;
|
||||
};
|
||||
}
|
||||
|
||||
if (hrtimePresent) {
|
||||
clock.hrtime = function (prev) {
|
||||
if (Array.isArray(prev)) {
|
||||
var oldSecs = (prev[0] + prev[1] / 1e9);
|
||||
var newSecs = (clock.hrNow / 1000);
|
||||
var difference = (newSecs - oldSecs);
|
||||
var secs = fixedFloor(difference);
|
||||
var nanosecs = fixedModulo(difference * 1e9, 1e9);
|
||||
return [
|
||||
secs,
|
||||
nanosecs
|
||||
];
|
||||
}
|
||||
return [
|
||||
fixedFloor(clock.hrNow / 1000),
|
||||
fixedModulo(clock.hrNow * 1e6, 1e9)
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
return clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param config {Object} optional config
|
||||
* @param config.target {Object} the target to install timers in (default `window`)
|
||||
* @param config.now {number|Date} a number (in milliseconds) or a Date object (default epoch)
|
||||
* @param config.toFake {string[]} names of the methods that should be faked.
|
||||
* @param config.loopLimit {number} the maximum number of timers that will be run when calling runAll()
|
||||
* @param config.shouldAdvanceTime {Boolean} tells lolex to increment mocked time automatically (default false)
|
||||
* @param config.advanceTimeDelta {Number} increment mocked time every <<advanceTimeDelta>> ms (default: 20ms)
|
||||
*/
|
||||
function install(config) {
|
||||
if ( arguments.length > 1 || config instanceof Date || Array.isArray(config) || typeof config === "number") {
|
||||
throw new TypeError("lolex.install called with " + String(config) +
|
||||
" lolex 2.0+ requires an object parameter - see https://github.com/sinonjs/lolex");
|
||||
}
|
||||
config = typeof config !== "undefined" ? config : {};
|
||||
config.shouldAdvanceTime = config.shouldAdvanceTime || false;
|
||||
config.advanceTimeDelta = config.advanceTimeDelta || 20;
|
||||
|
||||
var i, l;
|
||||
var target = config.target || _global;
|
||||
var clock = createClock(config.now, config.loopLimit);
|
||||
|
||||
clock.uninstall = function () {
|
||||
return uninstall(clock, target, config);
|
||||
};
|
||||
|
||||
clock.methods = config.toFake || [];
|
||||
|
||||
if (clock.methods.length === 0) {
|
||||
// do not fake nextTick by default - GitHub#126
|
||||
clock.methods = keys(timers).filter(function (key) {return key !== "nextTick";});
|
||||
}
|
||||
|
||||
for (i = 0, l = clock.methods.length; i < l; i++) {
|
||||
if (clock.methods[i] === "hrtime") {
|
||||
if (target.process && typeof target.process.hrtime === "function") {
|
||||
hijackMethod(target.process, clock.methods[i], clock);
|
||||
}
|
||||
} else if (clock.methods[i] === "nextTick") {
|
||||
if (target.process && typeof target.process.nextTick === "function") {
|
||||
hijackMethod(target.process, clock.methods[i], clock);
|
||||
}
|
||||
} else {
|
||||
if (clock.methods[i] === "setInterval" && config.shouldAdvanceTime === true) {
|
||||
var intervalTick = doIntervalTick.bind(null, clock, config.advanceTimeDelta);
|
||||
var intervalId = target[clock.methods[i]](
|
||||
intervalTick,
|
||||
config.advanceTimeDelta);
|
||||
clock.attachedInterval = intervalId;
|
||||
}
|
||||
hijackMethod(target, clock.methods[i], clock);
|
||||
}
|
||||
}
|
||||
|
||||
return clock;
|
||||
}
|
||||
|
||||
return {
|
||||
timers: timers,
|
||||
createClock: createClock,
|
||||
install: install,
|
||||
withGlobal: withGlobal
|
||||
};
|
||||
}
|
||||
|
||||
var defaultImplementation = withGlobal(global || window);
|
||||
|
||||
exports.timers = defaultImplementation.timers;
|
||||
exports.createClock = defaultImplementation.createClock;
|
||||
exports.install = defaultImplementation.install;
|
||||
exports.withGlobal = withGlobal;
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
1.6.0 / 2016-10-03
|
||||
==================
|
||||
|
||||
* Populate `RegExp.keys` when using the `tokensToRegExp` method (making it consistent with the main export)
|
||||
* Allow a `delimiter` option to be passed in with `parse`
|
||||
* Updated TypeScript definition with `Keys` and `Options` updated
|
||||
|
||||
1.5.3 / 2016-06-15
|
||||
==================
|
||||
|
||||
* Add `\\` to the ignore character group to avoid backtracking on mismatched parens
|
||||
|
||||
1.5.2 / 2016-06-15
|
||||
==================
|
||||
|
||||
* Escape `\\` in string segments of regexp
|
||||
|
||||
1.5.1 / 2016-06-08
|
||||
==================
|
||||
|
||||
* Add `index.d.ts` to NPM package
|
||||
|
||||
1.5.0 / 2016-05-20
|
||||
==================
|
||||
|
||||
* Handle partial token segments (better)
|
||||
* Allow compile to handle asterisk token segments
|
||||
|
||||
1.4.0 / 2016-05-18
|
||||
==================
|
||||
|
||||
* Handle RegExp unions in path matching groups
|
||||
|
||||
1.3.0 / 2016-05-08
|
||||
==================
|
||||
|
||||
* Clarify README language and named parameter token support
|
||||
* Support advanced Closure Compiler with type annotations
|
||||
* Add pretty paths options to compiled function output
|
||||
* Add TypeScript definition to project
|
||||
* Improved prefix handling with non-complete segment parameters (E.g. `/:foo?-bar`)
|
||||
|
||||
1.2.1 / 2015-08-17
|
||||
==================
|
||||
|
||||
* Encode values before validation with path compilation function
|
||||
* More examples of using compilation in README
|
||||
|
||||
1.2.0 / 2015-05-20
|
||||
==================
|
||||
|
||||
* Add support for matching an asterisk (`*`) as an unnamed match everything group (`(.*)`)
|
||||
|
||||
1.1.1 / 2015-05-11
|
||||
==================
|
||||
|
||||
* Expose methods for working with path tokens
|
||||
|
||||
1.1.0 / 2015-05-09
|
||||
==================
|
||||
|
||||
* Expose the parser implementation to consumers
|
||||
* Implement a compiler function to generate valid strings
|
||||
* Huge refactor of tests to be more DRY and cover new parse and compile functions
|
||||
* Use chai in tests
|
||||
* Add .editorconfig
|
||||
|
||||
1.0.3 / 2015-01-17
|
||||
==================
|
||||
|
||||
* Optimised function runtime
|
||||
* Added `files` to `package.json`
|
||||
|
||||
1.0.2 / 2014-12-17
|
||||
==================
|
||||
|
||||
* Use `Array.isArray` shim
|
||||
* Remove ES5 incompatible code
|
||||
* Fixed repository path
|
||||
* Added new readme badges
|
||||
|
||||
1.0.1 / 2014-08-27
|
||||
==================
|
||||
|
||||
* Ensure installation works correctly on 0.8
|
||||
|
||||
1.0.0 / 2014-08-17
|
||||
==================
|
||||
|
||||
* No more API changes
|
||||
|
||||
0.2.5 / 2014-08-07
|
||||
==================
|
||||
|
||||
* Allow keys parameter to be omitted
|
||||
|
||||
0.2.4 / 2014-08-02
|
||||
==================
|
||||
|
||||
* Code coverage badge
|
||||
* Updated readme
|
||||
* Attach keys to the generated regexp
|
||||
|
||||
0.2.3 / 2014-07-09
|
||||
==================
|
||||
|
||||
* Add MIT license
|
||||
|
||||
0.2.2 / 2014-07-06
|
||||
==================
|
||||
|
||||
* A passed in trailing slash in non-strict mode will become optional
|
||||
* In non-end mode, the optional trailing slash will only match at the end
|
||||
|
||||
0.2.1 / 2014-06-11
|
||||
==================
|
||||
|
||||
* Fixed a major capturing group regexp regression
|
||||
|
||||
0.2.0 / 2014-06-09
|
||||
==================
|
||||
|
||||
* Improved support for arrays
|
||||
* Improved support for regexps
|
||||
* Better support for non-ending strict mode matches with a trailing slash
|
||||
* Travis CI support
|
||||
* Block using regexp special characters in the path
|
||||
* Removed support for the asterisk to match all
|
||||
* New support for parameter suffixes - `*`, `+` and `?`
|
||||
* Updated readme
|
||||
* Provide delimiter information with keys array
|
||||
|
||||
0.1.2 / 2014-03-10
|
||||
==================
|
||||
|
||||
* Move testing dependencies to `devDependencies`
|
||||
|
||||
0.1.1 / 2014-03-10
|
||||
==================
|
||||
|
||||
* Match entire substring with `options.end`
|
||||
* Properly handle ending and non-ending matches
|
||||
|
||||
0.1.0 / 2014-03-06
|
||||
==================
|
||||
|
||||
* Add `options.end`
|
||||
|
||||
0.0.2 / 2013-02-10
|
||||
==================
|
||||
|
||||
* Update to match current express
|
||||
* Add .license property to component.json
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com)
|
||||
|
||||
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.
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
# Path-to-RegExp
|
||||
|
||||
> Turn an Express-style path string such as `/user/:name` into a regular expression.
|
||||
|
||||
[![NPM version][npm-image]][npm-url]
|
||||
[![Build status][travis-image]][travis-url]
|
||||
[![Test coverage][coveralls-image]][coveralls-url]
|
||||
[![Dependency Status][david-image]][david-url]
|
||||
[![License][license-image]][license-url]
|
||||
[![Downloads][downloads-image]][downloads-url]
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
npm install path-to-regexp --save
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```javascript
|
||||
var pathToRegexp = require('path-to-regexp')
|
||||
|
||||
// pathToRegexp(path, keys, options)
|
||||
// pathToRegexp.parse(path)
|
||||
// pathToRegexp.compile(path)
|
||||
```
|
||||
|
||||
- **path** An Express-style string, an array of strings, or a regular expression.
|
||||
- **keys** An array to be populated with the keys found in the path.
|
||||
- **options**
|
||||
- **sensitive** When `true` the route will be case sensitive. (default: `false`)
|
||||
- **strict** When `false` the trailing slash is optional. (default: `false`)
|
||||
- **end** When `false` the path will match at the beginning. (default: `true`)
|
||||
- **delimiter** Set the default delimiter for repeat parameters. (default: `'/'`)
|
||||
|
||||
```javascript
|
||||
var keys = []
|
||||
var re = pathToRegexp('/foo/:bar', keys)
|
||||
// re = /^\/foo\/([^\/]+?)\/?$/i
|
||||
// keys = [{ name: 'bar', prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '[^\\/]+?' }]
|
||||
```
|
||||
|
||||
**Please note:** The `RegExp` returned by `path-to-regexp` is intended for use with pathnames or hostnames. It can not handle the query strings or fragments of a URL.
|
||||
|
||||
### Parameters
|
||||
|
||||
The path string can be used to define parameters and populate the keys.
|
||||
|
||||
#### Named Parameters
|
||||
|
||||
Named parameters are defined by prefixing a colon to the parameter name (`:foo`). By default, the parameter will match until the following path segment.
|
||||
|
||||
```js
|
||||
var re = pathToRegexp('/:foo/:bar', keys)
|
||||
// keys = [{ name: 'foo', prefix: '/', ... }, { name: 'bar', prefix: '/', ... }]
|
||||
|
||||
re.exec('/test/route')
|
||||
//=> ['/test/route', 'test', 'route']
|
||||
```
|
||||
|
||||
**Please note:** Named parameters must be made up of "word characters" (`[A-Za-z0-9_]`).
|
||||
|
||||
```js
|
||||
var re = pathToRegexp('/(apple-)?icon-:res(\\d+).png', keys)
|
||||
// keys = [{ name: 0, prefix: '/', ... }, { name: 'res', prefix: '', ... }]
|
||||
|
||||
re.exec('/icon-76.png')
|
||||
//=> ['/icon-76.png', undefined, '76']
|
||||
```
|
||||
|
||||
#### Modified Parameters
|
||||
|
||||
##### Optional
|
||||
|
||||
Parameters can be suffixed with a question mark (`?`) to make the parameter optional. This will also make the prefix optional.
|
||||
|
||||
```js
|
||||
var re = pathToRegexp('/:foo/:bar?', keys)
|
||||
// keys = [{ name: 'foo', ... }, { name: 'bar', delimiter: '/', optional: true, repeat: false }]
|
||||
|
||||
re.exec('/test')
|
||||
//=> ['/test', 'test', undefined]
|
||||
|
||||
re.exec('/test/route')
|
||||
//=> ['/test', 'test', 'route']
|
||||
```
|
||||
|
||||
##### Zero or more
|
||||
|
||||
Parameters can be suffixed with an asterisk (`*`) to denote a zero or more parameter matches. The prefix is taken into account for each match.
|
||||
|
||||
```js
|
||||
var re = pathToRegexp('/:foo*', keys)
|
||||
// keys = [{ name: 'foo', delimiter: '/', optional: true, repeat: true }]
|
||||
|
||||
re.exec('/')
|
||||
//=> ['/', undefined]
|
||||
|
||||
re.exec('/bar/baz')
|
||||
//=> ['/bar/baz', 'bar/baz']
|
||||
```
|
||||
|
||||
##### One or more
|
||||
|
||||
Parameters can be suffixed with a plus sign (`+`) to denote a one or more parameter matches. The prefix is taken into account for each match.
|
||||
|
||||
```js
|
||||
var re = pathToRegexp('/:foo+', keys)
|
||||
// keys = [{ name: 'foo', delimiter: '/', optional: false, repeat: true }]
|
||||
|
||||
re.exec('/')
|
||||
//=> null
|
||||
|
||||
re.exec('/bar/baz')
|
||||
//=> ['/bar/baz', 'bar/baz']
|
||||
```
|
||||
|
||||
#### Custom Match Parameters
|
||||
|
||||
All parameters can be provided a custom regexp, which overrides the default (`[^\/]+`).
|
||||
|
||||
```js
|
||||
var re = pathToRegexp('/:foo(\\d+)', keys)
|
||||
// keys = [{ name: 'foo', ... }]
|
||||
|
||||
re.exec('/123')
|
||||
//=> ['/123', '123']
|
||||
|
||||
re.exec('/abc')
|
||||
//=> null
|
||||
```
|
||||
|
||||
**Please note:** Backslashes need to be escaped with another backslash in strings.
|
||||
|
||||
#### Unnamed Parameters
|
||||
|
||||
It is possible to write an unnamed parameter that only consists of a matching group. It works the same as a named parameter, except it will be numerically indexed.
|
||||
|
||||
```js
|
||||
var re = pathToRegexp('/:foo/(.*)', keys)
|
||||
// keys = [{ name: 'foo', ... }, { name: 0, ... }]
|
||||
|
||||
re.exec('/test/route')
|
||||
//=> ['/test/route', 'test', 'route']
|
||||
```
|
||||
|
||||
#### Asterisk
|
||||
|
||||
An asterisk can be used for matching everything. It is equivalent to an unnamed matching group of `(.*)`.
|
||||
|
||||
```js
|
||||
var re = pathToRegexp('/foo/*', keys)
|
||||
// keys = [{ name: '0', ... }]
|
||||
|
||||
re.exec('/foo/bar/baz')
|
||||
//=> ['/foo/bar/baz', 'bar/baz']
|
||||
```
|
||||
|
||||
### Parse
|
||||
|
||||
The parse function is exposed via `pathToRegexp.parse`. This will return an array of strings and keys.
|
||||
|
||||
```js
|
||||
var tokens = pathToRegexp.parse('/route/:foo/(.*)')
|
||||
|
||||
console.log(tokens[0])
|
||||
//=> "/route"
|
||||
|
||||
console.log(tokens[1])
|
||||
//=> { name: 'foo', prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '[^\\/]+?' }
|
||||
|
||||
console.log(tokens[2])
|
||||
//=> { name: 0, prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '.*' }
|
||||
```
|
||||
|
||||
**Note:** This method only works with Express-style strings.
|
||||
|
||||
### Compile ("Reverse" Path-To-RegExp)
|
||||
|
||||
Path-To-RegExp exposes a compile function for transforming an Express-style path into a valid path.
|
||||
|
||||
```js
|
||||
var toPath = pathToRegexp.compile('/user/:id')
|
||||
|
||||
toPath({ id: 123 }) //=> "/user/123"
|
||||
toPath({ id: 'café' }) //=> "/user/caf%C3%A9"
|
||||
toPath({ id: '/' }) //=> "/user/%2F"
|
||||
|
||||
toPath({ id: ':' }) //=> "/user/%3A"
|
||||
toPath({ id: ':' }, { pretty: true }) //=> "/user/:"
|
||||
|
||||
var toPathRepeated = pathToRegexp.compile('/:segment+')
|
||||
|
||||
toPathRepeated({ segment: 'foo' }) //=> "/foo"
|
||||
toPathRepeated({ segment: ['a', 'b', 'c'] }) //=> "/a/b/c"
|
||||
|
||||
var toPathRegexp = pathToRegexp.compile('/user/:id(\\d+)')
|
||||
|
||||
toPathRegexp({ id: 123 }) //=> "/user/123"
|
||||
toPathRegexp({ id: '123' }) //=> "/user/123"
|
||||
toPathRegexp({ id: 'abc' }) //=> Throws `TypeError`.
|
||||
```
|
||||
|
||||
**Note:** The generated function will throw on invalid input. It will do all necessary checks to ensure the generated path is valid. This method only works with strings.
|
||||
|
||||
### Working with Tokens
|
||||
|
||||
Path-To-RegExp exposes the two functions used internally that accept an array of tokens.
|
||||
|
||||
* `pathToRegexp.tokensToRegExp(tokens, options)` Transform an array of tokens into a matching regular expression.
|
||||
* `pathToRegexp.tokensToFunction(tokens)` Transform an array of tokens into a path generator function.
|
||||
|
||||
#### Token Information
|
||||
|
||||
* `name` The name of the token (`string` for named or `number` for index)
|
||||
* `prefix` The prefix character for the segment (`/` or `.`)
|
||||
* `delimiter` The delimiter for the segment (same as prefix or `/`)
|
||||
* `optional` Indicates the token is optional (`boolean`)
|
||||
* `repeat` Indicates the token is repeated (`boolean`)
|
||||
* `partial` Indicates this token is a partial path segment (`boolean`)
|
||||
* `pattern` The RegExp used to match this token (`string`)
|
||||
* `asterisk` Indicates the token is an `*` match (`boolean`)
|
||||
|
||||
## Compatibility with Express <= 4.x
|
||||
|
||||
Path-To-RegExp breaks compatibility with Express <= `4.x`:
|
||||
|
||||
* No longer a direct conversion to a RegExp with sugar on top - it's a path matcher with named and unnamed matching groups
|
||||
* It's unlikely you previously abused this feature, it's rare and you could always use a RegExp instead
|
||||
* All matching RegExp special characters can be used in a matching group. E.g. `/:user(.*)`
|
||||
* Other RegExp features are not support - no nested matching groups, non-capturing groups or look aheads
|
||||
* Parameters have suffixes that augment meaning - `*`, `+` and `?`. E.g. `/:user*`
|
||||
|
||||
## TypeScript
|
||||
|
||||
Includes a [`.d.ts`](index.d.ts) file for TypeScript users.
|
||||
|
||||
## Live Demo
|
||||
|
||||
You can see a live demo of this library in use at [express-route-tester](http://forbeslindesay.github.com/express-route-tester/).
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
[npm-image]: https://img.shields.io/npm/v/path-to-regexp.svg?style=flat
|
||||
[npm-url]: https://npmjs.org/package/path-to-regexp
|
||||
[travis-image]: https://img.shields.io/travis/pillarjs/path-to-regexp.svg?style=flat
|
||||
[travis-url]: https://travis-ci.org/pillarjs/path-to-regexp
|
||||
[coveralls-image]: https://img.shields.io/coveralls/pillarjs/path-to-regexp.svg?style=flat
|
||||
[coveralls-url]: https://coveralls.io/r/pillarjs/path-to-regexp?branch=master
|
||||
[david-image]: http://img.shields.io/david/pillarjs/path-to-regexp.svg?style=flat
|
||||
[david-url]: https://david-dm.org/pillarjs/path-to-regexp
|
||||
[license-image]: http://img.shields.io/npm/l/path-to-regexp.svg?style=flat
|
||||
[license-url]: LICENSE.md
|
||||
[downloads-image]: http://img.shields.io/npm/dm/path-to-regexp.svg?style=flat
|
||||
[downloads-url]: https://npmjs.org/package/path-to-regexp
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
declare function pathToRegexp (path: pathToRegexp.Path, options?: pathToRegexp.RegExpOptions & pathToRegexp.ParseOptions): pathToRegexp.PathRegExp;
|
||||
declare function pathToRegexp (path: pathToRegexp.Path, keys?: pathToRegexp.Key[], options?: pathToRegexp.RegExpOptions & pathToRegexp.ParseOptions): pathToRegexp.PathRegExp;
|
||||
|
||||
declare namespace pathToRegexp {
|
||||
export interface PathRegExp extends RegExp {
|
||||
// An array to be populated with the keys found in the path.
|
||||
keys: Key[];
|
||||
}
|
||||
|
||||
export interface RegExpOptions {
|
||||
/**
|
||||
* When `true` the route will be case sensitive. (default: `false`)
|
||||
*/
|
||||
sensitive?: boolean;
|
||||
/**
|
||||
* When `false` the trailing slash is optional. (default: `false`)
|
||||
*/
|
||||
strict?: boolean;
|
||||
/**
|
||||
* When `false` the path will match at the beginning. (default: `true`)
|
||||
*/
|
||||
end?: boolean;
|
||||
/**
|
||||
* Sets the final character for non-ending optimistic matches. (default: `/`)
|
||||
*/
|
||||
delimiter?: string;
|
||||
}
|
||||
|
||||
export interface ParseOptions {
|
||||
/**
|
||||
* Set the default delimiter for repeat parameters. (default: `'/'`)
|
||||
*/
|
||||
delimiter?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an Express-style path into an array of tokens.
|
||||
*/
|
||||
export function parse (path: string, options?: ParseOptions): Token[];
|
||||
|
||||
/**
|
||||
* Transforming an Express-style path into a valid path.
|
||||
*/
|
||||
export function compile (path: string, options?: ParseOptions): PathFunction;
|
||||
|
||||
/**
|
||||
* Transform an array of tokens into a path generator function.
|
||||
*/
|
||||
export function tokensToFunction (tokens: Token[]): PathFunction;
|
||||
|
||||
/**
|
||||
* Transform an array of tokens into a matching regular expression.
|
||||
*/
|
||||
export function tokensToRegExp (tokens: Token[], options?: RegExpOptions): PathRegExp;
|
||||
export function tokensToRegExp (tokens: Token[], keys?: Key[], options?: RegExpOptions): PathRegExp;
|
||||
|
||||
export interface Key {
|
||||
name: string | number;
|
||||
prefix: string;
|
||||
delimiter: string;
|
||||
optional: boolean;
|
||||
repeat: boolean;
|
||||
pattern: string;
|
||||
partial: boolean;
|
||||
asterisk: boolean;
|
||||
}
|
||||
|
||||
interface PathFunctionOptions {
|
||||
pretty?: boolean;
|
||||
}
|
||||
|
||||
export type Token = string | Key;
|
||||
export type Path = string | RegExp | Array<string | RegExp>;
|
||||
export type PathFunction = (data?: Object, options?: PathFunctionOptions) => string;
|
||||
}
|
||||
|
||||
export = pathToRegexp;
|
||||
+426
@@ -0,0 +1,426 @@
|
||||
var isarray = require('isarray')
|
||||
|
||||
/**
|
||||
* Expose `pathToRegexp`.
|
||||
*/
|
||||
module.exports = pathToRegexp
|
||||
module.exports.parse = parse
|
||||
module.exports.compile = compile
|
||||
module.exports.tokensToFunction = tokensToFunction
|
||||
module.exports.tokensToRegExp = tokensToRegExp
|
||||
|
||||
/**
|
||||
* The main path matching regexp utility.
|
||||
*
|
||||
* @type {RegExp}
|
||||
*/
|
||||
var PATH_REGEXP = new RegExp([
|
||||
// Match escaped characters that would otherwise appear in future matches.
|
||||
// This allows the user to escape special characters that won't transform.
|
||||
'(\\\\.)',
|
||||
// Match Express-style parameters and un-named parameters with a prefix
|
||||
// and optional suffixes. Matches appear as:
|
||||
//
|
||||
// "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined]
|
||||
// "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined]
|
||||
// "/*" => ["/", undefined, undefined, undefined, undefined, "*"]
|
||||
'([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))'
|
||||
].join('|'), 'g')
|
||||
|
||||
/**
|
||||
* Parse a string for the raw tokens.
|
||||
*
|
||||
* @param {string} str
|
||||
* @param {Object=} options
|
||||
* @return {!Array}
|
||||
*/
|
||||
function parse (str, options) {
|
||||
var tokens = []
|
||||
var key = 0
|
||||
var index = 0
|
||||
var path = ''
|
||||
var defaultDelimiter = options && options.delimiter || '/'
|
||||
var res
|
||||
|
||||
while ((res = PATH_REGEXP.exec(str)) != null) {
|
||||
var m = res[0]
|
||||
var escaped = res[1]
|
||||
var offset = res.index
|
||||
path += str.slice(index, offset)
|
||||
index = offset + m.length
|
||||
|
||||
// Ignore already escaped sequences.
|
||||
if (escaped) {
|
||||
path += escaped[1]
|
||||
continue
|
||||
}
|
||||
|
||||
var next = str[index]
|
||||
var prefix = res[2]
|
||||
var name = res[3]
|
||||
var capture = res[4]
|
||||
var group = res[5]
|
||||
var modifier = res[6]
|
||||
var asterisk = res[7]
|
||||
|
||||
// Push the current path onto the tokens.
|
||||
if (path) {
|
||||
tokens.push(path)
|
||||
path = ''
|
||||
}
|
||||
|
||||
var partial = prefix != null && next != null && next !== prefix
|
||||
var repeat = modifier === '+' || modifier === '*'
|
||||
var optional = modifier === '?' || modifier === '*'
|
||||
var delimiter = res[2] || defaultDelimiter
|
||||
var pattern = capture || group
|
||||
|
||||
tokens.push({
|
||||
name: name || key++,
|
||||
prefix: prefix || '',
|
||||
delimiter: delimiter,
|
||||
optional: optional,
|
||||
repeat: repeat,
|
||||
partial: partial,
|
||||
asterisk: !!asterisk,
|
||||
pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?')
|
||||
})
|
||||
}
|
||||
|
||||
// Match any characters still remaining.
|
||||
if (index < str.length) {
|
||||
path += str.substr(index)
|
||||
}
|
||||
|
||||
// If the path exists, push it onto the end.
|
||||
if (path) {
|
||||
tokens.push(path)
|
||||
}
|
||||
|
||||
return tokens
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a string to a template function for the path.
|
||||
*
|
||||
* @param {string} str
|
||||
* @param {Object=} options
|
||||
* @return {!function(Object=, Object=)}
|
||||
*/
|
||||
function compile (str, options) {
|
||||
return tokensToFunction(parse(str, options))
|
||||
}
|
||||
|
||||
/**
|
||||
* Prettier encoding of URI path segments.
|
||||
*
|
||||
* @param {string}
|
||||
* @return {string}
|
||||
*/
|
||||
function encodeURIComponentPretty (str) {
|
||||
return encodeURI(str).replace(/[\/?#]/g, function (c) {
|
||||
return '%' + c.charCodeAt(0).toString(16).toUpperCase()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the asterisk parameter. Similar to `pretty`, but allows slashes.
|
||||
*
|
||||
* @param {string}
|
||||
* @return {string}
|
||||
*/
|
||||
function encodeAsterisk (str) {
|
||||
return encodeURI(str).replace(/[?#]/g, function (c) {
|
||||
return '%' + c.charCodeAt(0).toString(16).toUpperCase()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose a method for transforming tokens into the path function.
|
||||
*/
|
||||
function tokensToFunction (tokens) {
|
||||
// Compile all the tokens into regexps.
|
||||
var matches = new Array(tokens.length)
|
||||
|
||||
// Compile all the patterns before compilation.
|
||||
for (var i = 0; i < tokens.length; i++) {
|
||||
if (typeof tokens[i] === 'object') {
|
||||
matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$')
|
||||
}
|
||||
}
|
||||
|
||||
return function (obj, opts) {
|
||||
var path = ''
|
||||
var data = obj || {}
|
||||
var options = opts || {}
|
||||
var encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent
|
||||
|
||||
for (var i = 0; i < tokens.length; i++) {
|
||||
var token = tokens[i]
|
||||
|
||||
if (typeof token === 'string') {
|
||||
path += token
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var value = data[token.name]
|
||||
var segment
|
||||
|
||||
if (value == null) {
|
||||
if (token.optional) {
|
||||
// Prepend partial segment prefixes.
|
||||
if (token.partial) {
|
||||
path += token.prefix
|
||||
}
|
||||
|
||||
continue
|
||||
} else {
|
||||
throw new TypeError('Expected "' + token.name + '" to be defined')
|
||||
}
|
||||
}
|
||||
|
||||
if (isarray(value)) {
|
||||
if (!token.repeat) {
|
||||
throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`')
|
||||
}
|
||||
|
||||
if (value.length === 0) {
|
||||
if (token.optional) {
|
||||
continue
|
||||
} else {
|
||||
throw new TypeError('Expected "' + token.name + '" to not be empty')
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = 0; j < value.length; j++) {
|
||||
segment = encode(value[j])
|
||||
|
||||
if (!matches[i].test(segment)) {
|
||||
throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received `' + JSON.stringify(segment) + '`')
|
||||
}
|
||||
|
||||
path += (j === 0 ? token.prefix : token.delimiter) + segment
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
segment = token.asterisk ? encodeAsterisk(value) : encode(value)
|
||||
|
||||
if (!matches[i].test(segment)) {
|
||||
throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"')
|
||||
}
|
||||
|
||||
path += token.prefix + segment
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape a regular expression string.
|
||||
*
|
||||
* @param {string} str
|
||||
* @return {string}
|
||||
*/
|
||||
function escapeString (str) {
|
||||
return str.replace(/([.+*?=^!:${}()[\]|\/\\])/g, '\\$1')
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape the capturing group by escaping special characters and meaning.
|
||||
*
|
||||
* @param {string} group
|
||||
* @return {string}
|
||||
*/
|
||||
function escapeGroup (group) {
|
||||
return group.replace(/([=!:$\/()])/g, '\\$1')
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach the keys as a property of the regexp.
|
||||
*
|
||||
* @param {!RegExp} re
|
||||
* @param {Array} keys
|
||||
* @return {!RegExp}
|
||||
*/
|
||||
function attachKeys (re, keys) {
|
||||
re.keys = keys
|
||||
return re
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the flags for a regexp from the options.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {string}
|
||||
*/
|
||||
function flags (options) {
|
||||
return options.sensitive ? '' : 'i'
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull out keys from a regexp.
|
||||
*
|
||||
* @param {!RegExp} path
|
||||
* @param {!Array} keys
|
||||
* @return {!RegExp}
|
||||
*/
|
||||
function regexpToRegexp (path, keys) {
|
||||
// Use a negative lookahead to match only capturing groups.
|
||||
var groups = path.source.match(/\((?!\?)/g)
|
||||
|
||||
if (groups) {
|
||||
for (var i = 0; i < groups.length; i++) {
|
||||
keys.push({
|
||||
name: i,
|
||||
prefix: null,
|
||||
delimiter: null,
|
||||
optional: false,
|
||||
repeat: false,
|
||||
partial: false,
|
||||
asterisk: false,
|
||||
pattern: null
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return attachKeys(path, keys)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an array into a regexp.
|
||||
*
|
||||
* @param {!Array} path
|
||||
* @param {Array} keys
|
||||
* @param {!Object} options
|
||||
* @return {!RegExp}
|
||||
*/
|
||||
function arrayToRegexp (path, keys, options) {
|
||||
var parts = []
|
||||
|
||||
for (var i = 0; i < path.length; i++) {
|
||||
parts.push(pathToRegexp(path[i], keys, options).source)
|
||||
}
|
||||
|
||||
var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options))
|
||||
|
||||
return attachKeys(regexp, keys)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a path regexp from string input.
|
||||
*
|
||||
* @param {string} path
|
||||
* @param {!Array} keys
|
||||
* @param {!Object} options
|
||||
* @return {!RegExp}
|
||||
*/
|
||||
function stringToRegexp (path, keys, options) {
|
||||
return tokensToRegExp(parse(path, options), keys, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose a function for taking tokens and returning a RegExp.
|
||||
*
|
||||
* @param {!Array} tokens
|
||||
* @param {(Array|Object)=} keys
|
||||
* @param {Object=} options
|
||||
* @return {!RegExp}
|
||||
*/
|
||||
function tokensToRegExp (tokens, keys, options) {
|
||||
if (!isarray(keys)) {
|
||||
options = /** @type {!Object} */ (keys || options)
|
||||
keys = []
|
||||
}
|
||||
|
||||
options = options || {}
|
||||
|
||||
var strict = options.strict
|
||||
var end = options.end !== false
|
||||
var route = ''
|
||||
|
||||
// Iterate over the tokens and create our regexp string.
|
||||
for (var i = 0; i < tokens.length; i++) {
|
||||
var token = tokens[i]
|
||||
|
||||
if (typeof token === 'string') {
|
||||
route += escapeString(token)
|
||||
} else {
|
||||
var prefix = escapeString(token.prefix)
|
||||
var capture = '(?:' + token.pattern + ')'
|
||||
|
||||
keys.push(token)
|
||||
|
||||
if (token.repeat) {
|
||||
capture += '(?:' + prefix + capture + ')*'
|
||||
}
|
||||
|
||||
if (token.optional) {
|
||||
if (!token.partial) {
|
||||
capture = '(?:' + prefix + '(' + capture + '))?'
|
||||
} else {
|
||||
capture = prefix + '(' + capture + ')?'
|
||||
}
|
||||
} else {
|
||||
capture = prefix + '(' + capture + ')'
|
||||
}
|
||||
|
||||
route += capture
|
||||
}
|
||||
}
|
||||
|
||||
var delimiter = escapeString(options.delimiter || '/')
|
||||
var endsWithDelimiter = route.slice(-delimiter.length) === delimiter
|
||||
|
||||
// In non-strict mode we allow a slash at the end of match. If the path to
|
||||
// match already ends with a slash, we remove it for consistency. The slash
|
||||
// is valid at the end of a path match, not in the middle. This is important
|
||||
// in non-ending mode, where "/test/" shouldn't match "/test//route".
|
||||
if (!strict) {
|
||||
route = (endsWithDelimiter ? route.slice(0, -delimiter.length) : route) + '(?:' + delimiter + '(?=$))?'
|
||||
}
|
||||
|
||||
if (end) {
|
||||
route += '$'
|
||||
} else {
|
||||
// In non-ending mode, we need the capturing groups to match as much as
|
||||
// possible by using a positive lookahead to the end or next path segment.
|
||||
route += strict && endsWithDelimiter ? '' : '(?=' + delimiter + '|$)'
|
||||
}
|
||||
|
||||
return attachKeys(new RegExp('^' + route, flags(options)), keys)
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the given path string, returning a regular expression.
|
||||
*
|
||||
* An empty array can be passed in for the keys, which will hold the
|
||||
* placeholder key descriptions. For example, using `/user/:id`, `keys` will
|
||||
* contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
|
||||
*
|
||||
* @param {(string|RegExp|Array)} path
|
||||
* @param {(Array|Object)=} keys
|
||||
* @param {Object=} options
|
||||
* @return {!RegExp}
|
||||
*/
|
||||
function pathToRegexp (path, keys, options) {
|
||||
if (!isarray(keys)) {
|
||||
options = /** @type {!Object} */ (keys || options)
|
||||
keys = []
|
||||
}
|
||||
|
||||
options = options || {}
|
||||
|
||||
if (path instanceof RegExp) {
|
||||
return regexpToRegexp(path, /** @type {!Array} */ (keys))
|
||||
}
|
||||
|
||||
if (isarray(path)) {
|
||||
return arrayToRegexp(/** @type {!Array} */ (path), /** @type {!Array} */ (keys), options)
|
||||
}
|
||||
|
||||
return stringToRegexp(/** @type {string} */ (path), /** @type {!Array} */ (keys), options)
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"_from": "path-to-regexp@^1.7.0",
|
||||
"_id": "path-to-regexp@1.7.0",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=",
|
||||
"_location": "/nise/path-to-regexp",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "path-to-regexp@^1.7.0",
|
||||
"name": "path-to-regexp",
|
||||
"escapedName": "path-to-regexp",
|
||||
"rawSpec": "^1.7.0",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "^1.7.0"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/nise"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz",
|
||||
"_shasum": "59fde0f435badacba103a84e9d3bc64e96b9937d",
|
||||
"_spec": "path-to-regexp@^1.7.0",
|
||||
"_where": "/Users/josh.burman/Projects/braid/node_modules/nise",
|
||||
"bugs": {
|
||||
"url": "https://github.com/pillarjs/path-to-regexp/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"component": {
|
||||
"scripts": {
|
||||
"path-to-regexp": "index.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": "0.0.1"
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "Express style path to RegExp utility",
|
||||
"devDependencies": {
|
||||
"chai": "^2.3.0",
|
||||
"istanbul": "~0.3.0",
|
||||
"mocha": "~2.2.4",
|
||||
"standard": "~3.7.3",
|
||||
"ts-node": "^0.5.5",
|
||||
"typescript": "^1.8.7",
|
||||
"typings": "^1.0.4"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"index.d.ts",
|
||||
"LICENSE"
|
||||
],
|
||||
"homepage": "https://github.com/pillarjs/path-to-regexp#readme",
|
||||
"keywords": [
|
||||
"express",
|
||||
"regexp",
|
||||
"route",
|
||||
"routing"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"name": "path-to-regexp",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/pillarjs/path-to-regexp.git"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "standard",
|
||||
"prepublish": "typings install",
|
||||
"test": "npm run lint && npm run test-cov",
|
||||
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require ts-node/register -R spec test.ts",
|
||||
"test-spec": "mocha --require ts-node/register -R spec --bail test.ts"
|
||||
},
|
||||
"typings": "index.d.ts",
|
||||
"version": "1.7.0"
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"_from": "nise@^1.4.10",
|
||||
"_id": "nise@1.4.10",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-sa0RRbj53dovjc7wombHmVli9ZihXbXCQ2uH3TNm03DyvOSIQbxg+pbqDKrk2oxMK1rtLGVlKxcB9rrc6X5YjA==",
|
||||
"_location": "/nise",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "nise@^1.4.10",
|
||||
"name": "nise",
|
||||
"escapedName": "nise",
|
||||
"rawSpec": "^1.4.10",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "^1.4.10"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/sinon"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/nise/-/nise-1.4.10.tgz",
|
||||
"_shasum": "ae46a09a26436fae91a38a60919356ae6db143b6",
|
||||
"_spec": "nise@^1.4.10",
|
||||
"_where": "/Users/josh.burman/Projects/braid/node_modules/sinon",
|
||||
"author": "",
|
||||
"bugs": {
|
||||
"url": "https://github.com/sinonjs/nise/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"dependencies": {
|
||||
"@sinonjs/formatio": "^3.1.0",
|
||||
"@sinonjs/text-encoding": "^0.7.1",
|
||||
"just-extend": "^4.0.2",
|
||||
"lolex": "^2.3.2",
|
||||
"path-to-regexp": "^1.7.0"
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "Fake XHR and server",
|
||||
"devDependencies": {
|
||||
"@sinonjs/referee": "^2.0.0",
|
||||
"browserify": "^16.2.3",
|
||||
"eslint": "^4.17.0",
|
||||
"eslint-config-sinon": "^1.0.1",
|
||||
"eslint-plugin-ie11": "1.0.0",
|
||||
"eslint-plugin-mocha": "^4.9.0",
|
||||
"husky": "^0.14.3",
|
||||
"jsdom": "11.6.2",
|
||||
"jsdom-global": "3.0.2",
|
||||
"mocha": "^5.0.0",
|
||||
"mochify": "^5.8.1",
|
||||
"nyc": "^13.3.0",
|
||||
"proxyquire": "^1.8.0",
|
||||
"proxyquire-universal": "^1.0.8",
|
||||
"proxyquireify": "^3.2.1",
|
||||
"sinon": "^7"
|
||||
},
|
||||
"files": [
|
||||
"nise.js",
|
||||
"lib/**/*.js"
|
||||
],
|
||||
"homepage": "https://github.com/sinonjs/nise#readme",
|
||||
"keywords": [
|
||||
"test",
|
||||
"testing",
|
||||
"fake",
|
||||
"mock",
|
||||
"xhr",
|
||||
"server"
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"main": "lib/index.js",
|
||||
"module": "nise.js",
|
||||
"name": "nise",
|
||||
"nyc": {
|
||||
"exclude": [
|
||||
"nise.js",
|
||||
"coverage/**",
|
||||
"**/*.test.js"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+ssh://git@github.com/sinonjs/nise.git"
|
||||
},
|
||||
"scripts": {
|
||||
"bundle": "browserify -s nise -o nise.js lib/index.js",
|
||||
"lint": "eslint .",
|
||||
"precommit": "npm run lint -- --fix && npm run test",
|
||||
"prepublish": "npm run bundle",
|
||||
"prepublishOnly": "mkdocs gh-deploy -r upstream || mkdocs gh-deploy -r origin",
|
||||
"prepush": "npm run lint && npm run test",
|
||||
"test": "mocha lib/**/*.test.js",
|
||||
"test:coverage": "nyc --reporter=lcov --reporter=text --all npm test -- --reporter dot",
|
||||
"test:headless": "mochify --https-server --plugin [ proxyquire-universal ] lib/**/*.test.js"
|
||||
},
|
||||
"version": "1.4.10"
|
||||
}
|
||||
Reference in New Issue
Block a user