remarkable 3.1.8 → 3.1.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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