remarkable 3.0.8 → 3.0.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.
@@ -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