module HyperSpec # Defines a series of methods that will build a test page # This module is typically included into the HyperSpecTestController class. module ControllerHelpers # These methods are dependent on the stack being used. See the # RailsControllerHelpers module and rack.rb for two implementations. # Each method should append the appropriate code to the @page array # return an empty 204 status either by setting headers or # returning and appropriate response for rack. def ping! raise 'must implement' end def json! # this can be a no-op but if json is not included by the application, # hyper-spec will fail, with an error complaining about to_json end # return a script or style_sheet tag pointing to some asset. # typically you be pointing to a path on the server or using # sprockets to deliver the file. def require!(_file_) raise 'must implement' end def style_sheet!(_file_) raise 'must implement' end # deliver the page. The @page variable will contain the html ready to go, # any additional options that are passed through from the spec will be # in the @render_params variable. For example layout: 'my_special_layout' def deliver! raise 'must implement' end # generate a react_render top level block. This will only be called if # you use the mount directive in your specs, so it is optional. def mount_component! raise 'mount_component not implemented in HyperSpecTestController' end # by default the route back to the controller will be the controller name, less the # word Controller, and underscored. If you want some other name redefine this # method in the HyperSpecController class. def self.included(base) def base.route_root # Implement underscore without using rails underscore, so we don't have a # dependency on ActiveSupport name.gsub(/Controller$/, '') .gsub(/::/, '/') .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') .gsub(/([a-z\d])([A-Z])/, '\1_\2') .tr('-', '_') .downcase end end # The remainder of the methods should work for most implementations. # helper method checking the render_on parameter def on_client? @render_on != :server_only end # The controllers behavior is kept as an array of values in the Controller cache # under a unique id for each test run. Grab the parameters and move them to instance vars # If this is just a ping! Then we can just exit with nil. def initialize! return if params[:id] == 'ping' key = "/#{self.class.route_root}/#{params[:id]}" test_params = Internal::Controller.cache_read(key) @component_name = test_params[0] @component_params = test_params[1] @html_block = test_params[2] @render_params = test_params[3] @render_on = @render_params.delete(:render_on) || :client_only @_mock_time = @render_params.delete(:mock_time) @style_sheet = @render_params.delete(:style_sheet) @javascript = @render_params.delete(:javascript) @code = @render_params.delete(:code) @page = ['
'] end # add any html code generated by the insert_html directive def html_block! @page << @html_block end # patch behavior of the HyperComponent TopLevelRailsComponent class # so that things like events are passed back to the test harness TOP_LEVEL_COMPONENT_PATCH = Opal.compile(File.read(File.expand_path('../sources/top_level_rails_component.rb', __dir__))) # patch time cop and lolex so they stay in sync across the client and server TIME_COP_CLIENT_PATCH = Opal.compile(File.read(File.expand_path('../hyper-spec/internal/time_cop.rb', __dir__))) + "\n#{File.read(File.expand_path('../sources/lolex.js', __dir__))}" def client_code! if @component_name @page << "" end @page << "" if @code end def time_cop_patch! @page << "" end # Add the go_function to the client console. This is used to stop a hyper-spec pause directive. def go_function! @page << "' end # First lines displayed on the console will be the name of the spec def example_title! title = Internal::Controller.current_example_description! @page << "" end # generate each piece of the page, and then deliver it def style_sheet_file @style_sheet || (!@render_params[:layout] && 'application') end def application_file @javascript || (on_client? && !@render_params[:layout] && 'application') end def test return ping! unless initialize! html_block! example_title! if Internal::Controller.current_example go_function! if on_client? style_sheet!(style_sheet_file) if style_sheet_file application!(application_file) if application_file json! time_cop_patch! if on_client? || Lolex.initialized? client_code! if on_client? mount_component! if @component_name @page = @page.join("\n") + "\n\n" deliver! end end end