remarkable 3.0.8 → 3.0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,7 +8,16 @@ module Remarkable
8
8
 
9
9
  module ClassMethods
10
10
  protected
11
- # Class method that accepts a block or a symbol which is called after initialization.
11
+ # Class method that accepts a block or a symbol which is called after
12
+ # initialization.
13
+ #
14
+ # == Examples
15
+ #
16
+ # after_initialize :evaluate_given_blocks
17
+ #
18
+ # after_initialize do
19
+ # # code
20
+ # end
12
21
  #
13
22
  def after_initialize(symbol=nil, &block)
14
23
  if block_given?
@@ -18,7 +27,16 @@ module Remarkable
18
27
  end
19
28
  end
20
29
 
21
- # Class method that accepts a block or a symbol which is called before assertion.
30
+ # Class method that accepts a block or a symbol which is called before
31
+ # running assertions.
32
+ #
33
+ # == Examples
34
+ #
35
+ # before_assert :evaluate_given_blocks
36
+ #
37
+ # before_assert do
38
+ # # code
39
+ # end
22
40
  #
23
41
  def before_assert(symbol=nil, &block)
24
42
  if block_given?
@@ -1,5 +1,102 @@
1
1
  module Remarkable
2
- module DSL
2
+ module DSL
3
+ # This module is responsable for create optional handlers and providing macro
4
+ # configration blocks. Consider the matcher below:
5
+ #
6
+ # class AllowValuesForMatcher < Remarkable::ActiveRecord::Base
7
+ # arguments :collection => :attributes, :as => :attribute
8
+ #
9
+ # optional :message
10
+ # optional :in, :splat => true
11
+ # optional :allow_nil, :allow_blank, :default => true
12
+ # end
13
+ #
14
+ # This allow the matcher to be called as:
15
+ #
16
+ # it { should allow_values_for(:email).in("jose.valim@gmail.com", "jose@another.com").message(:invalid).allow_nil }
17
+ #
18
+ # It also allow macros to be configured with blocks:
19
+ #
20
+ # should_allow_values_for :email do |m|
21
+ # m.message :invalid
22
+ # m.allow_nil
23
+ # m.in "jose.valim@gmail.com"
24
+ # m.in "jose@another.com"
25
+ # end
26
+ #
27
+ # Which could be also writen as:
28
+ #
29
+ # should_allow_values_for :email do |m|
30
+ # m.message = :invalid
31
+ # m.allow_nil = true
32
+ # m.in = [ "jose.valim@gmail.com", "jose@another.com" ]
33
+ # end
34
+ #
35
+ # The difference between the them are: 1) optional= always require an argument
36
+ # even if :default is given. 2) optional= always overwrite all previous values
37
+ # even if :splat is given.
38
+ #
39
+ # Blocks can be also given when :block => true is set:
40
+ #
41
+ # should_set_session :user_id do |m|
42
+ # m.to { @user.id }
43
+ # end
44
+ #
45
+ # == I18n
46
+ #
47
+ # Optionals will be included in description messages if you assign them
48
+ # properly on your locale file. If you have a validate_uniqueness_of
49
+ # matcher with the following on your locale file:
50
+ #
51
+ # description: validate uniqueness of {{attributes}}
52
+ # optionals:
53
+ # scope:
54
+ # positive: scoped to {{inspect}}
55
+ # case_sensitive:
56
+ # positive: case sensitive
57
+ # negative: case insensitive
58
+ #
59
+ # When invoked like below will generate the following messages:
60
+ #
61
+ # validate_uniqueness_of :project_id, :scope => :company_id
62
+ # #=> "validate uniqueness of project_id scoped to :company_id"
63
+ #
64
+ # validate_uniqueness_of :project_id, :scope => :company_id, :case_sensitive => true
65
+ # #=> "validate uniqueness of project_id scoped to :company_id and case sensitive"
66
+ #
67
+ # validate_uniqueness_of :project_id, :scope => :company_id, :case_sensitive => false
68
+ # #=> "validate uniqueness of project_id scoped to :company_id and case insensitive"
69
+ #
70
+ # == Interpolation options
71
+ #
72
+ # The default interpolation options available are "inspect" and "value". Whenever
73
+ # you use :splat => true, it also adds a new interpolation option called {{sentence}}.
74
+ #
75
+ # Given the following matcher call:
76
+ #
77
+ # validate_uniqueness_of :id, :scope => [ :company_id, :project_id ]
78
+ #
79
+ # The following yml setting and outputs are:
80
+ #
81
+ # scope:
82
+ # positive: scoped to {{inspect}}
83
+ # # Outputs: "validate uniqueness of project_id scoped to [ :company_id, :project_id ]"
84
+ #
85
+ # positive: scoped to {{value}}
86
+ # # Outputs: "validate uniqueness of project_id scoped to company_idproject_id"
87
+ #
88
+ # positive: scoped to {{value}}
89
+ # # Outputs: "validate uniqueness of project_id scoped to company_id and project_id"
90
+ #
91
+ # == Interpolation keys
92
+ #
93
+ # Three keys are available to be used in I18n files and control how optionals
94
+ # are appended to your description:
95
+ #
96
+ # * <tt>positive</tt> - When the optional is given and it evaluates to true (everything but false and nil).
97
+ # * <tt>negative</tt> - When the optional is given and it evaluates to false (false or nil).
98
+ # * <tt>not_given</tt> - When the optional is not given.
99
+ #
3
100
  module Optionals
4
101
 
5
102
  OPTIONAL_KEYS = [ :positive, :negative, :not_given ]
@@ -12,81 +109,66 @@ module Remarkable
12
109
 
13
110
  protected
14
111
 
15
- # Creates optional handlers for matchers dynamically. The following
16
- # statement:
112
+ # Creates optional handlers for matchers dynamically.
17
113
  #
18
- # optional :range, :default => 0..10
19
- #
20
- # Will generate:
21
- #
22
- # def range(value=0..10)
23
- # @options ||= {}
24
- # @options[:range] = value
25
- # self
26
- # end
27
- #
28
- # Options:
114
+ # == Options
29
115
  #
30
116
  # * <tt>:default</tt> - The default value for this optional
31
117
  # * <tt>:alias</tt> - An alias for this optional
32
- # * <tt>:splat</tt> - Should be true if you expects multiple arguments
33
- #
34
- # Examples:
35
- #
36
- # optional :name, :title
37
- # optional :range, :default => 0..10, :alias => :within
38
- #
39
- # Optionals will be included in description messages if you assign them
40
- # properly on your locale file. If you have a validate_uniqueness_of
41
- # matcher with the following on your locale file:
42
- #
43
- # description: validate uniqueness of {{attributes}}
44
- # optionals:
45
- # scope:
46
- # positive: scoped to {{value}}
47
- # case_sensitive:
48
- # positive: case sensitive
49
- # negative: case insensitive
50
- #
51
- # When invoked like below will generate the following messages:
52
- #
53
- # validate_uniqueness_of :project_id, :scope => :company_id
54
- # #=> "validate uniqueness of project_id scoped to company_id"
55
- #
56
- # validate_uniqueness_of :project_id, :scope => :company_id, :case_sensitive => true
57
- # #=> "validate uniqueness of project_id scoped to company_id and case sensitive"
118
+ # * <tt>:splat</tt> - Should be true if you expects multiple arguments
119
+ # * <tt>:block</tt> - Tell this optional can also receive blocks
58
120
  #
59
- # validate_uniqueness_of :project_id, :scope => :company_id, :case_sensitive => false
60
- # #=> "validate uniqueness of project_id scoped to company_id and case insensitive"
121
+ # == Examples
61
122
  #
62
- # The interpolation options available are "value" and "inspect". Where
63
- # the first is the optional value transformed into a string and the
64
- # second is the inspected value.
123
+ # class AllowValuesForMatcher < Remarkable::ActiveRecord::Base
124
+ # arguments :collection => :attributes, :as => :attribute
65
125
  #
66
- # Three keys are available to be used in I18n files and control how
67
- # optionals are appended to your description:
68
- #
69
- # * <tt>positive</tt> - When the optional is given and it evaluates to true (everything but false and nil).
70
- # * <tt>negative</tt> - When the optional is given and it evaluates to false (false or nil).
71
- # * <tt>not_given</tt> - When the optional is not given.
126
+ # optional :message
127
+ # optional :in, :splat => true
128
+ # optional :allow_nil, :allow_blank, :default => true
129
+ # end
72
130
  #
73
131
  def optionals(*names)
74
- options = names.extract_options!
75
- @matcher_optionals += names
132
+ options = names.extract_options!
76
133
 
77
- splat = options[:splat] ? '*' : ''
78
- default = options[:default] ? "=#{options[:default].inspect}" : ""
134
+ @matcher_optionals += names
135
+ default = options[:default] ? "=#{options[:default].inspect}" : nil
136
+
137
+ block = if options[:block]
138
+ @matcher_optionals_block += names
139
+ default ||= "=nil"
140
+ ', &block'
141
+ else
142
+ nil
143
+ end
144
+
145
+ splat = if options[:splat]
146
+ @matcher_optionals_splat += names
147
+ '*'
148
+ else
149
+ nil
150
+ end
79
151
 
80
152
  names.each do |name|
81
153
  class_eval <<-END, __FILE__, __LINE__
82
- def #{name}(#{splat}value#{default})
83
- @options ||= {}
84
- @options[:#{name}] = value
154
+ def #{name}(#{splat}value#{default}#{block})
155
+ @options ||= {}
156
+ #{"@options[:#{name}] ||= []" if splat}
157
+ @options[:#{name}] #{:+ if splat}= #{"block ||" if block} value
85
158
  self
159
+ end
160
+ def #{name}=(value)
161
+ @options ||= {}
162
+ @options[:#{name}] = value
163
+ self
86
164
  end
87
165
  END
88
- end
89
- class_eval "alias_method(:#{options[:alias]}, :#{names.last})" if options[:alias]
166
+ end
167
+
168
+ class_eval %{
169
+ alias :#{options[:alias]} :#{names.last}
170
+ alias :#{options[:alias]}= :#{names.last}=
171
+ } if options[:alias]
90
172
 
91
173
  # Call unique to avoid duplicate optionals.
92
174
  @matcher_optionals.uniq!
@@ -94,8 +176,8 @@ module Remarkable
94
176
  alias :optional :optionals
95
177
 
96
178
  # Instead of appending, prepend optionals to the beginning of optionals
97
- # array. This is important because this decide how the description
98
- # message is generated.
179
+ # array. This is important because the optionals declaration order
180
+ # changes how the description message is generated.
99
181
  #
100
182
  def prepend_optionals(*names)
101
183
  current_optionals = @matcher_optionals.dup
@@ -117,14 +199,20 @@ module Remarkable
117
199
 
118
200
  optionals = self.class.matcher_optionals.map do |optional|
119
201
  if @options.key?(optional)
120
- value = @options[optional]
121
- defaults = [ (value ? :positive : :negative) ]
202
+ value = @options[optional]
122
203
 
204
+ defaults = [ (value ? :positive : :negative) ]
123
205
  # If optional is a symbol and it's not any to any of the reserved symbols, search for it also
124
206
  defaults.unshift(value) if value.is_a?(Symbol) && !OPTIONAL_KEYS.include?(value)
125
207
  defaults << ''
126
208
 
127
209
  options = { :default => defaults, :inspect => value.inspect, :value => value.to_s }
210
+
211
+ if self.class.matcher_optionals_splat.include?(optional)
212
+ value = [ value ] unless Array === value
213
+ options[:sentence] = array_to_sentence(value, true)
214
+ end
215
+
128
216
  translate_optionals_with_namespace(optional, defaults.shift, options)
129
217
  else
130
218
  translate_optionals_with_namespace(optional, :not_given, :default => '')
@@ -1,30 +1,53 @@
1
- # This is a wrapper for I18n default functionality
2
- module Remarkable
1
+ module Remarkable
2
+ # This is a wrapper for I18n default functionality.
3
+ #
4
+ # Remarkable shouldn't rely on I18n default locale, because it might change
5
+ # throughout tests. So it's Remarkable responsibility to hold its own locale
6
+ # and send it to I18n.
7
+ #
3
8
  module I18n
4
9
 
5
- # Add locale files to I18n and to load path, if it exists.
10
+ # Add locale files to I18n and to load path, if it exists.
11
+ #
12
+ # == Examples
13
+ #
14
+ # Remarkable.add_locale "path/to/locale"
15
+ #
6
16
  def add_locale(*locales)
7
17
  ::I18n.backend.load_translations *locales
8
18
  ::I18n.load_path += locales if ::I18n.respond_to?(:load_path)
9
19
  end
10
20
 
11
- # Set Remarkable locale (which is not necessarily the same as the application)
21
+ # Set Remarkable own locale.
22
+ #
23
+ # == Examples
24
+ #
25
+ # Remarkable.locale = :en
26
+ #
12
27
  def locale=(locale)
13
28
  @@locale = locale
14
29
  end
15
30
 
16
- # Get Remarkable locale (which is not necessarily the same as the application)
31
+ # Get Remarkable own locale.
32
+ #
33
+ # == Examples
34
+ #
35
+ # Remarkable.locale = :en
36
+ # Remarkable.locale #=> :en
37
+ #
17
38
  def locale
18
39
  @@locale
19
40
  end
20
41
 
21
- # Wrapper for translation
42
+ # Wrapper for I18n.translate
43
+ #
22
44
  def translate(string, options = {})
23
45
  ::I18n.translate string, { :locale => @@locale }.merge(options)
24
46
  end
25
47
  alias :t :translate
26
48
 
27
- # Wrapper for localization
49
+ # Wrapper for I18n.localize
50
+ #
28
51
  def localize(object, options = {})
29
52
  ::I18n.localize object, { :locale => @@locale }.merge(options)
30
53
  end
@@ -1,4 +1,7 @@
1
- module Remarkable
1
+ module Remarkable
2
+ # This class is responsable for converting matchers to macros. You shouldn't
3
+ # worry with what happens here, because it happens automatically.
4
+ #
2
5
  module Macros
3
6
 
4
7
  protected
@@ -4,15 +4,23 @@ module Remarkable
4
4
  # to include matchers in Test::Unit as well.
5
5
  module Matchers; end
6
6
 
7
- # Helper that includes required Remarkable modules into the given klass.
8
- def self.include_matchers!(base, klass)
9
- klass.send :extend, Remarkable::Macros
7
+ # Helper that includes required Remarkable modules into the given klass.
8
+ #
9
+ # If the module to be included responds to :after_include, it's called with the
10
+ # target as argument.
11
+ #
12
+ def self.include_matchers!(base, target)
13
+ target.send :extend, Remarkable::Macros
10
14
 
11
15
  if defined?(base::Matchers)
12
- klass.send :include, base::Matchers
16
+ target.send :include, base::Matchers
13
17
 
14
18
  Remarkable::Matchers.send :extend, base::Matchers
15
19
  Remarkable::Matchers.send :include, base::Matchers
20
+ end
21
+
22
+ if base.respond_to?(:after_include)
23
+ base.after_include(target)
16
24
  end
17
25
  end
18
26
  end
@@ -1,4 +1,7 @@
1
- module Remarkable
1
+ module Remarkable
2
+ # Holds the methods required by rspec for each matcher plus a collection of
3
+ # helpers to deal with I18n.
4
+ #
2
5
  module Messages
3
6
 
4
7
  # Provides a default description message. Overwrite it if needed.
@@ -77,10 +80,12 @@ module Remarkable
77
80
 
78
81
  # Converts an array to a sentence
79
82
  #
80
- def array_to_sentence(array)
83
+ def array_to_sentence(array, inspect=false)
81
84
  words_connector = Remarkable.t 'remarkable.core.helpers.words_connector'
82
85
  two_words_connector = Remarkable.t 'remarkable.core.helpers.two_words_connector'
83
86
  last_word_connector = Remarkable.t 'remarkable.core.helpers.last_word_connector'
87
+
88
+ array.map!{|i| i.inspect} if inspect
84
89
 
85
90
  case array.length
86
91
  when 0
@@ -2,7 +2,16 @@ module Remarkable
2
2
  module Macros
3
3
 
4
4
  protected
5
-
5
+
6
+ # Adds a pending block to your specs.
7
+ #
8
+ # == Examples
9
+ #
10
+ # pending 'create manager resource' do
11
+ # should_have_one :manager
12
+ # should_validate_associated :manager
13
+ # end
14
+ #
6
15
  def pending(description='TODO', &block)
7
16
  PendingSandbox.new(description, self).instance_eval(&block)
8
17
  end
@@ -1,5 +1,3 @@
1
- # Hacks into rspec to provide I18n.
2
- #
3
1
  module Spec #:nodoc:
4
2
  module Matchers #:nodoc:
5
3
  # Overwrites to provide I18n on should and should_not.
@@ -1,3 +1,3 @@
1
1
  module Remarkable
2
- VERSION = '3.0.8' unless self.const_defined?('VERSION')
2
+ VERSION = '3.0.9' unless self.const_defined?('VERSION')
3
3
  end