mirror of
https://github.com/thoughtbot/shoulda-matchers.git
synced 2022-11-09 12:01:38 -05:00
Rewrite tests for ensure_inclusion_of
ensure_inclusion_of is pretty complex. Not only does it support two ways of specifying valid values (in_array, in_range), but it also supports multiple column types (string, integer, float, decimal, boolean). When it comes to handling numbers and strings, ensure_inclusion_of has very similar logic, and we can test these cases similarly. We also do not have tests for handling regular Ruby attributes as opposed to database columns (validates_inclusion_of works with both). Those tests will be added in a future commit, this commit lays the groundwork for that.
This commit is contained in:
parent
8f933f8064
commit
c7b1505990
1 changed files with 561 additions and 272 deletions
|
@ -1,320 +1,609 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::Matchers::ActiveModel::EnsureInclusionOfMatcher do
|
||||
context 'with no validations' do
|
||||
it 'rejects an array which does not have a validator defined' do
|
||||
expect(define_model(:example, attr: :string).new).
|
||||
not_to ensure_inclusion_of(:attr).in_array(%w(Yes No))
|
||||
end
|
||||
end
|
||||
shared_context 'for a generic attribute' do
|
||||
def self.testing_values_of_option(option_name, &block)
|
||||
[nil, true, false].each do |option_value|
|
||||
context_name = "+ #{option_name}"
|
||||
option_args = []
|
||||
matches_or_not = ['matches', 'does not match']
|
||||
to_or_not_to = [:to, :not_to]
|
||||
|
||||
context 'with an integer column' do
|
||||
it 'can verify a zero in the array' do
|
||||
model = define_model(:example, attr: :integer) do
|
||||
validates_inclusion_of :attr, in: [0, 1, 2]
|
||||
end.new
|
||||
unless option_value == nil
|
||||
context_name << "(#{option_value})"
|
||||
option_args = [option_value]
|
||||
end
|
||||
|
||||
expect(model).to ensure_inclusion_of(:attr).in_array([0,1,2])
|
||||
end
|
||||
end
|
||||
if option_value == false
|
||||
matches_or_not.reverse!
|
||||
to_or_not_to.reverse!
|
||||
end
|
||||
|
||||
context 'with an decimal column' do
|
||||
it 'can verify decimal values' do
|
||||
model = define_model(:example, attr: :decimal) do
|
||||
validates_inclusion_of :attr, in: [0.0, 0.1]
|
||||
end.new
|
||||
|
||||
expect(model).to ensure_inclusion_of(:attr).in_array([0.0, 0.1])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with true/false values' do
|
||||
it 'can verify outside values to ensure the negative case' do
|
||||
expect(define_model(:example, attr: :string).new).
|
||||
not_to ensure_inclusion_of(:attr).in_array([true, false])
|
||||
end
|
||||
end
|
||||
|
||||
context 'where we cannot determine a value outside the array' do
|
||||
it 'raises a custom exception' do
|
||||
model = define_model(:example, attr: :string).new
|
||||
|
||||
arbitrary_string = described_class::ARBITRARY_OUTSIDE_STRING
|
||||
expect { expect(model).to ensure_inclusion_of(:attr).in_array([arbitrary_string]) }.to raise_error Shoulda::Matchers::ActiveModel::CouldNotDetermineValueOutsideOfArray
|
||||
end
|
||||
end
|
||||
|
||||
context 'an attribute which must be included in a range' do
|
||||
it 'accepts ensuring the correct range' do
|
||||
expect(validating_inclusion(in: 2..5)).
|
||||
to ensure_inclusion_of(:attr).in_range(2..5)
|
||||
end
|
||||
end
|
||||
|
||||
it 'rejects ensuring a lower minimum value' do
|
||||
expect(validating_inclusion(in: 2..5)).
|
||||
not_to ensure_inclusion_of(:attr).in_range(1..5)
|
||||
end
|
||||
context 'against an integer attribute' do
|
||||
it_behaves_like 'it supports in_array',
|
||||
possible_values: (1..5).to_a,
|
||||
zero: 0,
|
||||
reserved_outside_value: described_class::ARBITRARY_OUTSIDE_FIXNUM
|
||||
|
||||
it 'rejects ensuring a higher minimum value' do
|
||||
expect(validating_inclusion(in: 2..5)).
|
||||
not_to ensure_inclusion_of(:attr).in_range(3..5)
|
||||
end
|
||||
it_behaves_like 'it supports in_range',
|
||||
possible_values: 1..5,
|
||||
zero: 0
|
||||
|
||||
it 'rejects ensuring a lower maximum value' do
|
||||
expect(validating_inclusion(in: 2..5)).
|
||||
not_to ensure_inclusion_of(:attr).in_range(2..4)
|
||||
end
|
||||
|
||||
it 'rejects ensuring a higher maximum value' do
|
||||
expect(validating_inclusion(in: 2..5)).
|
||||
not_to ensure_inclusion_of(:attr).in_range(2..6)
|
||||
end
|
||||
|
||||
it 'does not override the default message with a blank' do
|
||||
expect(validating_inclusion(in: 2..5)).
|
||||
to ensure_inclusion_of(:attr).in_range(2..5).with_message(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'an attribute which must be included in a range with excluded end' do
|
||||
it 'accepts ensuring the correct range' do
|
||||
expect(validating_inclusion(in: 2...5)).
|
||||
to ensure_inclusion_of(:attr).in_range(2...5)
|
||||
end
|
||||
|
||||
it 'rejects ensuring a lower maximum value' do
|
||||
expect(validating_inclusion(in: 2...5)).
|
||||
not_to ensure_inclusion_of(:attr).in_range(2...4)
|
||||
end
|
||||
end
|
||||
|
||||
context 'an attribute with a custom ranged value validation' do
|
||||
it 'accepts ensuring the correct range' do
|
||||
expect(validating_inclusion(in: 2..4, message: 'not good')).
|
||||
to ensure_inclusion_of(:attr).in_range(2..4).with_message(/not good/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'an attribute with custom range validations' do
|
||||
it 'accepts ensuring the correct range and messages' do
|
||||
model = custom_validation do
|
||||
if attr < 2
|
||||
errors.add(:attr, 'too low')
|
||||
elsif attr > 5
|
||||
errors.add(:attr, 'too high')
|
||||
context 'when attribute validates a range of values via custom validation' do
|
||||
it 'matches ensuring the correct range and messages' do
|
||||
expect_to_match_ensuring_range_and_messages(2..5, 2, 5)
|
||||
expect_to_match_ensuring_range_and_messages(2...5, 2, 4)
|
||||
end
|
||||
end
|
||||
|
||||
expect(model).to ensure_inclusion_of(:attr).in_range(2..5).
|
||||
with_low_message(/low/).with_high_message(/high/)
|
||||
|
||||
model = custom_validation do
|
||||
if attr < 2
|
||||
errors.add(:attr, 'too low')
|
||||
elsif attr > 4
|
||||
errors.add(:attr, 'too high')
|
||||
end
|
||||
def build_object(options = {}, &block)
|
||||
build_object_with_generic_attribute(
|
||||
options.merge(column_type: :integer),
|
||||
&block
|
||||
)
|
||||
end
|
||||
|
||||
expect(model).to ensure_inclusion_of(:attr).in_range(2...5).
|
||||
with_low_message(/low/).with_high_message(/high/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'an attribute which must be included in an array' do
|
||||
it 'accepts with correct array' do
|
||||
expect(validating_inclusion(in: %w(one two))).
|
||||
to ensure_inclusion_of(:attr).in_array(%w(one two))
|
||||
end
|
||||
|
||||
it 'rejects when only part of array matches' do
|
||||
expect(validating_inclusion(in: %w(one two))).
|
||||
not_to ensure_inclusion_of(:attr).in_array(%w(one wrong_value))
|
||||
end
|
||||
|
||||
it 'rejects when array does not match at all' do
|
||||
expect(validating_inclusion(in: %w(one two))).
|
||||
not_to ensure_inclusion_of(:attr).in_array(%w(cat dog))
|
||||
end
|
||||
|
||||
it 'has correct description' do
|
||||
expect(ensure_inclusion_of(:attr).in_array([true, "dog"]).description).
|
||||
to eq 'ensure inclusion of attr in [true, "dog"]'
|
||||
end
|
||||
|
||||
it 'rejects allow_blank' do
|
||||
expect(validating_inclusion(in: %w(one two))).
|
||||
not_to ensure_inclusion_of(:attr).in_array(%w(one two)).allow_blank(true)
|
||||
end
|
||||
|
||||
it 'accepts allow_blank(false)' do
|
||||
expect(validating_inclusion(in: %w(one two))).
|
||||
to ensure_inclusion_of(:attr).in_array(%w(one two)).allow_blank(false)
|
||||
end
|
||||
|
||||
it 'rejects allow_nil' do
|
||||
expect(validating_inclusion(in: %w(one two))).
|
||||
not_to ensure_inclusion_of(:attr).in_array(%w(one two)).allow_nil(true)
|
||||
end
|
||||
|
||||
it 'accepts allow_nil(false)' do
|
||||
expect(validating_inclusion(in: %w(one two))).
|
||||
to ensure_inclusion_of(:attr).in_array(%w(one two)).allow_nil(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with allowed blank and allowed nil' do
|
||||
it 'accepts allow_blank' do
|
||||
expect(validating_inclusion(in: %w(one two), allow_blank: true)).
|
||||
to ensure_inclusion_of(:attr).in_array(%w(one two)).allow_blank
|
||||
end
|
||||
|
||||
it 'rejects allow_blank(false)' do
|
||||
expect(validating_inclusion(in: %w(one two), allow_blank: true)).
|
||||
not_to ensure_inclusion_of(:attr).in_array(%w(one two)).allow_blank(false)
|
||||
end
|
||||
|
||||
it 'accepts allow_nil' do
|
||||
expect(validating_inclusion(in: %w(one two), allow_nil: true)).
|
||||
to ensure_inclusion_of(:attr).in_array(%w(one two)).allow_nil
|
||||
end
|
||||
|
||||
it 'rejects allow_nil' do
|
||||
expect(validating_inclusion(in: %w(one two), allow_nil: true)).
|
||||
not_to ensure_inclusion_of(:attr).in_array(%w(one two)).allow_nil(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'an attribute allowing some blank values but not others' do
|
||||
it 'rejects allow_blank' do
|
||||
expect(validating_inclusion(in: ['one', 'two', ''])).
|
||||
not_to ensure_inclusion_of(:attr).in_array(['one', 'two', '']).allow_blank(true)
|
||||
end
|
||||
end
|
||||
|
||||
if active_model_3_2?
|
||||
context 'a strict attribute which must be included in a range' do
|
||||
it 'accepts ensuring the correct range' do
|
||||
expect(validating_inclusion(in: 2..5, strict: true)).
|
||||
to ensure_inclusion_of(:attr).in_range(2..5).strict
|
||||
end
|
||||
|
||||
it 'rejects ensuring another range' do
|
||||
expect(validating_inclusion(in: 2..5, strict: true)).
|
||||
not_to ensure_inclusion_of(:attr).in_range(2..6).strict
|
||||
def add_outside_value_to(values)
|
||||
values + [values.last + 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'against a boolean attribute' do
|
||||
context 'which is nullable' do
|
||||
context 'when ensuring inclusion of true' do
|
||||
it "doesn't raise an error" do
|
||||
record = validating_inclusion_of_boolean_in(:attr, [true], null: true)
|
||||
expect(record).to ensure_inclusion_of(:attr).in_array([true])
|
||||
end
|
||||
context "against a float attribute" do
|
||||
it_behaves_like 'it supports in_array',
|
||||
possible_values: [1.0, 2.0, 3.0, 4.0, 5.0],
|
||||
zero: 0.0,
|
||||
reserved_outside_value: described_class::ARBITRARY_OUTSIDE_FIXNUM
|
||||
|
||||
it_behaves_like 'it supports in_range',
|
||||
possible_values: 1.0..5.0,
|
||||
zero: 0.0
|
||||
|
||||
def build_object(options = {}, &block)
|
||||
build_object_with_generic_attribute(
|
||||
options.merge(column_type: :float),
|
||||
&block
|
||||
)
|
||||
end
|
||||
|
||||
context 'when ensuring inclusion of false' do
|
||||
it "doesn't raise an error" do
|
||||
record = validating_inclusion_of_boolean_in(:attr, [false], null: true)
|
||||
expect(record).to ensure_inclusion_of(:attr).in_array([false])
|
||||
end
|
||||
def add_outside_value_to(values)
|
||||
values + [values.last + 1]
|
||||
end
|
||||
end
|
||||
|
||||
context "against a decimal attribute" do
|
||||
it_behaves_like 'it supports in_array',
|
||||
possible_values: [1.0, 2.0, 3.0, 4.0, 5.0],
|
||||
zero: 0.0,
|
||||
reserved_outside_value: described_class::ARBITRARY_OUTSIDE_DECIMAL
|
||||
|
||||
it_behaves_like 'it supports in_range',
|
||||
possible_values: 1.0..5.0,
|
||||
zero: 0.0
|
||||
|
||||
def build_object(options = {}, &block)
|
||||
build_object_with_generic_attribute(
|
||||
options.merge(column_type: :decimal),
|
||||
&block
|
||||
)
|
||||
end
|
||||
|
||||
context 'when ensuring inclusion of true and false' do
|
||||
it "doesn't raise an error" do
|
||||
record = validating_inclusion_of_boolean_in(:attr, [true, false], null: true)
|
||||
capture(:stderr) do
|
||||
expect(record).to ensure_inclusion_of(:attr).in_array([true, false])
|
||||
def add_outside_value_to(values)
|
||||
values + [values.last + 1]
|
||||
end
|
||||
end
|
||||
|
||||
context 'against a boolean attribute' do
|
||||
context 'which is nullable' do
|
||||
include_context 'against a boolean attribute for true and false'
|
||||
|
||||
context 'when ensuring inclusion of nil' do
|
||||
it 'matches' do
|
||||
valid_values = [nil]
|
||||
builder = build_object_allowing(valid_values)
|
||||
capture(:stderr) do
|
||||
expect_to_match_in_array(builder, valid_values)
|
||||
end
|
||||
end
|
||||
|
||||
it 'prints a warning' do
|
||||
valid_values = [nil]
|
||||
builder = build_object_allowing(valid_values)
|
||||
message = 'You are using `ensure_inclusion_of` to assert that a boolean column allows nil'
|
||||
|
||||
stderr = capture(:stderr) do
|
||||
expect_to_match_in_array(builder, valid_values)
|
||||
end
|
||||
|
||||
expect(stderr.gsub(/\n+/, ' ')).to include(message)
|
||||
end
|
||||
end
|
||||
|
||||
it 'prints a warning' do
|
||||
record = validating_inclusion_of_boolean_in(:attr, [true, false], null: true)
|
||||
stderr = capture(:stderr) do
|
||||
expect(record).to ensure_inclusion_of(:attr).in_array([true, false])
|
||||
end
|
||||
expect(stderr.gsub(/\n+/, ' ')).
|
||||
to include('You are using `ensure_inclusion_of` to assert that a boolean column allows boolean values and disallows non-boolean ones')
|
||||
def build_object(options = {}, &block)
|
||||
super(options.merge(column_options: { null: true }))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ensuring inclusion of nil' do
|
||||
it "doesn't raise an error" do
|
||||
record = validating_inclusion_of_boolean_in(:attr, [nil], null: true)
|
||||
capture(:stderr) do
|
||||
expect(record).to ensure_inclusion_of(:attr).in_array([nil])
|
||||
context 'which is non-nullable' do
|
||||
include_context 'against a boolean attribute for true and false'
|
||||
|
||||
context 'when ensuring inclusion of nil' do
|
||||
it 'raises a specific error' do
|
||||
valid_values = [nil]
|
||||
builder = build_object_allowing(valid_values)
|
||||
error_class = Shoulda::Matchers::ActiveModel::NonNullableBooleanError
|
||||
|
||||
expect {
|
||||
expect_to_match_in_array(builder, valid_values)
|
||||
}.to raise_error(error_class)
|
||||
end
|
||||
end
|
||||
|
||||
it 'prints a warning' do
|
||||
record = validating_inclusion_of_boolean_in(:attr, [nil], null: true)
|
||||
stderr = capture(:stderr) do
|
||||
expect(record).to ensure_inclusion_of(:attr).in_array([nil])
|
||||
end
|
||||
expect(stderr.gsub(/\n+/, ' ')).
|
||||
to include('You are using `ensure_inclusion_of` to assert that a boolean column allows nil')
|
||||
def build_object(options = {}, &block)
|
||||
super(options.merge(column_options: { null: false }))
|
||||
end
|
||||
end
|
||||
|
||||
def build_object(options = {}, &block)
|
||||
build_object_with_generic_attribute(
|
||||
options.merge(column_type: :boolean),
|
||||
&block
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'which is non-nullable' do
|
||||
context 'when ensuring inclusion of true' do
|
||||
it "doesn't raise an error" do
|
||||
record = validating_inclusion_of_boolean_in(:attr, [true], null: false)
|
||||
expect(record).to ensure_inclusion_of(:attr).in_array([true])
|
||||
context 'against a string attribute' do
|
||||
it_behaves_like 'it supports in_array',
|
||||
possible_values: %w(foo bar baz),
|
||||
reserved_outside_value: described_class::ARBITRARY_OUTSIDE_STRING
|
||||
|
||||
def build_object(options = {}, &block)
|
||||
build_object_with_generic_attribute(
|
||||
options.merge(column_type: :string),
|
||||
&block
|
||||
)
|
||||
end
|
||||
|
||||
def add_outside_value_to(values)
|
||||
values + %w(qux)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'it supports allow_nil' do |args|
|
||||
valid_values = args.fetch(:valid_values)
|
||||
|
||||
testing_values_of_option 'allow_nil' do |option_args, matches_or_not, to_or_not_to|
|
||||
it "#{matches_or_not[0]} when the validation specifies allow_nil" do
|
||||
builder = build_object_allowing(valid_values, allow_nil: true)
|
||||
|
||||
__send__("expect_#{to_or_not_to[0]}_match_on_values", builder, valid_values) do |matcher|
|
||||
matcher.allow_nil(*option_args)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ensuring inclusion of false' do
|
||||
it "doesn't raise an error" do
|
||||
record = validating_inclusion_of_boolean_in(:attr, [false], null: false)
|
||||
expect(record).to ensure_inclusion_of(:attr).in_array([false])
|
||||
end
|
||||
end
|
||||
it "#{matches_or_not[1]} when the validation does not specify allow_nil" do
|
||||
builder = build_object_allowing(valid_values)
|
||||
|
||||
context 'when ensuring inclusion of true and false' do
|
||||
it "doesn't raise an error" do
|
||||
record = validating_inclusion_of_boolean_in(:attr, [true, false], null: false)
|
||||
capture(:stderr) do
|
||||
expect(record).to ensure_inclusion_of(:attr).in_array([true, false])
|
||||
end
|
||||
end
|
||||
|
||||
it 'prints a warning' do
|
||||
record = validating_inclusion_of_boolean_in(:attr, [true, false], null: false)
|
||||
stderr = capture(:stderr) do
|
||||
expect(record).to ensure_inclusion_of(:attr).in_array([true, false])
|
||||
end
|
||||
expect(stderr.gsub(/\n+/, ' ')).
|
||||
to include('You are using `ensure_inclusion_of` to assert that a boolean column allows boolean values and disallows non-boolean ones')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ensuring inclusion of nil' do
|
||||
it 'raises a specific error' do
|
||||
record = validating_inclusion_of_boolean_in(:attr, [nil], null: false)
|
||||
error_class = Shoulda::Matchers::ActiveModel::NonNullableBooleanError
|
||||
expect {
|
||||
expect(record).to ensure_inclusion_of(:attr).in_array([nil])
|
||||
}.to raise_error(error_class)
|
||||
__send__("expect_#{to_or_not_to[1]}_match_on_values", builder, valid_values) do |matcher|
|
||||
matcher.allow_nil(*option_args)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def validating_inclusion(options)
|
||||
define_model(:example, attr: :string) do
|
||||
validates_inclusion_of :attr, options
|
||||
end.new
|
||||
shared_examples_for 'it supports allow_blank' do |args|
|
||||
valid_values = args.fetch(:valid_values)
|
||||
|
||||
testing_values_of_option 'allow_blank' do |option_args, matches_or_not, to_or_not_to|
|
||||
it "#{matches_or_not[0]} when the validation specifies allow_blank" do
|
||||
builder = build_object_allowing(valid_values, allow_blank: true)
|
||||
|
||||
__send__("expect_#{to_or_not_to[0]}_match_on_values", builder, valid_values) do |matcher|
|
||||
matcher.allow_blank(*option_args)
|
||||
end
|
||||
end
|
||||
|
||||
it "#{matches_or_not[1]} when the validation does not specify allow_blank" do
|
||||
builder = build_object_allowing(valid_values)
|
||||
|
||||
__send__("expect_#{to_or_not_to[1]}_match_on_values", builder, valid_values) do |matcher|
|
||||
matcher.allow_blank(*option_args)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def validating_inclusion_of_boolean_in(attribute, values, options = {})
|
||||
null = options.fetch(:null, true)
|
||||
column_options = { type: :boolean, options: { null: null } }
|
||||
define_model(:example, attribute => column_options) do
|
||||
validates_inclusion_of attribute, in: values
|
||||
end.new
|
||||
shared_examples_for 'it supports with_message' do |args|
|
||||
valid_values = args.fetch(:valid_values)
|
||||
|
||||
context 'given a string' do
|
||||
it 'matches when validation uses given message' do
|
||||
builder = build_object_allowing(valid_values, message: 'a message')
|
||||
|
||||
expect_to_match_on_values(builder, valid_values) do |matcher|
|
||||
matcher.with_message('a message')
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not match when validation uses the default message instead of given message' do
|
||||
pending 'does not work'
|
||||
|
||||
builder = build_object_allowing(valid_values)
|
||||
|
||||
expect_not_to_match_on_values(builder, valid_values) do |matcher|
|
||||
matcher.with_message('a message')
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not match when validation uses a message but it is not same as given' do
|
||||
pending 'does not work'
|
||||
|
||||
builder = build_object_allowing(valid_values, message: 'a different message')
|
||||
|
||||
expect_not_to_match_on_values(builder, valid_values) do |matcher|
|
||||
matcher.with_message('a message')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'given a regex' do
|
||||
it 'matches when validation uses a message that matches the regex' do
|
||||
builder = build_object_allowing(valid_values, message: 'this is a message')
|
||||
|
||||
expect_to_match_on_values(builder, valid_values) do |matcher|
|
||||
matcher.with_message(/a message/)
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not match when validation uses the default message instead of given message' do
|
||||
pending 'does not work'
|
||||
|
||||
builder = build_object_allowing(valid_values)
|
||||
|
||||
expect_not_to_match_on_values(builder, valid_values) do |matcher|
|
||||
matcher.with_message(/a message/)
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not match when validation uses a message but it does not match regex' do
|
||||
pending 'does not work'
|
||||
|
||||
builder = build_object_allowing(valid_values, message: 'a different message')
|
||||
|
||||
expect_not_to_match_on_values(builder, valid_values) do |matcher|
|
||||
matcher.with_message(/a message/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'given nil' do
|
||||
it 'is as if with_message had never been called' do
|
||||
builder = build_object_allowing(valid_values)
|
||||
|
||||
expect_to_match_on_values(builder, valid_values) do |matcher|
|
||||
matcher.with_message(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'it supports in_array' do |args|
|
||||
possible_values = args.fetch(:possible_values)
|
||||
zero = args[:zero]
|
||||
reserved_outside_value = args.fetch(:reserved_outside_value)
|
||||
|
||||
it 'does not match a record with no validations' do
|
||||
builder = build_object
|
||||
expect_not_to_match_on_values(builder, possible_values)
|
||||
end
|
||||
|
||||
it 'matches given the same array of valid values' do
|
||||
builder = build_object_allowing(possible_values)
|
||||
expect_to_match_on_values(builder, possible_values)
|
||||
end
|
||||
|
||||
it 'matches given a subset of the valid values' do
|
||||
builder = build_object_allowing(possible_values)
|
||||
expect_to_match_on_values(builder, possible_values[1..-1])
|
||||
end
|
||||
|
||||
if zero
|
||||
it 'matches when one of the given values is a 0' do
|
||||
valid_values = possible_values + [zero]
|
||||
builder = build_object_allowing(valid_values)
|
||||
expect_to_match_on_values(builder, valid_values)
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not match when one of the given values is invalid' do
|
||||
builder = build_object_allowing(possible_values)
|
||||
expect_not_to_match_on_values(builder, add_outside_value_to(possible_values))
|
||||
end
|
||||
|
||||
it 'raises an error when valid and given value is our test outside value' do
|
||||
error_class = Shoulda::Matchers::ActiveModel::CouldNotDetermineValueOutsideOfArray
|
||||
builder = build_object_allowing([reserved_outside_value])
|
||||
|
||||
expect { expect_to_match_on_values(builder, [reserved_outside_value]) }.
|
||||
to raise_error(error_class)
|
||||
end
|
||||
|
||||
it_behaves_like 'it supports allow_nil', valid_values: possible_values
|
||||
it_behaves_like 'it supports allow_blank', valid_values: possible_values
|
||||
it_behaves_like 'it supports with_message', valid_values: possible_values
|
||||
|
||||
if active_model_3_2?
|
||||
context '+ strict' do
|
||||
context 'when the validation specifies strict' do
|
||||
it 'matches when the given values match the valid values' do
|
||||
builder = build_object_allowing(possible_values, strict: true)
|
||||
|
||||
expect_to_match_on_values(builder, possible_values) do |matcher|
|
||||
matcher.strict
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not match when the given values do not match the valid values' do
|
||||
builder = build_object_allowing(possible_values, strict: true)
|
||||
|
||||
values = add_outside_value_to(possible_values)
|
||||
expect_not_to_match_on_values(builder, values) do |matcher|
|
||||
matcher.strict
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the validation does not specify strict' do
|
||||
it 'does not match' do
|
||||
builder = build_object_allowing(possible_values)
|
||||
|
||||
expect_not_to_match_on_values(builder, possible_values) do |matcher|
|
||||
matcher.strict
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def expect_to_match_on_values(builder, values, &block)
|
||||
expect_to_match_in_array(builder, values, &block)
|
||||
end
|
||||
|
||||
def expect_not_to_match_on_values(builder, values, &block)
|
||||
expect_not_to_match_in_array(builder, values, &block)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'it supports in_range' do |args|
|
||||
possible_values = args[:possible_values]
|
||||
|
||||
it 'does not match a record with no validations' do
|
||||
builder = build_object
|
||||
expect_not_to_match_on_values(builder, possible_values)
|
||||
end
|
||||
|
||||
it 'matches given a range that exactly matches the valid range' do
|
||||
builder = build_object_allowing(possible_values)
|
||||
expect_to_match_on_values(builder, possible_values)
|
||||
end
|
||||
|
||||
it 'does not match given a range whose start value falls outside valid range' do
|
||||
builder = build_object_allowing(possible_values)
|
||||
expect_not_to_match_on_values(builder,
|
||||
Range.new(possible_values.first - 1, possible_values.last)
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not match given a range whose start value falls inside valid range' do
|
||||
builder = build_object_allowing(possible_values)
|
||||
expect_not_to_match_on_values(builder,
|
||||
Range.new(possible_values.first + 1, possible_values.last)
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not match given a range whose end value falls inside valid range' do
|
||||
builder = build_object_allowing(possible_values)
|
||||
expect_not_to_match_on_values(builder,
|
||||
Range.new(possible_values.first, possible_values.last - 1)
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not match given a range whose end value falls outside valid range' do
|
||||
builder = build_object_allowing(possible_values)
|
||||
expect_not_to_match_on_values(builder,
|
||||
Range.new(possible_values.first, possible_values.last + 1)
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'it supports allow_nil', valid_values: possible_values
|
||||
it_behaves_like 'it supports allow_blank', valid_values: possible_values
|
||||
it_behaves_like 'it supports with_message', valid_values: possible_values
|
||||
|
||||
if active_model_3_2?
|
||||
context '+ strict' do
|
||||
context 'when the validation specifies strict' do
|
||||
it 'matches when the given range matches the range in the validation' do
|
||||
builder = build_object_allowing(possible_values, strict: true)
|
||||
|
||||
expect_to_match_on_values(builder, possible_values) do |matcher|
|
||||
matcher.strict
|
||||
end
|
||||
end
|
||||
|
||||
it 'matches when the given range does not match the range in the validation' do
|
||||
builder = build_object_allowing(possible_values, strict: true)
|
||||
|
||||
range = Range.new(possible_values.first, possible_values.last + 1)
|
||||
expect_not_to_match_on_values(builder, range) do |matcher|
|
||||
matcher.strict
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the validation does not specify strict' do
|
||||
it 'does not match' do
|
||||
builder = build_object_allowing(possible_values)
|
||||
|
||||
expect_not_to_match_on_values(builder, possible_values) do |matcher|
|
||||
matcher.strict
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def expect_to_match_on_values(builder, range, &block)
|
||||
expect_to_match_in_range(builder, range, &block)
|
||||
end
|
||||
|
||||
def expect_not_to_match_on_values(builder, range, &block)
|
||||
expect_not_to_match_in_range(builder, range, &block)
|
||||
end
|
||||
end
|
||||
|
||||
shared_context 'against a boolean attribute for true and false' do
|
||||
context 'when ensuring inclusion of true' do
|
||||
it 'matches' do
|
||||
valid_values = [true]
|
||||
builder = build_object_allowing(valid_values)
|
||||
expect_to_match_in_array(builder, valid_values)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ensuring inclusion of false' do
|
||||
it 'matches' do
|
||||
valid_values = [false]
|
||||
builder = build_object_allowing(valid_values)
|
||||
expect_to_match_in_array(builder, valid_values)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ensuring inclusion of true and false' do
|
||||
it 'matches' do
|
||||
valid_values = [true, false]
|
||||
builder = build_object_allowing(valid_values)
|
||||
capture(:stderr) do
|
||||
expect_to_match_in_array(builder, valid_values)
|
||||
end
|
||||
end
|
||||
|
||||
it 'prints a warning' do
|
||||
valid_values = [true, false]
|
||||
builder = build_object_allowing(valid_values)
|
||||
message = 'You are using `ensure_inclusion_of` to assert that a boolean column allows boolean values and disallows non-boolean ones'
|
||||
|
||||
stderr = capture(:stderr) do
|
||||
expect_to_match_in_array(builder, valid_values)
|
||||
end
|
||||
|
||||
expect(stderr.gsub(/\n+/, ' ')).to include(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a database column' do
|
||||
include_context 'for a generic attribute'
|
||||
|
||||
def build_object_with_generic_attribute(options = {}, &block)
|
||||
attribute_name = :attr
|
||||
column_type = options.fetch(:column_type)
|
||||
column_options = {
|
||||
type: column_type,
|
||||
options: options.fetch(:column_options, {})
|
||||
}
|
||||
validation_options = options[:validation_options]
|
||||
custom_validation = options[:custom_validation]
|
||||
|
||||
model = define_model :example, attribute_name => column_options do
|
||||
if validation_options
|
||||
validates_inclusion_of attribute_name, validation_options
|
||||
end
|
||||
|
||||
if custom_validation
|
||||
define_method :custom_validation do
|
||||
instance_exec(attribute_name, &custom_validation)
|
||||
end
|
||||
|
||||
validate :custom_validation
|
||||
end
|
||||
end
|
||||
|
||||
object = model.new
|
||||
|
||||
object_builder_class.new(attribute_name, object, validation_options)
|
||||
end
|
||||
end
|
||||
|
||||
def object_builder_class
|
||||
@_object_builder_class ||= Struct.new(:attribute, :object, :validation_options)
|
||||
end
|
||||
|
||||
def build_object_allowing(values, options = {})
|
||||
build_object(validation_options: options.merge(in: values))
|
||||
end
|
||||
|
||||
def expect_to_match(builder)
|
||||
matcher = ensure_inclusion_of(builder.attribute)
|
||||
yield matcher if block_given?
|
||||
expect(builder.object).to(matcher)
|
||||
end
|
||||
|
||||
def expect_not_to_match(builder)
|
||||
matcher = ensure_inclusion_of(builder.attribute)
|
||||
yield matcher if block_given?
|
||||
expect(builder.object).not_to(matcher)
|
||||
end
|
||||
|
||||
def expect_to_match_in_array(builder, array)
|
||||
expect_to_match(builder) do |matcher|
|
||||
matcher.in_array(array)
|
||||
yield matcher if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
def expect_not_to_match_in_array(builder, array)
|
||||
expect_not_to_match(builder) do |matcher|
|
||||
matcher.in_array(array)
|
||||
yield matcher if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
def expect_to_match_in_range(builder, range)
|
||||
expect_to_match(builder) do |matcher|
|
||||
matcher.in_range(range)
|
||||
yield matcher if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
def expect_not_to_match_in_range(builder, range)
|
||||
expect_not_to_match(builder) do |matcher|
|
||||
matcher.in_range(range)
|
||||
yield matcher if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
def expect_to_match_ensuring_range_and_messages(range, low_value, high_value)
|
||||
low_message = 'too low'
|
||||
high_message = 'too high'
|
||||
|
||||
builder = build_object custom_validation: ->(attribute) {
|
||||
value = __send__(attribute)
|
||||
|
||||
if value < low_value
|
||||
errors.add(attribute, low_message)
|
||||
elsif value > high_value
|
||||
errors.add(attribute, high_message)
|
||||
end
|
||||
}
|
||||
|
||||
expect_to_match(builder) do |matcher|
|
||||
matcher.
|
||||
in_range(range).
|
||||
with_low_message(low_message).
|
||||
with_high_message(high_message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue