lib/hyper-spec/time_cop.rb in hyper-spec-0.1.2 vs lib/hyper-spec/time_cop.rb in hyper-spec-0.99.0

- old
+ new

@@ -1,7 +1,157 @@ -require 'timecop' +if RUBY_ENGINE == 'opal' + # Wrap the Lolex js package + class Lolex + class << self -module HyperSpec + # to avoid forcing us to require time we make our own Time.parse method + # copied from opal.rb master branch + + def parse_time(str) + `new Date(Date.parse(str))` + end + + def stack + @stack ||= [] + end + + def push(time, scale = 1, resolution = 10) + time = parse_time(time) if time.is_a? String + stack << [Time.now, @scale, @resolution] + update_lolex(time, scale, resolution) + end + + def pop + update_lolex(*stack.pop) unless stack.empty? + end + + def unmock(time, resolution) + push(time, 1, resolution) + @backup_stack = stack + @stack = [] + end + + def restore + @stack = @backup_stack + pop + end + + def tick + real_clock = `(new #{@lolex}['_Date']).getTime()` + mock_clock = Time.now.to_f * 1000 + real_elapsed_time = real_clock - @real_start_time + mock_elapsed_time = mock_clock - @mock_start_time + + ticks = real_elapsed_time * @scale - mock_elapsed_time + + `#{@lolex}.tick(#{ticks.to_i})` + nil + end + + def create_ticker + return unless @scale && @scale > 0 + ticker = %x{ + #{@lolex}['_setInterval'].call( + window, + function() { #{tick} }, + #{@resolution} + ) + } + ticker + end + + def update_lolex(time, scale, resolution) + `#{@lolex}.uninstall()` && return if scale.nil? + @mock_start_time = time.to_f * 1000 + + if @lolex + `#{@lolex}['_clearInterval'].call(window, #{@ticker})` if @ticker + @real_start_time = `(new #{@lolex}['_Date']).getTime()` + `#{@lolex}.tick(#{@mock_start_time - Time.now.to_f * 1000})` + else + @real_start_time = Time.now.to_f * 1000 + @lolex = `lolex.install({ now: #{@mock_start_time} })` + end + + @scale = scale + @resolution = resolution + @ticker = create_ticker + nil # must return nil otherwise we try to return a timer to server! + end + end + end + +else + require 'timecop' + + # Interface to the Lolex package running on the client side + # Below we will monkey patch Timecop to call these methods + class Lolex + class << self + def init(page, client_time_zone, resolution) + @capybara_page = page + @resolution = resolution || 10 + @client_time_zone = client_time_zone + run_pending_evaluations + @initialized = true + end + + def initialized? + @initialized + end + + def push(mock_type, *args) + scale = if mock_type == :freeze + 0 + elsif mock_type == :scale + args[0] + else + 1 + end + evaluate_ruby do + "Lolex.push('#{time_string_in_zone}', #{scale}, #{@resolution})" + end + end + + def pop + evaluate_ruby { 'Lolex.pop' } + end + + def unmock + evaluate_ruby { "Lolex.unmock('#{time_string_in_zone}', #{@resolution})" } + end + + def restore + evaluate_ruby { 'Lolex.restore' } + end + + private + + def time_string_in_zone + Time.now.in_time_zone(@client_time_zone).strftime('%Y/%m/%d %H:%M:%S %z') + end + + def pending_evaluations + @pending_evaluations ||= [] + end + + def evaluate_ruby(&block) + if @capybara_page + @capybara_page.evaluate_ruby(yield) + else + pending_evaluations << block + end + end + + def run_pending_evaluations + return if pending_evaluations.empty? + @capybara_page.evaluate_ruby(pending_evaluations.collect(&:call).join("\n")) + @pending_evaluations ||= [] + end + end + end + + # Monkey patches to call our Lolex interface class Timecop private def travel(mock_type, *args, &block) raise SafeModeException if Timecop.safe_mode? && !block_given?