hyper-spec 0.1.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.
@@ -0,0 +1,88 @@
1
+ require 'capybara/rspec'
2
+ require 'capybara/poltergeist'
3
+ require 'opal'
4
+ require 'selenium-webdriver'
5
+
6
+ require 'hyper-spec/component_test_helpers'
7
+ require 'hyper-spec/rails/engine'
8
+ require 'hyper-spec/version'
9
+ require 'hyper-spec/wait_for_ajax'
10
+ require 'react/isomorphic_helpers'
11
+ require 'selenium/web_driver/firefox/profile'
12
+
13
+ RSpec.configure do |config|
14
+ config.include HyperSpec::ComponentTestHelpers
15
+ config.include HyperSpec::WaitForAjax
16
+ config.include Capybara::DSL
17
+
18
+ config.mock_with :rspec
19
+
20
+ if defined?(HyperMesh)
21
+ config.before(:each) do
22
+ HyperMesh.class_eval do
23
+ def self.on_server?
24
+ true
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ config.before(:each, js: true) do
31
+ size_window
32
+ end
33
+
34
+ config.after(:each, js: true) do
35
+ page.instance_variable_set('@hyper_spec_mounted', false)
36
+ end
37
+
38
+ config.after(:each) do |example|
39
+ unless example.exception
40
+ PusherFake::Channel.reset if defined? PusherFake
41
+ end
42
+ end
43
+ end
44
+
45
+ # Capybara config
46
+ RSpec.configure do |_config|
47
+ Capybara.default_max_wait_time = 10
48
+
49
+ # # In case Google ever fixes chromedriver to work with Opal...
50
+ # Capybara.register_driver :chrome do |app|
51
+ # caps = Selenium::WebDriver::Remote::Capabilities.chrome(
52
+ # 'chromeOptions' => { 'args' => ['--window-size=200,200'] }
53
+ # )
54
+ #
55
+ # Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: caps)
56
+ # end
57
+
58
+ Capybara.register_driver :poltergeist do |app|
59
+ options = {
60
+ js_errors: false, timeout: 180, inspector: true,
61
+ phantomjs_options: ['--load-images=no', '--ignore-ssl-errors=yes']
62
+ }.tap do |hash|
63
+ unless ENV['SHOW_LOGS']
64
+ hash[:phantomjs_logger] = StringIO.new
65
+ hash[:logger] = StringIO.new
66
+ end
67
+ end
68
+
69
+ Capybara::Poltergeist::Driver.new(app, options)
70
+ end
71
+
72
+ Capybara.register_driver :selenium_with_firebug do |app|
73
+ profile = Selenium::WebDriver::Firefox::Profile.new
74
+ profile.frame_position = ENV['DRIVER'] && ENV['DRIVER'][2]
75
+ profile.enable_firebug
76
+
77
+ Capybara::Selenium::Driver.new(app, browser: :firefox, profile: profile)
78
+ end
79
+
80
+ Capybara.javascript_driver =
81
+ if ENV['DRIVER'] =~ /^ff/
82
+ :selenium_with_firebug
83
+ # elsif ENV['DRIVER'] == 'chrome'
84
+ # Capybara.javascript_driver = :chrome
85
+ else
86
+ :poltergeist
87
+ end
88
+ end
@@ -0,0 +1,306 @@
1
+ # see component_test_helpers_spec.rb for examples
2
+
3
+ require 'parser/current'
4
+ require 'unparser'
5
+ require 'method_source'
6
+ require_relative '../../vendor/assets/javascripts/time_cop' # 'hyper-spec/time_cop'
7
+
8
+ module HyperSpec
9
+ module ComponentTestHelpers
10
+ TOP_LEVEL_COMPONENT_PATCH =
11
+ Opal.compile(File.read(File.expand_path('../../react/top_level_rails_component.rb', __FILE__)))
12
+
13
+ class << self
14
+ attr_accessor :current_example
15
+ attr_accessor :description_displayed
16
+
17
+ def display_example_description
18
+ "<script type='text/javascript'>console.log(console.log('%c#{current_example.description}'"\
19
+ ",'color:green; font-weight:bold; font-size: 200%'))</script>"
20
+ end
21
+ end
22
+
23
+ def build_test_url_for(controller)
24
+ unless controller
25
+ unless defined?(::ReactTestController)
26
+ Object.const_set('ReactTestController', Class.new(ActionController::Base))
27
+ end
28
+
29
+ controller = ::ReactTestController
30
+ end
31
+
32
+ route_root = controller.name.gsub(/Controller$/, '').underscore
33
+
34
+ unless controller.method_defined?(:test)
35
+ controller.class_eval do
36
+ define_method(:test) do
37
+ route_root = self.class.name.gsub(/Controller$/, '').underscore
38
+ test_params = ::Rails.cache.read("/#{route_root}/#{params[:id]}")
39
+ @component_name = test_params[0]
40
+ @component_params = test_params[1]
41
+ render_params = test_params[2]
42
+ render_on = render_params.delete(:render_on) || :client_only
43
+ _mock_time = render_params.delete(:mock_time)
44
+ style_sheet = render_params.delete(:style_sheet)
45
+ javascript = render_params.delete(:javascript)
46
+ code = render_params.delete(:code)
47
+
48
+ page = '<%= react_component @component_name, @component_params, '\
49
+ "{ prerender: #{render_on != :client_only} } %>"
50
+ page = "<script type='text/javascript'>\n#{TOP_LEVEL_COMPONENT_PATCH}\n</script>\n#{page}"
51
+
52
+ page = "<script type='text/javascript'>\n#{code}\n</script>\n#{page}" if code
53
+
54
+ page = "<%= javascript_include_tag 'time_cop' %>\n#{page}" if true || Lolex.initialized?
55
+
56
+ if (render_on != :server_only && !render_params[:layout]) || javascript
57
+ page = "<%= javascript_include_tag '#{javascript || 'application'}' %>\n#{page}"
58
+ end
59
+
60
+ if !render_params[:layout] || style_sheet
61
+ page = "<%= stylesheet_link_tag '#{style_sheet || 'application'}' %>\n#{page}"
62
+ end
63
+
64
+ if render_on == :server_only # so that test helper wait_for_ajax works
65
+ page = "<script type='text/javascript'>window.jQuery = {'active': 0}</script>\n#{page}"
66
+ else
67
+ page = "<%= javascript_include_tag 'jquery' %>\n"\
68
+ "<%= javascript_include_tag 'jquery_ujs' %>\n#{page}"
69
+ end
70
+
71
+ page = "<script type='text/javascript'>go = function() "\
72
+ "{window.hyper_spec_waiting_for_go = false}</script>\n#{page}"
73
+
74
+ title = view_context.escape_javascript(ComponentTestHelpers.current_example.description)
75
+ title = "#{title}...continued." if ComponentTestHelpers.description_displayed
76
+
77
+ page = "<script type='text/javascript'>console.log(console.log('%c#{title}',"\
78
+ "'color:green; font-weight:bold; font-size: 200%'))</script>\n#{page}"
79
+
80
+ ComponentTestHelpers.description_displayed = true
81
+ render_params[:inline] = page
82
+ render render_params
83
+ end
84
+ end
85
+
86
+ begin
87
+ routes = ::Rails.application.routes
88
+ routes.disable_clear_and_finalize = true
89
+ routes.clear!
90
+ routes.draw do
91
+ get "/#{route_root}/:id", to: "#{route_root}#test"
92
+ end
93
+ ::Rails.application.routes_reloader.paths.each { |path| load(path) }
94
+ routes.finalize!
95
+ ActiveSupport.on_load(:action_controller) { routes.finalize! }
96
+ ensure
97
+ routes.disable_clear_and_finalize = false
98
+ end
99
+ end
100
+
101
+ "/#{route_root}/#{@test_id = (@test_id || 0) + 1}"
102
+ end
103
+
104
+ def isomorphic(&block)
105
+ yield
106
+ on_client(&block)
107
+ end
108
+
109
+ def evaluate_ruby(str = '', opts = {}, &block)
110
+ insure_mount
111
+ if block
112
+ str = "#{str}\n#{Unparser.unparse Parser::CurrentRuby.parse(block.source).children.last}"
113
+ end
114
+ js = Opal.compile(str).delete("\n").gsub('(Opal);', '(Opal)')
115
+ JSON.parse(evaluate_script("[#{js}].$to_json()"), opts).first
116
+ end
117
+
118
+ def expect_evaluate_ruby(str = '', opts = {}, &block)
119
+ insure_mount
120
+ expect(evaluate_ruby(add_opal_block(str, block), opts))
121
+ end
122
+
123
+ def add_opal_block(str, block)
124
+ # big assumption here is that we are going to follow this with a .to
125
+ # hence .children.first followed by .children.last
126
+ # probably should do some kind of "search" to make this work nicely
127
+ return str unless block
128
+ "#{str}\n"\
129
+ "#{Unparser.unparse Parser::CurrentRuby.parse(block.source).children.first.children.last}"
130
+ end
131
+
132
+ def expect_promise(str = '', opts = {}, &block)
133
+ insure_mount
134
+
135
+ str = add_opal_block(str, block)
136
+ str = "#{str}.then { |args| args = [args]; `window.hyper_spec_promise_result = args` }"
137
+ js = Opal.compile(str).delete("\n").gsub('(Opal);', '(Opal)')
138
+ page.evaluate_script('window.hyper_spec_promise_result = false')
139
+ page.execute_script(js)
140
+
141
+ Timeout.timeout(Capybara.default_max_wait_time) do
142
+ loop do
143
+ sleep 0.25
144
+ break if page.evaluate_script('!!window.hyper_spec_promise_result')
145
+ end
146
+ end
147
+
148
+ result =
149
+ JSON.parse(page.evaluate_script('window.hyper_spec_promise_result.$to_json()'), opts).first
150
+ expect(result)
151
+ end
152
+
153
+ def ppr(str)
154
+ js = Opal.compile(str).delete("\n").gsub('(Opal);', '(Opal)')
155
+ execute_script("console.log(#{js})")
156
+ end
157
+
158
+ def on_client(&block)
159
+ @client_code =
160
+ "#{@client_code}#{Unparser.unparse Parser::CurrentRuby.parse(block.source).children.last}\n"
161
+ end
162
+
163
+ def debugger
164
+ `debugger`
165
+ nil
166
+ end
167
+
168
+ def insure_mount
169
+ # rescue in case page is not defined...
170
+ mount unless page.instance_variable_get('@hyper_spec_mounted')
171
+ end
172
+
173
+ def client_option(opts = {})
174
+ @client_options ||= {}
175
+ @client_options.merge! opts
176
+ end
177
+
178
+ alias client_options client_option
179
+
180
+ def mount(component_name = nil, params = nil, opts = {}, &block)
181
+ unless params
182
+ params = opts
183
+ opts = {}
184
+ end
185
+
186
+ opts = client_options opts
187
+ test_url = build_test_url_for(opts.delete(:controller))
188
+
189
+ if block || @client_code || component_name.nil?
190
+ block_with_helpers = <<-code
191
+ module ComponentHelpers
192
+ def self.js_eval(s)
193
+ `eval(s)`
194
+ end
195
+ def self.dasherize(s)
196
+ `s.replace(/[-_\\s]+/g, '-')
197
+ .replace(/([A-Z\\d]+)([A-Z][a-z])/g, '$1-$2')
198
+ .replace(/([a-z\\d])([A-Z])/g, '$1-$2')
199
+ .toLowerCase()`
200
+ end
201
+ def self.add_class(class_name, styles={})
202
+ style = styles.collect { |attr, value| "\#{dasherize(attr)}:\#{value}"}.join("; ")
203
+ s = "<style type='text/css'> .\#{class_name}{ \#{style} } </style>"
204
+ `$(\#{s}).appendTo("head");`
205
+ end
206
+ end
207
+ class React::Component::HyperTestDummy < React::Component::Base
208
+ def render; end
209
+ end
210
+ #{@client_code}
211
+ #{Unparser.unparse(Parser::CurrentRuby.parse(block.source).children.last) if block}
212
+ code
213
+ opts[:code] = Opal.compile(block_with_helpers)
214
+ end
215
+
216
+ component_name ||= 'React::Component::HyperTestDummy'
217
+ ::Rails.cache.write(test_url, [component_name, params, opts])
218
+ visit test_url
219
+ wait_for_ajax unless opts[:no_wait]
220
+ page.instance_variable_set('@hyper_spec_mounted', true)
221
+ Lolex.init(self, client_options[:time_zone], client_options[:clock_resolution])
222
+ end
223
+
224
+ [:callback_history_for, :last_callback_for, :clear_callback_history_for,
225
+ :event_history_for, :last_event_for, :clear_event_history_for].each do |method|
226
+ define_method(method) do |event_name|
227
+ evaluate_ruby("React::TopLevelRailsComponent.#{method}('#{event_name}')")
228
+ end
229
+ end
230
+
231
+ def run_on_client(&block)
232
+ script = Opal.compile(Unparser.unparse(Parser::CurrentRuby.parse(block.source).children.last))
233
+ execute_script(script)
234
+ end
235
+
236
+ def add_class(class_name, style)
237
+ @client_code = "#{@client_code}ComponentHelpers.add_class '#{class_name}', #{style}\n"
238
+ end
239
+
240
+ def open_in_chrome
241
+ if false && ['linux', 'freebsd'].include?(`uname`.downcase)
242
+ `google-chrome http://#{page.server.host}:#{page.server.port}#{page.current_path}`
243
+ else
244
+ `open http://#{page.server.host}:#{page.server.port}#{page.current_path}`
245
+ end
246
+
247
+ while true
248
+ sleep 1.hour
249
+ end
250
+ end
251
+
252
+ def pause(message = nil)
253
+ if message
254
+ puts message
255
+ page.evaluate_ruby "puts #{message.inspect}.to_s + ' (type go() to continue)'"
256
+ end
257
+
258
+ page.evaluate_script('window.hyper_spec_waiting_for_go = true')
259
+
260
+ loop do
261
+ sleep 0.25
262
+ break unless page.evaluate_script('window.hyper_spec_waiting_for_go')
263
+ end
264
+ end
265
+
266
+ def size_window(width = nil, height = nil)
267
+ width, height = [height, width] if width == :portrait
268
+ width, height = width if width.is_a? Array
269
+ portrait = true if height == :portrait
270
+
271
+ case width
272
+ when :small
273
+ width, height = [480, 320]
274
+ when :mobile
275
+ width, height = [640, 480]
276
+ when :tablet
277
+ width, height = [960, 640]
278
+ when :large
279
+ width, height = [1920, 6000]
280
+ when :default, nil
281
+ width, height = [1024, 768]
282
+ end
283
+
284
+ width, height = [height, width] if portrait
285
+
286
+ Capybara.current_session.current_window.resize_to(width, height)
287
+ end
288
+ end
289
+
290
+ RSpec.configure do |config|
291
+ config.before(:each) do |example|
292
+ ComponentTestHelpers.current_example = example
293
+ ComponentTestHelpers.description_displayed = false
294
+ end
295
+
296
+ if defined?(ActiveRecord)
297
+ config.before(:all) do
298
+ ActiveRecord::Base.class_eval do
299
+ def attributes_on_client(page)
300
+ page.evaluate_ruby("#{self.class.name}.find(#{id}).attributes", symbolize_names: true)
301
+ end
302
+ end
303
+ end
304
+ end
305
+ end
306
+ end
@@ -0,0 +1,66 @@
1
+ module HyperSpec
2
+ class Lolex
3
+ class << self
4
+ def init(page, client_time_zone, resolution)
5
+ @capybara_page = page
6
+ @resolution = resolution || 10
7
+ @client_time_zone = client_time_zone
8
+ run_pending_evaluations
9
+ @initialized = true
10
+ end
11
+
12
+ def initialized?
13
+ @initialized
14
+ end
15
+
16
+ def push(mock_type, *args)
17
+ scale = if mock_type == :freeze
18
+ 0
19
+ elsif mock_type == :scale
20
+ args[0]
21
+ else
22
+ 1
23
+ end
24
+ evaluate_ruby do
25
+ "Lolex.push('#{time_string_in_zone}', #{scale}, #{@resolution})"
26
+ end
27
+ end
28
+
29
+ def pop
30
+ evaluate_ruby { 'Lolex.pop' }
31
+ end
32
+
33
+ def unmock
34
+ evaluate_ruby { "Lolex.unmock('#{time_string_in_zone}', #{@resolution})" }
35
+ end
36
+
37
+ def restore
38
+ evaluate_ruby { 'Lolex.restore' }
39
+ end
40
+
41
+ private
42
+
43
+ def time_string_in_zone
44
+ Time.now.in_time_zone(@client_time_zone).strftime('%Y/%m/%d %H:%M:%S %z')
45
+ end
46
+
47
+ def pending_evaluations
48
+ @pending_evaluations ||= []
49
+ end
50
+
51
+ def evaluate_ruby(&block)
52
+ if @capybara_page
53
+ @capybara_page.evaluate_ruby(yield)
54
+ else
55
+ pending_evaluations << block
56
+ end
57
+ end
58
+
59
+ def run_pending_evaluations
60
+ return if pending_evaluations.empty?
61
+ @capybara_page.evaluate_ruby(pending_evaluations.collect(&:call).join("\n"))
62
+ @pending_evaluations ||= []
63
+ end
64
+ end
65
+ end
66
+ end