You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

proto_world.rb 7.3 kB

2 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. # frozen_string_literal: true
  2. require 'cucumber/gherkin/formatter/ansi_escapes'
  3. require 'cucumber/core/test/data_table'
  4. require 'cucumber/deprecate'
  5. require 'mini_mime'
  6. module Cucumber
  7. module Glue
  8. # Defines the basic API methods availlable in all Cucumber step definitions.
  9. #
  10. # You can, and probably should, extend this API with your own methods that
  11. # make sense in your domain. For more on that, see {Cucumber::Glue::Dsl#World}
  12. module ProtoWorld
  13. # Run a single Gherkin step
  14. # @example Call another step
  15. # step "I am logged in"
  16. # @example Call a step with quotes in the name
  17. # step %{the user "Dave" is logged in}
  18. # @example Passing a table
  19. # step "the following users exist:", table(%{
  20. # | name | email |
  21. # | Matt | matt@matt.com |
  22. # | Aslak | aslak@aslak.com |
  23. # })
  24. # @example Passing a multiline string
  25. # step "the email should contain:", "Dear sir,\nYou've won a prize!\n"
  26. # @param [String] name The name of the step
  27. # @param [String,Cucumber::Test::DocString,Cucumber::Ast::Table] multiline_argument
  28. def step(name, raw_multiline_arg = nil)
  29. super
  30. end
  31. # Run a snippet of Gherkin
  32. # @example
  33. # steps %{
  34. # Given the user "Susan" exists
  35. # And I am logged in as "Susan"
  36. # }
  37. # @param [String] steps_text The Gherkin snippet to run
  38. def steps(steps_text)
  39. super
  40. end
  41. # Parse Gherkin into a {Cucumber::Ast::Table} object.
  42. #
  43. # Useful in conjunction with the #step method.
  44. # @example Create a table
  45. # users = table(%{
  46. # | name | email |
  47. # | Matt | matt@matt.com |
  48. # | Aslak | aslak@aslak.com |
  49. # })
  50. # @param [String] text_or_table The Gherkin string that represents the table
  51. # Returns a Cucumber::MultilineArgument::DataTable for +text_or_table+, which can either
  52. # be a String:
  53. #
  54. # table(%{
  55. # | account | description | amount |
  56. # | INT-100 | Taxi | 114 |
  57. # | CUC-101 | Peeler | 22 |
  58. # })
  59. #
  60. # or a 2D Array:
  61. #
  62. # table([
  63. # %w{ account description amount },
  64. # %w{ INT-100 Taxi 114 },
  65. # %w{ CUC-101 Peeler 22 }
  66. # ])
  67. #
  68. def table(text_or_table)
  69. MultilineArgument::DataTable.from(text_or_table)
  70. end
  71. # Pause the tests and ask the operator for input
  72. def ask(question, timeout_seconds = 60)
  73. super
  74. end
  75. def log(*messages)
  76. messages.each { |message| attach(message.to_s.dup, 'text/x.cucumber.log+plain') }
  77. end
  78. # Attach a file to the output
  79. # @param file [string|io] the file to attach.
  80. # It can be a string containing the file content itself,
  81. # the file path, or an IO ready to be read.
  82. # @param media_type [string] the media type. If file is a valid path,
  83. # media_type can be ommitted, it will then be inferred from the file name.
  84. def attach(file, media_type = nil)
  85. return super unless File.file?(file)
  86. content = File.read(file, mode: 'rb')
  87. media_type = MiniMime.lookup_by_filename(file)&.content_type if media_type.nil?
  88. super(content, media_type.to_s)
  89. rescue StandardError
  90. super
  91. end
  92. # Mark the matched step as pending.
  93. def pending(message = 'TODO')
  94. raise Pending, message unless block_given?
  95. begin
  96. yield
  97. rescue Exception # rubocop:disable Lint/RescueException
  98. raise Pending, message
  99. end
  100. raise Pending, "Expected pending '#{message}' to fail. No Error was raised. No longer pending?"
  101. end
  102. # Skips this step and the remaining steps in the scenario
  103. def skip_this_scenario(message = 'Scenario skipped')
  104. raise Core::Test::Result::Skipped, message
  105. end
  106. # Prints the list of modules that are included in the World
  107. def inspect
  108. super
  109. end
  110. # see {#inspect}
  111. def to_s
  112. inspect
  113. end
  114. # Dynamially generate the API module, closuring the dependencies
  115. def self.for(runtime, language) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
  116. Module.new do # rubocop:disable Metrics/BlockLength
  117. def self.extended(object)
  118. # wrap the dynamically generated module so that we can document the methods
  119. # for yardoc, which doesn't like define_method.
  120. object.extend(ProtoWorld)
  121. end
  122. # TODO: pass these in when building the module, instead of mutating them later
  123. # Extend the World with user-defined modules
  124. def add_modules!(world_modules, namespaced_world_modules)
  125. add_world_modules!(world_modules) if world_modules.any?
  126. add_namespaced_modules!(namespaced_world_modules) if namespaced_world_modules.any?
  127. end
  128. define_method(:step) do |name, raw_multiline_arg = nil|
  129. location = Core::Test::Location.of_caller
  130. runtime.invoke_dynamic_step(name, MultilineArgument.from(raw_multiline_arg, location))
  131. end
  132. define_method(:steps) do |steps_text|
  133. location = Core::Test::Location.of_caller
  134. runtime.invoke_dynamic_steps(steps_text, language, location)
  135. end
  136. define_method(:ask) do |question, timeout_seconds = 60|
  137. runtime.ask(question, timeout_seconds)
  138. end
  139. define_method(:attach) do |file, media_type|
  140. runtime.attach(file, media_type)
  141. end
  142. # Prints the list of modules that are included in the World
  143. def inspect
  144. modules = [self.class]
  145. (class << self; self; end).instance_eval do
  146. modules += included_modules
  147. end
  148. modules << stringify_namespaced_modules
  149. format('#<%<modules>s:0x%<object_id>x>', modules: modules.join('+'), object_id: object_id)
  150. end
  151. private
  152. # @private
  153. def add_world_modules!(modules)
  154. modules.each do |world_module|
  155. extend(world_module)
  156. end
  157. end
  158. # @private
  159. def add_namespaced_modules!(modules)
  160. @__namespaced_modules = modules
  161. modules.each do |namespace, world_modules|
  162. world_modules.each do |world_module|
  163. variable_name = "@__#{namespace}_world"
  164. inner_world = instance_variable_get(variable_name) || Object.new
  165. instance_variable_set(
  166. variable_name,
  167. inner_world.extend(world_module)
  168. )
  169. self.class.send(:define_method, namespace) do
  170. instance_variable_get(variable_name)
  171. end
  172. end
  173. end
  174. end
  175. # @private
  176. def stringify_namespaced_modules
  177. return '' if @__namespaced_modules.nil?
  178. @__namespaced_modules.map { |k, v| "#{v.join(',')} (as #{k})" }.join('+')
  179. end
  180. end
  181. end
  182. # @private
  183. AnsiEscapes = Cucumber::Gherkin::Formatter::AnsiEscapes
  184. end
  185. end
  186. end

No Description

Contributors (1)