lib/remarkable/dsl/assertions.rb in remarkable-3.1.8 vs lib/remarkable/dsl/assertions.rb in remarkable-3.1.9
- old
+ new
@@ -1,413 +1,413 @@
-module Remarkable
- module DSL
- # This module is responsable to create a basic matcher structure using a DSL.
- #
- # A matcher that checks if an element is included in an array can be done
- # just with:
- #
- # class IncludedMatcher < Remarkable::Base
- # arguments :value
- # assertion :is_included?
- #
- # protected
- # def is_included?
- # @subject.include?(@value)
- # end
- # end
- #
- # As you have noticed, the DSL also allows you to remove the messages from
- # matcher. Since it will look for it on I18n yml file.
- #
- # If you want to create a matcher that accepts multile values to be tested,
- # you just need to do:
- #
- # class IncludedMatcher < Remarkable::Base
- # arguments :collection => :values, :as => :value
- # collection_assertion :is_included?
- #
- # protected
- # def is_included?
- # @subject.include?(@value)
- # end
- # end
- #
- # Notice that the :is_included? logic didn't have to change, because Remarkable
- # handle this automatically for you.
- #
- module Assertions
-
- def self.included(base) # :nodoc:
- base.extend ClassMethods
- end
-
- module ClassMethods
-
- protected
-
- # It sets the arguments your matcher receives on initialization.
- #
- # == Options
- #
- # * <tt>:collection</tt> - if a collection is expected.
- # * <tt>:as</tt> - how each item of the collection will be available.
- # * <tt>:block</tt> - tell the matcher can receive blocks as argument and store
- # them under the variable given.
- #
- # Note: the expected block cannot have arity 1. This is already reserved
- # for macro configuration.
- #
- # == Examples
- #
- # Let's see for each example how the arguments declarion reflects on
- # the matcher API:
- #
- # arguments :assign
- # # Can be called as:
- # #=> should_assign :task
- # #=> should_assign :task, :with => Task.new
- #
- # This is roughly the same as:
- #
- # def initialize(assign, options = {})
- # @assign = name
- # @options = options
- # end
- #
- # As you noticed, a matcher can always receive options on initialization.
- # If you have a matcher that accepts only options, for example,
- # have_default_scope you just need to call <tt>arguments</tt>:
- #
- # arguments
- # # Can be called as:
- # #=> should_have_default_scope :limit => 10
- #
- # arguments :collection => :assigns, :as => :assign
- # # Can be called as:
- # #=> should_assign :task1, :task2
- # #=> should_assign :task1, :task2, :with => Task.new
- #
- # arguments :collection => :assigns, :as => :assign, :block => :buildeer
- # # Can be called as:
- # #=> should_assign :task1, :task2
- # #=> should_assign(:task1, :task2){ Task.new }
- #
- # The block will be available under the instance variable @builder.
- #
- # == I18n
- #
- # All the parameters given to arguments are available for interpolation
- # in I18n. So if you have the following declarion:
- #
- # class InRange < Remarkable::Base
- # arguments :range, :collection => :names, :as => :name
- #
- # You will have {{range}}, {{names}} and {{name}} available for I18n
- # messages:
- #
- # in_range:
- # description: "have {{names}} to be on range {{range}}"
- #
- # Before a collection is sent to I18n, it's transformed to a sentence.
- # So if the following matcher:
- #
- # in_range(2..20, :username, :password)
- #
- # Has the following description:
- #
- # "should have username and password in range 2..20"
- #
- def arguments(*names)
- options = names.extract_options!
- args = names.dup
-
- @matcher_arguments[:names] = names
-
- if collection = options.delete(:collection)
- @matcher_arguments[:collection] = collection
-
- if options[:as]
- @matcher_arguments[:as] = options.delete(:as)
- else
- raise ArgumentError, 'You gave me :collection as option but have not give me :as as well'
- end
-
- args << "*#{collection}"
- get_options = "#{collection}.extract_options!"
- set_collection = "@#{collection} = #{collection}"
- else
- args << 'options={}'
- get_options = 'options'
- set_collection = ''
- end
-
- if block = options.delete(:block)
- block = :block unless block.is_a?(Symbol)
- @matcher_arguments[:block] = block
- end
-
- # Blocks are always appended. If they have arity 1, they are used for
- # macro configuration, otherwise, they are stored in the :block variable.
- #
- args << "&block"
-
- assignments = names.map do |name|
- "@#{name} = #{name}"
- end.join("\n ")
-
- class_eval <<-END, __FILE__, __LINE__
- def initialize(#{args.join(',')})
- _builder, block = block, nil if block && block.arity == 1
- #{assignments}
- #{"@#{block} = block" if block}
- @options = default_options.merge(#{get_options})
- #{set_collection}
- run_after_initialize_callbacks
- _builder.call(self) if _builder
- end
- END
- end
-
- # Declare the assertions that are runned for each element in the collection.
- # It must be used with <tt>arguments</tt> methods in order to work properly.
- #
- # == Examples
- #
- # The example given in <tt>assertions</tt> can be transformed to
- # accept a collection just doing:
- #
- # class IncludedMatcher < Remarkable::Base
- # arguments :collection => :values, :as => :value
- # collection_assertion :is_included?
- #
- # protected
- # def is_included?
- # @subject.include?(@value)
- # end
- # end
- #
- # All further consideration done in <tt>assertions</tt> are also valid here.
- #
- def collection_assertions(*methods, &block)
- define_method methods.last, &block if block_given?
- @matcher_collection_assertions += methods
- end
- alias :collection_assertion :collection_assertions
-
- # Declares the assertions that are run once per matcher.
- #
- # == Examples
- #
- # A matcher that checks if an element is included in an array can be done
- # just with:
- #
- # class IncludedMatcher < Remarkable::Base
- # arguments :value
- # assertion :is_included?
- #
- # protected
- # def is_included?
- # @subject.include?(@value)
- # end
- # end
- #
- # Whenever the matcher is called, the :is_included? action is automatically
- # triggered. Each assertion must return true or false. In case it's false
- # it will seach for an expectation message on the I18n file. In this
- # case, the error message would be on:
- #
- # included:
- # description: "check {{value}} is included in the array"
- # expectations:
- # is_included: "{{value}} is included in the array"
- #
- # In case of failure, it will output:
- #
- # "Expected {{value}} is included in the array"
- #
- # Notice that on the yml file the question mark is removed for readability.
- #
- # == Shortcut declaration
- #
- # You can shortcut declaration by giving a name and block to assertion
- # method:
- #
- # class IncludedMatcher < Remarkable::Base
- # arguments :value
- #
- # assertion :is_included? do
- # @subject.include?(@value)
- # end
- # end
- #
- def assertions(*methods, &block)
- if block_given?
- define_method methods.last, &block
- protected methods.last
- end
-
- @matcher_single_assertions += methods
- end
- alias :assertion :assertions
-
- # Class method that accepts a block or a hash to set matcher's default
- # options. It's called on matcher initialization and stores the default
- # value in the @options instance variable.
- #
- # == Examples
- #
- # default_options do
- # { :name => @subject.name }
- # end
- #
- # default_options :message => :invalid
- #
- def default_options(hash = {}, &block)
- if block_given?
- define_method :default_options, &block
- else
- class_eval "def default_options; #{hash.inspect}; end"
- end
- end
- end
-
- # This method is responsable for connecting <tt>arguments</tt>, <tt>assertions</tt>
- # and <tt>collection_assertions</tt>.
- #
- # It's the one that executes the assertions once, executes the collection
- # assertions for each element in the collection and also responsable to set
- # the I18n messages.
- #
- def matches?(subject)
- @subject = subject
-
+module Remarkable
+ module DSL
+ # This module is responsable to create a basic matcher structure using a DSL.
+ #
+ # A matcher that checks if an element is included in an array can be done
+ # just with:
+ #
+ # class IncludedMatcher < Remarkable::Base
+ # arguments :value
+ # assertion :is_included?
+ #
+ # protected
+ # def is_included?
+ # @subject.include?(@value)
+ # end
+ # end
+ #
+ # As you have noticed, the DSL also allows you to remove the messages from
+ # matcher. Since it will look for it on I18n yml file.
+ #
+ # If you want to create a matcher that accepts multile values to be tested,
+ # you just need to do:
+ #
+ # class IncludedMatcher < Remarkable::Base
+ # arguments :collection => :values, :as => :value
+ # collection_assertion :is_included?
+ #
+ # protected
+ # def is_included?
+ # @subject.include?(@value)
+ # end
+ # end
+ #
+ # Notice that the :is_included? logic didn't have to change, because Remarkable
+ # handle this automatically for you.
+ #
+ module Assertions
+
+ def self.included(base) # :nodoc:
+ base.extend ClassMethods
+ end
+
+ module ClassMethods
+
+ protected
+
+ # It sets the arguments your matcher receives on initialization.
+ #
+ # == Options
+ #
+ # * <tt>:collection</tt> - if a collection is expected.
+ # * <tt>:as</tt> - how each item of the collection will be available.
+ # * <tt>:block</tt> - tell the matcher can receive blocks as argument and store
+ # them under the variable given.
+ #
+ # Note: the expected block cannot have arity 1. This is already reserved
+ # for macro configuration.
+ #
+ # == Examples
+ #
+ # Let's see for each example how the arguments declarion reflects on
+ # the matcher API:
+ #
+ # arguments :assign
+ # # Can be called as:
+ # #=> should_assign :task
+ # #=> should_assign :task, :with => Task.new
+ #
+ # This is roughly the same as:
+ #
+ # def initialize(assign, options = {})
+ # @assign = name
+ # @options = options
+ # end
+ #
+ # As you noticed, a matcher can always receive options on initialization.
+ # If you have a matcher that accepts only options, for example,
+ # have_default_scope you just need to call <tt>arguments</tt>:
+ #
+ # arguments
+ # # Can be called as:
+ # #=> should_have_default_scope :limit => 10
+ #
+ # arguments :collection => :assigns, :as => :assign
+ # # Can be called as:
+ # #=> should_assign :task1, :task2
+ # #=> should_assign :task1, :task2, :with => Task.new
+ #
+ # arguments :collection => :assigns, :as => :assign, :block => :buildeer
+ # # Can be called as:
+ # #=> should_assign :task1, :task2
+ # #=> should_assign(:task1, :task2){ Task.new }
+ #
+ # The block will be available under the instance variable @builder.
+ #
+ # == I18n
+ #
+ # All the parameters given to arguments are available for interpolation
+ # in I18n. So if you have the following declarion:
+ #
+ # class InRange < Remarkable::Base
+ # arguments :range, :collection => :names, :as => :name
+ #
+ # You will have {{range}}, {{names}} and {{name}} available for I18n
+ # messages:
+ #
+ # in_range:
+ # description: "have {{names}} to be on range {{range}}"
+ #
+ # Before a collection is sent to I18n, it's transformed to a sentence.
+ # So if the following matcher:
+ #
+ # in_range(2..20, :username, :password)
+ #
+ # Has the following description:
+ #
+ # "should have username and password in range 2..20"
+ #
+ def arguments(*names)
+ options = names.extract_options!
+ args = names.dup
+
+ @matcher_arguments[:names] = names
+
+ if collection = options.delete(:collection)
+ @matcher_arguments[:collection] = collection
+
+ if options[:as]
+ @matcher_arguments[:as] = options.delete(:as)
+ else
+ raise ArgumentError, 'You gave me :collection as option but have not give me :as as well'
+ end
+
+ args << "*#{collection}"
+ get_options = "#{collection}.extract_options!"
+ set_collection = "@#{collection} = #{collection}"
+ else
+ args << 'options={}'
+ get_options = 'options'
+ set_collection = ''
+ end
+
+ if block = options.delete(:block)
+ block = :block unless block.is_a?(Symbol)
+ @matcher_arguments[:block] = block
+ end
+
+ # Blocks are always appended. If they have arity 1, they are used for
+ # macro configuration, otherwise, they are stored in the :block variable.
+ #
+ args << "&block"
+
+ assignments = names.map do |name|
+ "@#{name} = #{name}"
+ end.join("\n ")
+
+ class_eval <<-END, __FILE__, __LINE__
+ def initialize(#{args.join(',')})
+ _builder, block = block, nil if block && block.arity == 1
+ #{assignments}
+ #{"@#{block} = block" if block}
+ @options = default_options.merge(#{get_options})
+ #{set_collection}
+ run_after_initialize_callbacks
+ _builder.call(self) if _builder
+ end
+ END
+ end
+
+ # Declare the assertions that are runned for each element in the collection.
+ # It must be used with <tt>arguments</tt> methods in order to work properly.
+ #
+ # == Examples
+ #
+ # The example given in <tt>assertions</tt> can be transformed to
+ # accept a collection just doing:
+ #
+ # class IncludedMatcher < Remarkable::Base
+ # arguments :collection => :values, :as => :value
+ # collection_assertion :is_included?
+ #
+ # protected
+ # def is_included?
+ # @subject.include?(@value)
+ # end
+ # end
+ #
+ # All further consideration done in <tt>assertions</tt> are also valid here.
+ #
+ def collection_assertions(*methods, &block)
+ define_method methods.last, &block if block_given?
+ @matcher_collection_assertions += methods
+ end
+ alias :collection_assertion :collection_assertions
+
+ # Declares the assertions that are run once per matcher.
+ #
+ # == Examples
+ #
+ # A matcher that checks if an element is included in an array can be done
+ # just with:
+ #
+ # class IncludedMatcher < Remarkable::Base
+ # arguments :value
+ # assertion :is_included?
+ #
+ # protected
+ # def is_included?
+ # @subject.include?(@value)
+ # end
+ # end
+ #
+ # Whenever the matcher is called, the :is_included? action is automatically
+ # triggered. Each assertion must return true or false. In case it's false
+ # it will seach for an expectation message on the I18n file. In this
+ # case, the error message would be on:
+ #
+ # included:
+ # description: "check {{value}} is included in the array"
+ # expectations:
+ # is_included: "{{value}} is included in the array"
+ #
+ # In case of failure, it will output:
+ #
+ # "Expected {{value}} is included in the array"
+ #
+ # Notice that on the yml file the question mark is removed for readability.
+ #
+ # == Shortcut declaration
+ #
+ # You can shortcut declaration by giving a name and block to assertion
+ # method:
+ #
+ # class IncludedMatcher < Remarkable::Base
+ # arguments :value
+ #
+ # assertion :is_included? do
+ # @subject.include?(@value)
+ # end
+ # end
+ #
+ def assertions(*methods, &block)
+ if block_given?
+ define_method methods.last, &block
+ protected methods.last
+ end
+
+ @matcher_single_assertions += methods
+ end
+ alias :assertion :assertions
+
+ # Class method that accepts a block or a hash to set matcher's default
+ # options. It's called on matcher initialization and stores the default
+ # value in the @options instance variable.
+ #
+ # == Examples
+ #
+ # default_options do
+ # { :name => @subject.name }
+ # end
+ #
+ # default_options :message => :invalid
+ #
+ def default_options(hash = {}, &block)
+ if block_given?
+ define_method :default_options, &block
+ else
+ class_eval "def default_options; #{hash.inspect}; end"
+ end
+ end
+ end
+
+ # This method is responsable for connecting <tt>arguments</tt>, <tt>assertions</tt>
+ # and <tt>collection_assertions</tt>.
+ #
+ # It's the one that executes the assertions once, executes the collection
+ # assertions for each element in the collection and also responsable to set
+ # the I18n messages.
+ #
+ def matches?(subject)
+ @subject = subject
+
run_before_assert_callbacks
assertions = self.class.matcher_single_assertions
unless assertions.empty?
value = send_methods_and_generate_message(assertions)
return negative? if positive? == !value
end
- matches_collection_assertions?
- end
+ matches_collection_assertions?
+ end
- protected
-
- # You can overwrite this instance method to provide default options on
- # initialization.
- #
- def default_options
- {}
- end
-
- # Overwrites default_i18n_options to provide arguments and optionals
- # to interpolation options.
- #
- # If you still need to provide more other interpolation options, you can
- # do that in two ways:
- #
- # 1. Overwrite interpolation_options:
- #
- # def interpolation_options
- # { :real_value => real_value }
- # end
- #
- # 2. Return a hash from your assertion method:
- #
- # def my_assertion
- # return true if real_value == expected_value
- # return false, :real_value => real_value
- # end
- #
- # In both cases, :real_value will be available as interpolation option.
- #
- def default_i18n_options #:nodoc:
- i18n_options = {}
-
- @options.each do |key, value|
- i18n_options[key] = value.inspect
- end if @options
-
- # Also add arguments as interpolation options.
- self.class.matcher_arguments[:names].each do |name|
- i18n_options[name] = instance_variable_get("@#{name}").inspect
- end
-
- # Add collection interpolation options.
- i18n_options.update(collection_interpolation)
-
- # Add default options (highest priority). They should not be overwritten.
- i18n_options.update(super)
- end
-
- # Method responsible to add collection as interpolation.
- #
- def collection_interpolation #:nodoc:
- options = {}
-
- if collection_name = self.class.matcher_arguments[:collection]
- collection_name = collection_name.to_sym
- collection = instance_variable_get("@#{collection_name}")
- options[collection_name] = array_to_sentence(collection) if collection
-
- object_name = self.class.matcher_arguments[:as].to_sym
- object = instance_variable_get("@#{object_name}")
- options[object_name] = object if object
- end
-
- options
- end
-
- # Send the assertion methods given and create a expectation message
- # if any of those methods returns false.
- #
- # Since most assertion methods ends with an question mark and it's not
- # readable in yml files, we remove question and exclation marks at the
- # end of the method name before translating it. So if you have a method
- # called is_valid? on I18n yml file we will check for a key :is_valid.
- #
- def send_methods_and_generate_message(methods) #:nodoc:
- methods.each do |method|
- bool, hash = send(method)
-
- if positive? == !bool
- parent_scope = matcher_i18n_scope.split('.')
+ protected
+
+ # You can overwrite this instance method to provide default options on
+ # initialization.
+ #
+ def default_options
+ {}
+ end
+
+ # Overwrites default_i18n_options to provide arguments and optionals
+ # to interpolation options.
+ #
+ # If you still need to provide more other interpolation options, you can
+ # do that in two ways:
+ #
+ # 1. Overwrite interpolation_options:
+ #
+ # def interpolation_options
+ # { :real_value => real_value }
+ # end
+ #
+ # 2. Return a hash from your assertion method:
+ #
+ # def my_assertion
+ # return true if real_value == expected_value
+ # return false, :real_value => real_value
+ # end
+ #
+ # In both cases, :real_value will be available as interpolation option.
+ #
+ def default_i18n_options #:nodoc:
+ i18n_options = {}
+
+ @options.each do |key, value|
+ i18n_options[key] = value.inspect
+ end if @options
+
+ # Also add arguments as interpolation options.
+ self.class.matcher_arguments[:names].each do |name|
+ i18n_options[name] = instance_variable_get("@#{name}").inspect
+ end
+
+ # Add collection interpolation options.
+ i18n_options.update(collection_interpolation)
+
+ # Add default options (highest priority). They should not be overwritten.
+ i18n_options.update(super)
+ end
+
+ # Method responsible to add collection as interpolation.
+ #
+ def collection_interpolation #:nodoc:
+ options = {}
+
+ if collection_name = self.class.matcher_arguments[:collection]
+ collection_name = collection_name.to_sym
+ collection = instance_variable_get("@#{collection_name}")
+ options[collection_name] = array_to_sentence(collection) if collection
+
+ object_name = self.class.matcher_arguments[:as].to_sym
+ object = instance_variable_get("@#{object_name}")
+ options[object_name] = object if object
+ end
+
+ options
+ end
+
+ # Send the assertion methods given and create a expectation message
+ # if any of those methods returns false.
+ #
+ # Since most assertion methods ends with an question mark and it's not
+ # readable in yml files, we remove question and exclation marks at the
+ # end of the method name before translating it. So if you have a method
+ # called is_valid? on I18n yml file we will check for a key :is_valid.
+ #
+ def send_methods_and_generate_message(methods) #:nodoc:
+ methods.each do |method|
+ bool, hash = send(method)
+
+ if positive? == !bool
+ parent_scope = matcher_i18n_scope.split('.')
matcher_name = parent_scope.pop
method_name = method.to_s.gsub(/(\?|\!)$/, '')
lookup = []
lookup << :"#{matcher_name}.negative_expectations.#{method_name}" if negative?
lookup << :"#{matcher_name}.expectations.#{method_name}"
lookup << :"negative_expectations.#{method_name}" if negative?
lookup << :"expectations.#{method_name}"
-
- hash = { :scope => parent_scope, :default => lookup }.merge(hash || {})
- @expectation ||= Remarkable.t lookup.shift, default_i18n_options.merge(hash)
-
- return negative?
- end
- end
-
- return positive?
- end
+ hash = { :scope => parent_scope, :default => lookup }.merge(hash || {})
+ @expectation ||= Remarkable.t lookup.shift, default_i18n_options.merge(hash)
+
+ return negative?
+ end
+ end
+
+ return positive?
+ end
+
def matches_single_assertions? #:nodoc:
assertions = self.class.matcher_single_assertions
send_methods_and_generate_message(assertions)
end
def matches_collection_assertions? #:nodoc:
arguments = self.class.matcher_arguments
assertions = self.class.matcher_collection_assertions
collection = instance_variable_get("@#{self.class.matcher_arguments[:collection]}") || []
- assert_collection(nil, collection) do |value|
- instance_variable_set("@#{arguments[:as]}", value)
- send_methods_and_generate_message(assertions)
+ assert_collection(nil, collection) do |value|
+ instance_variable_set("@#{arguments[:as]}", value)
+ send_methods_and_generate_message(assertions)
end
end
-
-
- end
- end
-end
+
+
+ end
+ end
+end