module HyperSpec module Internal module ClientExecution def internal_evaluate_ruby(*args, &block) insure_page_loaded add_promise_execute_and_wait(*process_params(*args, &block)) end private def add_opal_block(str, block) return str unless block source = block.source ast = Parser::CurrentRuby.parse(source) ast = find_block(ast) raise "could not find block within source: #{block.source}" unless ast "#{add_locals(str, block)}\n#{Unparser.unparse ast.children.last}" end def add_promise_execute_and_wait(str, opts) js = opal_compile(add_promise_wrapper(str)) page.execute_script("window.hyper_spec_promise_result = false; #{js}") Timeout.timeout(Capybara.default_max_wait_time) do loop do break if page.evaluate_script('!!window.hyper_spec_promise_result') page.evaluate_script('!!window.hyper_spec_promise_failed && Opal.Opal.$raise(window.hyper_spec_promise_failed)') sleep 0.25 end end JSON.parse(page.evaluate_script('window.hyper_spec_promise_result.$to_json()'), opts).first end def add_promise_wrapper(str) <<~RUBY (#{str}).tap do |r| if defined?(Promise) && r.is_a?(Promise) r.then { |args| `window.hyper_spec_promise_result = [args]` } .fail { |e| `window.hyper_spec_promise_failed = e` } else #after(0) do #puts "setting window.hyper_spec_promise_result = [\#{r}]" `window.hyper_spec_promise_result = [r]` #end end end RUBY end def find_block(node) # find a block with the ast tree. return false unless node.class == Parser::AST::Node return node if the_node_you_are_looking_for?(node) node.children.each do |child| found = find_block(child) return found if found end false end def process_params(*args, &block) args = ['', *args] if args[0].is_a? Hash args = [args[0], {}, args[1] || {}] if args.length < 3 str, opts, vars = args vars.each do |name, value| str = "#{name} = #{value.inspect}\n#{str}" end [add_opal_block(str, block), opts] end def the_node_you_are_looking_for?(node) # we could also check that the block is going to the right method # respond_to?(node.children.first.children[1]) && # method(node.children.first.children[1]) == method(:evaluate_ruby) # however that does not work for expect { ... }.on_client_to ... # because now the block is being sent to expect... so we could # check the above OR node.children.first.children[1] == :expect # but what if there are two blocks? on and on... node.type == :block && node.children.first.class == Parser::AST::Node && node.children.first.type == :send end def opal_compile(str) Opal.hyperspec_compile(str, arity_check: client_options[:arity_check]) end end end end