hyper-spec 0.1.2 → 0.99.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.gitignore +3 -0
- data/.travis.yml +26 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +222 -193
- data/README.md +3 -1
- data/Rakefile +6 -0
- data/hyper-spec.gemspec +45 -57
- data/lib/bin/firebug-2.0.19-fx.xpi +0 -0
- data/lib/hyper-spec.rb +57 -38
- data/lib/hyper-spec/component_test_helpers.rb +91 -37
- data/lib/hyper-spec/time_cop.rb +152 -2
- data/lib/hyper-spec/version.rb +1 -1
- data/lib/hyper-spec/wait_for_ajax.rb +23 -8
- data/lib/react/top_level_rails_component.rb +37 -47
- data/lib/selenium/web_driver/firefox/profile.rb +3 -3
- data/lib/sources/lolex.js +733 -0
- metadata +136 -87
- data/.rubocop.yml +0 -107
- data/CODE_OF_CONDUCT.md +0 -49
- data/LICENSE.txt +0 -21
- data/lib/hyper-spec/component_helpers.rb +0 -368
- data/lib/hyper-spec/lolex.rb +0 -66
- data/lib/hyper-spec/rails/engine.rb +0 -8
- data/lib/react/isomorphic_helpers.rb +0 -7
- data/vendor/assets/javascripts/lolex.js +0 -658
- data/vendor/assets/javascripts/time_cop.rb +0 -190
data/lib/hyper-spec/time_cop.rb
CHANGED
@@ -1,6 +1,156 @@
|
|
1
|
-
|
1
|
+
if RUBY_ENGINE == 'opal'
|
2
|
+
# Wrap the Lolex js package
|
3
|
+
class Lolex
|
4
|
+
class << self
|
2
5
|
|
3
|
-
|
6
|
+
# to avoid forcing us to require time we make our own Time.parse method
|
7
|
+
# copied from opal.rb master branch
|
8
|
+
|
9
|
+
def parse_time(str)
|
10
|
+
`new Date(Date.parse(str))`
|
11
|
+
end
|
12
|
+
|
13
|
+
def stack
|
14
|
+
@stack ||= []
|
15
|
+
end
|
16
|
+
|
17
|
+
def push(time, scale = 1, resolution = 10)
|
18
|
+
time = parse_time(time) if time.is_a? String
|
19
|
+
stack << [Time.now, @scale, @resolution]
|
20
|
+
update_lolex(time, scale, resolution)
|
21
|
+
end
|
22
|
+
|
23
|
+
def pop
|
24
|
+
update_lolex(*stack.pop) unless stack.empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
def unmock(time, resolution)
|
28
|
+
push(time, 1, resolution)
|
29
|
+
@backup_stack = stack
|
30
|
+
@stack = []
|
31
|
+
end
|
32
|
+
|
33
|
+
def restore
|
34
|
+
@stack = @backup_stack
|
35
|
+
pop
|
36
|
+
end
|
37
|
+
|
38
|
+
def tick
|
39
|
+
real_clock = `(new #{@lolex}['_Date']).getTime()`
|
40
|
+
mock_clock = Time.now.to_f * 1000
|
41
|
+
real_elapsed_time = real_clock - @real_start_time
|
42
|
+
mock_elapsed_time = mock_clock - @mock_start_time
|
43
|
+
|
44
|
+
ticks = real_elapsed_time * @scale - mock_elapsed_time
|
45
|
+
|
46
|
+
`#{@lolex}.tick(#{ticks.to_i})`
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def create_ticker
|
51
|
+
return unless @scale && @scale > 0
|
52
|
+
ticker = %x{
|
53
|
+
#{@lolex}['_setInterval'].call(
|
54
|
+
window,
|
55
|
+
function() { #{tick} },
|
56
|
+
#{@resolution}
|
57
|
+
)
|
58
|
+
}
|
59
|
+
ticker
|
60
|
+
end
|
61
|
+
|
62
|
+
def update_lolex(time, scale, resolution)
|
63
|
+
`#{@lolex}.uninstall()` && return if scale.nil?
|
64
|
+
@mock_start_time = time.to_f * 1000
|
65
|
+
|
66
|
+
if @lolex
|
67
|
+
`#{@lolex}['_clearInterval'].call(window, #{@ticker})` if @ticker
|
68
|
+
@real_start_time = `(new #{@lolex}['_Date']).getTime()`
|
69
|
+
`#{@lolex}.tick(#{@mock_start_time - Time.now.to_f * 1000})`
|
70
|
+
else
|
71
|
+
@real_start_time = Time.now.to_f * 1000
|
72
|
+
@lolex = `lolex.install({ now: #{@mock_start_time} })`
|
73
|
+
end
|
74
|
+
|
75
|
+
@scale = scale
|
76
|
+
@resolution = resolution
|
77
|
+
@ticker = create_ticker
|
78
|
+
nil # must return nil otherwise we try to return a timer to server!
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
else
|
84
|
+
require 'timecop'
|
85
|
+
|
86
|
+
# Interface to the Lolex package running on the client side
|
87
|
+
# Below we will monkey patch Timecop to call these methods
|
88
|
+
class Lolex
|
89
|
+
class << self
|
90
|
+
def init(page, client_time_zone, resolution)
|
91
|
+
@capybara_page = page
|
92
|
+
@resolution = resolution || 10
|
93
|
+
@client_time_zone = client_time_zone
|
94
|
+
run_pending_evaluations
|
95
|
+
@initialized = true
|
96
|
+
end
|
97
|
+
|
98
|
+
def initialized?
|
99
|
+
@initialized
|
100
|
+
end
|
101
|
+
|
102
|
+
def push(mock_type, *args)
|
103
|
+
scale = if mock_type == :freeze
|
104
|
+
0
|
105
|
+
elsif mock_type == :scale
|
106
|
+
args[0]
|
107
|
+
else
|
108
|
+
1
|
109
|
+
end
|
110
|
+
evaluate_ruby do
|
111
|
+
"Lolex.push('#{time_string_in_zone}', #{scale}, #{@resolution})"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def pop
|
116
|
+
evaluate_ruby { 'Lolex.pop' }
|
117
|
+
end
|
118
|
+
|
119
|
+
def unmock
|
120
|
+
evaluate_ruby { "Lolex.unmock('#{time_string_in_zone}', #{@resolution})" }
|
121
|
+
end
|
122
|
+
|
123
|
+
def restore
|
124
|
+
evaluate_ruby { 'Lolex.restore' }
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def time_string_in_zone
|
130
|
+
Time.now.in_time_zone(@client_time_zone).strftime('%Y/%m/%d %H:%M:%S %z')
|
131
|
+
end
|
132
|
+
|
133
|
+
def pending_evaluations
|
134
|
+
@pending_evaluations ||= []
|
135
|
+
end
|
136
|
+
|
137
|
+
def evaluate_ruby(&block)
|
138
|
+
if @capybara_page
|
139
|
+
@capybara_page.evaluate_ruby(yield)
|
140
|
+
else
|
141
|
+
pending_evaluations << block
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def run_pending_evaluations
|
146
|
+
return if pending_evaluations.empty?
|
147
|
+
@capybara_page.evaluate_ruby(pending_evaluations.collect(&:call).join("\n"))
|
148
|
+
@pending_evaluations ||= []
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Monkey patches to call our Lolex interface
|
4
154
|
class Timecop
|
5
155
|
private
|
6
156
|
|
data/lib/hyper-spec/version.rb
CHANGED
@@ -10,21 +10,36 @@ module HyperSpec
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def running?
|
13
|
-
|
14
|
-
|
13
|
+
jscode = <<-CODE
|
14
|
+
(function() {
|
15
|
+
if (typeof Opal !== "undefined" && Opal.Hyperloop !== undefined) {
|
16
|
+
try {
|
17
|
+
return Opal.Hyperloop.$const_get("HTTP")["$active?"]();
|
18
|
+
} catch(err) {
|
19
|
+
if (typeof jQuery !== "undefined" && jQuery.active !== undefined) {
|
20
|
+
return (jQuery.active > 0);
|
21
|
+
} else {
|
22
|
+
return false;
|
23
|
+
}
|
24
|
+
}
|
25
|
+
} else if (typeof jQuery !== "undefined" && jQuery.active !== undefined) {
|
26
|
+
return (jQuery.active > 0);
|
27
|
+
} else {
|
28
|
+
return false;
|
29
|
+
}
|
30
|
+
})();
|
31
|
+
CODE
|
32
|
+
page.evaluate_script(jscode)
|
15
33
|
rescue Exception => e
|
16
|
-
puts "wait_for_ajax failed while testing state of
|
34
|
+
puts "wait_for_ajax failed while testing state of ajax requests: #{e}"
|
17
35
|
end
|
18
36
|
|
19
37
|
def finished_all_ajax_requests?
|
20
|
-
|
21
|
-
sleep 0.25 # this was 1 second, not sure if its necessary to be so long...
|
22
|
-
!running?
|
23
|
-
end
|
38
|
+
!running?
|
24
39
|
rescue Capybara::NotSupportedByDriverError
|
25
40
|
true
|
26
41
|
rescue Exception => e
|
27
|
-
e.message == 'jQuery is not defined'
|
42
|
+
e.message == 'either jQuery or Hyperloop::HTTP is not defined'
|
28
43
|
end
|
29
44
|
end
|
30
45
|
end
|
@@ -31,57 +31,47 @@ module React
|
|
31
31
|
def component
|
32
32
|
return @component if @component
|
33
33
|
paths_searched = []
|
34
|
-
|
34
|
+
component = nil
|
35
35
|
if params.component_name.start_with?('::')
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
nil
|
44
|
-
end
|
45
|
-
|
46
|
-
return @component if @component && @component.method_defined?(:render)
|
36
|
+
# if absolute path of component is given, look it up and fail if not found
|
37
|
+
paths_searched << params.component_name
|
38
|
+
component = begin
|
39
|
+
Object.const_get(params.component_name)
|
40
|
+
rescue NameError
|
41
|
+
nil
|
42
|
+
end
|
47
43
|
else
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
44
|
+
# if relative path is given, look it up like this
|
45
|
+
# 1) we check each path + controller-name + component-name
|
46
|
+
# 2) if we can't find it there we check each path + component-name
|
47
|
+
# if we can't find it we just try const_get
|
48
|
+
# so (assuming controller name is Home)
|
49
|
+
# ::Foo::Bar will only resolve to some component named ::Foo::Bar
|
50
|
+
# but Foo::Bar will check (in this order) ::Home::Foo::Bar, ::Components::Home::Foo::Bar, ::Foo::Bar, ::Components::Foo::Bar
|
51
|
+
self.class.search_path.each do |scope|
|
52
|
+
paths_searched << "#{scope.name}::#{params.controller}::#{params.component_name}"
|
53
|
+
component = begin
|
54
|
+
scope.const_get(params.controller, false).const_get(params.component_name, false)
|
55
|
+
rescue NameError
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
break if component != nil
|
63
59
|
end
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
75
|
-
|
76
|
-
return @component if @component && @component.method_defined?(:render)
|
60
|
+
unless component
|
61
|
+
self.class.search_path.each do |scope|
|
62
|
+
paths_searched << "#{scope.name}::#{params.component_name}"
|
63
|
+
component = begin
|
64
|
+
scope.const_get(params.component_name, false)
|
65
|
+
rescue NameError
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
break if component != nil
|
69
|
+
end
|
77
70
|
end
|
78
71
|
end
|
79
|
-
|
80
|
-
@component
|
81
|
-
|
82
|
-
raise "Could not find component class '#{params.component_name}' "\
|
83
|
-
"for params.controller '#{params.controller}' in any component directory. "\
|
84
|
-
"Tried [#{paths_searched.join(', ')}]"
|
72
|
+
@component = component
|
73
|
+
return @component if @component && @component.method_defined?(:render)
|
74
|
+
raise "Could not find component class '#{params.component_name}' for params.controller '#{params.controller}' in any component directory. Tried [#{paths_searched.join(", ")}]"
|
85
75
|
end
|
86
76
|
|
87
77
|
before_mount do
|
@@ -98,7 +88,7 @@ module React
|
|
98
88
|
end
|
99
89
|
|
100
90
|
def render
|
101
|
-
|
91
|
+
React::RenderingContext.render(component, @render_params)
|
102
92
|
end
|
103
93
|
end
|
104
94
|
end
|
@@ -6,7 +6,7 @@ module Selenium
|
|
6
6
|
attr_accessor :firebug_version
|
7
7
|
|
8
8
|
def firebug_version
|
9
|
-
@firebug_version ||= '2.0.
|
9
|
+
@firebug_version ||= '2.0.19-fx'
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
@@ -15,7 +15,7 @@ module Selenium
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def frame_position=(position)
|
18
|
-
@frame_position = %w
|
18
|
+
@frame_position = %w[left right top detached].detect do |side|
|
19
19
|
position && position[0].downcase == side[0]
|
20
20
|
end || 'detached'
|
21
21
|
end
|
@@ -36,7 +36,7 @@ module Selenium
|
|
36
36
|
self['extensions.firebug.allPagesActivation'] = 'on'
|
37
37
|
|
38
38
|
# Enable all features.
|
39
|
-
%w
|
39
|
+
%w[console net script].each do |feature|
|
40
40
|
self["extensions.firebug.#{feature}.enableSites"] = true
|
41
41
|
end
|
42
42
|
|
@@ -0,0 +1,733 @@
|
|
1
|
+
(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)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
2
|
+
(function (global){
|
3
|
+
"use strict";
|
4
|
+
|
5
|
+
var userAgent = global.navigator && global.navigator.userAgent;
|
6
|
+
var isRunningInIE = userAgent && userAgent.indexOf("MSIE ") > -1;
|
7
|
+
var maxTimeout = Math.pow(2, 31) - 1; //see https://heycam.github.io/webidl/#abstract-opdef-converttoint
|
8
|
+
|
9
|
+
// Make properties writable in IE, as per
|
10
|
+
// http://www.adequatelygood.com/Replacing-setTimeout-Globally.html
|
11
|
+
if (isRunningInIE) {
|
12
|
+
global.setTimeout = global.setTimeout;
|
13
|
+
global.clearTimeout = global.clearTimeout;
|
14
|
+
global.setInterval = global.setInterval;
|
15
|
+
global.clearInterval = global.clearInterval;
|
16
|
+
global.Date = global.Date;
|
17
|
+
}
|
18
|
+
|
19
|
+
// setImmediate is not a standard function
|
20
|
+
// avoid adding the prop to the window object if not present
|
21
|
+
if (global.setImmediate !== undefined) {
|
22
|
+
global.setImmediate = global.setImmediate;
|
23
|
+
global.clearImmediate = global.clearImmediate;
|
24
|
+
}
|
25
|
+
|
26
|
+
// node expects setTimeout/setInterval to return a fn object w/ .ref()/.unref()
|
27
|
+
// browsers, a number.
|
28
|
+
// see https://github.com/cjohansen/Sinon.JS/pull/436
|
29
|
+
|
30
|
+
var NOOP = function () { return undefined; };
|
31
|
+
var timeoutResult = setTimeout(NOOP, 0);
|
32
|
+
var addTimerReturnsObject = typeof timeoutResult === "object";
|
33
|
+
var hrtimePresent = (global.process && typeof global.process.hrtime === "function");
|
34
|
+
var nextTickPresent = (global.process && typeof global.process.nextTick === "function");
|
35
|
+
var performancePresent = (global.performance && typeof global.performance.now === "function");
|
36
|
+
|
37
|
+
clearTimeout(timeoutResult);
|
38
|
+
|
39
|
+
var NativeDate = Date;
|
40
|
+
var uniqueTimerId = 1;
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Parse strings like "01:10:00" (meaning 1 hour, 10 minutes, 0 seconds) into
|
44
|
+
* number of milliseconds. This is used to support human-readable strings passed
|
45
|
+
* to clock.tick()
|
46
|
+
*/
|
47
|
+
function parseTime(str) {
|
48
|
+
if (!str) {
|
49
|
+
return 0;
|
50
|
+
}
|
51
|
+
|
52
|
+
var strings = str.split(":");
|
53
|
+
var l = strings.length;
|
54
|
+
var i = l;
|
55
|
+
var ms = 0;
|
56
|
+
var parsed;
|
57
|
+
|
58
|
+
if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) {
|
59
|
+
throw new Error("tick only understands numbers, 'm:s' and 'h:m:s'. Each part must be two digits");
|
60
|
+
}
|
61
|
+
|
62
|
+
while (i--) {
|
63
|
+
parsed = parseInt(strings[i], 10);
|
64
|
+
|
65
|
+
if (parsed >= 60) {
|
66
|
+
throw new Error("Invalid time " + str);
|
67
|
+
}
|
68
|
+
|
69
|
+
ms += parsed * Math.pow(60, (l - i - 1));
|
70
|
+
}
|
71
|
+
|
72
|
+
return ms * 1000;
|
73
|
+
}
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Floor function that also works for negative numbers
|
77
|
+
*/
|
78
|
+
function fixedFloor(n) {
|
79
|
+
return (n >= 0 ? Math.floor(n) : Math.ceil(n));
|
80
|
+
}
|
81
|
+
|
82
|
+
/**
|
83
|
+
* % operator that also works for negative numbers
|
84
|
+
*/
|
85
|
+
function fixedModulo(n, m) {
|
86
|
+
return ((n % m) + m) % m;
|
87
|
+
}
|
88
|
+
|
89
|
+
/**
|
90
|
+
* Used to grok the `now` parameter to createClock.
|
91
|
+
* @param epoch {Date|number} the system time
|
92
|
+
*/
|
93
|
+
function getEpoch(epoch) {
|
94
|
+
if (!epoch) { return 0; }
|
95
|
+
if (typeof epoch.getTime === "function") { return epoch.getTime(); }
|
96
|
+
if (typeof epoch === "number") { return epoch; }
|
97
|
+
throw new TypeError("now should be milliseconds since UNIX epoch");
|
98
|
+
}
|
99
|
+
|
100
|
+
function inRange(from, to, timer) {
|
101
|
+
return timer && timer.callAt >= from && timer.callAt <= to;
|
102
|
+
}
|
103
|
+
|
104
|
+
function mirrorDateProperties(target, source) {
|
105
|
+
var prop;
|
106
|
+
for (prop in source) {
|
107
|
+
if (source.hasOwnProperty(prop)) {
|
108
|
+
target[prop] = source[prop];
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
// set special now implementation
|
113
|
+
if (source.now) {
|
114
|
+
target.now = function now() {
|
115
|
+
return target.clock.now;
|
116
|
+
};
|
117
|
+
} else {
|
118
|
+
delete target.now;
|
119
|
+
}
|
120
|
+
|
121
|
+
// set special toSource implementation
|
122
|
+
if (source.toSource) {
|
123
|
+
target.toSource = function toSource() {
|
124
|
+
return source.toSource();
|
125
|
+
};
|
126
|
+
} else {
|
127
|
+
delete target.toSource;
|
128
|
+
}
|
129
|
+
|
130
|
+
// set special toString implementation
|
131
|
+
target.toString = function toString() {
|
132
|
+
return source.toString();
|
133
|
+
};
|
134
|
+
|
135
|
+
target.prototype = source.prototype;
|
136
|
+
target.parse = source.parse;
|
137
|
+
target.UTC = source.UTC;
|
138
|
+
target.prototype.toUTCString = source.prototype.toUTCString;
|
139
|
+
|
140
|
+
return target;
|
141
|
+
}
|
142
|
+
|
143
|
+
function createDate() {
|
144
|
+
function ClockDate(year, month, date, hour, minute, second, ms) {
|
145
|
+
// Defensive and verbose to avoid potential harm in passing
|
146
|
+
// explicit undefined when user does not pass argument
|
147
|
+
switch (arguments.length) {
|
148
|
+
case 0:
|
149
|
+
return new NativeDate(ClockDate.clock.now);
|
150
|
+
case 1:
|
151
|
+
return new NativeDate(year);
|
152
|
+
case 2:
|
153
|
+
return new NativeDate(year, month);
|
154
|
+
case 3:
|
155
|
+
return new NativeDate(year, month, date);
|
156
|
+
case 4:
|
157
|
+
return new NativeDate(year, month, date, hour);
|
158
|
+
case 5:
|
159
|
+
return new NativeDate(year, month, date, hour, minute);
|
160
|
+
case 6:
|
161
|
+
return new NativeDate(year, month, date, hour, minute, second);
|
162
|
+
default:
|
163
|
+
return new NativeDate(year, month, date, hour, minute, second, ms);
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
return mirrorDateProperties(ClockDate, NativeDate);
|
168
|
+
}
|
169
|
+
|
170
|
+
|
171
|
+
function enqueueJob(clock, job) {
|
172
|
+
// enqueues a microtick-deferred task - ecma262/#sec-enqueuejob
|
173
|
+
if (!clock.jobs) {
|
174
|
+
clock.jobs = [];
|
175
|
+
}
|
176
|
+
clock.jobs.push(job);
|
177
|
+
}
|
178
|
+
|
179
|
+
function runJobs(clock) {
|
180
|
+
// runs all microtick-deferred tasks - ecma262/#sec-runjobs
|
181
|
+
if (!clock.jobs) {
|
182
|
+
return;
|
183
|
+
}
|
184
|
+
for (var i = 0; i < clock.jobs.length; i++) {
|
185
|
+
var job = clock.jobs[i];
|
186
|
+
job.func.apply(null, job.args);
|
187
|
+
}
|
188
|
+
clock.jobs = [];
|
189
|
+
}
|
190
|
+
|
191
|
+
function addTimer(clock, timer) {
|
192
|
+
if (timer.func === undefined) {
|
193
|
+
throw new Error("Callback must be provided to timer calls");
|
194
|
+
}
|
195
|
+
|
196
|
+
if (timer.hasOwnProperty("delay")) {
|
197
|
+
timer.delay = timer.delay > maxTimeout ? 1 : timer.delay;
|
198
|
+
}
|
199
|
+
|
200
|
+
if (timer.hasOwnProperty("interval")) {
|
201
|
+
timer.interval = timer.interval > maxTimeout ? 1 : timer.interval;
|
202
|
+
}
|
203
|
+
|
204
|
+
if (!clock.timers) {
|
205
|
+
clock.timers = {};
|
206
|
+
}
|
207
|
+
|
208
|
+
timer.id = uniqueTimerId++;
|
209
|
+
timer.createdAt = clock.now;
|
210
|
+
timer.callAt = clock.now + (parseInt(timer.delay) || (clock.duringTick ? 1 : 0));
|
211
|
+
|
212
|
+
clock.timers[timer.id] = timer;
|
213
|
+
|
214
|
+
if (addTimerReturnsObject) {
|
215
|
+
return {
|
216
|
+
id: timer.id,
|
217
|
+
ref: NOOP,
|
218
|
+
unref: NOOP
|
219
|
+
};
|
220
|
+
}
|
221
|
+
|
222
|
+
return timer.id;
|
223
|
+
}
|
224
|
+
|
225
|
+
|
226
|
+
/* eslint consistent-return: "off" */
|
227
|
+
function compareTimers(a, b) {
|
228
|
+
// Sort first by absolute timing
|
229
|
+
if (a.callAt < b.callAt) {
|
230
|
+
return -1;
|
231
|
+
}
|
232
|
+
if (a.callAt > b.callAt) {
|
233
|
+
return 1;
|
234
|
+
}
|
235
|
+
|
236
|
+
// Sort next by immediate, immediate timers take precedence
|
237
|
+
if (a.immediate && !b.immediate) {
|
238
|
+
return -1;
|
239
|
+
}
|
240
|
+
if (!a.immediate && b.immediate) {
|
241
|
+
return 1;
|
242
|
+
}
|
243
|
+
|
244
|
+
// Sort next by creation time, earlier-created timers take precedence
|
245
|
+
if (a.createdAt < b.createdAt) {
|
246
|
+
return -1;
|
247
|
+
}
|
248
|
+
if (a.createdAt > b.createdAt) {
|
249
|
+
return 1;
|
250
|
+
}
|
251
|
+
|
252
|
+
// Sort next by id, lower-id timers take precedence
|
253
|
+
if (a.id < b.id) {
|
254
|
+
return -1;
|
255
|
+
}
|
256
|
+
if (a.id > b.id) {
|
257
|
+
return 1;
|
258
|
+
}
|
259
|
+
|
260
|
+
// As timer ids are unique, no fallback `0` is necessary
|
261
|
+
}
|
262
|
+
|
263
|
+
function firstTimerInRange(clock, from, to) {
|
264
|
+
var timers = clock.timers;
|
265
|
+
var timer = null;
|
266
|
+
var id, isInRange;
|
267
|
+
|
268
|
+
for (id in timers) {
|
269
|
+
if (timers.hasOwnProperty(id)) {
|
270
|
+
isInRange = inRange(from, to, timers[id]);
|
271
|
+
|
272
|
+
if (isInRange && (!timer || compareTimers(timer, timers[id]) === 1)) {
|
273
|
+
timer = timers[id];
|
274
|
+
}
|
275
|
+
}
|
276
|
+
}
|
277
|
+
|
278
|
+
return timer;
|
279
|
+
}
|
280
|
+
|
281
|
+
function firstTimer(clock) {
|
282
|
+
var timers = clock.timers;
|
283
|
+
var timer = null;
|
284
|
+
var id;
|
285
|
+
|
286
|
+
for (id in timers) {
|
287
|
+
if (timers.hasOwnProperty(id)) {
|
288
|
+
if (!timer || compareTimers(timer, timers[id]) === 1) {
|
289
|
+
timer = timers[id];
|
290
|
+
}
|
291
|
+
}
|
292
|
+
}
|
293
|
+
|
294
|
+
return timer;
|
295
|
+
}
|
296
|
+
|
297
|
+
function lastTimer(clock) {
|
298
|
+
var timers = clock.timers;
|
299
|
+
var timer = null;
|
300
|
+
var id;
|
301
|
+
|
302
|
+
for (id in timers) {
|
303
|
+
if (timers.hasOwnProperty(id)) {
|
304
|
+
if (!timer || compareTimers(timer, timers[id]) === -1) {
|
305
|
+
timer = timers[id];
|
306
|
+
}
|
307
|
+
}
|
308
|
+
}
|
309
|
+
|
310
|
+
return timer;
|
311
|
+
}
|
312
|
+
|
313
|
+
function callTimer(clock, timer) {
|
314
|
+
if (typeof timer.interval === "number") {
|
315
|
+
clock.timers[timer.id].callAt += timer.interval;
|
316
|
+
} else {
|
317
|
+
delete clock.timers[timer.id];
|
318
|
+
}
|
319
|
+
|
320
|
+
if (typeof timer.func === "function") {
|
321
|
+
timer.func.apply(null, timer.args);
|
322
|
+
} else {
|
323
|
+
/* eslint no-eval: "off" */
|
324
|
+
eval(timer.func);
|
325
|
+
}
|
326
|
+
}
|
327
|
+
|
328
|
+
function timerType(timer) {
|
329
|
+
if (timer.immediate) {
|
330
|
+
return "Immediate";
|
331
|
+
}
|
332
|
+
if (timer.interval !== undefined) {
|
333
|
+
return "Interval";
|
334
|
+
}
|
335
|
+
return "Timeout";
|
336
|
+
}
|
337
|
+
|
338
|
+
function clearTimer(clock, timerId, ttype) {
|
339
|
+
if (!timerId) {
|
340
|
+
// null appears to be allowed in most browsers, and appears to be
|
341
|
+
// relied upon by some libraries, like Bootstrap carousel
|
342
|
+
return;
|
343
|
+
}
|
344
|
+
|
345
|
+
if (!clock.timers) {
|
346
|
+
clock.timers = [];
|
347
|
+
}
|
348
|
+
|
349
|
+
// in Node, timerId is an object with .ref()/.unref(), and
|
350
|
+
// its .id field is the actual timer id.
|
351
|
+
if (typeof timerId === "object") {
|
352
|
+
timerId = timerId.id;
|
353
|
+
}
|
354
|
+
|
355
|
+
if (clock.timers.hasOwnProperty(timerId)) {
|
356
|
+
// check that the ID matches a timer of the correct type
|
357
|
+
var timer = clock.timers[timerId];
|
358
|
+
if (timerType(timer) === ttype) {
|
359
|
+
delete clock.timers[timerId];
|
360
|
+
} else {
|
361
|
+
throw new Error("Cannot clear timer: timer created with set" + timerType(timer)
|
362
|
+
+ "() but cleared with clear" + ttype + "()");
|
363
|
+
}
|
364
|
+
}
|
365
|
+
}
|
366
|
+
|
367
|
+
function uninstall(clock, target, config) {
|
368
|
+
var method,
|
369
|
+
i,
|
370
|
+
l;
|
371
|
+
var installedHrTime = "_hrtime";
|
372
|
+
var installedNextTick = "_nextTick";
|
373
|
+
|
374
|
+
for (i = 0, l = clock.methods.length; i < l; i++) {
|
375
|
+
method = clock.methods[i];
|
376
|
+
if (method === "hrtime" && target.process) {
|
377
|
+
target.process.hrtime = clock[installedHrTime];
|
378
|
+
} else if (method === "nextTick" && target.process) {
|
379
|
+
target.process.nextTick = clock[installedNextTick];
|
380
|
+
} else {
|
381
|
+
if (target[method] && target[method].hadOwnProperty) {
|
382
|
+
target[method] = clock["_" + method];
|
383
|
+
if (method === "clearInterval" && config.shouldAdvanceTime === true) {
|
384
|
+
target[method](clock.attachedInterval);
|
385
|
+
}
|
386
|
+
} else {
|
387
|
+
try {
|
388
|
+
delete target[method];
|
389
|
+
} catch (ignore) { /* eslint empty-block: "off" */ }
|
390
|
+
}
|
391
|
+
}
|
392
|
+
}
|
393
|
+
|
394
|
+
// Prevent multiple executions which will completely remove these props
|
395
|
+
clock.methods = [];
|
396
|
+
}
|
397
|
+
|
398
|
+
function hijackMethod(target, method, clock) {
|
399
|
+
var prop;
|
400
|
+
clock[method].hadOwnProperty = Object.prototype.hasOwnProperty.call(target, method);
|
401
|
+
clock["_" + method] = target[method];
|
402
|
+
|
403
|
+
if (method === "Date") {
|
404
|
+
var date = mirrorDateProperties(clock[method], target[method]);
|
405
|
+
target[method] = date;
|
406
|
+
} else {
|
407
|
+
target[method] = function () {
|
408
|
+
return clock[method].apply(clock, arguments);
|
409
|
+
};
|
410
|
+
|
411
|
+
for (prop in clock[method]) {
|
412
|
+
if (clock[method].hasOwnProperty(prop)) {
|
413
|
+
target[method][prop] = clock[method][prop];
|
414
|
+
}
|
415
|
+
}
|
416
|
+
}
|
417
|
+
|
418
|
+
target[method].clock = clock;
|
419
|
+
}
|
420
|
+
|
421
|
+
function doIntervalTick(clock, advanceTimeDelta) {
|
422
|
+
clock.tick(advanceTimeDelta);
|
423
|
+
}
|
424
|
+
|
425
|
+
var timers = {
|
426
|
+
setTimeout: setTimeout,
|
427
|
+
clearTimeout: clearTimeout,
|
428
|
+
setImmediate: global.setImmediate,
|
429
|
+
clearImmediate: global.clearImmediate,
|
430
|
+
setInterval: setInterval,
|
431
|
+
clearInterval: clearInterval,
|
432
|
+
Date: Date
|
433
|
+
};
|
434
|
+
|
435
|
+
if (hrtimePresent) {
|
436
|
+
timers.hrtime = global.process.hrtime;
|
437
|
+
}
|
438
|
+
|
439
|
+
if (nextTickPresent) {
|
440
|
+
timers.nextTick = global.process.nextTick;
|
441
|
+
}
|
442
|
+
|
443
|
+
if (performancePresent) {
|
444
|
+
timers.performance = global.performance;
|
445
|
+
}
|
446
|
+
|
447
|
+
var keys = Object.keys || function (obj) {
|
448
|
+
var ks = [];
|
449
|
+
var key;
|
450
|
+
|
451
|
+
for (key in obj) {
|
452
|
+
if (obj.hasOwnProperty(key)) {
|
453
|
+
ks.push(key);
|
454
|
+
}
|
455
|
+
}
|
456
|
+
|
457
|
+
return ks;
|
458
|
+
};
|
459
|
+
|
460
|
+
exports.timers = timers;
|
461
|
+
|
462
|
+
/**
|
463
|
+
* @param now {Date|number} the system time
|
464
|
+
* @param loopLimit {number} maximum number of timers that will be run when calling runAll()
|
465
|
+
*/
|
466
|
+
function createClock(now, loopLimit) {
|
467
|
+
loopLimit = loopLimit || 1000;
|
468
|
+
|
469
|
+
var clock = {
|
470
|
+
now: getEpoch(now),
|
471
|
+
hrNow: 0,
|
472
|
+
timeouts: {},
|
473
|
+
Date: createDate(),
|
474
|
+
loopLimit: loopLimit
|
475
|
+
};
|
476
|
+
|
477
|
+
clock.Date.clock = clock;
|
478
|
+
|
479
|
+
clock.setTimeout = function setTimeout(func, timeout) {
|
480
|
+
return addTimer(clock, {
|
481
|
+
func: func,
|
482
|
+
args: Array.prototype.slice.call(arguments, 2),
|
483
|
+
delay: timeout
|
484
|
+
});
|
485
|
+
};
|
486
|
+
|
487
|
+
clock.clearTimeout = function clearTimeout(timerId) {
|
488
|
+
return clearTimer(clock, timerId, "Timeout");
|
489
|
+
};
|
490
|
+
clock.nextTick = function nextTick(func) {
|
491
|
+
return enqueueJob(clock, {
|
492
|
+
func: func,
|
493
|
+
args: Array.prototype.slice.call(arguments, 1)
|
494
|
+
});
|
495
|
+
};
|
496
|
+
clock.setInterval = function setInterval(func, timeout) {
|
497
|
+
return addTimer(clock, {
|
498
|
+
func: func,
|
499
|
+
args: Array.prototype.slice.call(arguments, 2),
|
500
|
+
delay: timeout,
|
501
|
+
interval: timeout
|
502
|
+
});
|
503
|
+
};
|
504
|
+
|
505
|
+
clock.clearInterval = function clearInterval(timerId) {
|
506
|
+
return clearTimer(clock, timerId, "Interval");
|
507
|
+
};
|
508
|
+
|
509
|
+
clock.setImmediate = function setImmediate(func) {
|
510
|
+
return addTimer(clock, {
|
511
|
+
func: func,
|
512
|
+
args: Array.prototype.slice.call(arguments, 1),
|
513
|
+
immediate: true
|
514
|
+
});
|
515
|
+
};
|
516
|
+
|
517
|
+
clock.clearImmediate = function clearImmediate(timerId) {
|
518
|
+
return clearTimer(clock, timerId, "Immediate");
|
519
|
+
};
|
520
|
+
|
521
|
+
function updateHrTime(newNow) {
|
522
|
+
clock.hrNow += (newNow - clock.now);
|
523
|
+
}
|
524
|
+
|
525
|
+
clock.tick = function tick(ms) {
|
526
|
+
ms = typeof ms === "number" ? ms : parseTime(ms);
|
527
|
+
var tickFrom = clock.now;
|
528
|
+
var tickTo = clock.now + ms;
|
529
|
+
var previous = clock.now;
|
530
|
+
var timer = firstTimerInRange(clock, tickFrom, tickTo);
|
531
|
+
var oldNow, firstException;
|
532
|
+
|
533
|
+
clock.duringTick = true;
|
534
|
+
runJobs(clock);
|
535
|
+
|
536
|
+
while (timer && tickFrom <= tickTo) {
|
537
|
+
if (clock.timers[timer.id]) {
|
538
|
+
updateHrTime(timer.callAt);
|
539
|
+
tickFrom = timer.callAt;
|
540
|
+
clock.now = timer.callAt;
|
541
|
+
try {
|
542
|
+
runJobs(clock);
|
543
|
+
oldNow = clock.now;
|
544
|
+
callTimer(clock, timer);
|
545
|
+
} catch (e) {
|
546
|
+
firstException = firstException || e;
|
547
|
+
}
|
548
|
+
|
549
|
+
// compensate for any setSystemTime() call during timer callback
|
550
|
+
if (oldNow !== clock.now) {
|
551
|
+
tickFrom += clock.now - oldNow;
|
552
|
+
tickTo += clock.now - oldNow;
|
553
|
+
previous += clock.now - oldNow;
|
554
|
+
}
|
555
|
+
}
|
556
|
+
|
557
|
+
timer = firstTimerInRange(clock, previous, tickTo);
|
558
|
+
previous = tickFrom;
|
559
|
+
}
|
560
|
+
|
561
|
+
runJobs(clock);
|
562
|
+
clock.duringTick = false;
|
563
|
+
updateHrTime(tickTo);
|
564
|
+
clock.now = tickTo;
|
565
|
+
|
566
|
+
if (firstException) {
|
567
|
+
throw firstException;
|
568
|
+
}
|
569
|
+
|
570
|
+
return clock.now;
|
571
|
+
};
|
572
|
+
|
573
|
+
clock.next = function next() {
|
574
|
+
runJobs(clock);
|
575
|
+
var timer = firstTimer(clock);
|
576
|
+
if (!timer) {
|
577
|
+
return clock.now;
|
578
|
+
}
|
579
|
+
|
580
|
+
clock.duringTick = true;
|
581
|
+
try {
|
582
|
+
updateHrTime(timer.callAt);
|
583
|
+
clock.now = timer.callAt;
|
584
|
+
callTimer(clock, timer);
|
585
|
+
runJobs(clock);
|
586
|
+
return clock.now;
|
587
|
+
} finally {
|
588
|
+
clock.duringTick = false;
|
589
|
+
}
|
590
|
+
};
|
591
|
+
|
592
|
+
clock.runAll = function runAll() {
|
593
|
+
var numTimers, i;
|
594
|
+
runJobs(clock);
|
595
|
+
for (i = 0; i < clock.loopLimit; i++) {
|
596
|
+
if (!clock.timers) {
|
597
|
+
return clock.now;
|
598
|
+
}
|
599
|
+
|
600
|
+
numTimers = keys(clock.timers).length;
|
601
|
+
if (numTimers === 0) {
|
602
|
+
return clock.now;
|
603
|
+
}
|
604
|
+
|
605
|
+
clock.next();
|
606
|
+
}
|
607
|
+
|
608
|
+
throw new Error("Aborting after running " + clock.loopLimit + " timers, assuming an infinite loop!");
|
609
|
+
};
|
610
|
+
|
611
|
+
clock.runToLast = function runToLast() {
|
612
|
+
var timer = lastTimer(clock);
|
613
|
+
if (!timer) {
|
614
|
+
runJobs(clock);
|
615
|
+
return clock.now;
|
616
|
+
}
|
617
|
+
|
618
|
+
return clock.tick(timer.callAt);
|
619
|
+
};
|
620
|
+
|
621
|
+
clock.reset = function reset() {
|
622
|
+
clock.timers = {};
|
623
|
+
};
|
624
|
+
|
625
|
+
clock.setSystemTime = function setSystemTime(systemTime) {
|
626
|
+
// determine time difference
|
627
|
+
var newNow = getEpoch(systemTime);
|
628
|
+
var difference = newNow - clock.now;
|
629
|
+
var id, timer;
|
630
|
+
|
631
|
+
// update 'system clock'
|
632
|
+
clock.now = newNow;
|
633
|
+
|
634
|
+
// update timers and intervals to keep them stable
|
635
|
+
for (id in clock.timers) {
|
636
|
+
if (clock.timers.hasOwnProperty(id)) {
|
637
|
+
timer = clock.timers[id];
|
638
|
+
timer.createdAt += difference;
|
639
|
+
timer.callAt += difference;
|
640
|
+
}
|
641
|
+
}
|
642
|
+
};
|
643
|
+
|
644
|
+
if (performancePresent) {
|
645
|
+
clock.performance = Object.create(global.performance);
|
646
|
+
clock.performance.now = function lolexNow() {
|
647
|
+
return clock.hrNow;
|
648
|
+
};
|
649
|
+
}
|
650
|
+
if (hrtimePresent) {
|
651
|
+
clock.hrtime = function (prev) {
|
652
|
+
if (Array.isArray(prev)) {
|
653
|
+
var oldSecs = (prev[0] + prev[1] / 1e9);
|
654
|
+
var newSecs = (clock.hrNow / 1000);
|
655
|
+
var difference = (newSecs - oldSecs);
|
656
|
+
var secs = fixedFloor(difference);
|
657
|
+
var nanosecs = fixedModulo(difference * 1e9, 1e9);
|
658
|
+
return [
|
659
|
+
secs,
|
660
|
+
nanosecs
|
661
|
+
];
|
662
|
+
}
|
663
|
+
return [
|
664
|
+
fixedFloor(clock.hrNow / 1000),
|
665
|
+
fixedModulo(clock.hrNow * 1e6, 1e9)
|
666
|
+
];
|
667
|
+
};
|
668
|
+
}
|
669
|
+
|
670
|
+
return clock;
|
671
|
+
}
|
672
|
+
exports.createClock = createClock;
|
673
|
+
|
674
|
+
/**
|
675
|
+
* @param config {Object} optional config
|
676
|
+
* @param config.target {Object} the target to install timers in (default `window`)
|
677
|
+
* @param config.now {number|Date} a number (in milliseconds) or a Date object (default epoch)
|
678
|
+
* @param config.toFake {string[]} names of the methods that should be faked.
|
679
|
+
* @param config.loopLimit {number} the maximum number of timers that will be run when calling runAll()
|
680
|
+
* @param config.shouldAdvanceTime {Boolean} tells lolex to increment mocked time automatically (default false)
|
681
|
+
* @param config.advanceTimeDelta {Number} increment mocked time every <<advanceTimeDelta>> ms (default: 20ms)
|
682
|
+
*/
|
683
|
+
exports.install = function install(config) {
|
684
|
+
if ( arguments.length > 1 || config instanceof Date || Array.isArray(config) || typeof config === "number") {
|
685
|
+
throw new TypeError("lolex.install called with " + String(config) +
|
686
|
+
" lolex 2.0+ requires an object parameter - see https://github.com/sinonjs/lolex");
|
687
|
+
}
|
688
|
+
config = typeof config !== "undefined" ? config : {};
|
689
|
+
config.shouldAdvanceTime = config.shouldAdvanceTime || false;
|
690
|
+
config.advanceTimeDelta = config.advanceTimeDelta || 20;
|
691
|
+
|
692
|
+
var i, l;
|
693
|
+
var target = config.target || global;
|
694
|
+
var clock = createClock(config.now, config.loopLimit);
|
695
|
+
|
696
|
+
clock.uninstall = function () {
|
697
|
+
uninstall(clock, target, config);
|
698
|
+
};
|
699
|
+
|
700
|
+
clock.methods = config.toFake || ["setTimeout", "clearTimeout", "setImmediate", "clearImmediate","setInterval", "clearInterval", "Date"];
|
701
|
+
|
702
|
+
if (clock.methods.length === 0) {
|
703
|
+
// do not fake nextTick by default - GitHub#126
|
704
|
+
clock.methods = keys(timers).filter(function (key) {return key !== "nextTick";});
|
705
|
+
}
|
706
|
+
|
707
|
+
for (i = 0, l = clock.methods.length; i < l; i++) {
|
708
|
+
if (clock.methods[i] === "hrtime") {
|
709
|
+
if (target.process && typeof target.process.hrtime === "function") {
|
710
|
+
hijackMethod(target.process, clock.methods[i], clock);
|
711
|
+
}
|
712
|
+
} else if (clock.methods[i] === "nextTick") {
|
713
|
+
if (target.process && typeof target.process.nextTick === "function") {
|
714
|
+
hijackMethod(target.process, clock.methods[i], clock);
|
715
|
+
}
|
716
|
+
} else {
|
717
|
+
if (clock.methods[i] === "setInterval" && config.shouldAdvanceTime === true) {
|
718
|
+
var intervalTick = doIntervalTick.bind(null, clock, config.advanceTimeDelta);
|
719
|
+
var intervalId = target[clock.methods[i]](
|
720
|
+
intervalTick,
|
721
|
+
config.advanceTimeDelta);
|
722
|
+
clock.attachedInterval = intervalId;
|
723
|
+
}
|
724
|
+
hijackMethod(target, clock.methods[i], clock);
|
725
|
+
}
|
726
|
+
}
|
727
|
+
|
728
|
+
return clock;
|
729
|
+
};
|
730
|
+
|
731
|
+
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
732
|
+
},{}]},{},[1])(1)
|
733
|
+
});
|