carlosbrando-remarkable 0.0.99 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/History.txt +5 -5
  2. data/Manifest.txt +53 -35
  3. data/PostInstall.txt +1 -6
  4. data/README.rdoc +109 -15
  5. data/Rakefile +29 -29
  6. data/lib/remarkable/active_record/README.markdown +378 -0
  7. data/lib/remarkable/active_record/active_record.rb +12 -11
  8. data/lib/remarkable/active_record/helpers.rb +215 -5
  9. data/lib/remarkable/active_record/macros/associations/association_matcher.rb +242 -0
  10. data/lib/remarkable/active_record/macros/callbacks/callback_matcher.rb +46 -0
  11. data/lib/remarkable/active_record/macros/database/column_matcher.rb +122 -0
  12. data/lib/remarkable/active_record/macros/database/index_matcher.rb +103 -0
  13. data/lib/remarkable/active_record/macros/validations/allow_mass_assignment_of_matcher.rb +52 -0
  14. data/lib/remarkable/active_record/macros/validations/ensure_value_in_list_matcher.rb +83 -0
  15. data/lib/remarkable/active_record/macros/validations/ensure_value_in_range_matcher.rb +172 -0
  16. data/lib/remarkable/active_record/macros/validations/have_class_methods_matcher.rb +54 -0
  17. data/lib/remarkable/active_record/macros/validations/have_instance_methods_matcher.rb +54 -0
  18. data/lib/remarkable/active_record/macros/validations/have_named_scope_matcher.rb +94 -0
  19. data/lib/remarkable/active_record/macros/validations/have_readonly_attributes_matcher.rb +48 -0
  20. data/lib/remarkable/active_record/macros/validations/protect_attributes_matcher.rb +51 -0
  21. data/lib/remarkable/active_record/macros/validations/validate_acceptance_of_matcher.rb +79 -0
  22. data/lib/remarkable/active_record/macros/validations/validate_associated_matcher.rb +177 -0
  23. data/lib/remarkable/active_record/macros/validations/validate_confirmation_of_matcher.rb +74 -0
  24. data/lib/remarkable/active_record/macros/validations/validate_exclusion_of_matcher.rb +38 -0
  25. data/lib/remarkable/active_record/macros/validations/validate_format_of_matcher.rb +33 -0
  26. data/lib/remarkable/active_record/macros/validations/validate_inclusion_of_matcher.rb +45 -0
  27. data/lib/remarkable/active_record/macros/validations/validate_length_of_matcher.rb +248 -0
  28. data/lib/remarkable/active_record/macros/validations/validate_numericality_of_matcher.rb +206 -0
  29. data/lib/remarkable/active_record/macros/validations/validate_presence_of_matcher.rb +72 -0
  30. data/lib/remarkable/active_record/macros/validations/validate_uniqueness_of_matcher.rb +222 -0
  31. data/lib/remarkable/active_record/macros.rb +52 -0
  32. data/lib/remarkable/assertions.rb +29 -0
  33. data/lib/remarkable/controller/README.markdown +147 -0
  34. data/lib/remarkable/controller/controller.rb +11 -6
  35. data/lib/remarkable/controller/helpers.rb +4 -38
  36. data/lib/remarkable/controller/macros/assign_matcher.rb +85 -0
  37. data/lib/remarkable/controller/macros/filter_params_matcher.rb +63 -0
  38. data/lib/remarkable/controller/macros/metadata_matcher.rb +63 -0
  39. data/lib/remarkable/controller/macros/render_with_layout_matcher.rb +75 -0
  40. data/lib/remarkable/controller/macros/respond_with_content_type_matcher.rb +60 -0
  41. data/lib/remarkable/controller/macros/respond_with_matcher.rb +62 -0
  42. data/lib/remarkable/controller/macros/return_from_session_matcher.rb +58 -0
  43. data/lib/remarkable/controller/macros/route_matcher.rb +75 -0
  44. data/lib/remarkable/controller/macros/set_the_flash_to_matcher.rb +60 -0
  45. data/lib/remarkable/controller/macros.rb +78 -0
  46. data/lib/remarkable/dsl.rb +239 -0
  47. data/lib/remarkable/example/example_methods.rb +27 -7
  48. data/lib/remarkable/helpers.rb +28 -0
  49. data/lib/remarkable/matcher_base.rb +64 -0
  50. data/lib/remarkable/private_helpers.rb +10 -115
  51. data/lib/remarkable/rails.rb +27 -0
  52. data/lib/remarkable.rb +13 -5
  53. data/remarkable.gemspec +43 -0
  54. data/spec/controllers/posts_controller_spec.rb +58 -4
  55. data/spec/controllers/users_controller_spec.rb +1 -0
  56. data/spec/fixtures/fleas.yml +10 -0
  57. data/spec/fixtures/users.yml +7 -0
  58. data/spec/models/address_spec.rb +44 -0
  59. data/spec/models/dog_spec.rb +64 -3
  60. data/spec/models/flea_spec.rb +30 -0
  61. data/spec/models/post_spec.rb +36 -2
  62. data/spec/models/product_spec.rb +73 -8
  63. data/spec/models/tag_spec.rb +2 -2
  64. data/spec/models/tagging_spec.rb +24 -0
  65. data/spec/models/user_spec.rb +206 -21
  66. data/spec/other/custom_macros_spec.rb +27 -0
  67. data/spec/other/my_own_matcher_spec.rb +11 -0
  68. data/spec/other/private_helpers_spec.rb +31 -0
  69. data/spec/rails_root/app/controllers/posts_controller.rb +2 -0
  70. data/spec/rails_root/app/models/address.rb +2 -2
  71. data/spec/rails_root/app/models/flea.rb +4 -0
  72. data/spec/rails_root/app/models/pets/dog.rb +12 -0
  73. data/spec/rails_root/app/models/product.rb +7 -5
  74. data/spec/rails_root/app/models/tagging.rb +2 -0
  75. data/spec/rails_root/app/models/user.rb +20 -5
  76. data/spec/rails_root/app/views/layouts/posts.rhtml +8 -6
  77. data/spec/rails_root/config/database.yml +1 -2
  78. data/spec/rails_root/config/environment.rb +3 -1
  79. data/spec/rails_root/config/environments/{sqlite3.rb → test.rb} +0 -0
  80. data/spec/rails_root/config/locales/en.yml +8 -0
  81. data/spec/rails_root/db/migrate/001_create_users.rb +2 -2
  82. data/spec/rails_root/db/migrate/005_create_dogs.rb +1 -0
  83. data/spec/rails_root/db/migrate/011_add_fleas_color.rb +10 -0
  84. data/spec/rails_root/db/migrate/012_add_fleas_address.rb +10 -0
  85. data/spec/rails_root/spec/remarkable_macros/.keep +0 -0
  86. data/spec/rails_root/vendor/plugins/my_plugin/remarkable_macros/.keep +0 -0
  87. data/spec/spec_helper.rb +0 -2
  88. metadata +63 -43
  89. data/lib/remarkable/active_record/macros/associations/belong_to.rb +0 -81
  90. data/lib/remarkable/active_record/macros/associations/have_and_belong_to_many.rb +0 -77
  91. data/lib/remarkable/active_record/macros/associations/have_many.rb +0 -160
  92. data/lib/remarkable/active_record/macros/associations/have_one.rb +0 -133
  93. data/lib/remarkable/active_record/macros/database/have_db_column.rb +0 -81
  94. data/lib/remarkable/active_record/macros/database/have_db_columns.rb +0 -73
  95. data/lib/remarkable/active_record/macros/database/have_indices.rb +0 -75
  96. data/lib/remarkable/active_record/macros/validations/allow_values_for.rb +0 -103
  97. data/lib/remarkable/active_record/macros/validations/ensure_length_at_least.rb +0 -97
  98. data/lib/remarkable/active_record/macros/validations/ensure_length_in_range.rb +0 -134
  99. data/lib/remarkable/active_record/macros/validations/ensure_length_is.rb +0 -106
  100. data/lib/remarkable/active_record/macros/validations/ensure_value_in_range.rb +0 -117
  101. data/lib/remarkable/active_record/macros/validations/have_class_methods.rb +0 -74
  102. data/lib/remarkable/active_record/macros/validations/have_instance_methods.rb +0 -74
  103. data/lib/remarkable/active_record/macros/validations/have_named_scope.rb +0 -148
  104. data/lib/remarkable/active_record/macros/validations/have_readonly_attributes.rb +0 -81
  105. data/lib/remarkable/active_record/macros/validations/only_allow_numeric_values_for.rb +0 -89
  106. data/lib/remarkable/active_record/macros/validations/protect_attributes.rb +0 -89
  107. data/lib/remarkable/active_record/macros/validations/require_acceptance_of.rb +0 -94
  108. data/lib/remarkable/active_record/macros/validations/require_attributes.rb +0 -94
  109. data/lib/remarkable/active_record/macros/validations/require_unique_attributes.rb +0 -146
  110. data/lib/remarkable/controller/macros/assign_to.rb +0 -110
  111. data/lib/remarkable/controller/macros/filter_params.rb +0 -52
  112. data/lib/remarkable/controller/macros/redirect_to.rb +0 -24
  113. data/lib/remarkable/controller/macros/render_a_form.rb +0 -23
  114. data/lib/remarkable/controller/macros/render_template.rb +0 -18
  115. data/lib/remarkable/controller/macros/render_with_layout.rb +0 -61
  116. data/lib/remarkable/controller/macros/respond_with.rb +0 -86
  117. data/lib/remarkable/controller/macros/respond_with_content_type.rb +0 -45
  118. data/lib/remarkable/controller/macros/return_from_session.rb +0 -45
  119. data/lib/remarkable/controller/macros/route.rb +0 -91
  120. data/lib/remarkable/controller/macros/set_the_flash_to.rb +0 -58
  121. data/spec/rails_root/app/models/dog.rb +0 -5
@@ -0,0 +1,75 @@
1
+ module Remarkable # :nodoc:
2
+ module Controller # :nodoc:
3
+ module Matchers # :nodoc:
4
+ class Route < Remarkable::Matcher::Base
5
+ def initialize(method, path, options)
6
+ @method = method
7
+ @path = path
8
+ @options = options
9
+ end
10
+
11
+ def matches?(subject)
12
+ @subject = subject
13
+
14
+ initialize_with_spec!
15
+
16
+ unless @options[:controller]
17
+ @options[:controller] = @controller.class.name.gsub(/Controller$/, '').tableize
18
+ end
19
+ @options[:controller] = @options[:controller].to_s
20
+ @options[:action] = @options[:action].to_s
21
+
22
+ @populated_path = @path.dup
23
+ @options.each do |key, value|
24
+ @options[key] = value.to_param if value.respond_to?(:to_param)
25
+ @populated_path.gsub!(key.inspect, value.to_s)
26
+ end
27
+
28
+ ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
29
+ assert_matcher do
30
+ map_to_path? &&
31
+ generate_params?
32
+ end
33
+ end
34
+
35
+ def description
36
+ expectation
37
+ end
38
+
39
+ private
40
+
41
+ def initialize_with_spec!
42
+ # In Rspec 1.1.12 we can actually do:
43
+ #
44
+ # @controller = @subject
45
+ #
46
+ @controller = @spec.instance_eval { controller }
47
+ end
48
+
49
+ def map_to_path?
50
+ route_for = ActionController::Routing::Routes.generate(@options) rescue nil
51
+ return true if route_for == @populated_path
52
+
53
+ @missing = "not map #{@options.inspect} to #{@path.inspect}"
54
+ return false
55
+ end
56
+
57
+ def generate_params?
58
+ params_from = ActionController::Routing::Routes.recognize_path(@populated_path, :method => @method.to_sym)
59
+ return true if params_from == @options
60
+
61
+ @missing = "not generate params #{@options.inspect} from #{@method.to_s.upcase} to #{@path.inspect}"
62
+ return false
63
+ end
64
+
65
+ def expectation
66
+ "route #{@method.to_s.upcase} #{@populated_path} to/from #{@options.inspect}"
67
+ end
68
+ end
69
+
70
+ def route(method, path, options)
71
+ Route.new(method, path, options)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,60 @@
1
+ module Remarkable # :nodoc:
2
+ module Controller # :nodoc:
3
+ module Matchers # :nodoc:
4
+ class SetTheFlashTo < Remarkable::Matcher::Base
5
+ include Remarkable::Controller::Helpers
6
+
7
+ def initialize(val)
8
+ @val = val
9
+ end
10
+
11
+ def matches?(subject)
12
+ initialize_with_spec!
13
+
14
+ @subject = subject
15
+ assert_matcher do
16
+ flash_correct?
17
+ end
18
+ end
19
+
20
+ def description
21
+ expectation
22
+ end
23
+
24
+ private
25
+
26
+ def initialize_with_spec!
27
+ # In Rspec 1.1.12 we can actually do:
28
+ #
29
+ # @flash = @subject.flash
30
+ #
31
+ @flash = @spec.instance_eval { flash }
32
+ end
33
+
34
+ def flash_correct?
35
+ if @val
36
+ return true if assert_contains(@flash.values, @val)
37
+ @missing = "not have #{@val} in the flash"
38
+ else
39
+ return true if @flash == {}
40
+ @missing = "flash is not empty"
41
+ end
42
+ return false
43
+ end
44
+
45
+ def expectation
46
+ if @val
47
+ "have #{@val.inspect} in the flash"
48
+ else
49
+ "set the flash"
50
+ end
51
+ end
52
+ end
53
+
54
+ def set_the_flash_to(val = '')
55
+ SetTheFlashTo.new(val)
56
+ end
57
+ alias_method :set_the_flash, :set_the_flash_to
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,78 @@
1
+ module Remarkable # :nodoc:
2
+ module Controller # :nodoc:
3
+ module Macros # :nodoc:
4
+ include Matchers
5
+
6
+ def should_render_template(template)
7
+ it "should render template #{template.inspect}" do
8
+ response.should render_template(template.to_s)
9
+ end
10
+ end
11
+
12
+ def should_not_render_template(template)
13
+ it "should render template #{template.inspect}" do
14
+ response.should_not render_template(template.to_s)
15
+ end
16
+ end
17
+
18
+ def should_redirect_to(url)
19
+ it "should redirect to #{url.inspect}" do
20
+ redirect_url = self.instance_eval(url) rescue url
21
+ response.should redirect_to(redirect_url)
22
+ end
23
+ end
24
+
25
+ def should_not_redirect_to(url)
26
+ it "should not redirect to #{url.inspect}" do
27
+ redirect_url = self.instance_eval(url) rescue url
28
+ response.should_not redirect_to(redirect_url)
29
+ end
30
+ end
31
+
32
+ def should_not_set_the_flash
33
+ should_method_missing :set_the_flash_to, nil
34
+ end
35
+
36
+ def method_missing_with_remarkable(method_id, *args, &block)
37
+ if method_id.to_s =~ /^should_not_(.*)/
38
+ should_not_method_missing($1, *args)
39
+ elsif method_id.to_s =~ /^should_(.*)/
40
+ should_method_missing($1, *args)
41
+ elsif method_id.to_s =~ /^xshould_(not_)?(.*)/
42
+ pending_method_missing($2, $1, *args)
43
+ else
44
+ method_missing_without_remarkable(method_id, *args, &block)
45
+ end
46
+ end
47
+ alias_method_chain :method_missing, :remarkable
48
+
49
+ private
50
+
51
+ def should_method_missing(method, *args)
52
+ matcher = send(method, *args)
53
+ it "should #{matcher.description}" do
54
+ matcher.spec(self)
55
+ assert_accepts(matcher, subject_class)
56
+ end
57
+ end
58
+
59
+ def should_not_method_missing(method, *args)
60
+ matcher = send(method, *args)
61
+ it "should not #{matcher.description}" do
62
+ matcher.spec(self).negative
63
+ assert_rejects(matcher, subject_class)
64
+ end
65
+ end
66
+
67
+ def pending_method_missing(method, negative, *args)
68
+ matcher = send(method, *args)
69
+ matcher.negative if negative
70
+ description = matcher.description
71
+ xit "should #{'not ' if negative}#{description}"
72
+ rescue
73
+ xit "should #{'not ' if negative}#{method.to_s.gsub('_',' ')}"
74
+ end
75
+
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,239 @@
1
+ module Remarkable # :nodoc:
2
+ module Matcher # :nodoc:
3
+ module DSL
4
+
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ base.class_eval do
8
+ class_inheritable_accessor :loop_argument, :instance_writer => false
9
+ class_inheritable_reader :matcher_for_assertions, :matcher_assertions
10
+
11
+ # loop_argument is the value the we are going to loop with
12
+ # assert_matcher_for.
13
+ self.loop_argument = nil
14
+
15
+ # matcher_for_assertions contains the methods that should be called
16
+ # inside assert_matcher_for.
17
+ assertions()
18
+
19
+ # matcher_assertions contains the methods that should be called
20
+ # inside assert_matcher.
21
+ single_assertions()
22
+ end
23
+ end
24
+
25
+ module ClassMethods
26
+
27
+ protected
28
+
29
+ def assertions(*methods)
30
+ write_inheritable_array(:matcher_for_assertions, methods)
31
+ end
32
+
33
+ def single_assertions(*methods)
34
+ write_inheritable_array(:matcher_assertions, methods)
35
+ end
36
+
37
+ # Creates optional handlers for matchers dynamically. The following
38
+ # statement:
39
+ #
40
+ # optional :range, :default => 0..10
41
+ #
42
+ # Will generate:
43
+ #
44
+ # def range(value=0..10)
45
+ # @options ||= {}
46
+ # @options[:range] = value
47
+ # self
48
+ # end
49
+ #
50
+ # Options:
51
+ #
52
+ # * <tt>:default</tt> - The default value for this optional
53
+ # * <tt>:alias</tt> - An alias for this optional
54
+ #
55
+ # Examples:
56
+ #
57
+ # optional :name, :title
58
+ # optional :range, :default => 0..10, :alias => :within
59
+ #
60
+ def optional(*names)
61
+ options = names.extract_options!
62
+ names.each do |name|
63
+ class_eval <<-END, __FILE__, __LINE__
64
+ def #{name}(value#{ options[:default] ? "=#{options[:default].inspect}" : "" })
65
+ @options ||= {}
66
+ @options[:#{name}] = value
67
+ self
68
+ end
69
+ END
70
+ end
71
+ class_eval "alias_method(:#{options[:alias]}, :#{names.last})" if options[:alias]
72
+ end
73
+
74
+ # It sets the arguments your matcher receives on initialization. The
75
+ # last name given must be always plural and it will be the values
76
+ # that the matcher loops with. For example:
77
+ #
78
+ # @product.shuold validate_presence_of(:title, :name)
79
+ #
80
+ # validate_presence_of is a matcher declared as:
81
+ #
82
+ # class ValidatePresenceOfMatcher < Remarkable::Matcher::Base
83
+ # arguments :attributes
84
+ # end
85
+ #
86
+ # And this is the same as:
87
+ #
88
+ # class ValidatePresenceOfMatcher < Remarkable::Matcher::Base
89
+ # def initialize(*attributes)
90
+ # load_options(attributes.extract_options!)
91
+ # @attributes = attributes
92
+ # end
93
+ # end
94
+ #
95
+ # As you noticed, it will set instance variable with same name of the
96
+ # given values in <tt>arguments</tt>. Also, for each attribute given
97
+ # in @attributes the matcher will run all assertions declared with
98
+ # <tt>assertions</tt>.
99
+ #
100
+ # So validate_presence_of can be written as:
101
+ #
102
+ # class ValidatePresenceOfMatcher < Remarkable::Matcher::Base
103
+ # arguments :attributes
104
+ # assertions :check_nil
105
+ #
106
+ # protected
107
+ # def check_nil
108
+ # @subject.send("#{@attribute}=", nil)
109
+ # !@subject.save
110
+ # end
111
+ # end
112
+ #
113
+ # What it does is simple. For each attribute in @attributes, it will
114
+ # set the @attribute variable (in singular) and then call :check_nil.
115
+ #
116
+ # In check_nil, we can also see an instance_variable called @subject
117
+ # which is the object we called should on. Then we set the attribute
118
+ # given to nil and tries to save the object.
119
+ #
120
+ # If check nil returns true, that means the value does not accept nil
121
+ # and then it validate_presence_of is checked. If it return false for
122
+ # any given attribute, it will not pass.
123
+ #
124
+ # Let's see more examples:
125
+ #
126
+ # should allow_values_for(:email, "jose@valim.com", "carlos@brando.com")
127
+ #
128
+ # Is declared as:
129
+ #
130
+ # arguments :attribute, :good_values
131
+ #
132
+ # And this is the same as:
133
+ #
134
+ # class AllowValuesForMatcher < Remarkable::Matcher::Base
135
+ # def initialize(attribute, *good_values)
136
+ # @attribute = attribute
137
+ # load_options(good_values.extract_options!)
138
+ # @good_values = good_values
139
+ # end
140
+ # end
141
+ #
142
+ # Now, the variable we will loop is @good_values. In each assertion
143
+ # method we will have a @good_value variable instantiated with the
144
+ # value to assert. The instance variable @attribute is also available
145
+ # and is always the same, since it's not the variable we are looping.
146
+ #
147
+ def arguments(*names)
148
+ self.loop_argument = names.pop.to_s
149
+ args = (names + [ "*#{self.loop_argument}" ]).join(', ')
150
+
151
+ assignments = names.map do |name|
152
+ "@#{name} = #{name}"
153
+ end.join("\n")
154
+
155
+ # TODO:
156
+ #
157
+ # Do:
158
+ #
159
+ # @options = default_options.merge(#{self.loop_argument}.extract_options!)
160
+ #
161
+ # Instead of:
162
+ #
163
+ # load_options(#{self.loop_argument}.extract_options!)
164
+ #
165
+ # And deprecate load_options.
166
+ #
167
+ class_eval <<-END, __FILE__, __LINE__
168
+ def initialize(#{args})
169
+ #{assignments}
170
+ load_options(#{self.loop_argument}.extract_options!)
171
+ @#{self.loop_argument} = #{self.loop_argument}
172
+ after_initialize!
173
+ end
174
+ END
175
+ end
176
+ end
177
+
178
+ def matches?(subject)
179
+ @subject = subject
180
+
181
+ # Execute before_assert! callback
182
+ before_assert!
183
+
184
+ # Gets the loop_argument and loops it setting the singular name of
185
+ # loop argument. For example, if loop_argument is :good_values, we
186
+ # will get @good_values and then set the instance variable @good_value.
187
+ #
188
+ # Then we go for each method declared in assertions and eval it.
189
+ # Later we do the same for each method declared in single_assertion.
190
+ #
191
+ assert_matcher_for(instance_variable_get("@#{loop_argument}")) do |value|
192
+ instance_variable_set("@#{loop_argument.singularize}", value)
193
+
194
+ matcher_for_assertions.inject(true) do |bool, method|
195
+ bool && send_assertion_method(method)
196
+ end
197
+ end &&
198
+ assert_matcher do
199
+ matcher_assertions.inject(true) do |bool, method|
200
+ bool && send_assertion_method(method)
201
+ end
202
+ end
203
+ end
204
+
205
+ protected
206
+
207
+ # Do not overwrite this method. It's going to be deprecated.
208
+ #
209
+ def load_options(options = {})
210
+ @options = default_options.merge(options)
211
+ end
212
+
213
+ # Overwrite to provide default options.
214
+ #
215
+ def default_options
216
+ {}
217
+ end
218
+
219
+ def send_assertion_method(method)
220
+ if method.is_a? Array
221
+ send(*method)
222
+ else
223
+ send(method)
224
+ end
225
+ end
226
+
227
+ # Callback called after initialization.
228
+ #
229
+ def after_initialize!
230
+ end
231
+
232
+ # Callback before begin assertions.
233
+ #
234
+ def before_assert!
235
+ end
236
+
237
+ end
238
+ end
239
+ end
@@ -2,20 +2,26 @@ module Spec
2
2
  module Example
3
3
  module ExampleMethods
4
4
  def should(matcher)
5
- case matcher.class.name
6
- when "Spec::Rails::Matchers::RenderTemplate", "Spec::Rails::Matchers::RedirectTo"
5
+ if rspec_rails_controller_matcher?(matcher)
7
6
  remarkable_response.should matcher
7
+ elsif remarkable_matcher?(matcher)
8
+ remarkable_subject.should matcher.spec(self)
9
+ elsif exists_a_rspec_subject?
10
+ subject.should(matcher)
8
11
  else
9
- remarkable_subject.should matcher
12
+ super
10
13
  end
11
14
  end
12
15
 
13
16
  def should_not(matcher)
14
- case matcher.class.name
15
- when "Spec::Rails::Matchers::RenderTemplate", "Spec::Rails::Matchers::RedirectTo"
17
+ if rspec_rails_controller_matcher?(matcher)
16
18
  remarkable_response.should_not matcher
19
+ elsif remarkable_matcher?(matcher)
20
+ remarkable_subject.should_not matcher.spec(self).negative
21
+ elsif exists_a_rspec_subject?
22
+ subject.should_not(matcher)
17
23
  else
18
- remarkable_subject.should_not matcher
24
+ super
19
25
  end
20
26
  end
21
27
 
@@ -25,7 +31,21 @@ module Spec
25
31
  end
26
32
 
27
33
  def remarkable_response
28
- @remarkable_response ||= self.response if self.respond_to?(:response)
34
+ @remarkable_response ||= self.response if self.respond_to?(:response)
35
+ end
36
+
37
+ private
38
+
39
+ def rspec_rails_controller_matcher?(matcher)
40
+ %w( Spec::Rails::Matchers::RenderTemplate Spec::Rails::Matchers::RedirectTo ).include?(matcher.class.name)
41
+ end
42
+
43
+ def remarkable_matcher?(matcher)
44
+ matcher.class.name =~ /^Remarkable::\w+::Matchers::.+$/
45
+ end
46
+
47
+ def exists_a_rspec_subject?
48
+ !subject.nil?
29
49
  end
30
50
  end
31
51
  end
@@ -0,0 +1,28 @@
1
+ module Remarkable # :nodoc:
2
+ module Default # :nodoc:
3
+ module Helpers # :nodoc:
4
+ # Asserts that the given collection contains item x. If x is a regular expression, ensure that
5
+ # at least one element from the collection matches x. +extra_msg+ is appended to the error message if the assertion fails.
6
+ #
7
+ # assert_contains(['a', '1'], /\d/) => passes
8
+ # assert_contains(['a', '1'], 'a') => passes
9
+ # assert_contains(['a', '1'], /not there/) => fails
10
+ def assert_contains(collection, x) # :nodoc:
11
+ collection = [collection] unless collection.is_a?(Array)
12
+
13
+ case x
14
+ when Regexp
15
+ collection.detect { |e| e =~ x }
16
+ else
17
+ collection.include?(x)
18
+ end
19
+ end
20
+
21
+ # Asserts that the given collection does not contain item x. If x is a regular expression, ensure that
22
+ # none of the elements from the collection match x.
23
+ def assert_does_not_contain(collection, x) # :nodoc:
24
+ !assert_contains(collection, x)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,64 @@
1
+ module Remarkable # :nodoc:
2
+ module Matcher # :nodoc:
3
+ class Base
4
+ include Remarkable::Matcher::DSL
5
+
6
+ def negative
7
+ @negative = true
8
+ self
9
+ end
10
+
11
+ def failure_message
12
+ "Expected #{expectation} (#{@missing})"
13
+ end
14
+
15
+ def negative_failure_message
16
+ "Did not expect #{expectation}"
17
+ end
18
+
19
+ def spec(spec)
20
+ @spec = spec
21
+ self
22
+ end
23
+
24
+ private
25
+
26
+ def subject_class
27
+ @subject.is_a?(Class) ? @subject : @subject.class
28
+ end
29
+
30
+ def subject_name
31
+ subject_class.name
32
+ end
33
+
34
+ def positive?
35
+ @negative ? false : true
36
+ end
37
+
38
+ def negative?
39
+ @negative ? true : false
40
+ end
41
+
42
+ def assert_matcher(&block)
43
+ if positive?
44
+ return false unless yield
45
+ else
46
+ return true if yield
47
+ end
48
+ positive?
49
+ end
50
+
51
+ def assert_matcher_for(collection, &block)
52
+ collection.each do |item|
53
+ if positive?
54
+ return false unless yield(item)
55
+ else
56
+ return true if yield(item)
57
+ end
58
+ end
59
+ positive?
60
+ end
61
+
62
+ end
63
+ end
64
+ end