[Pkg-javascript-commits] [node-lolex] 01/08: Import Upstream version 1.5.1

Sruthi Chandran srud-guest at moszumanska.debian.org
Wed Oct 12 11:45:49 UTC 2016


This is an automated email from the git hooks/post-receive script.

srud-guest pushed a commit to branch master
in repository node-lolex.

commit bb1d748c7e0ca4f9dc9f481661e005639b8675e4
Author: Sruthi <srud at disroot.org>
Date:   Wed Oct 12 15:44:36 2016 +0530

    Import Upstream version 1.5.1
---
 .bithoundrc        |   15 +
 .editorconfig      |   17 +
 .eslintignore      |    1 +
 .eslintrc          |  172 +++++
 .gitignore         |    1 +
 .min-wd            |   16 +
 .travis.yml        |   20 +
 AUTHORS            |   15 +
 History.md         |  132 ++++
 LICENSE            |   11 +
 RELEASE.md         |   44 ++
 Readme.md          |  250 ++++++++
 lolex.js           |  661 ++++++++++++++++++++
 package.json       |   34 +
 script/ci-test.sh  |    5 +
 src/lolex-src.js   |  655 +++++++++++++++++++
 test/lolex-test.js | 1760 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 17 files changed, 3809 insertions(+)

diff --git a/.bithoundrc b/.bithoundrc
new file mode 100644
index 0000000..b0ed0fb
--- /dev/null
+++ b/.bithoundrc
@@ -0,0 +1,15 @@
+{
+  "critics": {
+    "//": "bitHound only supports running eslint rules greater than 1.0 but lower than 2.0.",
+    "lint": {"engine": "none"}
+  },
+
+  "ignore": [
+    "lolex.js",
+    "script",
+    "**/node_modules/**"
+  ],
+  "test": [
+    "test/**"
+  ]
+}
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..bdfa15e
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,17 @@
+; EditorConfig file: http://EditorConfig.org
+; Install the "EditorConfig" plugin into your editor to use
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+
+# Matches the exact files either package.json or .travis.yml
+[{package.json, .travis.yml}]
+indent_style = space
+indent_size = 2
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..a5122d6
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1 @@
+lolex.js
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..e3240a7
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,172 @@
+{
+    "ecmaFeatures": {},
+    "env": {
+        "browser": true,
+        "node": true
+    },
+    "rules": {
+        "no-alert": 2,
+        "no-array-constructor": 2,
+        "no-bitwise": 2,
+        "no-caller": 2,
+        "no-catch-shadow": 2,
+        "no-cond-assign": 2,
+        "no-console": 2,
+        "no-constant-condition": 2,
+        "no-continue": 2,
+        "no-control-regex": 2,
+        "no-debugger": 2,
+        "no-delete-var": 2,
+        "no-div-regex": 0,
+        "no-dupe-keys": 2,
+        "no-dupe-args": 2,
+        "no-duplicate-case": 2,
+        "no-else-return": 2,
+        "no-empty": 2,
+        "no-empty-character-class": 2,
+        "no-eq-null": 0,
+        "no-eval": 2,
+        "no-ex-assign": 2,
+        "no-extend-native": 2,
+        "no-extra-bind": 2,
+        "no-extra-boolean-cast": 2,
+        "no-extra-parens": [2, "functions"],
+        "no-extra-semi": 2,
+        "no-fallthrough": 2,
+        "no-floating-decimal": 0,
+        "no-func-assign": 2,
+        "no-implied-eval": 2,
+        "no-inline-comments": 0,
+        "no-inner-declarations": [2, "functions"],
+        "no-invalid-regexp": 2,
+        "no-irregular-whitespace": 2,
+        "no-iterator": 2,
+        "no-label-var": 2,
+        "no-labels": 2,
+        "no-lone-blocks": 2,
+        "no-lonely-if": 0,
+        "no-loop-func": 2,
+        "no-mixed-requires": [0, false],
+        "no-mixed-spaces-and-tabs": [2, false],
+        "linebreak-style": [0, "unix"],
+        "no-multi-spaces": 2,
+        "no-multi-str": 2,
+        "no-multiple-empty-lines": [2, {"max": 2}],
+        "no-negated-in-lhs": 2,
+        "no-nested-ternary": 2,
+        "no-new": 2,
+        "no-new-func": 2,
+        "no-new-object": 2,
+        "no-new-require": 0,
+        "no-new-wrappers": 2,
+        "no-obj-calls": 2,
+        "no-octal": 2,
+        "no-octal-escape": 2,
+        "no-param-reassign": 0,
+        "no-path-concat": 0,
+        "no-plusplus": 1,
+        "no-process-env": 0,
+        "no-process-exit": 2,
+        "no-proto": 2,
+        "no-redeclare": 2,
+        "no-regex-spaces": 2,
+        "no-reserved-keys": 0,
+        "no-restricted-modules": 0,
+        "no-return-assign": 2,
+        "no-script-url": 2,
+        "no-self-compare": 0,
+        "no-sequences": 2,
+        "no-shadow-restricted-names": 2,
+        "no-space-before-semi": 0,
+        "no-spaced-func": 2,
+        "no-sparse-arrays": 2,
+        "no-sync": 0,
+        "no-ternary": 0,
+        "no-trailing-spaces": 2,
+        "no-this-before-super": 0,
+        "no-throw-literal": 0,
+        "no-undef": 2,
+        "no-undef-init": 2,
+        "no-undefined": 0,
+        "no-unexpected-multiline": 0,
+        "no-underscore-dangle": 2,
+        "no-unneeded-ternary": 0,
+        "no-unreachable": 2,
+        "no-unused-expressions": 2,
+        "no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
+        "no-use-before-define": 2,
+        "no-void": 0,
+        "no-var": 0,
+        "prefer-const": 0,
+        "no-warning-comments": [0, { "terms": ["todo", "fixme", "xxx"], "location": "start" }],
+        "no-with": 2,
+
+        "array-bracket-spacing": [2, "never"],
+        "accessor-pairs": 2,
+        "block-scoped-var": 0,
+        "brace-style": [0, "1tbs"],
+        "callback-return": 0,
+        "camelcase": 2,
+        "comma-dangle": [2, "never"],
+        "comma-spacing": 2,
+        "comma-style": 2,
+        "complexity": [0, 11],
+        "computed-property-spacing": [0, "never"],
+        "consistent-return": 2,
+        "consistent-this": [0, "that"],
+        "constructor-super": 0,
+        "curly": [2, "all"],
+        "default-case": 1,
+        "dot-location": 0,
+        "dot-notation": [2, { "allowKeywords": true }],
+        "eol-last": 2,
+        "eqeqeq": [2, "allow-null"],
+        "func-names": 0,
+        "func-style": [0, "declaration"],
+        "generator-star": 0,
+        "generator-star-spacing": 0,
+        "guard-for-in": 2,
+        "handle-callback-err": 0,
+        "indent": [2, 4, {"SwitchCase": 1}],
+        "init-declarations": 0,
+        "key-spacing": [2, { "beforeColon": false, "afterColon": true }],
+        "lines-around-comment": 0,
+        "max-depth": [0, 4],
+        "max-len": [2, 120, 4],
+        "max-nested-callbacks": [1, 3],
+        "max-params": [0, 3],
+        "max-statements": [0, 10],
+        "new-cap": 2,
+        "new-parens": 2,
+        "newline-after-var": 0,
+        "object-curly-spacing": [0, "never"],
+        "object-shorthand": 0,
+        "operator-assignment": [0, "always"],
+        "operator-linebreak": 0,
+        "padded-blocks": 0,
+        "quote-props": 0,
+        "quotes": [2, "double"],
+        "radix": 0,
+        "require-yield": 0,
+        "semi": 2,
+        "semi-spacing": [2, {"before": false, "after": true}],
+        "sort-vars": 0,
+        "keyword-spacing": [ "error", { "after": true} ],
+        "space-before-blocks": [2, "always"],
+        "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
+        "space-before-function-parentheses": [0, "always"],
+        "space-in-brackets": [0, "never"],
+        "space-in-parens": [0, "never"],
+        "space-infix-ops": 2,
+        "space-unary-ops": [2, { "words": true, "nonwords": false }],
+        "spaced-comment": 0,
+        "spaced-line-comment": [0, "always"],
+        "use-isnan": 2,
+        "valid-jsdoc": 0,
+        "valid-typeof": 2,
+        "vars-on-top": 0,
+        "wrap-iife": 0,
+        "wrap-regex": 0,
+        "yoda": [2, "never"]
+    }
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..07e6e47
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/node_modules
diff --git a/.min-wd b/.min-wd
new file mode 100644
index 0000000..e6170f1
--- /dev/null
+++ b/.min-wd
@@ -0,0 +1,16 @@
+{
+  "sauceLabs": true,
+  "browsers": [{
+    "name": "firefox"
+  }, {
+    "name": "internet explorer",
+    "version": "9",
+    "url": "http://maxantoni.de/doctype.html"
+  }, {
+    "name": "internet explorer",
+    "version": "10"
+  }, {
+    "name": "internet explorer",
+    "version": "11"
+  }]
+}
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..c217705
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,20 @@
+language: node_js
+
+sudo: false
+
+node_js:
+  - "0.10"
+  - "0.12"
+  - "4.2"
+  - "5"
+
+env:
+  global:
+    - SAUCE_USERNAME=sinonjs
+    - secure: "Szc/biR+MJFC/r4IFzDVXJkIt1COglrCyCd/drmBrXj4AE70J2qn8QlU0n3uQUhrDH5k0NtSWnZHcfxuUTHpasvh9yPFGtqnNeqgftZjXQyJFS+rYye4i04cyYGEmc1qxhpljfAXbvCJV+bHDFmWxIF6KSHWMsVk6IbhoD0/Dis="
+
+script:
+  - "./script/ci-test.sh"
+  - 'if [ "x$TRAVIS_NODE_VERSION" = "x4.2" ]; then npm run lint; fi'
+  - 'if [ "x$TRAVIS_NODE_VERSION" = "x4.2" ] && [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then npm run test-cloud; fi'
+
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..5249785
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,15 @@
+Christian Johansen <christian at cjohansen.no>
+Carl-Erik Kopseng <carlerik at gmail.com>
+Morgan Roderick <morgan at roderick.dk>
+Maximilian Antoni <mail at maxantoni.de>
+Mark Wubben <mark at novemberborn.net>
+Soutaro Matsumoto <matsumoto at soutaro.com>
+Duncan Beevers <duncan at dweebd.com>
+Rogier Schouten <github at workingcode.ninja>
+Karl O'Keeffe <karl at geckoboard.com>
+Thibault Hild <gautaz at users.noreply.github.com>
+Curtis M. Humphrey, Ph.D <curtis at createdwithflair.com>
+Mark Banner <standard8 at mozilla.com>
+Simen Bekkhus <sbekkhus91 at gmail.com>
+Sylvain Fraïssé <fraisse.sylvain at gmail.com>
+Andy Edwards <jedwards at fastmail.com>
diff --git a/History.md b/History.md
new file mode 100644
index 0000000..e3464a8
--- /dev/null
+++ b/History.md
@@ -0,0 +1,132 @@
+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 at 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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..eb84755
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,11 @@
+Copyright (c) 2010-2014, Christian Johansen, christian at 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 PROF [...]
\ No newline at end of file
diff --git a/RELEASE.md b/RELEASE.md
new file mode 100644
index 0000000..acb28ad
--- /dev/null
+++ b/RELEASE.md
@@ -0,0 +1,44 @@
+# Rolling Lolex releases
+
+You'll need a working installation of [git-extras](https://github.com/tj/git-extras) for this.
+
+## Update Changelog.txt
+
+Compile interesting highlights from [`git changelog`](https://github.com/tj/git-extras/blob/master/Commands.md#git-changelog) into Changelog.md
+
+    git changelog --no-merges
+
+## Update AUTHORS
+
+    git authors --list > AUTHORS
+
+## Build a new bundle and commit changed files
+
+    npm run bundle
+    git add lolex.js AUTHORS History.md
+    git commit -m "Prepare for new release"
+
+## Create a new PR
+    The `master` branch is protected.
+    You can merge it yourself.
+
+## Create a new version
+
+```
+$ npm version x.y.z
+```
+
+Updates package.json and creates a new tag.
+
+
+## Push new commits and tags
+```
+git push && git push --tags
+```
+
+## Publish to NPM
+
+```
+$ npm publish
+```
+
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..312a050
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,250 @@
+# Lolex [![Build Status](https://secure.travis-ci.org/sinonjs/lolex.png)](http://travis-ci.org/sinonjs/lolex) [![bitHound Overall Score](https://www.bithound.io/github/sinonjs/lolex/badges/score.svg)](https://www.bithound.io/github/sinonjs/lolex)
+
+JavaScript implementation of the timer APIs; `setTimeout`, `clearTimeout`,
+`setImmediate`, `clearImmediate`, `setInterval` and `clearInterval`, along with
+a clock instance that controls the flow of time. Lolex also provides a `Date`
+implementation that gets its time from 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. Lolex is extracted from [Sinon.JS](https://github.com/sinonjs/sinon.js).
+
+## Installation
+
+Lolex can be installed using `npm`:
+
+```sh
+npm install lolex
+```
+
+If you want to use Lolex in a browser, you have a few options. Releases are
+hosted on the [sinonjs.org](http://sinonjs.org/download/) website. You can also
+get the node module and build a file for the browser using browserify:
+
+```sh
+npm install lolex
+npm install browserify # If you don't already have it globally installed
+browserify node_modules/lolex/lolex.js
+```
+
+## 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`, 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(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.
+
+## 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([context[, now[, toFake[, loopLimit]]]])`
+
+### `var clock = lolex.install([now[, toFake[, loopLimit]]])`
+
+Creates a clock and installs it onto the `context` object, or globally. The
+`now` argument is the same as in `lolex.createClock()`.
+
+`toFake` is an array of the names of the methods that should be faked. You can
+pick from `setTimeout`, `clearTimeout`, `setImmediate`, `clearImmediate`,
+`setInterval`, `clearInterval`, and `Date`. E.g. `lolex.install(["setTimeout",
+"clearTimeout"])`.
+
+The `loopLimit` argument is the same as in `lolex.createClock()`.
+
+### `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.hrtime(prevTime?)`
+Only available in Node.JS, mimicks process.hrtime().
+
+### `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.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.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 `context` that was passed to
+`lolex.install`, or the native timers if no `context` was given.
+
+### `Date`
+
+Implements the `Date` object but using the clock to provide the correct time.
+
+## 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)
diff --git a/lolex.js b/lolex.js
new file mode 100644
index 0000000..b49929a
--- /dev/null
+++ b/lolex.js
@@ -0,0 +1,661 @@
+(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 e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)r [...]
+(function (global){
+/*global global, window*/
+/**
+ * @author Christian Johansen (christian at cjohansen.no) and contributors
+ * @license BSD
+ *
+ * Copyright (c) 2010-2014 Christian Johansen
+ */
+
+(function (global) {
+    "use strict";
+
+    // Make properties writable in IE, as per
+    // http://www.adequatelygood.com/Replacing-setTimeout-Globally.html
+    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 = setTimeout(NOOP, 0);
+    var addTimerReturnsObject = typeof timeoutResult === "object";
+    var hrtimePresent = (global.process && typeof global.process.hrtime === "function");
+    clearTimeout(timeoutResult);
+
+    var NativeDate = Date;
+    var uniqueTimerId = 1;
+
+    /**
+     * 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, i = l;
+        var ms = 0, 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 ((n % m) + m) % m;
+    }
+
+    /**
+     * Used to grok the `now` parameter to createClock.
+     */
+    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 addTimer(clock, timer) {
+        if (timer.func === undefined) {
+            throw new Error("Callback must be provided to timer calls");
+        }
+
+        if (!clock.timers) {
+            clock.timers = {};
+        }
+
+        timer.id = uniqueTimerId++;
+        timer.createdAt = clock.now;
+        timer.callAt = clock.now + (timer.delay || (clock.duringTick ? 1 : 0));
+
+        clock.timers[timer.id] = timer;
+
+        if (addTimerReturnsObject) {
+            return {
+                id: timer.id,
+                ref: NOOP,
+                unref: NOOP
+            };
+        }
+
+        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,
+            timer = null,
+            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,
+            timer = null,
+            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,
+            timer = null,
+            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) {
+        var exception;
+
+        if (typeof timer.interval === "number") {
+            clock.timers[timer.id].callAt += timer.interval;
+        } else {
+            delete clock.timers[timer.id];
+        }
+
+        try {
+            if (typeof timer.func === "function") {
+                timer.func.apply(null, timer.args);
+            } else {
+                /* eslint no-eval: "off" */
+                eval(timer.func);
+            }
+        } catch (e) {
+            exception = e;
+        }
+
+        if (!clock.timers[timer.id]) {
+            if (exception) {
+                throw exception;
+            }
+            return;
+        }
+
+        if (exception) {
+            throw exception;
+        }
+    }
+
+    function timerType(timer) {
+        if (timer.immediate) {
+            return "Immediate";
+        }
+        if (timer.interval !== undefined) {
+            return "Interval";
+        }
+        return "Timeout";
+    }
+
+    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 (timerType(timer) === ttype) {
+                delete clock.timers[timerId];
+            } else {
+                throw new Error("Cannot clear timer: timer created with set" + timerType(timer)
+                                + "() but cleared with clear" + ttype + "()");
+            }
+        }
+    }
+
+    function uninstall(clock, target) {
+        var method,
+            i,
+            l;
+        var installedHrTime = "_hrtime";
+
+        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 (target[method] && target[method].hadOwnProperty) {
+                    target[method] = clock["_" + method];
+                } else {
+                    try {
+                        delete target[method];
+                    } catch (ignore) { /* eslint empty-block: "off" */ }
+                }
+            }
+        }
+
+        // Prevent multiple executions which will completely remove these props
+        clock.methods = [];
+    }
+
+    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 {
+            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;
+    }
+
+    var timers = {
+        setTimeout: setTimeout,
+        clearTimeout: clearTimeout,
+        setImmediate: global.setImmediate,
+        clearImmediate: global.clearImmediate,
+        setInterval: setInterval,
+        clearInterval: clearInterval,
+        Date: Date
+    };
+
+    if (hrtimePresent) {
+        timers.hrtime = global.process.hrtime;
+    }
+
+    var keys = Object.keys || function (obj) {
+        var ks = [],
+            key;
+
+        for (key in obj) {
+            if (obj.hasOwnProperty(key)) {
+                ks.push(key);
+            }
+        }
+
+        return ks;
+    };
+
+    exports.timers = timers;
+
+    function createClock(now, loopLimit) {
+        loopLimit = loopLimit || 1000;
+
+        var clock = {
+            now: getEpoch(now),
+            hrNow: 0,
+            timeouts: {},
+            Date: createDate(),
+            loopLimit: loopLimit
+        };
+
+        clock.Date.clock = clock;
+
+        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.setInterval = function setInterval(func, timeout) {
+            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.tick = function tick(ms) {
+            ms = typeof ms === "number" ? ms : parseTime(ms);
+            var tickFrom = clock.now, tickTo = clock.now + ms, previous = clock.now;
+            var timer = firstTimerInRange(clock, tickFrom, tickTo);
+            var oldNow;
+
+            clock.duringTick = true;
+
+            function updateHrTime(newNow) {
+                clock.hrNow += (newNow - clock.now);
+            }
+
+            var firstException;
+            while (timer && tickFrom <= tickTo) {
+                if (clock.timers[timer.id]) {
+                    updateHrTime(timer.callAt);
+                    tickFrom = timer.callAt;
+                    clock.now = timer.callAt;
+                    try {
+                        oldNow = clock.now;
+                        callTimer(clock, timer);
+                        // compensate for any setSystemTime() call during timer callback
+                        if (oldNow !== clock.now) {
+                            tickFrom += clock.now - oldNow;
+                            tickTo += clock.now - oldNow;
+                            previous += clock.now - oldNow;
+                        }
+                    } catch (e) {
+                        firstException = firstException || e;
+                    }
+                }
+
+                timer = firstTimerInRange(clock, previous, tickTo);
+                previous = tickFrom;
+            }
+
+            clock.duringTick = false;
+            updateHrTime(tickTo);
+            clock.now = tickTo;
+
+            if (firstException) {
+                throw firstException;
+            }
+
+            return clock.now;
+        };
+
+        clock.next = function next() {
+            var timer = firstTimer(clock);
+            if (!timer) {
+                return clock.now;
+            }
+
+            clock.duringTick = true;
+            try {
+                clock.now = timer.callAt;
+                callTimer(clock, timer);
+                return clock.now;
+            } finally {
+                clock.duringTick = false;
+            }
+        };
+
+        clock.runAll = function runAll() {
+            var numTimers, i;
+            for (i = 0; i < clock.loopLimit; i++) {
+                if (!clock.timers) {
+                    return clock.now;
+                }
+
+                numTimers = Object.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.runToLast = function runToLast() {
+            var timer = lastTimer(clock);
+            if (!timer) {
+                return clock.now;
+            }
+
+            return clock.tick(timer.callAt);
+        };
+
+        clock.reset = function reset() {
+            clock.timers = {};
+        };
+
+        clock.setSystemTime = function setSystemTime(now) {
+            // determine time difference
+            var newNow = getEpoch(now);
+            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 (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;
+    }
+    exports.createClock = createClock;
+
+    exports.install = function install(target, now, toFake, loopLimit) {
+        var i,
+            l;
+
+        if (typeof target === "number") {
+            toFake = now;
+            now = target;
+            target = null;
+        }
+
+        if (!target) {
+            target = global;
+        }
+
+        var clock = createClock(now, loopLimit);
+
+        clock.uninstall = function () {
+            uninstall(clock, target);
+        };
+
+        clock.methods = toFake || [];
+
+        if (clock.methods.length === 0) {
+            clock.methods = keys(timers);
+        }
+
+        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 {
+                hijackMethod(target, clock.methods[i], clock);
+            }
+        }
+
+        return clock;
+    };
+
+}(global || this));
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}]},{},[1])(1)
+});
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..20c7d54
--- /dev/null
+++ b/package.json
@@ -0,0 +1,34 @@
+{
+  "name": "lolex",
+  "description": "Fake JavaScript timers",
+  "version": "1.5.1",
+  "homepage": "http://github.com/sinonjs/lolex",
+  "author": "Christian Johansen",
+  "repository": {
+    "type": "git",
+    "url": "http://github.com/sinonjs/lolex.git"
+  },
+  "bugs": {
+    "mail": "christian at cjohansen.no",
+    "url": "http://github.com/sinonjs/lolex/issues"
+  },
+  "license": "BSD-3-Clause",
+  "scripts": {
+    "lint": "eslint .",
+    "test-node": "mocha -R dot",
+    "test-headless": "mochify",
+    "test-cloud": "mochify --wd",
+    "test": "npm run lint && npm run test-node && npm run test-headless",
+    "bundle": "browserify -s lolex -o lolex.js src/lolex-src.js",
+    "prepublish": "npm run bundle"
+  },
+  "devDependencies": {
+    "eslint": "^3.0.1",
+    "browserify": "^13.0.1",
+    "mocha": "^2.5.3",
+    "mochify": "^2.18.0",
+    "referee": "^1.2.0",
+    "sinon": "^1.17.4"
+  },
+  "main": "./src/lolex-src.js"
+}
diff --git a/script/ci-test.sh b/script/ci-test.sh
new file mode 100755
index 0000000..8a15e58
--- /dev/null
+++ b/script/ci-test.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+set -eu
+
+npm run test-node
+npm run test-headless
diff --git a/src/lolex-src.js b/src/lolex-src.js
new file mode 100644
index 0000000..8ee5b34
--- /dev/null
+++ b/src/lolex-src.js
@@ -0,0 +1,655 @@
+/*global global, window*/
+/**
+ * @author Christian Johansen (christian at cjohansen.no) and contributors
+ * @license BSD
+ *
+ * Copyright (c) 2010-2014 Christian Johansen
+ */
+
+(function (global) {
+    "use strict";
+
+    // Make properties writable in IE, as per
+    // http://www.adequatelygood.com/Replacing-setTimeout-Globally.html
+    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 = setTimeout(NOOP, 0);
+    var addTimerReturnsObject = typeof timeoutResult === "object";
+    var hrtimePresent = (global.process && typeof global.process.hrtime === "function");
+    clearTimeout(timeoutResult);
+
+    var NativeDate = Date;
+    var uniqueTimerId = 1;
+
+    /**
+     * 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, i = l;
+        var ms = 0, 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 ((n % m) + m) % m;
+    }
+
+    /**
+     * Used to grok the `now` parameter to createClock.
+     */
+    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 addTimer(clock, timer) {
+        if (timer.func === undefined) {
+            throw new Error("Callback must be provided to timer calls");
+        }
+
+        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) {
+            return {
+                id: timer.id,
+                ref: NOOP,
+                unref: NOOP
+            };
+        }
+
+        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,
+            timer = null,
+            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,
+            timer = null,
+            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,
+            timer = null,
+            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) {
+        var exception;
+
+        if (typeof timer.interval === "number") {
+            clock.timers[timer.id].callAt += timer.interval;
+        } else {
+            delete clock.timers[timer.id];
+        }
+
+        try {
+            if (typeof timer.func === "function") {
+                timer.func.apply(null, timer.args);
+            } else {
+                /* eslint no-eval: "off" */
+                eval(timer.func);
+            }
+        } catch (e) {
+            exception = e;
+        }
+
+        if (!clock.timers[timer.id]) {
+            if (exception) {
+                throw exception;
+            }
+            return;
+        }
+
+        if (exception) {
+            throw exception;
+        }
+    }
+
+    function timerType(timer) {
+        if (timer.immediate) {
+            return "Immediate";
+        }
+        if (timer.interval !== undefined) {
+            return "Interval";
+        }
+        return "Timeout";
+    }
+
+    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 (timerType(timer) === ttype) {
+                delete clock.timers[timerId];
+            } else {
+                throw new Error("Cannot clear timer: timer created with set" + timerType(timer)
+                                + "() but cleared with clear" + ttype + "()");
+            }
+        }
+    }
+
+    function uninstall(clock, target) {
+        var method,
+            i,
+            l;
+        var installedHrTime = "_hrtime";
+
+        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 (target[method] && target[method].hadOwnProperty) {
+                    target[method] = clock["_" + method];
+                } else {
+                    try {
+                        delete target[method];
+                    } catch (ignore) { /* eslint empty-block: "off" */ }
+                }
+            }
+        }
+
+        // Prevent multiple executions which will completely remove these props
+        clock.methods = [];
+    }
+
+    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 {
+            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;
+    }
+
+    var timers = {
+        setTimeout: setTimeout,
+        clearTimeout: clearTimeout,
+        setImmediate: global.setImmediate,
+        clearImmediate: global.clearImmediate,
+        setInterval: setInterval,
+        clearInterval: clearInterval,
+        Date: Date
+    };
+
+    if (hrtimePresent) {
+        timers.hrtime = global.process.hrtime;
+    }
+
+    var keys = Object.keys || function (obj) {
+        var ks = [],
+            key;
+
+        for (key in obj) {
+            if (obj.hasOwnProperty(key)) {
+                ks.push(key);
+            }
+        }
+
+        return ks;
+    };
+
+    exports.timers = timers;
+
+    function createClock(now, loopLimit) {
+        loopLimit = loopLimit || 1000;
+
+        var clock = {
+            now: getEpoch(now),
+            hrNow: 0,
+            timeouts: {},
+            Date: createDate(),
+            loopLimit: loopLimit
+        };
+
+        clock.Date.clock = clock;
+
+        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.setInterval = function setInterval(func, timeout) {
+            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.tick = function tick(ms) {
+            ms = typeof ms === "number" ? ms : parseTime(ms);
+            var tickFrom = clock.now, tickTo = clock.now + ms, previous = clock.now;
+            var timer = firstTimerInRange(clock, tickFrom, tickTo);
+            var oldNow;
+
+            clock.duringTick = true;
+
+            function updateHrTime(newNow) {
+                clock.hrNow += (newNow - clock.now);
+            }
+
+            var firstException;
+            while (timer && tickFrom <= tickTo) {
+                if (clock.timers[timer.id]) {
+                    updateHrTime(timer.callAt);
+                    tickFrom = timer.callAt;
+                    clock.now = timer.callAt;
+                    try {
+                        oldNow = clock.now;
+                        callTimer(clock, timer);
+                        // compensate for any setSystemTime() call during timer callback
+                        if (oldNow !== clock.now) {
+                            tickFrom += clock.now - oldNow;
+                            tickTo += clock.now - oldNow;
+                            previous += clock.now - oldNow;
+                        }
+                    } catch (e) {
+                        firstException = firstException || e;
+                    }
+                }
+
+                timer = firstTimerInRange(clock, previous, tickTo);
+                previous = tickFrom;
+            }
+
+            clock.duringTick = false;
+            updateHrTime(tickTo);
+            clock.now = tickTo;
+
+            if (firstException) {
+                throw firstException;
+            }
+
+            return clock.now;
+        };
+
+        clock.next = function next() {
+            var timer = firstTimer(clock);
+            if (!timer) {
+                return clock.now;
+            }
+
+            clock.duringTick = true;
+            try {
+                clock.now = timer.callAt;
+                callTimer(clock, timer);
+                return clock.now;
+            } finally {
+                clock.duringTick = false;
+            }
+        };
+
+        clock.runAll = function runAll() {
+            var numTimers, i;
+            for (i = 0; i < clock.loopLimit; i++) {
+                if (!clock.timers) {
+                    return clock.now;
+                }
+
+                numTimers = Object.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.runToLast = function runToLast() {
+            var timer = lastTimer(clock);
+            if (!timer) {
+                return clock.now;
+            }
+
+            return clock.tick(timer.callAt);
+        };
+
+        clock.reset = function reset() {
+            clock.timers = {};
+        };
+
+        clock.setSystemTime = function setSystemTime(now) {
+            // determine time difference
+            var newNow = getEpoch(now);
+            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 (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;
+    }
+    exports.createClock = createClock;
+
+    exports.install = function install(target, now, toFake, loopLimit) {
+        var i,
+            l;
+
+        if (typeof target === "number") {
+            toFake = now;
+            now = target;
+            target = null;
+        }
+
+        if (!target) {
+            target = global;
+        }
+
+        var clock = createClock(now, loopLimit);
+
+        clock.uninstall = function () {
+            uninstall(clock, target);
+        };
+
+        clock.methods = toFake || [];
+
+        if (clock.methods.length === 0) {
+            clock.methods = keys(timers);
+        }
+
+        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 {
+                hijackMethod(target, clock.methods[i], clock);
+            }
+        }
+
+        return clock;
+    };
+
+}(global || this));
diff --git a/test/lolex-test.js b/test/lolex-test.js
new file mode 100644
index 0000000..2fe03d5
--- /dev/null
+++ b/test/lolex-test.js
@@ -0,0 +1,1760 @@
+/*global
+    describe,
+    beforeEach,
+    afterEach,
+    it
+*/
+/**
+ * @author Christian Johansen (christian at cjohansen.no)
+ * @license BSD
+ *
+ * Copyright (c) 2010-2014 Christian Johansen
+ */
+"use strict";
+
+if (typeof require === "function" && typeof module === "object") {
+    var referee = require("referee");
+    var lolex = require("../src/lolex-src");
+    var sinon = require("sinon");
+
+    global.lolex = lolex; // For testing eval
+}
+
+var assert = referee.assert;
+var refute = referee.refute;
+var GlobalDate = Date;
+var NOOP = function NOOP() { return undefined; };
+var hrtimePresent = (global.process && typeof global.process.hrtime === "function");
+
+describe("issue #59", function () {
+    var context = {
+        Date: Date,
+        setTimeout: setTimeout,
+        clearTimeout: clearTimeout
+    };
+    var clock;
+
+    it("should install and uninstall the clock on a custom target", function () {
+        clock = lolex.install(context);
+        // this would throw an error before issue #59 was fixed
+        clock.uninstall();
+    });
+});
+
+describe("lolex", function () {
+
+    describe("setTimeout", function () {
+
+        beforeEach(function () {
+            this.clock = lolex.createClock();
+            lolex.evalCalled = false;
+        });
+
+        afterEach(function () {
+            delete lolex.evalCalled;
+        });
+
+        it("throws if no arguments", function () {
+            var clock = this.clock;
+
+            assert.exception(function () { clock.setTimeout(); });
+        });
+
+        it("returns numeric id or object with numeric id", function () {
+            var result = this.clock.setTimeout("");
+
+            if (typeof result === "object") {
+                assert.isNumber(result.id);
+            } else {
+                assert.isNumber(result);
+            }
+        });
+
+        it("returns unique id", function () {
+            var id1 = this.clock.setTimeout("");
+            var id2 = this.clock.setTimeout("");
+
+            refute.equals(id2, id1);
+        });
+
+        it("sets timers on instance", function () {
+            var clock1 = lolex.createClock();
+            var clock2 = lolex.createClock();
+            var stubs = [sinon.stub(), sinon.stub()];
+
+            clock1.setTimeout(stubs[0], 100);
+            clock2.setTimeout(stubs[1], 100);
+            clock2.tick(200);
+
+            assert.isFalse(stubs[0].called);
+            assert(stubs[1].called);
+        });
+
+        it("parses numeric string times", function () {
+            this.clock.setTimeout(function () { lolex.evalCalled = true; }, "10");
+            this.clock.tick(10);
+
+            assert(lolex.evalCalled);
+        });
+
+        it("parses no-numeric string times", function () {
+            this.clock.setTimeout(function () { lolex.evalCalled = true; }, "string");
+            this.clock.tick(10);
+
+            assert(lolex.evalCalled);
+        });
+
+        it("evals non-function callbacks", function () {
+            this.clock.setTimeout("lolex.evalCalled = true", 10);
+            this.clock.tick(10);
+
+            assert(lolex.evalCalled);
+        });
+
+        it("passes setTimeout parameters", function () {
+            var clock = lolex.createClock();
+            var stub = sinon.stub();
+
+            clock.setTimeout(stub, 2, "the first", "the second");
+
+            clock.tick(3);
+
+            assert.isTrue(stub.calledWithExactly("the first", "the second"));
+        });
+
+        it("calls correct timeout on recursive tick", function () {
+            var clock = lolex.createClock();
+            var stub = sinon.stub();
+            var recurseCallback = function () { clock.tick(100); };
+
+            clock.setTimeout(recurseCallback, 50);
+            clock.setTimeout(stub, 100);
+
+            clock.tick(50);
+            assert(stub.called);
+        });
+
+        it("does not depend on this", function () {
+            var clock = lolex.createClock();
+            var stub = sinon.stub();
+            var setTimeout = clock.setTimeout;
+
+            setTimeout(stub, 100);
+
+            clock.tick(100);
+            assert(stub.called);
+        });
+
+        it("is not influenced by forward system clock changes", function () {
+            var stub = sinon.stub();
+            this.clock.setTimeout(stub, 5000);
+            this.clock.tick(1000);
+            this.clock.setSystemTime((new this.clock.Date()).getTime() + 1000);
+            this.clock.tick(3990);
+            assert.equals(stub.callCount, 0);
+            this.clock.tick(20);
+            assert.equals(stub.callCount, 1);
+        });
+
+        it("is not influenced by backward system clock changes", function () {
+            var stub = sinon.stub();
+            this.clock.setTimeout(stub, 5000);
+            this.clock.tick(1000);
+            this.clock.setSystemTime((new this.clock.Date()).getTime() - 1000);
+            this.clock.tick(3990);
+            assert.equals(stub.callCount, 0);
+            this.clock.tick(20);
+            assert.equals(stub.callCount, 1);
+        });
+    });
+
+    describe("setImmediate", function () {
+
+        beforeEach(function () {
+            this.clock = lolex.createClock();
+        });
+
+        it("returns numeric id or object with numeric id", function () {
+            var result = this.clock.setImmediate(NOOP);
+
+            if (typeof result === "object") {
+                assert.isNumber(result.id);
+            } else {
+                assert.isNumber(result);
+            }
+        });
+
+        it("calls the given callback immediately", function () {
+            var stub = sinon.stub();
+
+            this.clock.setImmediate(stub);
+            this.clock.tick(0);
+
+            assert(stub.called);
+        });
+
+        it("throws if no arguments", function () {
+            var clock = this.clock;
+
+            assert.exception(function () {
+                clock.setImmediate();
+            });
+        });
+
+        it("manages separate timers per clock instance", function () {
+            var clock1 = lolex.createClock();
+            var clock2 = lolex.createClock();
+            var stubs = [sinon.stub(), sinon.stub()];
+
+            clock1.setImmediate(stubs[0]);
+            clock2.setImmediate(stubs[1]);
+            clock2.tick(0);
+
+            assert.isFalse(stubs[0].called);
+            assert(stubs[1].called);
+        });
+
+        it("passes extra parameters through to the callback", function () {
+            var stub = sinon.stub();
+
+            this.clock.setImmediate(stub, "value1", 2);
+            this.clock.tick(1);
+
+            assert(stub.calledWithExactly("value1", 2));
+        });
+
+        it("calls the given callback before setTimeout", function () {
+            var stub1 = sinon.stub.create();
+            var stub2 = sinon.stub.create();
+
+            this.clock.setTimeout(stub1, 0);
+            this.clock.setImmediate(stub2);
+            this.clock.tick(0);
+
+            assert(stub1.calledOnce);
+            assert(stub2.calledOnce);
+            assert(stub2.calledBefore(stub1));
+        });
+
+        it("does not stuck next tick even if nested", function () {
+            var clock = this.clock;
+
+            clock.setImmediate(function f() {
+                clock.setImmediate(f);
+            });
+
+            clock.tick(0);
+        });
+    });
+
+    describe("clearImmediate", function () {
+
+        beforeEach(function () {
+            this.clock = lolex.createClock();
+        });
+
+        it("removes immediate callbacks", function () {
+            var callback = sinon.stub();
+
+            var id = this.clock.setImmediate(callback);
+            this.clock.clearImmediate(id);
+            this.clock.tick(1);
+
+            assert.isFalse(callback.called);
+        });
+
+        it("does not remove timeout", function () {
+            var callback = sinon.stub();
+
+            var id = this.clock.setTimeout(callback, 50);
+            assert.exception(function () {
+                this.clock.clearImmediate(id);
+            }.bind(this), {
+                message: "Cannot clear timer: timer created with setTimeout() but cleared with clearImmediate()"
+            });
+            this.clock.tick(55);
+
+            assert.isTrue(callback.called);
+        });
+
+        it("does not remove interval", function () {
+            var callback = sinon.stub();
+
+            var id = this.clock.setInterval(callback, 50);
+            assert.exception(function () {
+                this.clock.clearImmediate(id);
+            }.bind(this), {
+                message: "Cannot clear timer: timer created with setInterval() but cleared with clearImmediate()"
+            });
+            this.clock.tick(55);
+
+            assert.isTrue(callback.called);
+        });
+
+    });
+
+    describe("tick", function () {
+
+        beforeEach(function () {
+            this.clock = lolex.install(0);
+        });
+
+        afterEach(function () {
+            this.clock.uninstall();
+        });
+
+        it("triggers immediately without specified delay", function () {
+            var stub = sinon.stub();
+            this.clock.setTimeout(stub);
+
+            this.clock.tick(0);
+
+            assert(stub.called);
+        });
+
+        it("does not trigger without sufficient delay", function () {
+            var stub = sinon.stub();
+            this.clock.setTimeout(stub, 100);
+            this.clock.tick(10);
+
+            assert.isFalse(stub.called);
+        });
+
+        it("triggers after sufficient delay", function () {
+            var stub = sinon.stub();
+            this.clock.setTimeout(stub, 100);
+            this.clock.tick(100);
+
+            assert(stub.called);
+        });
+
+        it("triggers simultaneous timers", function () {
+            var spies = [sinon.spy(), sinon.spy()];
+            this.clock.setTimeout(spies[0], 100);
+            this.clock.setTimeout(spies[1], 100);
+
+            this.clock.tick(100);
+
+            assert(spies[0].called);
+            assert(spies[1].called);
+        });
+
+        it("triggers multiple simultaneous timers", function () {
+            var spies = [sinon.spy(), sinon.spy(), sinon.spy(), sinon.spy()];
+            this.clock.setTimeout(spies[0], 100);
+            this.clock.setTimeout(spies[1], 100);
+            this.clock.setTimeout(spies[2], 99);
+            this.clock.setTimeout(spies[3], 100);
+
+            this.clock.tick(100);
+
+            assert(spies[0].called);
+            assert(spies[1].called);
+            assert(spies[2].called);
+            assert(spies[3].called);
+        });
+
+        it("triggers multiple simultaneous timers with zero callAt", function () {
+            var test = this;
+            var spies = [
+                sinon.spy(function () {
+                    test.clock.setTimeout(spies[1], 0);
+                }),
+                sinon.spy(),
+                sinon.spy()
+            ];
+
+            // First spy calls another setTimeout with delay=0
+            this.clock.setTimeout(spies[0], 0);
+            this.clock.setTimeout(spies[2], 10);
+
+            this.clock.tick(10);
+
+            assert(spies[0].called);
+            assert(spies[1].called);
+            assert(spies[2].called);
+        });
+
+        it("waits after setTimeout was called", function () {
+            this.clock.tick(100);
+            var stub = sinon.stub();
+            this.clock.setTimeout(stub, 150);
+            this.clock.tick(50);
+
+            assert.isFalse(stub.called);
+            this.clock.tick(100);
+            assert(stub.called);
+        });
+
+        it("mini integration test", function () {
+            var stubs = [sinon.stub(), sinon.stub(), sinon.stub()];
+            this.clock.setTimeout(stubs[0], 100);
+            this.clock.setTimeout(stubs[1], 120);
+            this.clock.tick(10);
+            this.clock.tick(89);
+            assert.isFalse(stubs[0].called);
+            assert.isFalse(stubs[1].called);
+            this.clock.setTimeout(stubs[2], 20);
+            this.clock.tick(1);
+            assert(stubs[0].called);
+            assert.isFalse(stubs[1].called);
+            assert.isFalse(stubs[2].called);
+            this.clock.tick(19);
+            assert.isFalse(stubs[1].called);
+            assert(stubs[2].called);
+            this.clock.tick(1);
+            assert(stubs[1].called);
+        });
+
+        it("triggers even when some throw", function () {
+            var clock = this.clock;
+            var stubs = [sinon.stub().throws(), sinon.stub()];
+
+            clock.setTimeout(stubs[0], 100);
+            clock.setTimeout(stubs[1], 120);
+
+            assert.exception(function () {
+                clock.tick(120);
+            });
+
+            assert(stubs[0].called);
+            assert(stubs[1].called);
+        });
+
+        it("calls function with global object or null (strict mode) as this", function () {
+            var clock = this.clock;
+            var stub = sinon.stub().throws();
+            clock.setTimeout(stub, 100);
+
+            assert.exception(function () {
+                clock.tick(100);
+            });
+
+            assert(stub.calledOn(global) || stub.calledOn(null));
+        });
+
+        it("triggers in the order scheduled", function () {
+            var spies = [sinon.spy(), sinon.spy()];
+            this.clock.setTimeout(spies[0], 13);
+            this.clock.setTimeout(spies[1], 11);
+
+            this.clock.tick(15);
+
+            assert(spies[1].calledBefore(spies[0]));
+        });
+
+        it("creates updated Date while ticking", function () {
+            var spy = sinon.spy();
+
+            this.clock.setInterval(function () {
+                spy(new Date().getTime());
+            }, 10);
+
+            this.clock.tick(100);
+
+            assert.equals(spy.callCount, 10);
+            assert(spy.calledWith(10));
+            assert(spy.calledWith(20));
+            assert(spy.calledWith(30));
+            assert(spy.calledWith(40));
+            assert(spy.calledWith(50));
+            assert(spy.calledWith(60));
+            assert(spy.calledWith(70));
+            assert(spy.calledWith(80));
+            assert(spy.calledWith(90));
+            assert(spy.calledWith(100));
+        });
+
+        it("fires timer in intervals of 13", function () {
+            var spy = sinon.spy();
+            this.clock.setInterval(spy, 13);
+
+            this.clock.tick(500);
+
+            assert.equals(spy.callCount, 38);
+        });
+
+        it("fires timers in correct order", function () {
+            var spy13 = sinon.spy();
+            var spy10 = sinon.spy();
+
+            this.clock.setInterval(function () {
+                spy13(new Date().getTime());
+            }, 13);
+
+            this.clock.setInterval(function () {
+                spy10(new Date().getTime());
+            }, 10);
+
+            this.clock.tick(500);
+
+            assert.equals(spy13.callCount, 38);
+            assert.equals(spy10.callCount, 50);
+
+            assert(spy13.calledWith(416));
+            assert(spy10.calledWith(320));
+
+            assert(spy10.getCall(0).calledBefore(spy13.getCall(0)));
+            assert(spy10.getCall(4).calledBefore(spy13.getCall(3)));
+        });
+
+        it("triggers timeouts and intervals in the order scheduled", function () {
+            var spies = [sinon.spy(), sinon.spy()];
+            this.clock.setInterval(spies[0], 10);
+            this.clock.setTimeout(spies[1], 50);
+
+            this.clock.tick(100);
+
+            assert(spies[0].calledBefore(spies[1]));
+            assert.equals(spies[0].callCount, 10);
+            assert.equals(spies[1].callCount, 1);
+        });
+
+        it("does not fire canceled intervals", function () {
+            var id;
+            var callback = sinon.spy(function () {
+                if (callback.callCount === 3) {
+                    clearInterval(id);
+                }
+            });
+
+            id = this.clock.setInterval(callback, 10);
+            this.clock.tick(100);
+
+            assert.equals(callback.callCount, 3);
+        });
+
+        it("passes 8 seconds", function () {
+            var spy = sinon.spy();
+            this.clock.setInterval(spy, 4000);
+
+            this.clock.tick("08");
+
+            assert.equals(spy.callCount, 2);
+        });
+
+        it("passes 1 minute", function () {
+            var spy = sinon.spy();
+            this.clock.setInterval(spy, 6000);
+
+            this.clock.tick("01:00");
+
+            assert.equals(spy.callCount, 10);
+        });
+
+        it("passes 2 hours, 34 minutes and 10 seconds", function () {
+            var spy = sinon.spy();
+            this.clock.setInterval(spy, 10000);
+
+            this.clock.tick("02:34:10");
+
+            assert.equals(spy.callCount, 925);
+        });
+
+        it("throws for invalid format", function () {
+            var spy = sinon.spy();
+            this.clock.setInterval(spy, 10000);
+            var test = this;
+
+            assert.exception(function () {
+                test.clock.tick("12:02:34:10");
+            });
+
+            assert.equals(spy.callCount, 0);
+        });
+
+        it("throws for invalid minutes", function () {
+            var spy = sinon.spy();
+            this.clock.setInterval(spy, 10000);
+            var test = this;
+
+            assert.exception(function () {
+                test.clock.tick("67:10");
+            });
+
+            assert.equals(spy.callCount, 0);
+        });
+
+        it("throws for negative minutes", function () {
+            var spy = sinon.spy();
+            this.clock.setInterval(spy, 10000);
+            var test = this;
+
+            assert.exception(function () {
+                test.clock.tick("-7:10");
+            });
+
+            assert.equals(spy.callCount, 0);
+        });
+
+        it("treats missing argument as 0", function () {
+            this.clock.tick();
+
+            assert.equals(this.clock.now, 0);
+        });
+
+        it("fires nested setTimeout calls properly", function () {
+            var i = 0;
+            var clock = this.clock;
+
+            var callback = function () {
+                ++i;
+                clock.setTimeout(function () {
+                    callback();
+                }, 100);
+            };
+
+            callback();
+
+            clock.tick(1000);
+
+            assert.equals(i, 11);
+        });
+
+        it("does not silently catch errors", function () {
+            var clock = this.clock;
+
+            clock.setTimeout(function () {
+                throw new Error("oh no!");
+            }, 1000);
+
+            assert.exception(function () {
+                clock.tick(1000);
+            });
+        });
+
+        it("returns the current now value", function () {
+            var clock = this.clock;
+            var value = clock.tick(200);
+            assert.equals(clock.now, value);
+        });
+    });
+
+    describe("next", function () {
+
+        beforeEach(function () {
+            this.clock = lolex.install(0);
+        });
+
+        afterEach(function () {
+            this.clock.uninstall();
+        });
+
+        it("triggers the next timer", function () {
+            var stub = sinon.stub();
+            this.clock.setTimeout(stub, 100);
+
+            this.clock.next();
+
+            assert(stub.called);
+        });
+
+        it("does not trigger simultaneous timers", function () {
+            var spies = [sinon.spy(), sinon.spy()];
+            this.clock.setTimeout(spies[0], 100);
+            this.clock.setTimeout(spies[1], 100);
+
+            this.clock.next();
+
+            assert(spies[0].called);
+            assert.isFalse(spies[1].called);
+        });
+
+        it("subsequent calls trigger simultaneous timers", function () {
+            var spies = [sinon.spy(), sinon.spy(), sinon.spy(), sinon.spy()];
+            this.clock.setTimeout(spies[0], 100);
+            this.clock.setTimeout(spies[1], 100);
+            this.clock.setTimeout(spies[2], 99);
+            this.clock.setTimeout(spies[3], 100);
+
+            this.clock.next();
+            assert(spies[2].called);
+            assert.isFalse(spies[0].called);
+            assert.isFalse(spies[1].called);
+            assert.isFalse(spies[3].called);
+
+            this.clock.next();
+            assert(spies[0].called);
+            assert.isFalse(spies[1].called);
+            assert.isFalse(spies[3].called);
+
+            this.clock.next();
+            assert(spies[1].called);
+            assert.isFalse(spies[3].called);
+
+            this.clock.next();
+            assert(spies[3].called);
+        });
+
+        it("subsequent calls triggers simultaneous timers with zero callAt", function () {
+            var test = this;
+            var spies = [
+                sinon.spy(function () {
+                    test.clock.setTimeout(spies[1], 0);
+                }),
+                sinon.spy(),
+                sinon.spy()
+            ];
+
+            // First spy calls another setTimeout with delay=0
+            this.clock.setTimeout(spies[0], 0);
+            this.clock.setTimeout(spies[2], 10);
+
+            this.clock.next();
+            assert(spies[0].called);
+            assert.isFalse(spies[1].called);
+
+            this.clock.next();
+            assert(spies[1].called);
+
+            this.clock.next();
+            assert(spies[2].called);
+        });
+
+        it("throws exception thrown by timer", function () {
+            var clock = this.clock;
+            var stub = sinon.stub().throws();
+
+            clock.setTimeout(stub, 100);
+
+            assert.exception(function () {
+                clock.next();
+            });
+
+            assert(stub.called);
+        });
+
+        it("calls function with global object or null (strict mode) as this", function () {
+            var clock = this.clock;
+            var stub = sinon.stub().throws();
+            clock.setTimeout(stub, 100);
+
+            assert.exception(function () {
+                clock.next();
+            });
+
+            assert(stub.calledOn(global) || stub.calledOn(null));
+        });
+
+        it("subsequent calls trigger in the order scheduled", function () {
+            var spies = [sinon.spy(), sinon.spy()];
+            this.clock.setTimeout(spies[0], 13);
+            this.clock.setTimeout(spies[1], 11);
+
+            this.clock.next();
+            this.clock.next();
+
+            assert(spies[1].calledBefore(spies[0]));
+        });
+
+        it("subsequent calls create updated Date", function () {
+            var spy = sinon.spy();
+
+            this.clock.setInterval(function () {
+                spy(new Date().getTime());
+            }, 10);
+
+            this.clock.next();
+            this.clock.next();
+            this.clock.next();
+            this.clock.next();
+            this.clock.next();
+            this.clock.next();
+            this.clock.next();
+            this.clock.next();
+            this.clock.next();
+            this.clock.next();
+
+            assert.equals(spy.callCount, 10);
+            assert(spy.calledWith(10));
+            assert(spy.calledWith(20));
+            assert(spy.calledWith(30));
+            assert(spy.calledWith(40));
+            assert(spy.calledWith(50));
+            assert(spy.calledWith(60));
+            assert(spy.calledWith(70));
+            assert(spy.calledWith(80));
+            assert(spy.calledWith(90));
+            assert(spy.calledWith(100));
+        });
+
+        it("subsequent calls trigger timeouts and intervals in the order scheduled", function () {
+            var spies = [sinon.spy(), sinon.spy()];
+            this.clock.setInterval(spies[0], 10);
+            this.clock.setTimeout(spies[1], 50);
+
+            this.clock.next();
+            this.clock.next();
+            this.clock.next();
+            this.clock.next();
+            this.clock.next();
+            this.clock.next();
+
+            assert(spies[0].calledBefore(spies[1]));
+            assert.equals(spies[0].callCount, 5);
+            assert.equals(spies[1].callCount, 1);
+        });
+
+        it("subsequent calls do not fire canceled intervals", function () {
+            var id;
+            var callback = sinon.spy(function () {
+                if (callback.callCount === 3) {
+                    clearInterval(id);
+                }
+            });
+
+            id = this.clock.setInterval(callback, 10);
+            this.clock.next();
+            this.clock.next();
+            this.clock.next();
+            this.clock.next();
+
+            assert.equals(callback.callCount, 3);
+        });
+
+        it("advances the clock based on when the timer was supposed to be called", function () {
+            var clock = this.clock;
+            clock.setTimeout(sinon.spy(), 55);
+            clock.next();
+            assert.equals(clock.now, 55);
+        });
+
+        it("returns the current now value", function () {
+            var clock = this.clock;
+            clock.setTimeout(sinon.spy(), 55);
+            var value = clock.next();
+            assert.equals(clock.now, value);
+        });
+    });
+
+    describe("runAll", function () {
+
+        it("if there are no timers just return", function () {
+            this.clock = lolex.createClock();
+            this.clock.runAll();
+        });
+
+        it("runs all timers", function () {
+            this.clock = lolex.createClock();
+            var spies = [sinon.spy(), sinon.spy()];
+            this.clock.setTimeout(spies[0], 10);
+            this.clock.setTimeout(spies[1], 50);
+
+            this.clock.runAll();
+
+            assert(spies[0].called);
+            assert(spies[1].called);
+        });
+
+        it("new timers added while running are also run", function () {
+            this.clock = lolex.createClock();
+            var test = this;
+            var spies = [
+                sinon.spy(function () {
+                    test.clock.setTimeout(spies[1], 50);
+                }),
+                sinon.spy()
+            ];
+
+            // Spy calls another setTimeout
+            this.clock.setTimeout(spies[0], 10);
+
+            this.clock.runAll();
+
+            assert(spies[0].called);
+            assert(spies[1].called);
+        });
+
+        it("throws before allowing infinite recursion", function () {
+            this.clock = lolex.createClock();
+            var test = this;
+            var recursiveCallback = function () {
+                test.clock.setTimeout(recursiveCallback, 10);
+            };
+
+            this.clock.setTimeout(recursiveCallback, 10);
+
+            assert.exception(function () {
+                test.clock.runAll();
+            });
+        });
+
+        it("the loop limit can be set when creating a clock", function () {
+            this.clock = lolex.createClock(0, 1);
+            var test = this;
+
+            var spies = [sinon.spy(), sinon.spy()];
+            this.clock.setTimeout(spies[0], 10);
+            this.clock.setTimeout(spies[1], 50);
+
+            assert.exception(function () {
+                test.clock.runAll();
+            });
+        });
+
+        it("the loop limit can be set when installing a clock", function () {
+            this.clock = lolex.install(0, null, null, 1);
+            var test = this;
+
+            var spies = [sinon.spy(), sinon.spy()];
+            setTimeout(spies[0], 10);
+            setTimeout(spies[1], 50);
+
+            assert.exception(function () {
+                test.clock.runAll();
+            });
+
+            this.clock.uninstall();
+        });
+
+    });
+
+    describe("runToLast", function () {
+
+        it("returns current time when there are no timers", function () {
+            this.clock = lolex.createClock();
+
+            var time = this.clock.runToLast();
+
+            assert.equals(time, 0);
+        });
+
+        it("runs all existing timers", function () {
+            this.clock = lolex.createClock();
+            var spies = [sinon.spy(), sinon.spy()];
+            this.clock.setTimeout(spies[0], 10);
+            this.clock.setTimeout(spies[1], 50);
+
+            this.clock.runToLast();
+
+            assert(spies[0].called);
+            assert(spies[1].called);
+        });
+
+        it("returns time of the last timer", function () {
+            this.clock = lolex.createClock();
+            var spies = [sinon.spy(), sinon.spy()];
+            this.clock.setTimeout(spies[0], 10);
+            this.clock.setTimeout(spies[1], 50);
+
+            var time = this.clock.runToLast();
+
+            assert.equals(time, 50);
+        });
+
+        it("runs all existing timers when two timers are matched for being last", function () {
+            this.clock = lolex.createClock();
+            var spies = [sinon.spy(), sinon.spy()];
+            this.clock.setTimeout(spies[0], 10);
+            this.clock.setTimeout(spies[1], 10);
+
+            this.clock.runToLast();
+
+            assert(spies[0].called);
+            assert(spies[1].called);
+        });
+
+        it("new timers added with a call time later than the last existing timer are NOT run", function () {
+            this.clock = lolex.createClock();
+            var test = this;
+            var spies = [
+                sinon.spy(function () {
+                    test.clock.setTimeout(spies[1], 50);
+                }),
+                sinon.spy()
+            ];
+
+            // Spy calls another setTimeout
+            this.clock.setTimeout(spies[0], 10);
+
+            this.clock.runToLast();
+
+            assert.isTrue(spies[0].called);
+            assert.isFalse(spies[1].called);
+        });
+
+        it("new timers added with a call time ealier than the last existing timer are run", function () {
+            this.clock = lolex.createClock();
+            var test = this;
+            var spies = [
+                sinon.spy(),
+                sinon.spy(function () {
+                    test.clock.setTimeout(spies[2], 50);
+                }),
+                sinon.spy()
+            ];
+
+            this.clock.setTimeout(spies[0], 100);
+            // Spy calls another setTimeout
+            this.clock.setTimeout(spies[1], 10);
+
+            this.clock.runToLast();
+
+            assert.isTrue(spies[0].called);
+            assert.isTrue(spies[1].called);
+            assert.isTrue(spies[2].called);
+        });
+
+        it("new timers cannot cause an infinite loop", function () {
+            this.clock = lolex.createClock();
+            var test = this;
+            var spy = sinon.spy();
+            var recursiveCallback = function () {
+                test.clock.setTimeout(recursiveCallback, 0);
+            };
+
+            this.clock.setTimeout(recursiveCallback, 0);
+            this.clock.setTimeout(spy, 100);
+
+            this.clock.runToLast();
+
+            assert.isTrue(spy.called);
+        });
+
+    });
+
+    describe("clearTimeout", function () {
+
+        beforeEach(function () {
+            this.clock = lolex.createClock();
+        });
+
+        it("removes timeout", function () {
+            var stub = sinon.stub();
+            var id = this.clock.setTimeout(stub, 50);
+            this.clock.clearTimeout(id);
+            this.clock.tick(50);
+
+            assert.isFalse(stub.called);
+        });
+
+        it("does not remove interval", function () {
+            var stub = sinon.stub();
+            var id = this.clock.setInterval(stub, 50);
+            assert.exception(function () {
+                this.clock.clearTimeout(id);
+            }.bind(this), {
+                message: "Cannot clear timer: timer created with setInterval() but cleared with clearTimeout()"
+            });
+            this.clock.tick(50);
+
+            assert.isTrue(stub.called);
+        });
+
+        it("does not remove immediate", function () {
+            var stub = sinon.stub();
+            var id = this.clock.setImmediate(stub);
+            assert.exception(function () {
+                this.clock.clearTimeout(id);
+            }.bind(this), {
+                message: "Cannot clear timer: timer created with setImmediate() but cleared with clearTimeout()"
+            });
+            this.clock.tick(50);
+
+            assert.isTrue(stub.called);
+        });
+
+        it("ignores null argument", function () {
+            this.clock.clearTimeout(null);
+            assert(true); // doesn't fail
+        });
+    });
+
+    describe("reset", function () {
+
+        beforeEach(function () {
+            this.clock = lolex.createClock();
+        });
+
+        it("empties timeouts queue", function () {
+            var stub = sinon.stub();
+            this.clock.setTimeout(stub);
+            this.clock.reset();
+            this.clock.tick(0);
+
+            assert.isFalse(stub.called);
+        });
+    });
+
+    describe("setInterval", function () {
+
+        beforeEach(function () {
+            this.clock = lolex.createClock();
+        });
+
+        it("throws if no arguments", function () {
+            var clock = this.clock;
+
+            assert.exception(function () {
+                clock.setInterval();
+            });
+        });
+
+        it("returns numeric id or object with numeric id", function () {
+            var result = this.clock.setInterval("");
+
+            if (typeof result === "object") {
+                assert.isNumber(result.id);
+            } else {
+                assert.isNumber(result);
+            }
+        });
+
+        it("returns unique id", function () {
+            var id1 = this.clock.setInterval("");
+            var id2 = this.clock.setInterval("");
+
+            refute.equals(id2, id1);
+        });
+
+        it("schedules recurring timeout", function () {
+            var stub = sinon.stub();
+            this.clock.setInterval(stub, 10);
+            this.clock.tick(99);
+
+            assert.equals(stub.callCount, 9);
+        });
+
+        it("is not influenced by forward system clock changes", function () {
+            var stub = sinon.stub();
+            this.clock.setInterval(stub, 10);
+            this.clock.tick(11);
+            assert.equals(stub.callCount, 1);
+            this.clock.setSystemTime((new this.clock.Date()).getTime() + 1000);
+            this.clock.tick(8);
+            assert.equals(stub.callCount, 1);
+            this.clock.tick(3);
+            assert.equals(stub.callCount, 2);
+        });
+
+        it("is not influenced by backward system clock changes", function () {
+            var stub = sinon.stub();
+            this.clock.setInterval(stub, 10);
+            this.clock.tick(5);
+            this.clock.setSystemTime((new this.clock.Date()).getTime() - 1000);
+            this.clock.tick(6);
+            assert.equals(stub.callCount, 1);
+            this.clock.tick(10);
+            assert.equals(stub.callCount, 2);
+        });
+
+        it("does not schedule recurring timeout when cleared", function () {
+            var clock = this.clock;
+            var id;
+            var stub = sinon.spy(function () {
+                if (stub.callCount === 3) {
+                    clock.clearInterval(id);
+                }
+            });
+
+            id = this.clock.setInterval(stub, 10);
+            this.clock.tick(100);
+
+            assert.equals(stub.callCount, 3);
+        });
+
+        it("passes setTimeout parameters", function () {
+            var clock = lolex.createClock();
+            var stub = sinon.stub();
+
+            clock.setInterval(stub, 2, "the first", "the second");
+
+            clock.tick(3);
+
+            assert.isTrue(stub.calledWithExactly("the first", "the second"));
+        });
+    });
+
+    describe("clearInterval", function () {
+
+        beforeEach(function () {
+            this.clock = lolex.createClock();
+        });
+
+        it("removes interval", function () {
+            var stub = sinon.stub();
+            var id = this.clock.setInterval(stub, 50);
+            this.clock.clearInterval(id);
+            this.clock.tick(50);
+
+            assert.isFalse(stub.called);
+        });
+
+        it("does not remove timeout", function () {
+            var stub = sinon.stub();
+            var id = this.clock.setTimeout(stub, 50);
+            assert.exception(function () {
+                this.clock.clearInterval(id);
+            }.bind(this), {
+                message: "Cannot clear timer: timer created with setTimeout() but cleared with clearInterval()"
+            });
+            this.clock.tick(50);
+            assert.isTrue(stub.called);
+        });
+
+        it("does not remove immediate", function () {
+            var stub = sinon.stub();
+            var id = this.clock.setImmediate(stub);
+            assert.exception(function () {
+                this.clock.clearInterval(id);
+            }.bind(this), {
+                message: "Cannot clear timer: timer created with setImmediate() but cleared with clearInterval()"
+            });
+            this.clock.tick(50);
+
+            assert.isTrue(stub.called);
+        });
+
+        it("ignores null argument", function () {
+            this.clock.clearInterval(null);
+            assert(true); // doesn't fail
+        });
+    });
+
+    describe("date", function () {
+
+        beforeEach(function () {
+            this.now = new GlobalDate().getTime() - 3000;
+            this.clock = lolex.createClock(this.now);
+            this.Date = global.Date;
+        });
+
+        afterEach(function () {
+            global.Date = this.Date;
+        });
+
+        it("provides date constructor", function () {
+            assert.isFunction(this.clock.Date);
+        });
+
+        it("creates real Date objects", function () {
+            var date = new this.clock.Date();
+
+            assert(Date.prototype.isPrototypeOf(date));
+        });
+
+        it("creates real Date objects when called as function", function () {
+            var date = this.clock.Date();
+
+            assert(Date.prototype.isPrototypeOf(date));
+        });
+
+        it("creates real Date objects when Date constructor is gone", function () {
+            var realDate = new Date();
+            Date = NOOP;
+            global.Date = NOOP;
+
+            var date = new this.clock.Date();
+
+            assert.same(date.constructor.prototype, realDate.constructor.prototype);
+        });
+
+        it("creates Date objects representing clock time", function () {
+            var date = new this.clock.Date();
+
+            assert.equals(date.getTime(), new Date(this.now).getTime());
+        });
+
+        it("returns Date object representing clock time", function () {
+            var date = this.clock.Date();
+
+            assert.equals(date.getTime(), new Date(this.now).getTime());
+        });
+
+        it("listens to ticking clock", function () {
+            var date1 = new this.clock.Date();
+            this.clock.tick(3);
+            var date2 = new this.clock.Date();
+
+            assert.equals(date2.getTime() - date1.getTime(), 3);
+        });
+
+        it("listens to system clock changes", function () {
+            var date1 = new this.clock.Date();
+            this.clock.setSystemTime(date1.getTime() + 1000);
+            var date2 = new this.clock.Date();
+
+            assert.equals(date2.getTime() - date1.getTime(), 1000);
+        });
+
+        it("creates regular date when passing timestamp", function () {
+            var date = new Date();
+            var fakeDate = new this.clock.Date(date.getTime());
+
+            assert.equals(fakeDate.getTime(), date.getTime());
+        });
+
+        it("creates regular date when passing a date as string", function () {
+            var date = new Date();
+            var fakeDate = new this.clock.Date(date.toISOString());
+
+            assert.equals(fakeDate.getTime(), date.getTime());
+        });
+
+        it("creates regular date when passing a date as RFC 2822 string", function () {
+            var date = new Date("Sat Apr 12 2014 12:22:00 GMT+1000");
+            var fakeDate = new this.clock.Date("Sat Apr 12 2014 12:22:00 GMT+1000");
+
+            assert.equals(fakeDate.getTime(), date.getTime());
+        });
+
+        it("returns regular date when calling with timestamp", function () {
+            var date = new Date();
+            var fakeDate = this.clock.Date(date.getTime());
+
+            assert.equals(fakeDate.getTime(), date.getTime());
+        });
+
+        it("creates regular date when passing year, month", function () {
+            var date = new Date(2010, 4);
+            var fakeDate = new this.clock.Date(2010, 4);
+
+            assert.equals(fakeDate.getTime(), date.getTime());
+        });
+
+        it("returns regular date when calling with year, month", function () {
+            var date = new Date(2010, 4);
+            var fakeDate = this.clock.Date(2010, 4);
+
+            assert.equals(fakeDate.getTime(), date.getTime());
+        });
+
+        it("creates regular date when passing y, m, d", function () {
+            var date = new Date(2010, 4, 2);
+            var fakeDate = new this.clock.Date(2010, 4, 2);
+
+            assert.equals(fakeDate.getTime(), date.getTime());
+        });
+
+        it("returns regular date when calling with y, m, d", function () {
+            var date = new Date(2010, 4, 2);
+            var fakeDate = this.clock.Date(2010, 4, 2);
+
+            assert.equals(fakeDate.getTime(), date.getTime());
+        });
+
+        it("creates regular date when passing y, m, d, h", function () {
+            var date = new Date(2010, 4, 2, 12);
+            var fakeDate = new this.clock.Date(2010, 4, 2, 12);
+
+            assert.equals(fakeDate.getTime(), date.getTime());
+        });
+
+        it("returns regular date when calling with y, m, d, h", function () {
+            var date = new Date(2010, 4, 2, 12);
+            var fakeDate = this.clock.Date(2010, 4, 2, 12);
+
+            assert.equals(fakeDate.getTime(), date.getTime());
+        });
+
+        it("creates regular date when passing y, m, d, h, m", function () {
+            var date = new Date(2010, 4, 2, 12, 42);
+            var fakeDate = new this.clock.Date(2010, 4, 2, 12, 42);
+
+            assert.equals(fakeDate.getTime(), date.getTime());
+        });
+
+        it("returns regular date when calling with y, m, d, h, m", function () {
+            var date = new Date(2010, 4, 2, 12, 42);
+            var fakeDate = this.clock.Date(2010, 4, 2, 12, 42);
+
+            assert.equals(fakeDate.getTime(), date.getTime());
+        });
+
+        it("creates regular date when passing y, m, d, h, m, s", function () {
+            var date = new Date(2010, 4, 2, 12, 42, 53);
+            var fakeDate = new this.clock.Date(2010, 4, 2, 12, 42, 53);
+
+            assert.equals(fakeDate.getTime(), date.getTime());
+        });
+
+        it("returns regular date when calling with y, m, d, h, m, s", function () {
+            var date = new Date(2010, 4, 2, 12, 42, 53);
+            var fakeDate = this.clock.Date(2010, 4, 2, 12, 42, 53);
+
+            assert.equals(fakeDate.getTime(), date.getTime());
+        });
+
+        it("creates regular date when passing y, m, d, h, m, s, ms", function () {
+            var date = new Date(2010, 4, 2, 12, 42, 53, 498);
+            var fakeDate = new this.clock.Date(2010, 4, 2, 12, 42, 53, 498);
+
+            assert.equals(fakeDate.getTime(), date.getTime());
+        });
+
+        it("returns regular date when calling with y, m, d, h, m, s, ms", function () {
+            var date = new Date(2010, 4, 2, 12, 42, 53, 498);
+            var fakeDate = this.clock.Date(2010, 4, 2, 12, 42, 53, 498);
+
+            assert.equals(fakeDate.getTime(), date.getTime());
+        });
+
+        it("mirrors native Date.prototype", function () {
+            assert.same(this.clock.Date.prototype, Date.prototype);
+        });
+
+        it("supports now method if present", function () {
+            assert.same(typeof this.clock.Date.now, typeof Date.now);
+        });
+
+        if (Date.now) {
+            describe("now", function () {
+                it("returns clock.now", function () {
+                    /* eslint camelcase: "off" */
+                    var clock_now = this.clock.Date.now();
+                    var global_now = GlobalDate.now();
+
+                    assert(this.now <= clock_now && clock_now <= global_now);
+                });
+            });
+        } else {
+            describe("unsupported now", function () {
+                it("is undefined", function () {
+                    refute.defined(this.clock.Date.now);
+                });
+            });
+        }
+
+        it("mirrors parse method", function () {
+            assert.same(this.clock.Date.parse, Date.parse);
+        });
+
+        it("mirrors UTC method", function () {
+            assert.same(this.clock.Date.UTC, Date.UTC);
+        });
+
+        it("mirrors toUTCString method", function () {
+            assert.same(this.clock.Date.prototype.toUTCString, Date.prototype.toUTCString);
+        });
+
+        if (Date.toSource) {
+            describe("toSource", function () {
+
+                it("is mirrored", function () {
+                    assert.same(this.clock.Date.toSource(), Date.toSource());
+                });
+
+            });
+        } else {
+            describe("unsupported toSource", function () {
+
+                it("is undefined", function () {
+                    refute.defined(this.clock.Date.toSource);
+                });
+
+            });
+        }
+
+        it("mirrors toString", function () {
+            assert.same(this.clock.Date.toString(), Date.toString());
+        });
+    });
+
+    describe("stubTimers", function () {
+
+        beforeEach(function () {
+            this.dateNow = global.Date.now;
+        });
+
+        afterEach(function () {
+            if (this.clock) {
+                this.clock.uninstall();
+            }
+
+            clearTimeout(this.timer);
+            if (this.dateNow === undefined) {
+                delete global.Date.now;
+            } else {
+                global.Date.now = this.dateNow;
+            }
+        });
+
+        it("returns clock object", function () {
+            this.clock = lolex.install();
+
+            assert.isObject(this.clock);
+            assert.isFunction(this.clock.tick);
+        });
+
+        it("has clock property", function () {
+            this.clock = lolex.install();
+
+            assert.same(setTimeout.clock, this.clock);
+            assert.same(clearTimeout.clock, this.clock);
+            assert.same(setInterval.clock, this.clock);
+            assert.same(clearInterval.clock, this.clock);
+            assert.same(Date.clock, this.clock);
+        });
+
+        it("sets initial timestamp", function () {
+            this.clock = lolex.install(1400);
+
+            assert.equals(this.clock.now, 1400);
+        });
+
+        it("replaces global setTimeout", function () {
+            this.clock = lolex.install();
+            var stub = sinon.stub();
+
+            setTimeout(stub, 1000);
+            this.clock.tick(1000);
+
+            assert(stub.called);
+        });
+
+        it("global fake setTimeout should return id", function () {
+            this.clock = lolex.install();
+            var stub = sinon.stub();
+
+            var to = setTimeout(stub, 1000);
+
+            if (typeof (setTimeout(NOOP, 0)) === "object") {
+                assert.isNumber(to.id);
+                assert.isFunction(to.ref);
+                assert.isFunction(to.unref);
+            } else {
+                assert.isNumber(to);
+            }
+        });
+
+        it("replaces global clearTimeout", function () {
+            this.clock = lolex.install();
+            var stub = sinon.stub();
+
+            clearTimeout(setTimeout(stub, 1000));
+            this.clock.tick(1000);
+
+            assert.isFalse(stub.called);
+        });
+
+        it("uninstalls global setTimeout", function () {
+            this.clock = lolex.install();
+            var stub = sinon.stub();
+            this.clock.uninstall();
+
+            this.timer = setTimeout(stub, 1000);
+            this.clock.tick(1000);
+
+            assert.isFalse(stub.called);
+            assert.same(setTimeout, lolex.timers.setTimeout);
+        });
+
+        it("uninstalls global clearTimeout", function () {
+            this.clock = lolex.install();
+            sinon.stub();
+            this.clock.uninstall();
+
+            assert.same(clearTimeout, lolex.timers.clearTimeout);
+        });
+
+        it("replaces global setInterval", function () {
+            this.clock = lolex.install();
+            var stub = sinon.stub();
+
+            setInterval(stub, 500);
+            this.clock.tick(1000);
+
+            assert(stub.calledTwice);
+        });
+
+        it("replaces global clearInterval", function () {
+            this.clock = lolex.install();
+            var stub = sinon.stub();
+
+            clearInterval(setInterval(stub, 500));
+            this.clock.tick(1000);
+
+            assert.isFalse(stub.called);
+        });
+
+        it("uninstalls global setInterval", function () {
+            this.clock = lolex.install();
+            var stub = sinon.stub();
+            this.clock.uninstall();
+
+            this.timer = setInterval(stub, 1000);
+            this.clock.tick(1000);
+
+            assert.isFalse(stub.called);
+            assert.same(setInterval, lolex.timers.setInterval);
+        });
+
+        it("uninstalls global clearInterval", function () {
+            this.clock = lolex.install();
+            sinon.stub();
+            this.clock.uninstall();
+
+            assert.same(clearInterval, lolex.timers.clearInterval);
+        });
+
+        if (hrtimePresent) {
+            it("replaces global process.hrtime", function () {
+                this.clock = lolex.install();
+                var prev = process.hrtime();
+                this.clock.tick(1000);
+                var result = process.hrtime(prev);
+                assert.same(result[0], 1);
+                assert.same(result[1], 0);
+            });
+
+            it("uninstalls global process.hrtime", function () {
+                this.clock = lolex.install();
+                this.clock.uninstall();
+                assert.same(process.hrtime, lolex.timers.hrtime);
+                var prev = process.hrtime();
+                this.clock.tick(1000);
+                var result = process.hrtime(prev);
+                assert.same(result[0], 0);
+            });
+        }
+
+        if (Object.getPrototypeOf(global)) {
+            delete global.hasOwnPropertyTest;
+            Object.getPrototypeOf(global).hasOwnPropertyTest = function () {};
+
+            if (!global.hasOwnProperty("hasOwnPropertyTest")) {
+                it("deletes global property on uninstall if it was inherited onto the global object", function () {
+                    // Give the global object an inherited 'tick' method
+                    delete global.tick;
+                    Object.getPrototypeOf(global).tick = function () { };
+
+                    this.clock = lolex.install(0, ["tick"]);
+                    assert.isTrue(global.hasOwnProperty("tick"));
+                    this.clock.uninstall();
+
+                    assert.isFalse(global.hasOwnProperty("tick"));
+                    delete Object.getPrototypeOf(global).tick;
+                });
+            }
+
+            delete Object.getPrototypeOf(global).hasOwnPropertyTest;
+        }
+
+        it("uninstalls global property on uninstall if it is present on the global object itself", function () {
+            // Directly give the global object a tick method
+            global.tick = NOOP;
+
+            this.clock = lolex.install(0, ["tick"]);
+            assert.isTrue(global.hasOwnProperty("tick"));
+            this.clock.uninstall();
+
+            assert.isTrue(global.hasOwnProperty("tick"));
+            delete global.tick;
+        });
+
+        it("fakes Date constructor", function () {
+            this.clock = lolex.install(0);
+            var now = new Date();
+
+            refute.same(Date, lolex.timers.Date);
+            assert.equals(now.getTime(), 0);
+        });
+
+        it("fake Date constructor should mirror Date's properties", function () {
+            this.clock = lolex.install(0);
+
+            assert(!!Date.parse);
+            assert(!!Date.UTC);
+        });
+
+        it("decide on Date.now support at call-time when supported", function () {
+            global.Date.now = NOOP;
+            this.clock = lolex.install(0);
+
+            assert.equals(typeof Date.now, "function");
+        });
+
+        it("decide on Date.now support at call-time when unsupported", function () {
+            global.Date.now = undefined;
+            this.clock = lolex.install(0);
+
+            refute.defined(Date.now);
+        });
+
+        it("mirrors custom Date properties", function () {
+            var f = function () { return ""; };
+            global.Date.format = f;
+            this.clock = lolex.install();
+
+            assert.equals(Date.format, f);
+        });
+
+        it("uninstalls Date constructor", function () {
+            this.clock = lolex.install(0);
+            this.clock.uninstall();
+
+            assert.same(GlobalDate, lolex.timers.Date);
+        });
+
+        it("fakes provided methods", function () {
+            this.clock = lolex.install(0, ["setTimeout", "Date", "setImmediate"]);
+
+            refute.same(setTimeout, lolex.timers.setTimeout);
+            refute.same(Date, lolex.timers.Date);
+        });
+
+        it("resets faked methods", function () {
+            this.clock = lolex.install(0, ["setTimeout", "Date", "setImmediate"]);
+            this.clock.uninstall();
+
+            assert.same(setTimeout, lolex.timers.setTimeout);
+            assert.same(Date, lolex.timers.Date);
+        });
+
+        it("does not fake methods not provided", function () {
+            this.clock = lolex.install(0, ["setTimeout", "Date", "setImmediate"]);
+
+            assert.same(clearTimeout, lolex.timers.clearTimeout);
+            assert.same(setInterval, lolex.timers.setInterval);
+            assert.same(clearInterval, lolex.timers.clearInterval);
+        });
+
+        it("does not be able to use date object for now", function () {
+            assert.exception(function () {
+                lolex.install(new Date(2011, 9, 1));
+            });
+        });
+    });
+
+    if (hrtimePresent) {
+        describe("process.hrtime()", function () {
+
+            it("should start at 0", function () {
+                var clock = lolex.createClock(1001);
+                var result = clock.hrtime();
+                assert.same(result[0], 0);
+                assert.same(result[1], 0);
+            });
+
+            it("should run along with clock.tick", function () {
+                var clock = lolex.createClock(0);
+                clock.tick(5001);
+                var prev = clock.hrtime();
+                clock.tick(5001);
+                var result = clock.hrtime(prev);
+                assert.same(result[0], 5);
+                assert.same(result[1], 1000000);
+            });
+
+            it("should run along with clock.tick when timers set", function () {
+                var clock = lolex.createClock(0);
+                var prev = clock.hrtime();
+                clock.setTimeout(function () {
+                    var result = clock.hrtime(prev);
+                    assert.same(result[0], 2);
+                    assert.same(result[1], 500000000);
+                }, 2500);
+                clock.tick(5000);
+            });
+
+            it("should not move with setSystemTime", function () {
+                var clock = lolex.createClock(0);
+                var prev = clock.hrtime();
+                clock.setSystemTime(50000);
+                var result = clock.hrtime(prev);
+                assert.same(result[0], 0);
+                assert.same(result[1], 0);
+            });
+        });
+    }
+});

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-lolex.git



More information about the Pkg-javascript-commits mailing list