hyper-spec 1.0.alpha1.3 → 1.0.alpha1.8

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.
@@ -0,0 +1,70 @@
1
+ module HyperSpec
2
+ module Internal
3
+ module Controller
4
+ class << self
5
+ attr_accessor :current_example
6
+ attr_accessor :description_displayed
7
+
8
+ def test_id
9
+ @_hyperspec_private_test_id ||= 0
10
+ @_hyperspec_private_test_id += 1
11
+ end
12
+
13
+ include ActionView::Helpers::JavaScriptHelper
14
+
15
+ def current_example_description!
16
+ title = "#{title}...continued." if description_displayed
17
+ self.description_displayed = true
18
+ "#{escape_javascript(current_example.description)}#{title}"
19
+ end
20
+
21
+ def file_cache
22
+ @file_cache ||= FileCache.new('cache', '/tmp/hyper-spec-caches', 30, 3)
23
+ end
24
+
25
+ def cache_read(key)
26
+ file_cache.get(key)
27
+ end
28
+
29
+ def cache_write(key, value)
30
+ file_cache.set(key, value)
31
+ end
32
+
33
+ def cache_delete(key)
34
+ file_cache.delete(key)
35
+ rescue StandardError
36
+ nil
37
+ end
38
+ end
39
+
40
+ # By default we assume we are operating in a Rails environment and will
41
+ # hook in using a rails controller. To override this define the
42
+ # HyperSpecController class in your spec helper. See the rack.rb file
43
+ # for an example of how to do this.
44
+
45
+ def hyper_spec_test_controller
46
+ return ::HyperSpecTestController if defined?(::HyperSpecTestController)
47
+
48
+ base = if defined? ApplicationController
49
+ Class.new ApplicationController
50
+ elsif defined? ::ActionController::Base
51
+ Class.new ::ActionController::Base
52
+ else
53
+ raise "Unless using Rails you must define the HyperSpecTestController\n"\
54
+ 'For rack apps try requiring hyper-spec/rack.'
55
+ end
56
+ Object.const_set('HyperSpecTestController', base)
57
+ end
58
+
59
+ # First insure we have a controller, then make sure it responds to the test method
60
+ # if not, then add the rails specific controller methods. The RailsControllerHelpers
61
+ # module will automatically add a top level route back to the controller.
62
+
63
+ def route_root_for(controller)
64
+ controller ||= hyper_spec_test_controller
65
+ controller.include RailsControllerHelpers unless controller.method_defined?(:test)
66
+ controller.route_root
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,103 @@
1
+ module HyperSpec
2
+ module Internal
3
+ module CopyLocals
4
+ private
5
+
6
+ def build_var_inclusion_lists
7
+ build_included_list
8
+ build_excluded_list
9
+ end
10
+
11
+ def build_included_list
12
+ @_hyperspec_private_included_vars = nil
13
+ return unless @_hyperspec_private_client_options.key? :include_vars
14
+
15
+ included = @_hyperspec_private_client_options[:include_vars]
16
+ if included.is_a? Symbol
17
+ @_hyperspec_private_included_vars = [included]
18
+ elsif included.is_a?(Array)
19
+ @_hyperspec_private_included_vars = included
20
+ elsif !included
21
+ @_hyperspec_private_included_vars = []
22
+ end
23
+ end
24
+
25
+ PRIVATE_VARIABLES = %i[
26
+ @__inspect_output @__memoized @example @_hyperspec_private_client_code
27
+ @_hyperspec_private_html_block @fixture_cache
28
+ @fixture_connections @connection_subscriber @loaded_fixtures
29
+ @_hyperspec_private_client_options
30
+ @_hyperspec_private_included_vars
31
+ @_hyperspec_private_excluded_vars
32
+ b __ _ _ex_ pry_instance _out_ _in_ _dir_ _file_
33
+ ]
34
+
35
+ def build_excluded_list
36
+ return unless @_hyperspec_private_client_options
37
+
38
+ excluded = @_hyperspec_private_client_options[:exclude_vars]
39
+ if excluded.is_a? Symbol
40
+ @_hyperspec_private_excluded_vars = [excluded]
41
+ elsif excluded.is_a?(Array)
42
+ @_hyperspec_private_excluded_vars = excluded
43
+ elsif excluded
44
+ @_hyperspec_private_included_vars = []
45
+ end
46
+ end
47
+
48
+ def var_excluded?(var, binding)
49
+ return true if PRIVATE_VARIABLES.include? var
50
+
51
+ excluded = binding.eval('instance_variable_get(:@_hyperspec_private_excluded_vars)')
52
+ return true if excluded&.include?(var)
53
+
54
+ included = binding.eval('instance_variable_get(:@_hyperspec_private_included_vars)')
55
+ included && !included.include?(var)
56
+ end
57
+
58
+ def add_locals(in_str, block)
59
+ b = block.binding
60
+ add_instance_vars(b, add_local_vars(b, add_memoized_vars(b, in_str)))
61
+ end
62
+
63
+ def add_memoized_vars(binding, in_str)
64
+ memoized = binding.eval('__memoized').instance_variable_get(:@memoized)
65
+ return in_str unless memoized
66
+
67
+ memoized.inject(in_str) do |str, pair|
68
+ next str if var_excluded?(pair.first, binding)
69
+
70
+ "#{str}\n#{set_local_var(pair.first, pair.last)}"
71
+ end
72
+ end
73
+
74
+ def add_local_vars(binding, in_str)
75
+ binding.local_variables.inject(in_str) do |str, var|
76
+ next str if var_excluded?(var, binding)
77
+
78
+ "#{str}\n#{set_local_var(var, binding.local_variable_get(var))}"
79
+ end
80
+ end
81
+
82
+ def add_instance_vars(binding, in_str)
83
+ binding.eval('instance_variables').inject(in_str) do |str, var|
84
+ next str if var_excluded?(var, binding)
85
+
86
+ "#{str}\n#{set_local_var(var, binding.eval("instance_variable_get('#{var}')"))}"
87
+ end
88
+ end
89
+
90
+ def set_local_var(name, object)
91
+ serialized = object.opal_serialize
92
+ if serialized
93
+ "#{name} = #{serialized}"
94
+ else
95
+ "self.class.define_method(:#{name}) "\
96
+ "{ raise 'Attempt to access the variable #{name} "\
97
+ 'that was defined in the spec, but its value could not be serialized '\
98
+ "so it is undefined on the client.' }"
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,86 @@
1
+ module Opal
2
+ # strips off stuff that confuses things when transmitting to the client
3
+ # and prints offending code if it can't be compiled
4
+ def self.hyperspec_compile(str, opts = {})
5
+ compile(str, opts).gsub("// Prepare super implicit arguments\n", '')
6
+ .delete("\n").gsub('(Opal);', '(Opal)')
7
+ # rubocop:disable Lint/RescueException
8
+ # we are going to reraise it anyway, so its fine to catch EVERYTHING!
9
+ rescue Exception => e
10
+ puts "puts could not compile: \n\n#{str}\n\n"
11
+ raise e
12
+ end
13
+ # rubocop:enable Lint/RescueException
14
+ end
15
+
16
+ module Unparser
17
+ class Emitter
18
+ # Emitter for send
19
+ class Send < self
20
+ def local_variable_clash?
21
+ selector =~ /^[A-Z]/ ||
22
+ local_variable_scope.local_variable_defined_for_node?(node, selector)
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ module MethodSource
29
+ class << self
30
+ alias original_lines_for_before_hyper_spec lines_for
31
+ alias original_source_helper_before_hyper_spec source_helper
32
+
33
+ def source_helper(source_location, name = nil)
34
+ source_location[1] = 1 if source_location[0] == '(pry)'
35
+ original_source_helper_before_hyper_spec source_location, name
36
+ end
37
+
38
+ def lines_for(file_name, name = nil)
39
+ if file_name == '(pry)'
40
+ HyperSpec.current_pry_code_block
41
+ else
42
+ original_lines_for_before_hyper_spec file_name, name
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ class Object
49
+ def opal_serialize
50
+ nil
51
+ end
52
+ end
53
+
54
+ class Hash
55
+ def opal_serialize
56
+ "{#{collect { |k, v| "#{k.opal_serialize} => #{v.opal_serialize}" }.join(', ')}}"
57
+ end
58
+ end
59
+
60
+ class Array
61
+ def opal_serialize
62
+ "[#{collect { |v| v.opal_serialize }.join(', ')}]"
63
+ end
64
+ end
65
+
66
+ [FalseClass, Float, Integer, NilClass, String, Symbol, TrueClass].each do |klass|
67
+ klass.send(:define_method, :opal_serialize) do
68
+ inspect
69
+ end
70
+ end
71
+
72
+ # rubocop:disable Lint/UnifiedInteger - patch for ruby prior to 2.4
73
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4.0')
74
+ [Bignum, Fixnum].each do |klass|
75
+ klass.send(:define_method, :opal_serialize) do
76
+ inspect
77
+ end
78
+ end
79
+ end
80
+ # rubocop:enable Lint/UnifiedInteger
81
+
82
+ class Time
83
+ def to_opal_expression
84
+ "Time.parse('#{inspect}')"
85
+ end
86
+ end
@@ -0,0 +1,50 @@
1
+ module HyperSpec
2
+ module Internal
3
+ module RailsControllerHelpers
4
+ def self.included(base)
5
+ base.include ControllerHelpers
6
+ base.include Helpers
7
+ routes = ::Rails.application.routes
8
+ routes.disable_clear_and_finalize = true
9
+ routes.clear!
10
+ routes.draw { get "/#{base.route_root}/:id", to: "#{base.route_root}#test" }
11
+ ::Rails.application.routes_reloader.paths.each { |path| load(path) }
12
+ routes.finalize!
13
+ ActiveSupport.on_load(:action_controller) { routes.finalize! }
14
+ ensure
15
+ routes.disable_clear_and_finalize = false
16
+ end
17
+
18
+ module Helpers
19
+ def ping!
20
+ head(:no_content)
21
+ nil
22
+ end
23
+
24
+ def mount_component!
25
+ @page << '<%= react_component @component_name, @component_params, '\
26
+ "{ prerender: #{@render_on != :client_only} } %>"
27
+ end
28
+
29
+ def application!(file)
30
+ @page << "<%= javascript_include_tag '#{file}' %>"
31
+ end
32
+
33
+ def style_sheet!(file)
34
+ @page << "<%= stylesheet_link_tag '#{file}' %>"
35
+ end
36
+
37
+ def deliver!
38
+ @render_params[:inline] = @page
39
+ response.headers['Cache-Control'] = 'max-age=120'
40
+ response.headers['X-Tracking-ID'] = '123456'
41
+ render @render_params
42
+ end
43
+
44
+ def server_only?
45
+ @render_on == :server_only
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -59,6 +59,10 @@ if RUBY_ENGINE == 'opal'
59
59
  ticker
60
60
  end
61
61
 
62
+ def init(scale: 1, resolution: 10)
63
+ update_lolex(Time.now, scale, resolution)
64
+ end
65
+
62
66
  def update_lolex(time, scale, resolution)
63
67
  `#{@lolex}.uninstall()` && return if scale.nil?
64
68
  @mock_start_time = time.to_f * 1000
@@ -80,6 +84,14 @@ if RUBY_ENGINE == 'opal'
80
84
  end
81
85
  end
82
86
 
87
+ # create an alias for Lolex.init so we can say Timecop.init on the client
88
+
89
+ class Timecop
90
+ def self.init(*args)
91
+ Lolex.init(*args)
92
+ end
93
+ end
94
+
83
95
  else
84
96
  require 'timecop'
85
97
 
@@ -136,7 +148,7 @@ else
136
148
 
137
149
  def evaluate_ruby(&block)
138
150
  if @capybara_page
139
- @capybara_page.evaluate_ruby(yield)
151
+ @capybara_page.internal_evaluate_ruby(yield)
140
152
  else
141
153
  pending_evaluations << block
142
154
  end
@@ -144,7 +156,7 @@ else
144
156
 
145
157
  def run_pending_evaluations
146
158
  return if pending_evaluations.empty?
147
- @capybara_page.evaluate_ruby(pending_evaluations.collect(&:call).join("\n"))
159
+ @capybara_page.internal_evaluate_ruby(pending_evaluations.collect(&:call).join("\n"))
148
160
  @pending_evaluations ||= []
149
161
  end
150
162
  end
@@ -0,0 +1,73 @@
1
+ module HyperSpec
2
+ module Internal
3
+ module WindowSizing
4
+ private
5
+
6
+ STD_SIZES = {
7
+ small: [480, 320],
8
+ mobile: [640, 480],
9
+ tablet: [960, 640],
10
+ large: [1920, 6000],
11
+ default: [1024, 768]
12
+ }
13
+
14
+ def determine_size(width, height)
15
+ width, height = [height, width] if width == :portrait
16
+ width, height = width if width.is_a? Array
17
+ portrait = true if height == :portrait
18
+ width ||= :default
19
+ width, height = STD_SIZES[width] if STD_SIZES[width]
20
+ width, height = [height, width] if portrait
21
+ [width + debugger_width, height]
22
+ end
23
+
24
+ def debugger_width
25
+ RSpec.configuration.debugger_width ||= begin
26
+ hs_internal_resize_to(1000, 500) do
27
+ sleep RSpec.configuration.wait_for_initialization_time
28
+ end
29
+ inner_width = evaluate_script('window.innerWidth')
30
+ 1000 - inner_width
31
+ end
32
+ RSpec.configuration.debugger_width
33
+ end
34
+
35
+ def hs_internal_resize_to(width, height)
36
+ Capybara.current_session.current_window.resize_to(width, height)
37
+ yield if block_given?
38
+ wait_for_size(width, height)
39
+ end
40
+
41
+ def wait_for_size(width, height)
42
+ @start_time = Capybara::Helpers.monotonic_time
43
+ @stable_count_w = @stable_count_h = 0
44
+ prev_size = [0, 0]
45
+ loop do
46
+ sleep 0.05
47
+ curr_size = evaluate_script('[window.innerWidth, window.innerHeight]')
48
+
49
+ return true if curr_size == [width, height] || stalled?(prev_size, curr_size)
50
+
51
+ prev_size = curr_size
52
+ check_time!
53
+ end
54
+ end
55
+
56
+ def check_time!
57
+ if (Capybara::Helpers.monotonic_time - @start_time) >
58
+ Capybara.current_session.config.default_max_wait_time
59
+ raise Capybara::WindowError,
60
+ 'Window size not stable within '\
61
+ "#{Capybara.current_session.config.default_max_wait_time} seconds."
62
+ end
63
+ end
64
+
65
+ def stalled?(prev_size, curr_size)
66
+ # some maximum or minimum is reached and size doesn't change anymore
67
+ @stable_count_w += 1 if prev_size[0] == curr_size[0]
68
+ @stable_count_h += 1 if prev_size[1] == curr_size[1]
69
+ @stable_count_w > 4 || @stable_count_h > 4
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,67 @@
1
+ require 'hyper-spec'
2
+
3
+ class HyperSpecTestController < SimpleDelegator
4
+ include HyperSpec::ControllerHelpers
5
+
6
+ class << self
7
+ attr_reader :sprocket_server
8
+ attr_reader :asset_path
9
+
10
+ def wrap(app:, append_path: 'app', asset_path: '/assets')
11
+ @sprocket_server = Opal::Sprockets::Server.new do |s|
12
+ s.append_path append_path
13
+ end
14
+
15
+ @asset_path = asset_path
16
+
17
+ ::Rack::Builder.app(app) do
18
+ map "/#{HyperSpecTestController.route_root}" do
19
+ use HyperSpecTestController
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ def sprocket_server
26
+ self.class.sprocket_server
27
+ end
28
+
29
+ def asset_path
30
+ self.class.asset_path
31
+ end
32
+
33
+ def ping!
34
+ [204, {}, []]
35
+ end
36
+
37
+ def application!(file)
38
+ @page << Opal::Sprockets.javascript_include_tag(
39
+ file,
40
+ debug: true,
41
+ sprockets: sprocket_server.sprockets,
42
+ prefix: asset_path
43
+ )
44
+ end
45
+
46
+ def json!
47
+ @page << Opal::Sprockets.javascript_include_tag(
48
+ 'json',
49
+ debug: true,
50
+ sprockets: sprocket_server.sprockets,
51
+ prefix: asset_path
52
+ )
53
+ end
54
+
55
+
56
+ def style_sheet!(_file_); end
57
+
58
+ def deliver!
59
+ [200, { 'Content-Type' => 'text/html' }, [@page]]
60
+ end
61
+
62
+ def call(env)
63
+ __setobj__(Rack::Request.new(env))
64
+ params[:id] = path.split('/').last
65
+ test
66
+ end
67
+ end