421 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
			
		
		
	
	
			421 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
/*
 | 
						|
Copyright (c) 2008-2015 Pivotal Labs
 | 
						|
 | 
						|
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.
 | 
						|
*/
 | 
						|
 | 
						|
/* This is jasmine's implementation of a mock clock, lifted from the depths of
 | 
						|
 * jasmine-core and exposed as a standalone module. The interface is just the
 | 
						|
 * same as that of jasmine.clock. For example:
 | 
						|
 *
 | 
						|
 *    var mock_clock = require("mock-clock").clock();
 | 
						|
 *    mock_clock.install();
 | 
						|
 *    setTimeout(function() {
 | 
						|
 *        timerCallback();
 | 
						|
 *    }, 100);
 | 
						|
 *
 | 
						|
 *    expect(timerCallback).not.toHaveBeenCalled();
 | 
						|
 *    mock_clock.tick(101);
 | 
						|
 *    expect(timerCallback).toHaveBeenCalled();
 | 
						|
 *
 | 
						|
 *    mock_clock.uninstall();
 | 
						|
 *
 | 
						|
 *
 | 
						|
 * The reason for C&Ping jasmine's clock here is that jasmine itself is
 | 
						|
 * difficult to webpack, and we don't really want all of it. Sinon also has a
 | 
						|
 * mock-clock implementation, but again, it is difficult to webpack.
 | 
						|
 */
 | 
						|
 | 
						|
const j$ = {};
 | 
						|
 | 
						|
j$.Clock = function() {
 | 
						|
  function Clock(global, delayedFunctionSchedulerFactory, mockDate) {
 | 
						|
    let self = this,
 | 
						|
      realTimingFunctions = {
 | 
						|
        setTimeout: global.setTimeout,
 | 
						|
        clearTimeout: global.clearTimeout,
 | 
						|
        setInterval: global.setInterval,
 | 
						|
        clearInterval: global.clearInterval,
 | 
						|
      },
 | 
						|
      fakeTimingFunctions = {
 | 
						|
        setTimeout: setTimeout,
 | 
						|
        clearTimeout: clearTimeout,
 | 
						|
        setInterval: setInterval,
 | 
						|
        clearInterval: clearInterval,
 | 
						|
      },
 | 
						|
      installed = false,
 | 
						|
      delayedFunctionScheduler,
 | 
						|
      timer;
 | 
						|
 | 
						|
 | 
						|
    self.install = function() {
 | 
						|
      if(!originalTimingFunctionsIntact()) {
 | 
						|
        throw new Error('Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?');
 | 
						|
      }
 | 
						|
      replace(global, fakeTimingFunctions);
 | 
						|
      timer = fakeTimingFunctions;
 | 
						|
      delayedFunctionScheduler = delayedFunctionSchedulerFactory();
 | 
						|
      installed = true;
 | 
						|
 | 
						|
      return self;
 | 
						|
    };
 | 
						|
 | 
						|
    self.uninstall = function() {
 | 
						|
      delayedFunctionScheduler = null;
 | 
						|
      mockDate.uninstall();
 | 
						|
      replace(global, realTimingFunctions);
 | 
						|
 | 
						|
      timer = realTimingFunctions;
 | 
						|
      installed = false;
 | 
						|
    };
 | 
						|
 | 
						|
    self.withMock = function(closure) {
 | 
						|
      this.install();
 | 
						|
      try {
 | 
						|
        closure();
 | 
						|
      } finally {
 | 
						|
        this.uninstall();
 | 
						|
      }
 | 
						|
    };
 | 
						|
 | 
						|
    self.mockDate = function(initialDate) {
 | 
						|
      mockDate.install(initialDate);
 | 
						|
    };
 | 
						|
 | 
						|
    self.setTimeout = function(fn, delay, params) {
 | 
						|
      if (legacyIE()) {
 | 
						|
        if (arguments.length > 2) {
 | 
						|
          throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill');
 | 
						|
        }
 | 
						|
        return timer.setTimeout(fn, delay);
 | 
						|
      }
 | 
						|
      return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]);
 | 
						|
    };
 | 
						|
 | 
						|
    self.setInterval = function(fn, delay, params) {
 | 
						|
      if (legacyIE()) {
 | 
						|
        if (arguments.length > 2) {
 | 
						|
          throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill');
 | 
						|
        }
 | 
						|
        return timer.setInterval(fn, delay);
 | 
						|
      }
 | 
						|
      return Function.prototype.apply.apply(timer.setInterval, [global, arguments]);
 | 
						|
    };
 | 
						|
 | 
						|
    self.clearTimeout = function(id) {
 | 
						|
      return Function.prototype.call.apply(timer.clearTimeout, [global, id]);
 | 
						|
    };
 | 
						|
 | 
						|
    self.clearInterval = function(id) {
 | 
						|
      return Function.prototype.call.apply(timer.clearInterval, [global, id]);
 | 
						|
    };
 | 
						|
 | 
						|
    self.tick = function(millis) {
 | 
						|
      if (installed) {
 | 
						|
        mockDate.tick(millis);
 | 
						|
        delayedFunctionScheduler.tick(millis);
 | 
						|
      } else {
 | 
						|
        throw new Error('Mock clock is not installed, use jasmine.clock().install()');
 | 
						|
      }
 | 
						|
    };
 | 
						|
 | 
						|
    return self;
 | 
						|
 | 
						|
    function originalTimingFunctionsIntact() {
 | 
						|
      return global.setTimeout === realTimingFunctions.setTimeout &&
 | 
						|
        global.clearTimeout === realTimingFunctions.clearTimeout &&
 | 
						|
        global.setInterval === realTimingFunctions.setInterval &&
 | 
						|
        global.clearInterval === realTimingFunctions.clearInterval;
 | 
						|
    }
 | 
						|
 | 
						|
    function legacyIE() {
 | 
						|
      //if these methods are polyfilled, apply will be present
 | 
						|
      return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply;
 | 
						|
    }
 | 
						|
 | 
						|
    function replace(dest, source) {
 | 
						|
      for (const prop in source) {
 | 
						|
        dest[prop] = source[prop];
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    function setTimeout(fn, delay) {
 | 
						|
      return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2));
 | 
						|
    }
 | 
						|
 | 
						|
    function clearTimeout(id) {
 | 
						|
      return delayedFunctionScheduler.removeFunctionWithId(id);
 | 
						|
    }
 | 
						|
 | 
						|
    function setInterval(fn, interval) {
 | 
						|
      return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true);
 | 
						|
    }
 | 
						|
 | 
						|
    function clearInterval(id) {
 | 
						|
      return delayedFunctionScheduler.removeFunctionWithId(id);
 | 
						|
    }
 | 
						|
 | 
						|
    function argSlice(argsObj, n) {
 | 
						|
      return Array.prototype.slice.call(argsObj, n);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return Clock;
 | 
						|
}();
 | 
						|
 | 
						|
 | 
						|
j$.DelayedFunctionScheduler = function() {
 | 
						|
  function DelayedFunctionScheduler() {
 | 
						|
    const self = this;
 | 
						|
    const scheduledLookup = [];
 | 
						|
    const scheduledFunctions = {};
 | 
						|
    let currentTime = 0;
 | 
						|
    let delayedFnCount = 0;
 | 
						|
 | 
						|
    self.tick = function(millis) {
 | 
						|
      millis = millis || 0;
 | 
						|
      const endTime = currentTime + millis;
 | 
						|
 | 
						|
      runScheduledFunctions(endTime);
 | 
						|
      currentTime = endTime;
 | 
						|
    };
 | 
						|
 | 
						|
    self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) {
 | 
						|
      let f;
 | 
						|
      if (typeof(funcToCall) === 'string') {
 | 
						|
        /* jshint evil: true */
 | 
						|
        f = function() { return eval(funcToCall); };
 | 
						|
        /* jshint evil: false */
 | 
						|
      } else {
 | 
						|
        f = funcToCall;
 | 
						|
      }
 | 
						|
 | 
						|
      millis = millis || 0;
 | 
						|
      timeoutKey = timeoutKey || ++delayedFnCount;
 | 
						|
      runAtMillis = runAtMillis || (currentTime + millis);
 | 
						|
 | 
						|
      const funcToSchedule = {
 | 
						|
        runAtMillis: runAtMillis,
 | 
						|
        funcToCall: f,
 | 
						|
        recurring: recurring,
 | 
						|
        params: params,
 | 
						|
        timeoutKey: timeoutKey,
 | 
						|
        millis: millis,
 | 
						|
      };
 | 
						|
 | 
						|
      if (runAtMillis in scheduledFunctions) {
 | 
						|
        scheduledFunctions[runAtMillis].push(funcToSchedule);
 | 
						|
      } else {
 | 
						|
        scheduledFunctions[runAtMillis] = [funcToSchedule];
 | 
						|
        scheduledLookup.push(runAtMillis);
 | 
						|
        scheduledLookup.sort(function(a, b) {
 | 
						|
          return a - b;
 | 
						|
        });
 | 
						|
      }
 | 
						|
 | 
						|
      return timeoutKey;
 | 
						|
    };
 | 
						|
 | 
						|
    self.removeFunctionWithId = function(timeoutKey) {
 | 
						|
      for (const runAtMillis in scheduledFunctions) {
 | 
						|
        const funcs = scheduledFunctions[runAtMillis];
 | 
						|
        const i = indexOfFirstToPass(funcs, function(func) {
 | 
						|
          return func.timeoutKey === timeoutKey;
 | 
						|
        });
 | 
						|
 | 
						|
        if (i > -1) {
 | 
						|
          if (funcs.length === 1) {
 | 
						|
            delete scheduledFunctions[runAtMillis];
 | 
						|
            deleteFromLookup(runAtMillis);
 | 
						|
          } else {
 | 
						|
            funcs.splice(i, 1);
 | 
						|
          }
 | 
						|
 | 
						|
          // intervals get rescheduled when executed, so there's never more
 | 
						|
          // than a single scheduled function with a given timeoutKey
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    };
 | 
						|
 | 
						|
    return self;
 | 
						|
 | 
						|
    function indexOfFirstToPass(array, testFn) {
 | 
						|
      let index = -1;
 | 
						|
 | 
						|
      for (let i = 0; i < array.length; ++i) {
 | 
						|
        if (testFn(array[i])) {
 | 
						|
          index = i;
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      return index;
 | 
						|
    }
 | 
						|
 | 
						|
    function deleteFromLookup(key) {
 | 
						|
      const value = Number(key);
 | 
						|
      const i = indexOfFirstToPass(scheduledLookup, function(millis) {
 | 
						|
        return millis === value;
 | 
						|
      });
 | 
						|
 | 
						|
      if (i > -1) {
 | 
						|
        scheduledLookup.splice(i, 1);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    function reschedule(scheduledFn) {
 | 
						|
      self.scheduleFunction(scheduledFn.funcToCall,
 | 
						|
        scheduledFn.millis,
 | 
						|
        scheduledFn.params,
 | 
						|
        true,
 | 
						|
        scheduledFn.timeoutKey,
 | 
						|
        scheduledFn.runAtMillis + scheduledFn.millis);
 | 
						|
    }
 | 
						|
 | 
						|
    function forEachFunction(funcsToRun, callback) {
 | 
						|
      for (let i = 0; i < funcsToRun.length; ++i) {
 | 
						|
        callback(funcsToRun[i]);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    function runScheduledFunctions(endTime) {
 | 
						|
      if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      do {
 | 
						|
        currentTime = scheduledLookup.shift();
 | 
						|
 | 
						|
        const funcsToRun = scheduledFunctions[currentTime];
 | 
						|
        delete scheduledFunctions[currentTime];
 | 
						|
 | 
						|
        forEachFunction(funcsToRun, function(funcToRun) {
 | 
						|
          if (funcToRun.recurring) {
 | 
						|
            reschedule(funcToRun);
 | 
						|
          }
 | 
						|
        });
 | 
						|
 | 
						|
        forEachFunction(funcsToRun, function(funcToRun) {
 | 
						|
          funcToRun.funcToCall.apply(null, funcToRun.params || []);
 | 
						|
        });
 | 
						|
      } while (scheduledLookup.length > 0 &&
 | 
						|
              // checking first if we're out of time prevents setTimeout(0)
 | 
						|
              // scheduled in a funcToRun from forcing an extra iteration
 | 
						|
                 currentTime !== endTime &&
 | 
						|
                 scheduledLookup[0] <= endTime);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return DelayedFunctionScheduler;
 | 
						|
}();
 | 
						|
 | 
						|
 | 
						|
j$.MockDate = function() {
 | 
						|
  function MockDate(global) {
 | 
						|
    const self = this;
 | 
						|
    let currentTime = 0;
 | 
						|
 | 
						|
    if (!global || !global.Date) {
 | 
						|
      self.install = function() {};
 | 
						|
      self.tick = function() {};
 | 
						|
      self.uninstall = function() {};
 | 
						|
      return self;
 | 
						|
    }
 | 
						|
 | 
						|
    const GlobalDate = global.Date;
 | 
						|
 | 
						|
    self.install = function(mockDate) {
 | 
						|
      if (mockDate instanceof GlobalDate) {
 | 
						|
        currentTime = mockDate.getTime();
 | 
						|
      } else {
 | 
						|
        currentTime = new GlobalDate().getTime();
 | 
						|
      }
 | 
						|
 | 
						|
      global.Date = FakeDate;
 | 
						|
    };
 | 
						|
 | 
						|
    self.tick = function(millis) {
 | 
						|
      millis = millis || 0;
 | 
						|
      currentTime = currentTime + millis;
 | 
						|
    };
 | 
						|
 | 
						|
    self.uninstall = function() {
 | 
						|
      currentTime = 0;
 | 
						|
      global.Date = GlobalDate;
 | 
						|
    };
 | 
						|
 | 
						|
    createDateProperties();
 | 
						|
 | 
						|
    return self;
 | 
						|
 | 
						|
    function FakeDate() {
 | 
						|
      switch(arguments.length) {
 | 
						|
        case 0:
 | 
						|
          return new GlobalDate(currentTime);
 | 
						|
        case 1:
 | 
						|
          return new GlobalDate(arguments[0]);
 | 
						|
        case 2:
 | 
						|
          return new GlobalDate(arguments[0], arguments[1]);
 | 
						|
        case 3:
 | 
						|
          return new GlobalDate(arguments[0], arguments[1], arguments[2]);
 | 
						|
        case 4:
 | 
						|
          return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]);
 | 
						|
        case 5:
 | 
						|
          return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
 | 
						|
                                arguments[4]);
 | 
						|
        case 6:
 | 
						|
          return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
 | 
						|
                                arguments[4], arguments[5]);
 | 
						|
        default:
 | 
						|
          return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
 | 
						|
                                arguments[4], arguments[5], arguments[6]);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    function createDateProperties() {
 | 
						|
      FakeDate.prototype = GlobalDate.prototype;
 | 
						|
 | 
						|
      FakeDate.now = function() {
 | 
						|
        if (GlobalDate.now) {
 | 
						|
          return currentTime;
 | 
						|
        } else {
 | 
						|
          throw new Error('Browser does not support Date.now()');
 | 
						|
        }
 | 
						|
      };
 | 
						|
 | 
						|
      FakeDate.toSource = GlobalDate.toSource;
 | 
						|
      FakeDate.toString = GlobalDate.toString;
 | 
						|
      FakeDate.parse = GlobalDate.parse;
 | 
						|
      FakeDate.UTC = GlobalDate.UTC;
 | 
						|
    }
 | 
						|
	}
 | 
						|
 | 
						|
  return MockDate;
 | 
						|
}();
 | 
						|
 | 
						|
const clock = new j$.Clock(global, function() { return new j$.DelayedFunctionScheduler(); }, new j$.MockDate(global));
 | 
						|
 | 
						|
module.exports.clock = function() {
 | 
						|
    return clock;
 | 
						|
};
 | 
						|
 | 
						|
 |