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.
- data/CHANGELOG +22 -0
- data/README +18 -5
- data/lib/remarkable/base.rb +3 -1
- data/lib/remarkable/dsl.rb +23 -6
- data/lib/remarkable/dsl/assertions.rb +353 -147
- data/lib/remarkable/dsl/callbacks.rb +20 -2
- data/lib/remarkable/dsl/optionals.rb +152 -64
- data/lib/remarkable/i18n.rb +30 -7
- data/lib/remarkable/macros.rb +4 -1
- data/lib/remarkable/matchers.rb +12 -4
- data/lib/remarkable/messages.rb +7 -2
- data/lib/remarkable/pending.rb +10 -1
- data/lib/remarkable/rspec.rb +0 -2
- data/lib/remarkable/version.rb +1 -1
- data/spec/dsl/assertions_spec.rb +1 -1
- data/spec/dsl/optionals_spec.rb +220 -37
- data/spec/locale/en.yml +3 -1
- data/spec/macros_spec.rb +1 -1
- data/spec/matchers/be_a_person_matcher.rb +5 -1
- data/spec/matchers/collection_contain_matcher.rb +2 -2
- data/spec/matchers/contain_matcher.rb +2 -2
- data/spec/matchers/single_contain_matcher.rb +4 -3
- metadata +2 -3
- data/lib/remarkable/dsl/matches.rb +0 -139
@@ -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
|
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
|
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.
|
16
|
-
# statement:
|
112
|
+
# Creates optional handlers for matchers dynamically.
|
17
113
|
#
|
18
|
-
#
|
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
|
-
#
|
60
|
-
# #=> "validate uniqueness of project_id scoped to company_id and case insensitive"
|
121
|
+
# == Examples
|
61
122
|
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
# second is the inspected value.
|
123
|
+
# class AllowValuesForMatcher < Remarkable::ActiveRecord::Base
|
124
|
+
# arguments :collection => :attributes, :as => :attribute
|
65
125
|
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
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
|
-
|
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}]
|
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
|
-
|
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
|
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 => '')
|
data/lib/remarkable/i18n.rb
CHANGED
@@ -1,30 +1,53 @@
|
|
1
|
-
|
2
|
-
|
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
|
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
|
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
|
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
|
49
|
+
# Wrapper for I18n.localize
|
50
|
+
#
|
28
51
|
def localize(object, options = {})
|
29
52
|
::I18n.localize object, { :locale => @@locale }.merge(options)
|
30
53
|
end
|
data/lib/remarkable/macros.rb
CHANGED
data/lib/remarkable/matchers.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
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
|
-
|
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
|
data/lib/remarkable/messages.rb
CHANGED
@@ -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
|
data/lib/remarkable/pending.rb
CHANGED
@@ -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
|
data/lib/remarkable/rspec.rb
CHANGED
data/lib/remarkable/version.rb
CHANGED