UNPKG

time-enforcer

Version:

A JavaScript implementation of the Ruby Timecop gem https://github.com/travisjeffery/timecop.

199 lines (161 loc) 5.28 kB
var root = typeof global == 'undefined' ? this : global; var TimeEnforcer = { safeMode: false, stack: [], timers: {}, root: root, Date: root.Date, nextTick: root.process && root.process.nextTick, setTimeout: root.setTimeout, setInterval: root.setInterval, slice: Function.prototype.call.bind(Array.prototype.slice), toString: Function.prototype.call.bind(Object.prototype.toString), isFunction: function(object) { return TimeEnforcer.toString(object) == '[object Function]'; }, freeze: function() { var args = this.slice(arguments), scope = this.getScope(args), date = this.toDate(args); this.mockDate(date, true, 1); this.executeScope(scope); }, travel: function() { var args = this.slice(arguments), scope = this.getScope(args), date = this.toDate(args); this.mockDate(date, false, 1); this.executeScope(scope); }, scale: function(integer, scope) { this.mockDate(new this.Date(), false, integer); this.executeScope(scope); }, restore: function(force) { if (!force && this.stack.length) { this.root.Date = this.stack.pop(); } else { this.stack = []; this.root.Date = this.Date; this.isMocking = false; } }, generateDate: function(baseDate, start, lock, scale) { function Date() { if (!arguments.length) { if (lock) { return new TimeEnforcer.Date(baseDate); } return TimeEnforcer.calculateDate(baseDate, start, scale); } return TimeEnforcer.toDate(TimeEnforcer.slice(arguments)); } Date.local = function() { return TimeEnforcer.toDate(arguments); }; Date.now = function() { return new Date().getTime(); }; Date.parse = Function.prototype.call.bind(this.Date.parse, null); Date.UTC = Function.prototype.call.bind(this.Date.UTC, null); Date.Date = this.Date; return Date; }, calculateDate: function(baseDate, start, scale) { var realDate = this.Date.now(), difference = (realDate - start) * scale; return new this.Date(baseDate + difference); }, mockDate: function(date, lock, scale) { var root = this.root; if (this.isMocking) { this.stack.push(root.Date); } root.Date = this.generateDate(Number(date), this.Date.now(), lock, scale); if (!root.setTimeout._timecop) { root.setTimeout = this.mockTimer('setTimeout'); } if (!root.setInterval._timecop) { root.setInterval = this.mockTimer('setInterval'); } if (!root.process.nextTick._timecop) { root.process.nextTick = this.mockTimer('nextTick'); } this.isMocking = true; }, toDate: function(args) { var date; switch (args.length) { case 0: date = new this.Date(); break; case 1: if (isFinite(args[0])) { date = new this.Date(this.Date.now() + Number(args[0])); } break; case 3: date = new this.Date(args[0], args[1], args[2]); break; case 4: date = new this.Date(args[0], args[1], args[2], args[3]); break; case 5: date = new this.Date(args[0], args[1], args[2], args[3], args[4]); break; case 6: date = new this.Date(args[0], args[1], args[2], args[3], args[4], args[5]); break; default: date = new this.Date(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); } if (!date) { throw new SyntaxError('The wrong information was provided for toDate()'); } return date; }, getScope: function(args) { var lastArgument = args[args.length - 1]; return this.isFunction(lastArgument) ? args.pop() : null; }, mockTimer: function(name) { if (!this.timers[name]) { this.timers[name] = this.mockTimerFunction.bind(this, name, name == 'nextTick' ? root.process : root); this.timers[name]._timecop = true; } return this.timers[name]; }, mockTimerFunction: function(name, context, script, delay) { var fn = this.isFunction(script) ? script : new root.Function(script), callback = this.timeoutFunction.bind(context, root.Date, fn, this.slice(arguments, 4)); return this[name].call(context, callback, delay); }, timeoutFunction: function(mockedDate, fn, args) { var currentDate = root.Date; root.Date = mockedDate; try { fn.apply(this, args); } catch(e) { root.Date = currentDate; throw e; } root.Date = currentDate; }, executeScope: function(scope) { if (scope) { try { scope(); } catch(e) { this.restore(); throw e; } this.restore(); } else if (this.safeMode) { throw new this.SafeModeError('Safe mode is enabled, only calls passing a block are allowed.'); } }, SafeModeError: function() { Error.apply(this, arguments); } }; module.exports = TimeEnforcer;