remarkable 3.1.8 → 3.1.9

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.
@@ -1,19 +1,19 @@
1
- # Load core files
2
- dir = File.dirname(__FILE__)
3
- require File.join(dir, 'remarkable', 'version')
4
- require File.join(dir, 'remarkable', 'matchers')
5
- require File.join(dir, 'remarkable', 'i18n')
6
- require File.join(dir, 'remarkable', 'dsl')
7
- require File.join(dir, 'remarkable', 'messages')
8
-
9
- require File.join(dir, 'remarkable', 'base')
10
- require File.join(dir, 'remarkable', 'macros')
1
+ # Load core files
2
+ dir = File.dirname(__FILE__)
3
+ require File.join(dir, 'remarkable', 'version')
4
+ require File.join(dir, 'remarkable', 'matchers')
5
+ require File.join(dir, 'remarkable', 'i18n')
6
+ require File.join(dir, 'remarkable', 'dsl')
7
+ require File.join(dir, 'remarkable', 'messages')
8
+
9
+ require File.join(dir, 'remarkable', 'base')
10
+ require File.join(dir, 'remarkable', 'macros')
11
11
  require File.join(dir, 'remarkable', 'pending')
12
- require File.join(dir, 'remarkable', 'negative')
13
- require File.join(dir, 'remarkable', 'core_ext', 'array')
14
-
15
- if defined?(Spec)
16
- require File.join(dir, 'remarkable', 'rspec')
17
- end
18
-
19
- Remarkable.add_locale File.join(dir, '..', 'locale', 'en.yml')
12
+ require File.join(dir, 'remarkable', 'negative')
13
+ require File.join(dir, 'remarkable', 'core_ext', 'array')
14
+
15
+ if defined?(Spec)
16
+ require File.join(dir, 'remarkable', 'rspec')
17
+ end
18
+
19
+ Remarkable.add_locale File.join(dir, '..', 'locale', 'en.yml')
@@ -1,15 +1,15 @@
1
- module Remarkable
2
- # This class holds the basic structure for Remarkable matchers. All matchers
3
- # must inherit from it.
4
- class Base
5
- include Remarkable::Messages
6
- extend Remarkable::DSL
7
-
8
- # Optional to provide spec binding to matchers.
9
- def spec(binding)
10
- @spec = binding
11
- self
12
- end
1
+ module Remarkable
2
+ # This class holds the basic structure for Remarkable matchers. All matchers
3
+ # must inherit from it.
4
+ class Base
5
+ include Remarkable::Messages
6
+ extend Remarkable::DSL
7
+
8
+ # Optional to provide spec binding to matchers.
9
+ def spec(binding)
10
+ @spec = binding
11
+ self
12
+ end
13
13
 
14
14
  def positive?
15
15
  !negative?
@@ -18,25 +18,24 @@ module Remarkable
18
18
  def negative?
19
19
  false
20
20
  end
21
-
22
- protected
23
-
24
- # Returns the subject class unless it's a class object.
25
- def subject_class
26
- nil unless @subject
27
- @subject.is_a?(Class) ? @subject : @subject.class
28
- end
29
-
30
- # Returns the subject name based on its class. If the class respond to
31
- # human_name (which is usually localized) returns it.
32
- def subject_name
33
- nil unless @subject
34
- subject_class.respond_to?(:human_name) ? subject_class.human_name : subject_class.name
35
- end
36
-
37
- # Iterates over the collection given yielding the block and return false
38
- # if any of them also returns false.
39
- def assert_collection(key, collection, inspect=true) #:nodoc:
21
+
22
+ protected
23
+
24
+ # Returns the subject class unless it's a class object.
25
+ def subject_class
26
+ nil unless @subject
27
+ @subject.is_a?(Class) ? @subject : @subject.class
28
+ end
29
+
30
+ # Returns the subject name based on its class.
31
+ def subject_name
32
+ nil unless @subject
33
+ subject_class.name
34
+ end
35
+
36
+ # Iterates over the collection given yielding the block and return false
37
+ # if any of them also returns false.
38
+ def assert_collection(key, collection, inspect=true) #:nodoc:
40
39
  collection.each do |item|
41
40
  value = yield(item)
42
41
 
@@ -47,28 +46,28 @@ module Remarkable
47
46
  else
48
47
  return negative?
49
48
  end
50
- end
51
- end
52
- positive?
53
- end
54
-
55
- # Asserts that the given collection contains item x. If x is a regular
56
- # expression, ensure that at least one element from the collection matches x.
57
- #
58
- # assert_contains(['a', '1'], /\d/) => passes
59
- # assert_contains(['a', '1'], 'a') => passes
60
- # assert_contains(['a', '1'], /not there/) => fails
61
- #
62
- def assert_contains(collection, x)
63
- collection = [collection] unless collection.is_a?(Array)
64
-
65
- case x
66
- when Regexp
67
- collection.detect { |e| e =~ x }
68
- else
69
- collection.include?(x)
70
- end
71
- end
72
-
73
- end
74
- end
49
+ end
50
+ end
51
+ positive?
52
+ end
53
+
54
+ # Asserts that the given collection contains item x. If x is a regular
55
+ # expression, ensure that at least one element from the collection matches x.
56
+ #
57
+ # assert_contains(['a', '1'], /\d/) => passes
58
+ # assert_contains(['a', '1'], 'a') => passes
59
+ # assert_contains(['a', '1'], /not there/) => fails
60
+ #
61
+ def assert_contains(collection, x)
62
+ collection = [collection] unless collection.is_a?(Array)
63
+
64
+ case x
65
+ when Regexp
66
+ collection.detect { |e| e =~ x }
67
+ else
68
+ collection.include?(x)
69
+ end
70
+ end
71
+
72
+ end
73
+ end
@@ -1,19 +1,19 @@
1
- module Remarkable # :nodoc:
2
- module CoreExt
3
- module Array
4
- # Extracts options from a set of arguments. Removes and returns the last
5
- # element in the array if it's a hash, otherwise returns a blank hash.
6
- #
7
- # def options(*args)
8
- # args.extract_options!
9
- # end
10
- #
11
- # options(1, 2) # => {}
12
- # options(1, 2, :a => :b) # => {:a=>:b}
13
- def extract_options!
14
- last.is_a?(::Hash) ? pop : {}
15
- end
16
- end
17
- end
18
- end
19
- Array.send :include, Remarkable::CoreExt::Array
1
+ module Remarkable # :nodoc:
2
+ module CoreExt
3
+ module Array
4
+ # Extracts options from a set of arguments. Removes and returns the last
5
+ # element in the array if it's a hash, otherwise returns a blank hash.
6
+ #
7
+ # def options(*args)
8
+ # args.extract_options!
9
+ # end
10
+ #
11
+ # options(1, 2) # => {}
12
+ # options(1, 2, :a => :b) # => {:a=>:b}
13
+ def extract_options!
14
+ last.is_a?(::Hash) ? pop : {}
15
+ end
16
+ end
17
+ end
18
+ end
19
+ Array.send :include, Remarkable::CoreExt::Array
@@ -1,8 +1,8 @@
1
- dir = File.dirname(__FILE__)
2
- require File.join(dir, 'dsl', 'assertions')
3
- require File.join(dir, 'dsl', 'optionals')
4
- require File.join(dir, 'dsl', 'callbacks')
5
-
1
+ dir = File.dirname(__FILE__)
2
+ require File.join(dir, 'dsl', 'assertions')
3
+ require File.join(dir, 'dsl', 'optionals')
4
+ require File.join(dir, 'dsl', 'callbacks')
5
+
6
6
  module Remarkable
7
7
  # The DSL module is responsable for all Remarkable convenience methods.
8
8
  # It has three main submodules:
@@ -15,41 +15,41 @@ module Remarkable
15
15
  #
16
16
  # * <tt>Optionals</tt> - add an optionals DSL, which is also used for the auto configuring blocks
17
17
  # and dynamic descriptions.
18
- #
19
- module DSL
18
+ #
19
+ module DSL
20
20
  ATTR_READERS = [
21
21
  :matcher_arguments,
22
22
  :matcher_optionals,
23
23
  :matcher_optionals_splat,
24
24
  :matcher_optionals_block,
25
- :matcher_single_assertions,
25
+ :matcher_single_assertions,
26
26
  :matcher_collection_assertions,
27
27
  :before_assert_callbacks,
28
28
  :after_initialize_callbacks
29
- ] unless self.const_defined?(:ATTR_READERS)
30
-
31
- def self.extended(base) #:nodoc:
32
- base.send :include, Assertions
33
- base.send :include, Callbacks
34
- base.send :include, Optionals
35
-
36
- # Initialize matcher_arguments hash with names as an empty array
37
- base.instance_variable_set('@matcher_arguments', { :names => [] })
38
- end
39
-
40
- # Make Remarkable::Base DSL inheritable.
41
- #
42
- def inherited(base) #:nodoc:
43
- base.class_eval do
44
- class << self
45
- attr_reader *ATTR_READERS
46
- end
47
- end
48
-
49
- ATTR_READERS.each do |attr|
50
- current_value = self.instance_variable_get("@#{attr}")
51
- base.instance_variable_set("@#{attr}", current_value ? current_value.dup : [])
52
- end
53
- end
54
- end
55
- end
29
+ ] unless self.const_defined?(:ATTR_READERS)
30
+
31
+ def self.extended(base) #:nodoc:
32
+ base.send :include, Assertions
33
+ base.send :include, Callbacks
34
+ base.send :include, Optionals
35
+
36
+ # Initialize matcher_arguments hash with names as an empty array
37
+ base.instance_variable_set('@matcher_arguments', { :names => [] })
38
+ end
39
+
40
+ # Make Remarkable::Base DSL inheritable.
41
+ #
42
+ def inherited(base) #:nodoc:
43
+ base.class_eval do
44
+ class << self
45
+ attr_reader *ATTR_READERS
46
+ end
47
+ end
48
+
49
+ ATTR_READERS.each do |attr|
50
+ current_value = self.instance_variable_get("@#{attr}")
51
+ base.instance_variable_set("@#{attr}", current_value ? current_value.dup : [])
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,285 +1,285 @@
1
- module Remarkable
2
- module DSL
3
- # This module is responsable to create a basic matcher structure using a DSL.
4
- #
5
- # A matcher that checks if an element is included in an array can be done
6
- # just with:
7
- #
8
- # class IncludedMatcher < Remarkable::Base
9
- # arguments :value
10
- # assertion :is_included?
11
- #
12
- # protected
13
- # def is_included?
14
- # @subject.include?(@value)
15
- # end
16
- # end
17
- #
18
- # As you have noticed, the DSL also allows you to remove the messages from
19
- # matcher. Since it will look for it on I18n yml file.
20
- #
21
- # If you want to create a matcher that accepts multile values to be tested,
22
- # you just need to do:
23
- #
24
- # class IncludedMatcher < Remarkable::Base
25
- # arguments :collection => :values, :as => :value
26
- # collection_assertion :is_included?
27
- #
28
- # protected
29
- # def is_included?
30
- # @subject.include?(@value)
31
- # end
32
- # end
33
- #
34
- # Notice that the :is_included? logic didn't have to change, because Remarkable
35
- # handle this automatically for you.
36
- #
37
- module Assertions
38
-
39
- def self.included(base) # :nodoc:
40
- base.extend ClassMethods
41
- end
42
-
43
- module ClassMethods
44
-
45
- protected
46
-
47
- # It sets the arguments your matcher receives on initialization.
48
- #
49
- # == Options
50
- #
51
- # * <tt>:collection</tt> - if a collection is expected.
52
- # * <tt>:as</tt> - how each item of the collection will be available.
53
- # * <tt>:block</tt> - tell the matcher can receive blocks as argument and store
54
- # them under the variable given.
55
- #
56
- # Note: the expected block cannot have arity 1. This is already reserved
57
- # for macro configuration.
58
- #
59
- # == Examples
60
- #
61
- # Let's see for each example how the arguments declarion reflects on
62
- # the matcher API:
63
- #
64
- # arguments :assign
65
- # # Can be called as:
66
- # #=> should_assign :task
67
- # #=> should_assign :task, :with => Task.new
68
- #
69
- # This is roughly the same as:
70
- #
71
- # def initialize(assign, options = {})
72
- # @assign = name
73
- # @options = options
74
- # end
75
- #
76
- # As you noticed, a matcher can always receive options on initialization.
77
- # If you have a matcher that accepts only options, for example,
78
- # have_default_scope you just need to call <tt>arguments</tt>:
79
- #
80
- # arguments
81
- # # Can be called as:
82
- # #=> should_have_default_scope :limit => 10
83
- #
84
- # arguments :collection => :assigns, :as => :assign
85
- # # Can be called as:
86
- # #=> should_assign :task1, :task2
87
- # #=> should_assign :task1, :task2, :with => Task.new
88
- #
89
- # arguments :collection => :assigns, :as => :assign, :block => :buildeer
90
- # # Can be called as:
91
- # #=> should_assign :task1, :task2
92
- # #=> should_assign(:task1, :task2){ Task.new }
93
- #
94
- # The block will be available under the instance variable @builder.
95
- #
96
- # == I18n
97
- #
98
- # All the parameters given to arguments are available for interpolation
99
- # in I18n. So if you have the following declarion:
100
- #
101
- # class InRange < Remarkable::Base
102
- # arguments :range, :collection => :names, :as => :name
103
- #
104
- # You will have {{range}}, {{names}} and {{name}} available for I18n
105
- # messages:
106
- #
107
- # in_range:
108
- # description: "have {{names}} to be on range {{range}}"
109
- #
110
- # Before a collection is sent to I18n, it's transformed to a sentence.
111
- # So if the following matcher:
112
- #
113
- # in_range(2..20, :username, :password)
114
- #
115
- # Has the following description:
116
- #
117
- # "should have username and password in range 2..20"
118
- #
119
- def arguments(*names)
120
- options = names.extract_options!
121
- args = names.dup
122
-
123
- @matcher_arguments[:names] = names
124
-
125
- if collection = options.delete(:collection)
126
- @matcher_arguments[:collection] = collection
127
-
128
- if options[:as]
129
- @matcher_arguments[:as] = options.delete(:as)
130
- else
131
- raise ArgumentError, 'You gave me :collection as option but have not give me :as as well'
132
- end
133
-
134
- args << "*#{collection}"
135
- get_options = "#{collection}.extract_options!"
136
- set_collection = "@#{collection} = #{collection}"
137
- else
138
- args << 'options={}'
139
- get_options = 'options'
140
- set_collection = ''
141
- end
142
-
143
- if block = options.delete(:block)
144
- block = :block unless block.is_a?(Symbol)
145
- @matcher_arguments[:block] = block
146
- end
147
-
148
- # Blocks are always appended. If they have arity 1, they are used for
149
- # macro configuration, otherwise, they are stored in the :block variable.
150
- #
151
- args << "&block"
152
-
153
- assignments = names.map do |name|
154
- "@#{name} = #{name}"
155
- end.join("\n ")
156
-
157
- class_eval <<-END, __FILE__, __LINE__
158
- def initialize(#{args.join(',')})
159
- _builder, block = block, nil if block && block.arity == 1
160
- #{assignments}
161
- #{"@#{block} = block" if block}
162
- @options = default_options.merge(#{get_options})
163
- #{set_collection}
164
- run_after_initialize_callbacks
165
- _builder.call(self) if _builder
166
- end
167
- END
168
- end
169
-
170
- # Declare the assertions that are runned for each element in the collection.
171
- # It must be used with <tt>arguments</tt> methods in order to work properly.
172
- #
173
- # == Examples
174
- #
175
- # The example given in <tt>assertions</tt> can be transformed to
176
- # accept a collection just doing:
177
- #
178
- # class IncludedMatcher < Remarkable::Base
179
- # arguments :collection => :values, :as => :value
180
- # collection_assertion :is_included?
181
- #
182
- # protected
183
- # def is_included?
184
- # @subject.include?(@value)
185
- # end
186
- # end
187
- #
188
- # All further consideration done in <tt>assertions</tt> are also valid here.
189
- #
190
- def collection_assertions(*methods, &block)
191
- define_method methods.last, &block if block_given?
192
- @matcher_collection_assertions += methods
193
- end
194
- alias :collection_assertion :collection_assertions
195
-
196
- # Declares the assertions that are run once per matcher.
197
- #
198
- # == Examples
199
- #
200
- # A matcher that checks if an element is included in an array can be done
201
- # just with:
202
- #
203
- # class IncludedMatcher < Remarkable::Base
204
- # arguments :value
205
- # assertion :is_included?
206
- #
207
- # protected
208
- # def is_included?
209
- # @subject.include?(@value)
210
- # end
211
- # end
212
- #
213
- # Whenever the matcher is called, the :is_included? action is automatically
214
- # triggered. Each assertion must return true or false. In case it's false
215
- # it will seach for an expectation message on the I18n file. In this
216
- # case, the error message would be on:
217
- #
218
- # included:
219
- # description: "check {{value}} is included in the array"
220
- # expectations:
221
- # is_included: "{{value}} is included in the array"
222
- #
223
- # In case of failure, it will output:
224
- #
225
- # "Expected {{value}} is included in the array"
226
- #
227
- # Notice that on the yml file the question mark is removed for readability.
228
- #
229
- # == Shortcut declaration
230
- #
231
- # You can shortcut declaration by giving a name and block to assertion
232
- # method:
233
- #
234
- # class IncludedMatcher < Remarkable::Base
235
- # arguments :value
236
- #
237
- # assertion :is_included? do
238
- # @subject.include?(@value)
239
- # end
240
- # end
241
- #
242
- def assertions(*methods, &block)
243
- if block_given?
244
- define_method methods.last, &block
245
- protected methods.last
246
- end
247
-
248
- @matcher_single_assertions += methods
249
- end
250
- alias :assertion :assertions
251
-
252
- # Class method that accepts a block or a hash to set matcher's default
253
- # options. It's called on matcher initialization and stores the default
254
- # value in the @options instance variable.
255
- #
256
- # == Examples
257
- #
258
- # default_options do
259
- # { :name => @subject.name }
260
- # end
261
- #
262
- # default_options :message => :invalid
263
- #
264
- def default_options(hash = {}, &block)
265
- if block_given?
266
- define_method :default_options, &block
267
- else
268
- class_eval "def default_options; #{hash.inspect}; end"
269
- end
270
- end
271
- end
272
-
273
- # This method is responsable for connecting <tt>arguments</tt>, <tt>assertions</tt>
274
- # and <tt>collection_assertions</tt>.
275
- #
276
- # It's the one that executes the assertions once, executes the collection
277
- # assertions for each element in the collection and also responsable to set
278
- # the I18n messages.
279
- #
280
- def matches?(subject)
281
- @subject = subject
282
-
1
+ module Remarkable
2
+ module DSL
3
+ # This module is responsable to create a basic matcher structure using a DSL.
4
+ #
5
+ # A matcher that checks if an element is included in an array can be done
6
+ # just with:
7
+ #
8
+ # class IncludedMatcher < Remarkable::Base
9
+ # arguments :value
10
+ # assertion :is_included?
11
+ #
12
+ # protected
13
+ # def is_included?
14
+ # @subject.include?(@value)
15
+ # end
16
+ # end
17
+ #
18
+ # As you have noticed, the DSL also allows you to remove the messages from
19
+ # matcher. Since it will look for it on I18n yml file.
20
+ #
21
+ # If you want to create a matcher that accepts multile values to be tested,
22
+ # you just need to do:
23
+ #
24
+ # class IncludedMatcher < Remarkable::Base
25
+ # arguments :collection => :values, :as => :value
26
+ # collection_assertion :is_included?
27
+ #
28
+ # protected
29
+ # def is_included?
30
+ # @subject.include?(@value)
31
+ # end
32
+ # end
33
+ #
34
+ # Notice that the :is_included? logic didn't have to change, because Remarkable
35
+ # handle this automatically for you.
36
+ #
37
+ module Assertions
38
+
39
+ def self.included(base) # :nodoc:
40
+ base.extend ClassMethods
41
+ end
42
+
43
+ module ClassMethods
44
+
45
+ protected
46
+
47
+ # It sets the arguments your matcher receives on initialization.
48
+ #
49
+ # == Options
50
+ #
51
+ # * <tt>:collection</tt> - if a collection is expected.
52
+ # * <tt>:as</tt> - how each item of the collection will be available.
53
+ # * <tt>:block</tt> - tell the matcher can receive blocks as argument and store
54
+ # them under the variable given.
55
+ #
56
+ # Note: the expected block cannot have arity 1. This is already reserved
57
+ # for macro configuration.
58
+ #
59
+ # == Examples
60
+ #
61
+ # Let's see for each example how the arguments declarion reflects on
62
+ # the matcher API:
63
+ #
64
+ # arguments :assign
65
+ # # Can be called as:
66
+ # #=> should_assign :task
67
+ # #=> should_assign :task, :with => Task.new
68
+ #
69
+ # This is roughly the same as:
70
+ #
71
+ # def initialize(assign, options = {})
72
+ # @assign = name
73
+ # @options = options
74
+ # end
75
+ #
76
+ # As you noticed, a matcher can always receive options on initialization.
77
+ # If you have a matcher that accepts only options, for example,
78
+ # have_default_scope you just need to call <tt>arguments</tt>:
79
+ #
80
+ # arguments
81
+ # # Can be called as:
82
+ # #=> should_have_default_scope :limit => 10
83
+ #
84
+ # arguments :collection => :assigns, :as => :assign
85
+ # # Can be called as:
86
+ # #=> should_assign :task1, :task2
87
+ # #=> should_assign :task1, :task2, :with => Task.new
88
+ #
89
+ # arguments :collection => :assigns, :as => :assign, :block => :buildeer
90
+ # # Can be called as:
91
+ # #=> should_assign :task1, :task2
92
+ # #=> should_assign(:task1, :task2){ Task.new }
93
+ #
94
+ # The block will be available under the instance variable @builder.
95
+ #
96
+ # == I18n
97
+ #
98
+ # All the parameters given to arguments are available for interpolation
99
+ # in I18n. So if you have the following declarion:
100
+ #
101
+ # class InRange < Remarkable::Base
102
+ # arguments :range, :collection => :names, :as => :name
103
+ #
104
+ # You will have {{range}}, {{names}} and {{name}} available for I18n
105
+ # messages:
106
+ #
107
+ # in_range:
108
+ # description: "have {{names}} to be on range {{range}}"
109
+ #
110
+ # Before a collection is sent to I18n, it's transformed to a sentence.
111
+ # So if the following matcher:
112
+ #
113
+ # in_range(2..20, :username, :password)
114
+ #
115
+ # Has the following description:
116
+ #
117
+ # "should have username and password in range 2..20"
118
+ #
119
+ def arguments(*names)
120
+ options = names.extract_options!
121
+ args = names.dup
122
+
123
+ @matcher_arguments[:names] = names
124
+
125
+ if collection = options.delete(:collection)
126
+ @matcher_arguments[:collection] = collection
127
+
128
+ if options[:as]
129
+ @matcher_arguments[:as] = options.delete(:as)
130
+ else
131
+ raise ArgumentError, 'You gave me :collection as option but have not give me :as as well'
132
+ end
133
+
134
+ args << "*#{collection}"
135
+ get_options = "#{collection}.extract_options!"
136
+ set_collection = "@#{collection} = #{collection}"
137
+ else
138
+ args << 'options={}'
139
+ get_options = 'options'
140
+ set_collection = ''
141
+ end
142
+
143
+ if block = options.delete(:block)
144
+ block = :block unless block.is_a?(Symbol)
145
+ @matcher_arguments[:block] = block
146
+ end
147
+
148
+ # Blocks are always appended. If they have arity 1, they are used for
149
+ # macro configuration, otherwise, they are stored in the :block variable.
150
+ #
151
+ args << "&block"
152
+
153
+ assignments = names.map do |name|
154
+ "@#{name} = #{name}"
155
+ end.join("\n ")
156
+
157
+ class_eval <<-END, __FILE__, __LINE__
158
+ def initialize(#{args.join(',')})
159
+ _builder, block = block, nil if block && block.arity == 1
160
+ #{assignments}
161
+ #{"@#{block} = block" if block}
162
+ @options = default_options.merge(#{get_options})
163
+ #{set_collection}
164
+ run_after_initialize_callbacks
165
+ _builder.call(self) if _builder
166
+ end
167
+ END
168
+ end
169
+
170
+ # Declare the assertions that are runned for each element in the collection.
171
+ # It must be used with <tt>arguments</tt> methods in order to work properly.
172
+ #
173
+ # == Examples
174
+ #
175
+ # The example given in <tt>assertions</tt> can be transformed to
176
+ # accept a collection just doing:
177
+ #
178
+ # class IncludedMatcher < Remarkable::Base
179
+ # arguments :collection => :values, :as => :value
180
+ # collection_assertion :is_included?
181
+ #
182
+ # protected
183
+ # def is_included?
184
+ # @subject.include?(@value)
185
+ # end
186
+ # end
187
+ #
188
+ # All further consideration done in <tt>assertions</tt> are also valid here.
189
+ #
190
+ def collection_assertions(*methods, &block)
191
+ define_method methods.last, &block if block_given?
192
+ @matcher_collection_assertions += methods
193
+ end
194
+ alias :collection_assertion :collection_assertions
195
+
196
+ # Declares the assertions that are run once per matcher.
197
+ #
198
+ # == Examples
199
+ #
200
+ # A matcher that checks if an element is included in an array can be done
201
+ # just with:
202
+ #
203
+ # class IncludedMatcher < Remarkable::Base
204
+ # arguments :value
205
+ # assertion :is_included?
206
+ #
207
+ # protected
208
+ # def is_included?
209
+ # @subject.include?(@value)
210
+ # end
211
+ # end
212
+ #
213
+ # Whenever the matcher is called, the :is_included? action is automatically
214
+ # triggered. Each assertion must return true or false. In case it's false
215
+ # it will seach for an expectation message on the I18n file. In this
216
+ # case, the error message would be on:
217
+ #
218
+ # included:
219
+ # description: "check {{value}} is included in the array"
220
+ # expectations:
221
+ # is_included: "{{value}} is included in the array"
222
+ #
223
+ # In case of failure, it will output:
224
+ #
225
+ # "Expected {{value}} is included in the array"
226
+ #
227
+ # Notice that on the yml file the question mark is removed for readability.
228
+ #
229
+ # == Shortcut declaration
230
+ #
231
+ # You can shortcut declaration by giving a name and block to assertion
232
+ # method:
233
+ #
234
+ # class IncludedMatcher < Remarkable::Base
235
+ # arguments :value
236
+ #
237
+ # assertion :is_included? do
238
+ # @subject.include?(@value)
239
+ # end
240
+ # end
241
+ #
242
+ def assertions(*methods, &block)
243
+ if block_given?
244
+ define_method methods.last, &block
245
+ protected methods.last
246
+ end
247
+
248
+ @matcher_single_assertions += methods
249
+ end
250
+ alias :assertion :assertions
251
+
252
+ # Class method that accepts a block or a hash to set matcher's default
253
+ # options. It's called on matcher initialization and stores the default
254
+ # value in the @options instance variable.
255
+ #
256
+ # == Examples
257
+ #
258
+ # default_options do
259
+ # { :name => @subject.name }
260
+ # end
261
+ #
262
+ # default_options :message => :invalid
263
+ #
264
+ def default_options(hash = {}, &block)
265
+ if block_given?
266
+ define_method :default_options, &block
267
+ else
268
+ class_eval "def default_options; #{hash.inspect}; end"
269
+ end
270
+ end
271
+ end
272
+
273
+ # This method is responsable for connecting <tt>arguments</tt>, <tt>assertions</tt>
274
+ # and <tt>collection_assertions</tt>.
275
+ #
276
+ # It's the one that executes the assertions once, executes the collection
277
+ # assertions for each element in the collection and also responsable to set
278
+ # the I18n messages.
279
+ #
280
+ def matches?(subject)
281
+ @subject = subject
282
+
283
283
  run_before_assert_callbacks
284
284
 
285
285
  assertions = self.class.matcher_single_assertions
@@ -288,90 +288,90 @@ module Remarkable
288
288
  return negative? if positive? == !value
289
289
  end
290
290
 
291
- matches_collection_assertions?
292
- end
293
-
294
- protected
295
-
296
- # You can overwrite this instance method to provide default options on
297
- # initialization.
298
- #
299
- def default_options
300
- {}
301
- end
302
-
303
- # Overwrites default_i18n_options to provide arguments and optionals
304
- # to interpolation options.
305
- #
306
- # If you still need to provide more other interpolation options, you can
307
- # do that in two ways:
308
- #
309
- # 1. Overwrite interpolation_options:
310
- #
311
- # def interpolation_options
312
- # { :real_value => real_value }
313
- # end
314
- #
315
- # 2. Return a hash from your assertion method:
316
- #
317
- # def my_assertion
318
- # return true if real_value == expected_value
319
- # return false, :real_value => real_value
320
- # end
321
- #
322
- # In both cases, :real_value will be available as interpolation option.
323
- #
324
- def default_i18n_options #:nodoc:
325
- i18n_options = {}
326
-
327
- @options.each do |key, value|
328
- i18n_options[key] = value.inspect
329
- end if @options
330
-
331
- # Also add arguments as interpolation options.
332
- self.class.matcher_arguments[:names].each do |name|
333
- i18n_options[name] = instance_variable_get("@#{name}").inspect
334
- end
335
-
336
- # Add collection interpolation options.
337
- i18n_options.update(collection_interpolation)
338
-
339
- # Add default options (highest priority). They should not be overwritten.
340
- i18n_options.update(super)
341
- end
342
-
343
- # Method responsible to add collection as interpolation.
344
- #
345
- def collection_interpolation #:nodoc:
346
- options = {}
347
-
348
- if collection_name = self.class.matcher_arguments[:collection]
349
- collection_name = collection_name.to_sym
350
- collection = instance_variable_get("@#{collection_name}")
351
- options[collection_name] = array_to_sentence(collection) if collection
352
-
353
- object_name = self.class.matcher_arguments[:as].to_sym
354
- object = instance_variable_get("@#{object_name}")
355
- options[object_name] = object if object
356
- end
357
-
358
- options
359
- end
360
-
361
- # Send the assertion methods given and create a expectation message
362
- # if any of those methods returns false.
363
- #
364
- # Since most assertion methods ends with an question mark and it's not
365
- # readable in yml files, we remove question and exclation marks at the
366
- # end of the method name before translating it. So if you have a method
367
- # called is_valid? on I18n yml file we will check for a key :is_valid.
368
- #
369
- def send_methods_and_generate_message(methods) #:nodoc:
370
- methods.each do |method|
371
- bool, hash = send(method)
372
-
373
- if positive? == !bool
374
- parent_scope = matcher_i18n_scope.split('.')
291
+ matches_collection_assertions?
292
+ end
293
+
294
+ protected
295
+
296
+ # You can overwrite this instance method to provide default options on
297
+ # initialization.
298
+ #
299
+ def default_options
300
+ {}
301
+ end
302
+
303
+ # Overwrites default_i18n_options to provide arguments and optionals
304
+ # to interpolation options.
305
+ #
306
+ # If you still need to provide more other interpolation options, you can
307
+ # do that in two ways:
308
+ #
309
+ # 1. Overwrite interpolation_options:
310
+ #
311
+ # def interpolation_options
312
+ # { :real_value => real_value }
313
+ # end
314
+ #
315
+ # 2. Return a hash from your assertion method:
316
+ #
317
+ # def my_assertion
318
+ # return true if real_value == expected_value
319
+ # return false, :real_value => real_value
320
+ # end
321
+ #
322
+ # In both cases, :real_value will be available as interpolation option.
323
+ #
324
+ def default_i18n_options #:nodoc:
325
+ i18n_options = {}
326
+
327
+ @options.each do |key, value|
328
+ i18n_options[key] = value.inspect
329
+ end if @options
330
+
331
+ # Also add arguments as interpolation options.
332
+ self.class.matcher_arguments[:names].each do |name|
333
+ i18n_options[name] = instance_variable_get("@#{name}").inspect
334
+ end
335
+
336
+ # Add collection interpolation options.
337
+ i18n_options.update(collection_interpolation)
338
+
339
+ # Add default options (highest priority). They should not be overwritten.
340
+ i18n_options.update(super)
341
+ end
342
+
343
+ # Method responsible to add collection as interpolation.
344
+ #
345
+ def collection_interpolation #:nodoc:
346
+ options = {}
347
+
348
+ if collection_name = self.class.matcher_arguments[:collection]
349
+ collection_name = collection_name.to_sym
350
+ collection = instance_variable_get("@#{collection_name}")
351
+ options[collection_name] = array_to_sentence(collection) if collection
352
+
353
+ object_name = self.class.matcher_arguments[:as].to_sym
354
+ object = instance_variable_get("@#{object_name}")
355
+ options[object_name] = object if object
356
+ end
357
+
358
+ options
359
+ end
360
+
361
+ # Send the assertion methods given and create a expectation message
362
+ # if any of those methods returns false.
363
+ #
364
+ # Since most assertion methods ends with an question mark and it's not
365
+ # readable in yml files, we remove question and exclation marks at the
366
+ # end of the method name before translating it. So if you have a method
367
+ # called is_valid? on I18n yml file we will check for a key :is_valid.
368
+ #
369
+ def send_methods_and_generate_message(methods) #:nodoc:
370
+ methods.each do |method|
371
+ bool, hash = send(method)
372
+
373
+ if positive? == !bool
374
+ parent_scope = matcher_i18n_scope.split('.')
375
375
  matcher_name = parent_scope.pop
376
376
  method_name = method.to_s.gsub(/(\?|\!)$/, '')
377
377
 
@@ -380,16 +380,16 @@ module Remarkable
380
380
  lookup << :"#{matcher_name}.expectations.#{method_name}"
381
381
  lookup << :"negative_expectations.#{method_name}" if negative?
382
382
  lookup << :"expectations.#{method_name}"
383
-
384
- hash = { :scope => parent_scope, :default => lookup }.merge(hash || {})
385
- @expectation ||= Remarkable.t lookup.shift, default_i18n_options.merge(hash)
386
-
387
- return negative?
388
- end
389
- end
390
-
391
- return positive?
392
- end
383
+
384
+ hash = { :scope => parent_scope, :default => lookup }.merge(hash || {})
385
+ @expectation ||= Remarkable.t lookup.shift, default_i18n_options.merge(hash)
386
+
387
+ return negative?
388
+ end
389
+ end
390
+
391
+ return positive?
392
+ end
393
393
 
394
394
  def matches_single_assertions? #:nodoc:
395
395
  assertions = self.class.matcher_single_assertions
@@ -401,13 +401,13 @@ module Remarkable
401
401
  assertions = self.class.matcher_collection_assertions
402
402
  collection = instance_variable_get("@#{self.class.matcher_arguments[:collection]}") || []
403
403
 
404
- assert_collection(nil, collection) do |value|
405
- instance_variable_set("@#{arguments[:as]}", value)
406
- send_methods_and_generate_message(assertions)
404
+ assert_collection(nil, collection) do |value|
405
+ instance_variable_set("@#{arguments[:as]}", value)
406
+ send_methods_and_generate_message(assertions)
407
407
  end
408
408
  end
409
-
410
-
411
- end
412
- end
413
- end
409
+
410
+
411
+ end
412
+ end
413
+ end