aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/test/cases
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel/test/cases')
-rw-r--r--activemodel/test/cases/helper.rb20
-rw-r--r--activemodel/test/cases/observing_test.rb121
-rw-r--r--activemodel/test/cases/state_machine/event_test.rb49
-rw-r--r--activemodel/test/cases/state_machine/machine_test.rb43
-rw-r--r--activemodel/test/cases/state_machine/state_test.rb72
-rw-r--r--activemodel/test/cases/state_machine/state_transition_test.rb84
-rw-r--r--activemodel/test/cases/state_machine_test.rb312
-rw-r--r--activemodel/test/cases/tests_database.rb41
-rw-r--r--activemodel/test/cases/validations/acceptance_validation_test.rb80
-rw-r--r--activemodel/test/cases/validations/conditional_validation_test.rb140
-rw-r--r--activemodel/test/cases/validations/confirmation_validation_test.rb68
-rw-r--r--activemodel/test/cases/validations/exclusion_validation_test.rb46
-rw-r--r--activemodel/test/cases/validations/format_validation_test.rb98
-rw-r--r--activemodel/test/cases/validations/i18n_generate_message_validation_test.rb182
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb579
-rw-r--r--activemodel/test/cases/validations/inclusion_validation_test.rb80
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb449
-rw-r--r--activemodel/test/cases/validations/numericality_validation_test.rb179
-rw-r--r--activemodel/test/cases/validations/presence_validation_test.rb57
-rw-r--r--activemodel/test/cases/validations_test.rb178
20 files changed, 2878 insertions, 0 deletions
diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb
new file mode 100644
index 0000000000..f023109a60
--- /dev/null
+++ b/activemodel/test/cases/helper.rb
@@ -0,0 +1,20 @@
+$:.unshift(File.dirname(__FILE__) + '/../../lib')
+$:.unshift(File.dirname(__FILE__) + '/../../../activesupport/lib')
+
+require 'config'
+
+require 'active_model'
+
+# Show backtraces for deprecated behavior for quicker cleanup.
+ActiveSupport::Deprecation.debug = true
+
+require 'rubygems'
+require 'test/unit'
+gem 'mocha', '>= 0.9.5'
+require 'mocha'
+
+begin
+ require 'ruby-debug'
+ Debugger.start
+rescue LoadError
+end
diff --git a/activemodel/test/cases/observing_test.rb b/activemodel/test/cases/observing_test.rb
new file mode 100644
index 0000000000..421ac4b4f8
--- /dev/null
+++ b/activemodel/test/cases/observing_test.rb
@@ -0,0 +1,121 @@
+require 'cases/helper'
+
+class ObservedModel < ActiveModel::Base
+ class Observer
+ end
+end
+
+class FooObserver < ActiveModel::Observer
+ class << self
+ public :new
+ end
+
+ attr_accessor :stub
+
+ def on_spec(record)
+ stub.event_with(record) if stub
+ end
+end
+
+class Foo < ActiveModel::Base
+end
+
+class ObservingTest < ActiveModel::TestCase
+ def setup
+ ObservedModel.observers.clear
+ end
+
+ test "initializes model with no cached observers" do
+ assert ObservedModel.observers.empty?, "Not empty: #{ObservedModel.observers.inspect}"
+ end
+
+ test "stores cached observers in an array" do
+ ObservedModel.observers << :foo
+ assert ObservedModel.observers.include?(:foo), ":foo not in #{ObservedModel.observers.inspect}"
+ end
+
+ test "flattens array of assigned cached observers" do
+ ObservedModel.observers = [[:foo], :bar]
+ assert ObservedModel.observers.include?(:foo), ":foo not in #{ObservedModel.observers.inspect}"
+ assert ObservedModel.observers.include?(:bar), ":bar not in #{ObservedModel.observers.inspect}"
+ end
+
+ test "instantiates observer names passed as strings" do
+ ObservedModel.observers << 'foo_observer'
+ FooObserver.expects(:instance)
+ ObservedModel.instantiate_observers
+ end
+
+ test "instantiates observer names passed as symbols" do
+ ObservedModel.observers << :foo_observer
+ FooObserver.expects(:instance)
+ ObservedModel.instantiate_observers
+ end
+
+ test "instantiates observer classes" do
+ ObservedModel.observers << ObservedModel::Observer
+ ObservedModel::Observer.expects(:instance)
+ ObservedModel.instantiate_observers
+ end
+
+ test "passes observers to subclasses" do
+ FooObserver.instance
+ bar = Class.new(Foo)
+ assert_equal Foo.count_observers, bar.count_observers
+ end
+end
+
+class ObserverTest < ActiveModel::TestCase
+ def setup
+ ObservedModel.observers = :foo_observer
+ FooObserver.models = nil
+ end
+
+ test "guesses implicit observable model name" do
+ assert_equal 'Foo', FooObserver.observed_class_name
+ end
+
+ test "tracks implicit observable models" do
+ instance = FooObserver.new
+ assert instance.send(:observed_classes).include?(Foo), "Foo not in #{instance.send(:observed_classes).inspect}"
+ assert !instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{instance.send(:observed_classes).inspect}"
+ end
+
+ test "tracks explicit observed model class" do
+ old_instance = FooObserver.new
+ assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
+ FooObserver.observe ObservedModel
+ instance = FooObserver.new
+ assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
+ end
+
+ test "tracks explicit observed model as string" do
+ old_instance = FooObserver.new
+ assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
+ FooObserver.observe 'observed_model'
+ instance = FooObserver.new
+ assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
+ end
+
+ test "tracks explicit observed model as symbol" do
+ old_instance = FooObserver.new
+ assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
+ FooObserver.observe :observed_model
+ instance = FooObserver.new
+ assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
+ end
+
+ test "calls existing observer event" do
+ foo = Foo.new
+ FooObserver.instance.stub = stub
+ FooObserver.instance.stub.expects(:event_with).with(foo)
+ Foo.send(:changed)
+ Foo.send(:notify_observers, :on_spec, foo)
+ end
+
+ test "skips nonexistent observer event" do
+ foo = Foo.new
+ Foo.send(:changed)
+ Foo.send(:notify_observers, :whatever, foo)
+ end
+end
diff --git a/activemodel/test/cases/state_machine/event_test.rb b/activemodel/test/cases/state_machine/event_test.rb
new file mode 100644
index 0000000000..2a0ef53a3f
--- /dev/null
+++ b/activemodel/test/cases/state_machine/event_test.rb
@@ -0,0 +1,49 @@
+require 'cases/helper'
+
+class EventTest < ActiveModel::TestCase
+ def setup
+ @state_name = :close_order
+ @success = :success_callback
+ end
+
+ def new_event
+ @event = ActiveModel::StateMachine::Event.new(nil, @state_name, {:success => @success}) do
+ transitions :to => :closed, :from => [:open, :received]
+ end
+ end
+
+ test 'should set the name' do
+ assert_equal @state_name, new_event.name
+ end
+
+ test 'should set the success option' do
+ assert_equal @success, new_event.success
+ end
+
+ test 'should create StateTransitions' do
+ ActiveModel::StateMachine::StateTransition.expects(:new).with(:to => :closed, :from => :open)
+ ActiveModel::StateMachine::StateTransition.expects(:new).with(:to => :closed, :from => :received)
+ new_event
+ end
+end
+
+class EventBeingFiredTest < ActiveModel::TestCase
+ test 'should raise an AASM::InvalidTransition error if the transitions are empty' do
+ event = ActiveModel::StateMachine::Event.new(nil, :event)
+
+ assert_raise ActiveModel::StateMachine::InvalidTransition do
+ event.fire(nil)
+ end
+ end
+
+ test 'should return the state of the first matching transition it finds' do
+ event = ActiveModel::StateMachine::Event.new(nil, :event) do
+ transitions :to => :closed, :from => [:open, :received]
+ end
+
+ obj = stub
+ obj.stubs(:current_state).returns(:open)
+
+ assert_equal :closed, event.fire(obj)
+ end
+end
diff --git a/activemodel/test/cases/state_machine/machine_test.rb b/activemodel/test/cases/state_machine/machine_test.rb
new file mode 100644
index 0000000000..2aa954d80c
--- /dev/null
+++ b/activemodel/test/cases/state_machine/machine_test.rb
@@ -0,0 +1,43 @@
+require 'cases/helper'
+
+class MachineTestSubject
+ include ActiveModel::StateMachine
+
+ state_machine do
+ state :open
+ state :closed
+ end
+
+ state_machine :initial => :foo do
+ event :shutdown do
+ transitions :from => :open, :to => :closed
+ end
+
+ event :timeout do
+ transitions :from => :open, :to => :closed
+ end
+ end
+
+ state_machine :extra, :initial => :bar do
+ end
+end
+
+class StateMachineMachineTest < ActiveModel::TestCase
+ test "allows reuse of existing machines" do
+ assert_equal 2, MachineTestSubject.state_machines.size
+ end
+
+ test "sets #initial_state from :initial option" do
+ assert_equal :bar, MachineTestSubject.state_machine(:extra).initial_state
+ end
+
+ test "accesses non-default state machine" do
+ assert_kind_of ActiveModel::StateMachine::Machine, MachineTestSubject.state_machine(:extra)
+ end
+
+ test "finds events for given state" do
+ events = MachineTestSubject.state_machine.events_for(:open)
+ assert events.include?(:shutdown)
+ assert events.include?(:timeout)
+ end
+end
diff --git a/activemodel/test/cases/state_machine/state_test.rb b/activemodel/test/cases/state_machine/state_test.rb
new file mode 100644
index 0000000000..527bfd4c04
--- /dev/null
+++ b/activemodel/test/cases/state_machine/state_test.rb
@@ -0,0 +1,72 @@
+require 'cases/helper'
+
+class StateTestSubject
+ include ActiveModel::StateMachine
+
+ state_machine do
+ end
+end
+
+class StateTest < ActiveModel::TestCase
+ def setup
+ @state_name = :astate
+ @machine = StateTestSubject.state_machine
+ @options = { :crazy_custom_key => 'key', :machine => @machine }
+ end
+
+ def new_state(options={})
+ ActiveModel::StateMachine::State.new(@state_name, @options.merge(options))
+ end
+
+ test 'sets the name' do
+ assert_equal :astate, new_state.name
+ end
+
+ test 'sets the display_name from name' do
+ assert_equal "Astate", new_state.display_name
+ end
+
+ test 'sets the display_name from options' do
+ assert_equal "A State", new_state(:display => "A State").display_name
+ end
+
+ test 'sets the options and expose them as options' do
+ @options.delete(:machine)
+ assert_equal @options, new_state.options
+ end
+
+ test 'equals a symbol of the same name' do
+ assert_equal new_state, :astate
+ end
+
+ test 'equals a State of the same name' do
+ assert_equal new_state, new_state
+ end
+
+ test 'should send a message to the record for an action if the action is present as a symbol' do
+ state = new_state(:entering => :foo)
+
+ record = stub
+ record.expects(:foo)
+
+ state.call_action(:entering, record)
+ end
+
+ test 'should send a message to the record for an action if the action is present as a string' do
+ state = new_state(:entering => 'foo')
+
+ record = stub
+ record.expects(:foo)
+
+ state.call_action(:entering, record)
+ end
+
+ test 'should call a proc, passing in the record for an action if the action is present' do
+ state = new_state(:entering => Proc.new {|r| r.foobar})
+
+ record = stub
+ record.expects(:foobar)
+
+ state.call_action(:entering, record)
+ end
+end
diff --git a/activemodel/test/cases/state_machine/state_transition_test.rb b/activemodel/test/cases/state_machine/state_transition_test.rb
new file mode 100644
index 0000000000..17f9d88be7
--- /dev/null
+++ b/activemodel/test/cases/state_machine/state_transition_test.rb
@@ -0,0 +1,84 @@
+require 'cases/helper'
+
+class StateTransitionTest < ActiveModel::TestCase
+ test 'should set from, to, and opts attr readers' do
+ opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ assert_equal opts[:from], st.from
+ assert_equal opts[:to], st.to
+ assert_equal opts, st.options
+ end
+
+ test 'should pass equality check if from and to are the same' do
+ opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ obj = stub
+ obj.stubs(:from).returns(opts[:from])
+ obj.stubs(:to).returns(opts[:to])
+
+ assert_equal st, obj
+ end
+
+ test 'should fail equality check if from are not the same' do
+ opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ obj = stub
+ obj.stubs(:from).returns('blah')
+ obj.stubs(:to).returns(opts[:to])
+
+ assert_not_equal st, obj
+ end
+
+ test 'should fail equality check if to are not the same' do
+ opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ obj = stub
+ obj.stubs(:from).returns(opts[:from])
+ obj.stubs(:to).returns('blah')
+
+ assert_not_equal st, obj
+ end
+end
+
+class StateTransitionGuardCheckTest < ActiveModel::TestCase
+ test 'should return true of there is no guard' do
+ opts = {:from => 'foo', :to => 'bar'}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ assert st.perform(nil)
+ end
+
+ test 'should call the method on the object if guard is a symbol' do
+ opts = {:from => 'foo', :to => 'bar', :guard => :test_guard}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ obj = stub
+ obj.expects(:test_guard)
+
+ st.perform(obj)
+ end
+
+ test 'should call the method on the object if guard is a string' do
+ opts = {:from => 'foo', :to => 'bar', :guard => 'test_guard'}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ obj = stub
+ obj.expects(:test_guard)
+
+ st.perform(obj)
+ end
+
+ test 'should call the proc passing the object if the guard is a proc' do
+ opts = {:from => 'foo', :to => 'bar', :guard => Proc.new {|o| o.test_guard}}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ obj = stub
+ obj.expects(:test_guard)
+
+ st.perform(obj)
+ end
+end
diff --git a/activemodel/test/cases/state_machine_test.rb b/activemodel/test/cases/state_machine_test.rb
new file mode 100644
index 0000000000..f66299741e
--- /dev/null
+++ b/activemodel/test/cases/state_machine_test.rb
@@ -0,0 +1,312 @@
+require 'cases/helper'
+
+class StateMachineSubject
+ include ActiveModel::StateMachine
+
+ state_machine do
+ state :open, :exit => :exit
+ state :closed, :enter => :enter
+
+ event :close, :success => :success_callback do
+ transitions :to => :closed, :from => [:open]
+ end
+
+ event :null do
+ transitions :to => :closed, :from => [:open], :guard => :always_false
+ end
+ end
+
+ state_machine :bar do
+ state :read
+ state :ended
+
+ event :foo do
+ transitions :to => :ended, :from => [:read]
+ end
+ end
+
+ def always_false
+ false
+ end
+
+ def success_callback
+ end
+
+ def enter
+ end
+ def exit
+ end
+end
+
+class StateMachineSubjectSubclass < StateMachineSubject
+end
+
+class StateMachineClassLevelTest < ActiveModel::TestCase
+ test 'defines a class level #state_machine method on its including class' do
+ assert StateMachineSubject.respond_to?(:state_machine)
+ end
+
+ test 'defines a class level #state_machines method on its including class' do
+ assert StateMachineSubject.respond_to?(:state_machines)
+ end
+
+ test 'class level #state_machine returns machine instance' do
+ assert_kind_of ActiveModel::StateMachine::Machine, StateMachineSubject.state_machine
+ end
+
+ test 'class level #state_machine returns machine instance with given name' do
+ assert_kind_of ActiveModel::StateMachine::Machine, StateMachineSubject.state_machine(:default)
+ end
+
+ test 'class level #state_machines returns hash of machine instances' do
+ assert_kind_of ActiveModel::StateMachine::Machine, StateMachineSubject.state_machines[:default]
+ end
+
+ test "should return a select friendly array of states in the form of [['Friendly name', 'state_name']]" do
+ assert_equal [['Open', 'open'], ['Closed', 'closed']], StateMachineSubject.state_machine.states_for_select
+ end
+end
+
+class StateMachineInstanceLevelTest < ActiveModel::TestCase
+ def setup
+ @foo = StateMachineSubject.new
+ end
+
+ test 'defines an accessor for the current state' do
+ assert @foo.respond_to?(:current_state)
+ end
+
+ test 'defines a state querying instance method on including class' do
+ assert @foo.respond_to?(:open?)
+ end
+
+ test 'defines an event! instance method' do
+ assert @foo.respond_to?(:close!)
+ end
+
+ test 'defines an event instance method' do
+ assert @foo.respond_to?(:close)
+ end
+end
+
+class StateMachineInitialStatesTest < ActiveModel::TestCase
+ def setup
+ @foo = StateMachineSubject.new
+ end
+
+ test 'sets the initial state' do
+ assert_equal :open, @foo.current_state
+ end
+
+ test '#open? should be initially true' do
+ assert @foo.open?
+ end
+
+ test '#closed? should be initially false' do
+ assert !@foo.closed?
+ end
+
+ test 'uses the first state defined if no initial state is given' do
+ assert_equal :read, @foo.current_state(:bar)
+ end
+end
+
+class StateMachineEventFiringWithPersistenceTest < ActiveModel::TestCase
+ def setup
+ @subj = StateMachineSubject.new
+ end
+
+ test 'updates the current state' do
+ @subj.close!
+
+ assert_equal :closed, @subj.current_state
+ end
+
+ test 'fires the Event' do
+ @subj.class.state_machine.events[:close].expects(:fire).with(@subj)
+ @subj.close!
+ end
+
+ test 'calls the success callback if one was provided' do
+ @subj.expects(:success_callback)
+ @subj.close!
+ end
+
+ test 'attempts to persist if write_state is defined' do
+ def @subj.write_state
+ end
+
+ @subj.expects(:write_state)
+ @subj.close!
+ end
+end
+
+class StateMachineEventFiringWithoutPersistence < ActiveModel::TestCase
+ test 'updates the current state' do
+ subj = StateMachineSubject.new
+ assert_equal :open, subj.current_state
+ subj.close
+ assert_equal :closed, subj.current_state
+ end
+
+ test 'fires the Event' do
+ subj = StateMachineSubject.new
+
+ StateMachineSubject.state_machine.events[:close].expects(:fire).with(subj)
+ subj.close
+ end
+
+ test 'attempts to persist if write_state is defined' do
+ subj = StateMachineSubject.new
+
+ def subj.write_state
+ end
+
+ subj.expects(:write_state_without_persistence)
+
+ subj.close
+ end
+end
+
+class StateMachinePersistenceTest < ActiveModel::TestCase
+ test 'reads the state if it has not been set and read_state is defined' do
+ subj = StateMachineSubject.new
+ def subj.read_state
+ end
+
+ subj.expects(:read_state).with(StateMachineSubject.state_machine)
+
+ subj.current_state
+ end
+end
+
+class StateMachineEventCallbacksTest < ActiveModel::TestCase
+ test 'should call aasm_event_fired if defined and successful for bang fire' do
+ subj = StateMachineSubject.new
+ def subj.aasm_event_fired(from, to)
+ end
+
+ subj.expects(:event_fired)
+
+ subj.close!
+ end
+
+ test 'should call aasm_event_fired if defined and successful for non-bang fire' do
+ subj = StateMachineSubject.new
+ def subj.aasm_event_fired(from, to)
+ end
+
+ subj.expects(:event_fired)
+
+ subj.close
+ end
+
+ test 'should call aasm_event_failed if defined and transition failed for bang fire' do
+ subj = StateMachineSubject.new
+ def subj.event_failed(event)
+ end
+
+ subj.expects(:event_failed)
+
+ subj.null!
+ end
+
+ test 'should call aasm_event_failed if defined and transition failed for non-bang fire' do
+ subj = StateMachineSubject.new
+ def subj.aasm_event_failed(event)
+ end
+
+ subj.expects(:event_failed)
+
+ subj.null
+ end
+end
+
+class StateMachineStateActionsTest < ActiveModel::TestCase
+ test "calls enter when entering state" do
+ subj = StateMachineSubject.new
+ subj.expects(:enter)
+ subj.close
+ end
+
+ test "calls exit when exiting state" do
+ subj = StateMachineSubject.new
+ subj.expects(:exit)
+ subj.close
+ end
+end
+
+class StateMachineInheritanceTest < ActiveModel::TestCase
+ test "has the same states as its parent" do
+ assert_equal StateMachineSubject.state_machine.states, StateMachineSubjectSubclass.state_machine.states
+ end
+
+ test "has the same events as its parent" do
+ assert_equal StateMachineSubject.state_machine.events, StateMachineSubjectSubclass.state_machine.events
+ end
+end
+
+class StateMachineSubject
+ state_machine :chetan_patil, :initial => :sleeping do
+ state :sleeping
+ state :showering
+ state :working
+ state :dating
+
+ event :wakeup do
+ transitions :from => :sleeping, :to => [:showering, :working]
+ end
+
+ event :dress do
+ transitions :from => :sleeping, :to => :working, :on_transition => :wear_clothes
+ transitions :from => :showering, :to => [:working, :dating], :on_transition => Proc.new { |obj, *args| obj.wear_clothes(*args) }
+ end
+ end
+
+ def wear_clothes(shirt_color, trouser_type)
+ end
+end
+
+class StateMachineWithComplexTransitionsTest < ActiveModel::TestCase
+ def setup
+ @subj = StateMachineSubject.new
+ end
+
+ test 'transitions to specified next state (sleeping to showering)' do
+ @subj.wakeup! :showering
+
+ assert_equal :showering, @subj.current_state(:chetan_patil)
+ end
+
+ test 'transitions to specified next state (sleeping to working)' do
+ @subj.wakeup! :working
+
+ assert_equal :working, @subj.current_state(:chetan_patil)
+ end
+
+ test 'transitions to default (first or showering) state' do
+ @subj.wakeup!
+
+ assert_equal :showering, @subj.current_state(:chetan_patil)
+ end
+
+ test 'transitions to default state when on_transition invoked' do
+ @subj.dress!(nil, 'purple', 'dressy')
+
+ assert_equal :working, @subj.current_state(:chetan_patil)
+ end
+
+ test 'calls on_transition method with args' do
+ @subj.wakeup! :showering
+
+ @subj.expects(:wear_clothes).with('blue', 'jeans')
+ @subj.dress! :working, 'blue', 'jeans'
+ end
+
+ test 'calls on_transition proc' do
+ @subj.wakeup! :showering
+
+ @subj.expects(:wear_clothes).with('purple', 'slacks')
+ @subj.dress!(:dating, 'purple', 'slacks')
+ end
+end
diff --git a/activemodel/test/cases/tests_database.rb b/activemodel/test/cases/tests_database.rb
new file mode 100644
index 0000000000..0f4475fa2d
--- /dev/null
+++ b/activemodel/test/cases/tests_database.rb
@@ -0,0 +1,41 @@
+require 'logger'
+
+$:.unshift(File.dirname(__FILE__) + '/../../../activerecord/lib')
+require 'active_record'
+require 'active_record/fixtures'
+
+module ActiveModel
+ module TestsDatabase
+ mattr_accessor :connected
+
+ def self.included(base)
+ unless self.connected
+ setup_connection
+ setup_schema
+ end
+
+ base.send :include, ActiveRecord::TestFixtures
+ end
+
+ def self.setup_schema
+ original, $stdout = $stdout, StringIO.new
+ load(SCHEMA_FILE)
+ ensure
+ $stdout = original
+ self.connected = true
+ end
+
+ def self.setup_connection
+ defaults = { :database => ':memory:' }
+ begin
+ adapter = defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'
+ options = defaults.merge :adapter => adapter, :timeout => 500
+ ActiveRecord::Base.establish_connection(options)
+ rescue Exception
+ $stderr.puts 'SQLite 3 unavailable; trying SQLite 2.'
+ options = defaults.merge :adapter => 'sqlite'
+ ActiveRecord::Base.establish_connection(options)
+ end
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations/acceptance_validation_test.rb b/activemodel/test/cases/validations/acceptance_validation_test.rb
new file mode 100644
index 0000000000..88e5fdb358
--- /dev/null
+++ b/activemodel/test/cases/validations/acceptance_validation_test.rb
@@ -0,0 +1,80 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+require 'models/reply'
+require 'models/developer'
+require 'models/person'
+
+class AcceptanceValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ def test_terms_of_service_agreement_no_acceptance
+ Topic.validates_acceptance_of(:terms_of_service, :on => :create)
+
+ t = Topic.create("title" => "We should not be confirmed")
+ assert t.save
+ end
+
+ def test_terms_of_service_agreement
+ Topic.validates_acceptance_of(:terms_of_service, :on => :create)
+
+ t = Topic.create("title" => "We should be confirmed","terms_of_service" => "")
+ assert !t.save
+ assert_equal ["must be accepted"], t.errors[:terms_of_service]
+
+ t.terms_of_service = "1"
+ assert t.save
+ end
+
+ def test_eula
+ Topic.validates_acceptance_of(:eula, :message => "must be abided", :on => :create)
+
+ t = Topic.create("title" => "We should be confirmed","eula" => "")
+ assert !t.save
+ assert_equal ["must be abided"], t.errors[:eula]
+
+ t.eula = "1"
+ assert t.save
+ end
+
+ def test_terms_of_service_agreement_with_accept_value
+ Topic.validates_acceptance_of(:terms_of_service, :on => :create, :accept => "I agree.")
+
+ t = Topic.create("title" => "We should be confirmed", "terms_of_service" => "")
+ assert !t.save
+ assert_equal ["must be accepted"], t.errors[:terms_of_service]
+
+ t.terms_of_service = "I agree."
+ assert t.save
+ end
+
+ def test_validates_acceptance_of_with_custom_error_using_quotes
+ repair_validations(Developer) do
+ Developer.validates_acceptance_of :salary, :message=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.salary = "0"
+ assert !d.valid?
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors[:salary].last
+ end
+ end
+
+ def test_validates_acceptance_of_for_ruby_class
+ repair_validations(Person) do
+ Person.validates_acceptance_of :karma
+
+ p = Person.new
+ p.karma = ""
+
+ assert p.invalid?
+ assert_equal ["must be accepted"], p.errors[:karma]
+
+ p.karma = "1"
+ assert p.valid?
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb
new file mode 100644
index 0000000000..4c716d5d48
--- /dev/null
+++ b/activemodel/test/cases/validations/conditional_validation_test.rb
@@ -0,0 +1,140 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+
+class ConditionalValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ def test_if_validation_using_method_true
+ # When the method returns true
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => :condition_is_true )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 5"], t.errors["title"]
+ end
+
+ def test_unless_validation_using_method_true
+ # When the method returns true
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => :condition_is_true )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert t.valid?
+ assert !t.errors[:title].any?
+ end
+
+ def test_if_validation_using_method_false
+ # When the method returns false
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => :condition_is_true_but_its_not )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert t.valid?
+ assert t.errors[:title].empty?
+ end
+
+ def test_unless_validation_using_method_false
+ # When the method returns false
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => :condition_is_true_but_its_not )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 5"], t.errors["title"]
+ end
+
+ def test_if_validation_using_string_true
+ # When the evaluated string returns true
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => "a = 1; a == 1" )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 5"], t.errors["title"]
+ end
+
+ def test_unless_validation_using_string_true
+ # When the evaluated string returns true
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => "a = 1; a == 1" )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert t.valid?
+ assert t.errors[:title].empty?
+ end
+
+ def test_if_validation_using_string_false
+ # When the evaluated string returns false
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => "false")
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert t.valid?
+ assert t.errors[:title].empty?
+ end
+
+ def test_unless_validation_using_string_false
+ # When the evaluated string returns false
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => "false")
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 5"], t.errors["title"]
+ end
+
+ def test_if_validation_using_block_true
+ # When the block returns true
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}",
+ :if => Proc.new { |r| r.content.size > 4 } )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 5"], t.errors["title"]
+ end
+
+ def test_unless_validation_using_block_true
+ # When the block returns true
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}",
+ :unless => Proc.new { |r| r.content.size > 4 } )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert t.valid?
+ assert t.errors[:title].empty?
+ end
+
+ def test_if_validation_using_block_false
+ # When the block returns false
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}",
+ :if => Proc.new { |r| r.title != "uhohuhoh"} )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert t.valid?
+ assert t.errors[:title].empty?
+ end
+
+ def test_unless_validation_using_block_false
+ # When the block returns false
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}",
+ :unless => Proc.new { |r| r.title != "uhohuhoh"} )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 5"], t.errors["title"]
+ end
+
+ # previous implementation of validates_presence_of eval'd the
+ # string with the wrong binding, this regression test is to
+ # ensure that it works correctly
+ def test_validation_with_if_as_string
+ Topic.validates_presence_of(:title)
+ Topic.validates_presence_of(:author_name, :if => "title.to_s.match('important')")
+
+ t = Topic.new
+ assert t.invalid?, "A topic without a title should not be valid"
+ assert t.errors[:author_name].empty?, "A topic without an 'important' title should not require an author"
+
+ t.title = "Just a title"
+ assert t.valid?, "A topic with a basic title should be valid"
+
+ t.title = "A very important title"
+ assert !t.valid?, "A topic with an important title, but without an author, should not be valid"
+ assert t.errors[:author_name].any?, "A topic with an 'important' title should require an author"
+
+ t.author_name = "Hubert J. Farnsworth"
+ assert t.valid?, "A topic with an important title and author should be valid"
+ end
+end
diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb
new file mode 100644
index 0000000000..1d6f2a6ec5
--- /dev/null
+++ b/activemodel/test/cases/validations/confirmation_validation_test.rb
@@ -0,0 +1,68 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+require 'models/developer'
+require 'models/person'
+
+class ConfirmationValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ def test_no_title_confirmation
+ Topic.validates_confirmation_of(:title)
+
+ t = Topic.new(:author_name => "Plutarch")
+ assert t.valid?
+
+ t.title_confirmation = "Parallel Lives"
+ assert !t.valid?
+
+ t.title_confirmation = nil
+ t.title = "Parallel Lives"
+ assert t.valid?
+
+ t.title_confirmation = "Parallel Lives"
+ assert t.valid?
+ end
+
+ def test_title_confirmation
+ Topic.validates_confirmation_of(:title)
+
+ t = Topic.create("title" => "We should be confirmed","title_confirmation" => "")
+ assert !t.save
+
+ t.title_confirmation = "We should be confirmed"
+ assert t.save
+ end
+
+ def test_validates_confirmation_of_with_custom_error_using_quotes
+ repair_validations(Developer) do
+ Developer.validates_confirmation_of :name, :message=> "confirm 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = "John"
+ d.name_confirmation = "Johnny"
+ assert !d.valid?
+ assert_equal ["confirm 'single' and \"double\" quotes"], d.errors[:name]
+ end
+ end
+
+ def test_validates_confirmation_of_for_ruby_class
+ repair_validations(Person) do
+ Person.validates_confirmation_of :karma
+
+ p = Person.new
+ p.karma_confirmation = "None"
+ assert p.invalid?
+
+ assert_equal ["doesn't match confirmation"], p.errors[:karma]
+
+ p.karma = "None"
+ assert p.valid?
+ end
+ end
+
+end
diff --git a/activemodel/test/cases/validations/exclusion_validation_test.rb b/activemodel/test/cases/validations/exclusion_validation_test.rb
new file mode 100644
index 0000000000..584f009e84
--- /dev/null
+++ b/activemodel/test/cases/validations/exclusion_validation_test.rb
@@ -0,0 +1,46 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+require 'models/person'
+
+class ExclusionValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ def test_validates_exclusion_of
+ Topic.validates_exclusion_of( :title, :in => %w( abe monkey ) )
+
+ assert Topic.create("title" => "something", "content" => "abc").valid?
+ assert !Topic.create("title" => "monkey", "content" => "abc").valid?
+ end
+
+ def test_validates_exclusion_of_with_formatted_message
+ Topic.validates_exclusion_of( :title, :in => %w( abe monkey ), :message => "option {{value}} is restricted" )
+
+ assert Topic.create("title" => "something", "content" => "abc")
+
+ t = Topic.create("title" => "monkey")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["option monkey is restricted"], t.errors[:title]
+ end
+
+ def test_validates_exclusion_of_for_ruby_class
+ repair_validations(Person) do
+ Person.validates_exclusion_of :karma, :in => %w( abe monkey )
+
+ p = Person.new
+ p.karma = "abe"
+ assert p.invalid?
+
+ assert_equal ["is reserved"], p.errors[:karma]
+
+ p.karma = "Lifo"
+ assert p.valid?
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb
new file mode 100644
index 0000000000..2c06a9dd02
--- /dev/null
+++ b/activemodel/test/cases/validations/format_validation_test.rb
@@ -0,0 +1,98 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+require 'models/developer'
+require 'models/person'
+
+class PresenceValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ def test_validate_format
+ Topic.validates_format_of(:title, :content, :with => /^Validation\smacros \w+!$/, :message => "is bad data")
+
+ t = Topic.create("title" => "i'm incorrect", "content" => "Validation macros rule!")
+ assert !t.valid?, "Shouldn't be valid"
+ assert !t.save, "Shouldn't save because it's invalid"
+ assert_equal ["is bad data"], t.errors[:title]
+ assert t.errors[:content].empty?
+
+ t.title = "Validation macros rule!"
+
+ assert t.save
+ assert t.errors[:title].empty?
+
+ assert_raise(ArgumentError) { Topic.validates_format_of(:title, :content) }
+ end
+
+ def test_validate_format_with_allow_blank
+ Topic.validates_format_of(:title, :with => /^Validation\smacros \w+!$/, :allow_blank=>true)
+ assert !Topic.create("title" => "Shouldn't be valid").valid?
+ assert Topic.create("title" => "").valid?
+ assert Topic.create("title" => nil).valid?
+ assert Topic.create("title" => "Validation macros rule!").valid?
+ end
+
+ # testing ticket #3142
+ def test_validate_format_numeric
+ Topic.validates_format_of(:title, :content, :with => /^[1-9][0-9]*$/, :message => "is bad data")
+
+ t = Topic.create("title" => "72x", "content" => "6789")
+ assert !t.valid?, "Shouldn't be valid"
+ assert !t.save, "Shouldn't save because it's invalid"
+ assert_equal ["is bad data"], t.errors[:title]
+ assert t.errors[:content].empty?
+
+ t.title = "-11"
+ assert !t.valid?, "Shouldn't be valid"
+
+ t.title = "03"
+ assert !t.valid?, "Shouldn't be valid"
+
+ t.title = "z44"
+ assert !t.valid?, "Shouldn't be valid"
+
+ t.title = "5v7"
+ assert !t.valid?, "Shouldn't be valid"
+
+ t.title = "1"
+
+ assert t.save
+ assert t.errors[:title].empty?
+ end
+
+ def test_validate_format_with_formatted_message
+ Topic.validates_format_of(:title, :with => /^Valid Title$/, :message => "can't be {{value}}")
+ t = Topic.create(:title => 'Invalid title')
+ assert_equal ["can't be Invalid title"], t.errors[:title]
+ end
+
+ def test_validates_format_of_with_custom_error_using_quotes
+ repair_validations(Developer) do
+ Developer.validates_format_of :name, :with => /^(A-Z*)$/, :message=> "format 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = d.name_confirmation = "John 32"
+ assert !d.valid?
+ assert_equal ["format 'single' and \"double\" quotes"], d.errors[:name]
+ end
+ end
+
+ def test_validates_format_of_for_ruby_class
+ repair_validations(Person) do
+ Person.validates_format_of :karma, :with => /\A\d+\Z/
+
+ p = Person.new
+ p.karma = "Pixies"
+ assert p.invalid?
+
+ assert_equal ["is invalid"], p.errors[:karma]
+
+ p.karma = "1234"
+ assert p.valid?
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
new file mode 100644
index 0000000000..37bba5e95e
--- /dev/null
+++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -0,0 +1,182 @@
+require "cases/helper"
+require 'cases/tests_database'
+
+require 'models/person'
+
+class I18nGenerateMessageValidationTest < Test::Unit::TestCase
+ def setup
+ reset_callbacks Person
+ @person = Person.new
+
+ @old_load_path, @old_backend = I18n.load_path, I18n.backend
+ I18n.load_path.clear
+ I18n.backend = I18n::Backend::Simple.new
+
+ I18n.backend.store_translations :'en', {
+ :activemodel => {
+ :errors => {
+ :messages => {
+ :inclusion => "is not included in the list",
+ :exclusion => "is reserved",
+ :invalid => "is invalid",
+ :confirmation => "doesn't match confirmation",
+ :accepted => "must be accepted",
+ :empty => "can't be empty",
+ :blank => "can't be blank",
+ :too_long => "is too long (maximum is {{count}} characters)",
+ :too_short => "is too short (minimum is {{count}} characters)",
+ :wrong_length => "is the wrong length (should be {{count}} characters)",
+ :not_a_number => "is not a number",
+ :greater_than => "must be greater than {{count}}",
+ :greater_than_or_equal_to => "must be greater than or equal to {{count}}",
+ :equal_to => "must be equal to {{count}}",
+ :less_than => "must be less than {{count}}",
+ :less_than_or_equal_to => "must be less than or equal to {{count}}",
+ :odd => "must be odd",
+ :even => "must be even"
+ }
+ }
+ }
+ }
+ end
+
+ def teardown
+ I18n.load_path.replace @old_load_path
+ I18n.backend = @old_backend
+ end
+
+ def reset_callbacks(*models)
+ models.each do |model|
+ model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ end
+ end
+
+ # validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value)
+ def test_generate_message_inclusion_with_default_message
+ assert_equal 'is not included in the list', @person.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title')
+ end
+
+ def test_generate_message_inclusion_with_custom_message
+ assert_equal 'custom message title', @person.errors.generate_message(:title, :inclusion, :default => 'custom message {{value}}', :value => 'title')
+ end
+
+ # validates_exclusion_of: generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value)
+ def test_generate_message_exclusion_with_default_message
+ assert_equal 'is reserved', @person.errors.generate_message(:title, :exclusion, :default => nil, :value => 'title')
+ end
+
+ def test_generate_message_exclusion_with_custom_message
+ assert_equal 'custom message title', @person.errors.generate_message(:title, :exclusion, :default => 'custom message {{value}}', :value => 'title')
+ end
+
+ # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
+ # validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
+ def test_generate_message_invalid_with_default_message
+ assert_equal 'is invalid', @person.errors.generate_message(:title, :invalid, :default => nil, :value => 'title')
+ end
+
+ def test_generate_message_invalid_with_custom_message
+ assert_equal 'custom message title', @person.errors.generate_message(:title, :invalid, :default => 'custom message {{value}}', :value => 'title')
+ end
+
+ # validates_confirmation_of: generate_message(attr_name, :confirmation, :default => configuration[:message])
+ def test_generate_message_confirmation_with_default_message
+ assert_equal "doesn't match confirmation", @person.errors.generate_message(:title, :confirmation, :default => nil)
+ end
+
+ def test_generate_message_confirmation_with_custom_message
+ assert_equal 'custom message', @person.errors.generate_message(:title, :confirmation, :default => 'custom message')
+ end
+
+ # validates_acceptance_of: generate_message(attr_name, :accepted, :default => configuration[:message])
+ def test_generate_message_accepted_with_default_message
+ assert_equal "must be accepted", @person.errors.generate_message(:title, :accepted, :default => nil)
+ end
+
+ def test_generate_message_accepted_with_custom_message
+ assert_equal 'custom message', @person.errors.generate_message(:title, :accepted, :default => 'custom message')
+ end
+
+ # add_on_empty: generate_message(attr, :empty, :default => custom_message)
+ def test_generate_message_empty_with_default_message
+ assert_equal "can't be empty", @person.errors.generate_message(:title, :empty, :default => nil)
+ end
+
+ def test_generate_message_empty_with_custom_message
+ assert_equal 'custom message', @person.errors.generate_message(:title, :empty, :default => 'custom message')
+ end
+
+ # add_on_blank: generate_message(attr, :blank, :default => custom_message)
+ def test_generate_message_blank_with_default_message
+ assert_equal "can't be blank", @person.errors.generate_message(:title, :blank, :default => nil)
+ end
+
+ def test_generate_message_blank_with_custom_message
+ assert_equal 'custom message', @person.errors.generate_message(:title, :blank, :default => 'custom message')
+ end
+
+ # validates_length_of: generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end)
+ def test_generate_message_too_long_with_default_message
+ assert_equal "is too long (maximum is 10 characters)", @person.errors.generate_message(:title, :too_long, :default => nil, :count => 10)
+ end
+
+ def test_generate_message_too_long_with_custom_message
+ assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_long, :default => 'custom message {{count}}', :count => 10)
+ end
+
+ # validates_length_of: generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin)
+ def test_generate_message_too_short_with_default_message
+ assert_equal "is too short (minimum is 10 characters)", @person.errors.generate_message(:title, :too_short, :default => nil, :count => 10)
+ end
+
+ def test_generate_message_too_short_with_custom_message
+ assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_short, :default => 'custom message {{count}}', :count => 10)
+ end
+
+ # validates_length_of: generate_message(attr, key, :default => custom_message, :count => option_value)
+ def test_generate_message_wrong_length_with_default_message
+ assert_equal "is the wrong length (should be 10 characters)", @person.errors.generate_message(:title, :wrong_length, :default => nil, :count => 10)
+ end
+
+ def test_generate_message_wrong_length_with_custom_message
+ assert_equal 'custom message 10', @person.errors.generate_message(:title, :wrong_length, :default => 'custom message {{count}}', :count => 10)
+ end
+
+ # validates_numericality_of: generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
+ def test_generate_message_not_a_number_with_default_message
+ assert_equal "is not a number", @person.errors.generate_message(:title, :not_a_number, :default => nil, :value => 'title')
+ end
+
+ def test_generate_message_not_a_number_with_custom_message
+ assert_equal 'custom message title', @person.errors.generate_message(:title, :not_a_number, :default => 'custom message {{value}}', :value => 'title')
+ end
+
+ # validates_numericality_of: generate_message(attr_name, option, :value => raw_value, :default => configuration[:message])
+ def test_generate_message_greater_than_with_default_message
+ assert_equal "must be greater than 10", @person.errors.generate_message(:title, :greater_than, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_greater_than_or_equal_to_with_default_message
+ assert_equal "must be greater than or equal to 10", @person.errors.generate_message(:title, :greater_than_or_equal_to, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_equal_to_with_default_message
+ assert_equal "must be equal to 10", @person.errors.generate_message(:title, :equal_to, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_less_than_with_default_message
+ assert_equal "must be less than 10", @person.errors.generate_message(:title, :less_than, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_less_than_or_equal_to_with_default_message
+ assert_equal "must be less than or equal to 10", @person.errors.generate_message(:title, :less_than_or_equal_to, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_odd_with_default_message
+ assert_equal "must be odd", @person.errors.generate_message(:title, :odd, :default => nil, :value => 'title', :count => 10)
+ end
+
+ def test_generate_message_even_with_default_message
+ assert_equal "must be even", @person.errors.generate_message(:title, :even, :default => nil, :value => 'title', :count => 10)
+ end
+end
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
new file mode 100644
index 0000000000..cc68d847a2
--- /dev/null
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -0,0 +1,579 @@
+require "cases/helper"
+require 'cases/tests_database'
+
+require 'models/person'
+
+class I18nValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+
+ def setup
+ reset_callbacks Person
+
+ @person = Person.new
+
+ @old_load_path, @old_backend = I18n.load_path, I18n.backend
+ I18n.load_path.clear
+ I18n.backend = I18n::Backend::Simple.new
+ I18n.backend.store_translations('en', :activemodel => {:errors => {:messages => {:custom => nil}}})
+ end
+
+ def teardown
+ reset_callbacks Person
+ I18n.load_path.replace @old_load_path
+ I18n.backend = @old_backend
+ end
+
+ def reset_callbacks(*models)
+ models.each do |model|
+ model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ end
+ end
+
+ def test_percent_s_interpolation_syntax_in_error_messages_was_deprecated
+ assert_not_deprecated do
+ default = "%s interpolation syntax was deprecated"
+ assert_equal default, I18n.t(:does_not_exist, :default => default, :value => 'this')
+ end
+ end
+
+ def test_percent_d_interpolation_syntax_in_error_messages_was_deprecated
+ assert_not_deprecated do
+ default = "%d interpolation syntaxes are deprecated"
+ assert_equal default, I18n.t(:does_not_exist, :default => default, :count => 2)
+ end
+ end
+
+ def test_errors_add_on_empty_generates_message
+ @person.errors.expects(:generate_message).with(:title, :empty, {:default => nil})
+ @person.errors.add_on_empty :title
+ end
+
+ def test_errors_add_on_empty_generates_message_with_custom_default_message
+ @person.errors.expects(:generate_message).with(:title, :empty, {:default => 'custom'})
+ @person.errors.add_on_empty :title, 'custom'
+ end
+
+ def test_errors_add_on_blank_generates_message
+ @person.errors.expects(:generate_message).with(:title, :blank, {:default => nil})
+ @person.errors.add_on_blank :title
+ end
+
+ def test_errors_add_on_blank_generates_message_with_custom_default_message
+ @person.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'})
+ @person.errors.add_on_blank :title, 'custom'
+ end
+
+ # ActiveRecord::Validations
+ # validates_confirmation_of w/ mocha
+ def test_validates_confirmation_of_generates_message
+ Person.validates_confirmation_of :title
+ @person.title_confirmation = 'foo'
+ @person.errors.expects(:generate_message).with(:title, :confirmation, {:default => nil})
+ @person.valid?
+ end
+
+ def test_validates_confirmation_of_generates_message_with_custom_default_message
+ Person.validates_confirmation_of :title, :message => 'custom'
+ @person.title_confirmation = 'foo'
+ @person.errors.expects(:generate_message).with(:title, :confirmation, {:default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_acceptance_of w/ mocha
+
+ def test_validates_acceptance_of_generates_message
+ Person.validates_acceptance_of :title, :allow_nil => false
+ @person.errors.expects(:generate_message).with(:title, :accepted, {:default => nil})
+ @person.valid?
+ end
+
+ def test_validates_acceptance_of_generates_message_with_custom_default_message
+ Person.validates_acceptance_of :title, :message => 'custom', :allow_nil => false
+ @person.errors.expects(:generate_message).with(:title, :accepted, {:default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_presence_of w/ mocha
+
+ def test_validates_presence_of_generates_message
+ Person.validates_presence_of :title
+ @person.errors.expects(:generate_message).with(:title, :blank, {:default => nil})
+ @person.valid?
+ end
+
+ def test_validates_presence_of_generates_message_with_custom_default_message
+ Person.validates_presence_of :title, :message => 'custom'
+ @person.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'})
+ @person.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_short
+ Person.validates_length_of :title, :within => 3..5
+ @person.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message
+ Person.validates_length_of :title, :within => 3..5, :too_short => 'custom'
+ @person.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'})
+ @person.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_long
+ Person.validates_length_of :title, :within => 3..5
+ @person.title = 'this title is too long'
+ @person.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message
+ Person.validates_length_of :title, :within => 3..5, :too_long => 'custom'
+ @person.title = 'this title is too long'
+ @person.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_length_of :within w/ mocha
+
+ def test_validates_length_of_within_generates_message_with_title_too_short
+ Person.validates_length_of :title, :within => 3..5
+ @person.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message
+ Person.validates_length_of :title, :within => 3..5, :too_short => 'custom'
+ @person.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'})
+ @person.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_long
+ Person.validates_length_of :title, :within => 3..5
+ @person.title = 'this title is too long'
+ @person.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message
+ Person.validates_length_of :title, :within => 3..5, :too_long => 'custom'
+ @person.title = 'this title is too long'
+ @person.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_length_of :is w/ mocha
+
+ def test_validates_length_of_is_generates_message
+ Person.validates_length_of :title, :is => 5
+ @person.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_length_of_is_generates_message_with_custom_default_message
+ Person.validates_length_of :title, :is => 5, :message => 'custom'
+ @person.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_format_of w/ mocha
+
+ def test_validates_format_of_generates_message
+ Person.validates_format_of :title, :with => /^[1-9][0-9]*$/
+ @person.title = '72x'
+ @person.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_format_of_generates_message_with_custom_default_message
+ Person.validates_format_of :title, :with => /^[1-9][0-9]*$/, :message => 'custom'
+ @person.title = '72x'
+ @person.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_inclusion_of w/ mocha
+
+ def test_validates_inclusion_of_generates_message
+ Person.validates_inclusion_of :title, :in => %w(a b c)
+ @person.title = 'z'
+ @person.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_inclusion_of_generates_message_with_custom_default_message
+ Person.validates_inclusion_of :title, :in => %w(a b c), :message => 'custom'
+ @person.title = 'z'
+ @person.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_exclusion_of w/ mocha
+
+ def test_validates_exclusion_of_generates_message
+ Person.validates_exclusion_of :title, :in => %w(a b c)
+ @person.title = 'a'
+ @person.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_exclusion_of_generates_message_with_custom_default_message
+ Person.validates_exclusion_of :title, :in => %w(a b c), :message => 'custom'
+ @person.title = 'a'
+ @person.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_numericality_of without :only_integer w/ mocha
+
+ def test_validates_numericality_of_generates_message
+ Person.validates_numericality_of :title
+ @person.title = 'a'
+ @person.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_numericality_of_generates_message_with_custom_default_message
+ Person.validates_numericality_of :title, :message => 'custom'
+ @person.title = 'a'
+ @person.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_numericality_of with :only_integer w/ mocha
+
+ def test_validates_numericality_of_only_integer_generates_message
+ Person.validates_numericality_of :title, :only_integer => true
+ @person.title = 'a'
+ @person.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_numericality_of_only_integer_generates_message_with_custom_default_message
+ Person.validates_numericality_of :title, :only_integer => true, :message => 'custom'
+ @person.title = 'a'
+ @person.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_numericality_of :odd w/ mocha
+
+ def test_validates_numericality_of_odd_generates_message
+ Person.validates_numericality_of :title, :only_integer => true, :odd => true
+ @person.title = 0
+ @person.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_numericality_of_odd_generates_message_with_custom_default_message
+ Person.validates_numericality_of :title, :only_integer => true, :odd => true, :message => 'custom'
+ @person.title = 0
+ @person.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_numericality_of :less_than w/ mocha
+
+ def test_validates_numericality_of_less_than_generates_message
+ Person.validates_numericality_of :title, :only_integer => true, :less_than => 0
+ @person.title = 1
+ @person.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => nil})
+ @person.valid?
+ end
+
+ def test_validates_numericality_of_odd_generates_message_with_custom_default_message
+ Person.validates_numericality_of :title, :only_integer => true, :less_than => 0, :message => 'custom'
+ @person.title = 1
+ @person.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => 'custom'})
+ @person.valid?
+ end
+
+ # validates_confirmation_of w/o mocha
+
+ def test_validates_confirmation_of_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:confirmation => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:confirmation => 'global message'}}}
+
+ Person.validates_confirmation_of :title
+ @person.title_confirmation = 'foo'
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_confirmation_of_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:confirmation => 'global message'}}}
+
+ Person.validates_confirmation_of :title
+ @person.title_confirmation = 'foo'
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_acceptance_of w/o mocha
+
+ def test_validates_acceptance_of_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:accepted => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:accepted => 'global message'}}}
+
+ Person.validates_acceptance_of :title, :allow_nil => false
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_acceptance_of_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:accepted => 'global message'}}}
+
+ Person.validates_acceptance_of :title, :allow_nil => false
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_presence_of w/o mocha
+
+ def test_validates_presence_of_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:blank => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:blank => 'global message'}}}
+
+ Person.validates_presence_of :title
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_presence_of_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:blank => 'global message'}}}
+
+ Person.validates_presence_of :title
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_length_of :within w/o mocha
+
+ def test_validates_length_of_within_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:too_short => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:too_short => 'global message'}}}
+
+ Person.validates_length_of :title, :within => 3..5
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_length_of_within_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:too_short => 'global message'}}}
+
+ Person.validates_length_of :title, :within => 3..5
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_length_of :is w/o mocha
+
+ def test_validates_length_of_is_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:wrong_length => 'global message'}}}
+
+ Person.validates_length_of :title, :is => 5
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_length_of_is_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:wrong_length => 'global message'}}}
+
+ Person.validates_length_of :title, :is => 5
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ def test_validates_length_of_is_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:wrong_length => 'global message'}}}
+
+ Person.validates_length_of :title, :is => 5
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_length_of_is_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:wrong_length => 'global message'}}}
+
+ Person.validates_length_of :title, :is => 5
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+
+ # validates_format_of w/o mocha
+
+ def test_validates_format_of_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:invalid => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:invalid => 'global message'}}}
+
+ Person.validates_format_of :title, :with => /^[1-9][0-9]*$/
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_format_of_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:invalid => 'global message'}}}
+
+ Person.validates_format_of :title, :with => /^[1-9][0-9]*$/
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_inclusion_of w/o mocha
+
+ def test_validates_inclusion_of_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:inclusion => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:inclusion => 'global message'}}}
+
+ Person.validates_inclusion_of :title, :in => %w(a b c)
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_inclusion_of_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:inclusion => 'global message'}}}
+
+ Person.validates_inclusion_of :title, :in => %w(a b c)
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_exclusion_of w/o mocha
+
+ def test_validates_exclusion_of_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:exclusion => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:exclusion => 'global message'}}}
+
+ Person.validates_exclusion_of :title, :in => %w(a b c)
+ @person.title = 'a'
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_exclusion_of_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:exclusion => 'global message'}}}
+
+ Person.validates_exclusion_of :title, :in => %w(a b c)
+ @person.title = 'a'
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_numericality_of without :only_integer w/o mocha
+
+ def test_validates_numericality_of_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:not_a_number => 'global message'}}}
+
+ Person.validates_numericality_of :title
+ @person.title = 'a'
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_numericality_of_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:not_a_number => 'global message'}}}
+
+ Person.validates_numericality_of :title, :only_integer => true
+ @person.title = 'a'
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_numericality_of with :only_integer w/o mocha
+
+ def test_validates_numericality_of_only_integer_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:not_a_number => 'global message'}}}
+
+ Person.validates_numericality_of :title, :only_integer => true
+ @person.title = 'a'
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_numericality_of_only_integer_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:not_a_number => 'global message'}}}
+
+ Person.validates_numericality_of :title, :only_integer => true
+ @person.title = 'a'
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_numericality_of :odd w/o mocha
+
+ def test_validates_numericality_of_odd_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:odd => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:odd => 'global message'}}}
+
+ Person.validates_numericality_of :title, :only_integer => true, :odd => true
+ @person.title = 0
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_numericality_of_odd_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:odd => 'global message'}}}
+
+ Person.validates_numericality_of :title, :only_integer => true, :odd => true
+ @person.title = 0
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ # validates_numericality_of :less_than w/o mocha
+
+ def test_validates_numericality_of_less_than_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:less_than => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:less_than => 'global message'}}}
+
+ Person.validates_numericality_of :title, :only_integer => true, :less_than => 0
+ @person.title = 1
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
+
+ def test_validates_numericality_of_less_than_finds_global_default_translation
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:less_than => 'global message'}}}
+
+ Person.validates_numericality_of :title, :only_integer => true, :less_than => 0
+ @person.title = 1
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
+
+ def test_validations_with_message_symbol_must_translate
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:custom_error => "I am a custom error"}}}
+ Person.validates_presence_of :title, :message => :custom_error
+ @person.title = nil
+ @person.valid?
+ assert_equal ["I am a custom error"], @person.errors[:title]
+ end
+
+ def test_validates_with_message_symbol_must_translate_per_attribute
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}}
+ Person.validates_presence_of :title, :message => :custom_error
+ @person.title = nil
+ @person.valid?
+ assert_equal ["I am a custom error"], @person.errors[:title]
+ end
+
+ def test_validates_with_message_symbol_must_translate_per_model
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:custom_error => "I am a custom error"}}}}
+ Person.validates_presence_of :title, :message => :custom_error
+ @person.title = nil
+ @person.valid?
+ assert_equal ["I am a custom error"], @person.errors[:title]
+ end
+
+ def test_validates_with_message_string
+ Person.validates_presence_of :title, :message => "I am a custom error"
+ @person.title = nil
+ @person.valid?
+ assert_equal ["I am a custom error"], @person.errors[:title]
+ end
+
+end \ No newline at end of file
diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb
new file mode 100644
index 0000000000..bc1b0365d2
--- /dev/null
+++ b/activemodel/test/cases/validations/inclusion_validation_test.rb
@@ -0,0 +1,80 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+require 'models/developer'
+require 'models/person'
+
+class InclusionValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ def test_validates_inclusion_of
+ Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ) )
+
+ assert !Topic.create("title" => "a!", "content" => "abc").valid?
+ assert !Topic.create("title" => "a b", "content" => "abc").valid?
+ assert !Topic.create("title" => nil, "content" => "def").valid?
+
+ t = Topic.create("title" => "a", "content" => "I know you are but what am I?")
+ assert t.valid?
+ t.title = "uhoh"
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["is not included in the list"], t.errors[:title]
+
+ assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => nil ) }
+ assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => 0) }
+
+ assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => "hi!" ) }
+ assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => {} ) }
+ assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => [] ) }
+ end
+
+ def test_validates_inclusion_of_with_allow_nil
+ Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :allow_nil=>true )
+
+ assert !Topic.create("title" => "a!", "content" => "abc").valid?
+ assert !Topic.create("title" => "", "content" => "abc").valid?
+ assert Topic.create("title" => nil, "content" => "abc").valid?
+ end
+
+ def test_validates_inclusion_of_with_formatted_message
+ Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :message => "option {{value}} is not in the list" )
+
+ assert Topic.create("title" => "a", "content" => "abc").valid?
+
+ t = Topic.create("title" => "uhoh", "content" => "abc")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["option uhoh is not in the list"], t.errors[:title]
+ end
+
+ def test_validates_inclusion_of_with_custom_error_using_quotes
+ repair_validations(Developer) do
+ Developer.validates_inclusion_of :salary, :in => 1000..80000, :message=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.salary = "90,000"
+ assert !d.valid?
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors[:salary].last
+ end
+ end
+
+ def test_validates_inclusion_of_for_ruby_class
+ repair_validations(Person) do
+ Person.validates_inclusion_of :karma, :in => %w( abe monkey )
+
+ p = Person.new
+ p.karma = "Lifo"
+ assert p.invalid?
+
+ assert_equal ["is not included in the list"], p.errors[:karma]
+
+ p.karma = "monkey"
+ assert p.valid?
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
new file mode 100644
index 0000000000..4a2f72feab
--- /dev/null
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -0,0 +1,449 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+require 'models/developer'
+require 'models/person'
+
+class LengthValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ def test_validates_length_of_with_allow_nil
+ Topic.validates_length_of( :title, :is => 5, :allow_nil=>true )
+
+ assert !Topic.create("title" => "ab").valid?
+ assert !Topic.create("title" => "").valid?
+ assert Topic.create("title" => nil).valid?
+ assert Topic.create("title" => "abcde").valid?
+ end
+
+ def test_validates_length_of_with_allow_blank
+ Topic.validates_length_of( :title, :is => 5, :allow_blank=>true )
+
+ assert !Topic.create("title" => "ab").valid?
+ assert Topic.create("title" => "").valid?
+ assert Topic.create("title" => nil).valid?
+ assert Topic.create("title" => "abcde").valid?
+ end
+
+ def test_validates_length_of_using_minimum
+ Topic.validates_length_of :title, :minimum => 5
+
+ t = Topic.create("title" => "valid", "content" => "whatever")
+ assert t.valid?
+
+ t.title = "not"
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["is too short (minimum is 5 characters)"], t.errors[:title]
+
+ t.title = ""
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["is too short (minimum is 5 characters)"], t.errors[:title]
+
+ t.title = nil
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"]
+ end
+
+ def test_optionally_validates_length_of_using_minimum
+ Topic.validates_length_of :title, :minimum => 5, :allow_nil => true
+
+ t = Topic.create("title" => "valid", "content" => "whatever")
+ assert t.valid?
+
+ t.title = nil
+ assert t.valid?
+ end
+
+ def test_validates_length_of_using_maximum
+ Topic.validates_length_of :title, :maximum => 5
+
+ t = Topic.create("title" => "valid", "content" => "whatever")
+ assert t.valid?
+
+ t.title = "notvalid"
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["is too long (maximum is 5 characters)"], t.errors[:title]
+
+ t.title = ""
+ assert t.valid?
+
+ t.title = nil
+ assert !t.valid?
+ end
+
+ def test_optionally_validates_length_of_using_maximum
+ Topic.validates_length_of :title, :maximum => 5, :allow_nil => true
+
+ t = Topic.create("title" => "valid", "content" => "whatever")
+ assert t.valid?
+
+ t.title = nil
+ assert t.valid?
+ end
+
+ def test_validates_length_of_using_within
+ Topic.validates_length_of(:title, :content, :within => 3..5)
+
+ t = Topic.new("title" => "a!", "content" => "I'm ooooooooh so very long")
+ assert !t.valid?
+ assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title]
+ assert_equal ["is too long (maximum is 5 characters)"], t.errors[:content]
+
+ t.title = nil
+ t.content = nil
+ assert !t.valid?
+ assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title]
+ assert_equal ["is too short (minimum is 3 characters)"], t.errors[:content]
+
+ t.title = "abe"
+ t.content = "mad"
+ assert t.valid?
+ end
+
+ def test_optionally_validates_length_of_using_within
+ Topic.validates_length_of :title, :content, :within => 3..5, :allow_nil => true
+
+ t = Topic.create('title' => 'abc', 'content' => 'abcd')
+ assert t.valid?
+
+ t.title = nil
+ assert t.valid?
+ end
+
+ def test_optionally_validates_length_of_using_within_on_create
+ Topic.validates_length_of :title, :content, :within => 5..10, :on => :create, :too_long => "my string is too long: {{count}}"
+
+ t = Topic.create("title" => "thisisnotvalid", "content" => "whatever")
+ assert !t.save
+ assert t.errors[:title].any?
+ assert_equal ["my string is too long: 10"], t.errors[:title]
+
+ t.title = "butthisis"
+ assert t.save
+
+ t.title = "few"
+ assert t.save
+
+ t.content = "andthisislong"
+ assert t.save
+
+ t.content = t.title = "iamfine"
+ assert t.save
+ end
+
+ def test_optionally_validates_length_of_using_within_on_update
+ Topic.validates_length_of :title, :content, :within => 5..10, :on => :update, :too_short => "my string is too short: {{count}}"
+
+ t = Topic.create("title" => "vali", "content" => "whatever")
+ assert !t.save
+ assert t.errors[:title].any?
+
+ t.title = "not"
+ assert !t.save
+ assert t.errors[:title].any?
+ assert_equal ["my string is too short: 5"], t.errors[:title]
+
+ t.title = "valid"
+ t.content = "andthisistoolong"
+ assert !t.save
+ assert t.errors[:content].any?
+
+ t.content = "iamfine"
+ assert t.save
+ end
+
+ def test_validates_length_of_using_is
+ Topic.validates_length_of :title, :is => 5
+
+ t = Topic.create("title" => "valid", "content" => "whatever")
+ assert t.valid?
+
+ t.title = "notvalid"
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["is the wrong length (should be 5 characters)"], t.errors[:title]
+
+ t.title = ""
+ assert !t.valid?
+
+ t.title = nil
+ assert !t.valid?
+ end
+
+ def test_optionally_validates_length_of_using_is
+ Topic.validates_length_of :title, :is => 5, :allow_nil => true
+
+ t = Topic.create("title" => "valid", "content" => "whatever")
+ assert t.valid?
+
+ t.title = nil
+ assert t.valid?
+ end
+
+ def test_validates_length_of_using_bignum
+ bigmin = 2 ** 30
+ bigmax = 2 ** 32
+ bigrange = bigmin...bigmax
+ assert_nothing_raised do
+ Topic.validates_length_of :title, :is => bigmin + 5
+ Topic.validates_length_of :title, :within => bigrange
+ Topic.validates_length_of :title, :in => bigrange
+ Topic.validates_length_of :title, :minimum => bigmin
+ Topic.validates_length_of :title, :maximum => bigmax
+ end
+ end
+
+ def test_validates_length_of_nasty_params
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>6, :maximum=>9) }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :maximum=>9) }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :minimum=>9) }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :is=>9) }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>"a") }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :maximum=>"a") }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>"a") }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is=>"a") }
+ end
+
+ def test_validates_length_of_custom_errors_for_minimum_with_message
+ Topic.validates_length_of( :title, :minimum=>5, :message=>"boo {{count}}" )
+ t = Topic.create("title" => "uhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["boo 5"], t.errors[:title]
+ end
+
+ def test_validates_length_of_custom_errors_for_minimum_with_too_short
+ Topic.validates_length_of( :title, :minimum=>5, :too_short=>"hoo {{count}}" )
+ t = Topic.create("title" => "uhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 5"], t.errors[:title]
+ end
+
+ def test_validates_length_of_custom_errors_for_maximum_with_message
+ Topic.validates_length_of( :title, :maximum=>5, :message=>"boo {{count}}" )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["boo 5"], t.errors[:title]
+ end
+
+ def test_validates_length_of_custom_errors_for_in
+ Topic.validates_length_of(:title, :in => 10..20, :message => "hoo {{count}}")
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 10"], t.errors["title"]
+
+ t = Topic.create("title" => "uhohuhohuhohuhohuhohuhohuhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 20"], t.errors["title"]
+ end
+
+ def test_validates_length_of_custom_errors_for_maximum_with_too_long
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}" )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 5"], t.errors["title"]
+ end
+
+ def test_validates_length_of_custom_errors_for_is_with_message
+ Topic.validates_length_of( :title, :is=>5, :message=>"boo {{count}}" )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["boo 5"], t.errors["title"]
+ end
+
+ def test_validates_length_of_custom_errors_for_is_with_wrong_length
+ Topic.validates_length_of( :title, :is=>5, :wrong_length=>"hoo {{count}}" )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["hoo 5"], t.errors["title"]
+ end
+
+ def test_validates_length_of_using_minimum_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of :title, :minimum => 5
+
+ t = Topic.create("title" => "一二三四五", "content" => "whatever")
+ assert t.valid?
+
+ t.title = "一二三四"
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"]
+ end
+ end
+
+ def test_validates_length_of_using_maximum_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of :title, :maximum => 5
+
+ t = Topic.create("title" => "一二三四五", "content" => "whatever")
+ assert t.valid?
+
+ t.title = "一二34五六"
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["is too long (maximum is 5 characters)"], t.errors["title"]
+ end
+ end
+
+ def test_validates_length_of_using_within_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of(:title, :content, :within => 3..5)
+
+ t = Topic.new("title" => "一二", "content" => "12三四五六七")
+ assert !t.valid?
+ assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title]
+ assert_equal ["is too long (maximum is 5 characters)"], t.errors[:content]
+ t.title = "一二三"
+ t.content = "12三"
+ assert t.valid?
+ end
+ end
+
+ def test_optionally_validates_length_of_using_within_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of :title, :within => 3..5, :allow_nil => true
+
+ t = Topic.create(:title => "一二三四五")
+ assert t.valid?, t.errors.inspect
+
+ t = Topic.create(:title => "一二三")
+ assert t.valid?, t.errors.inspect
+
+ t.title = nil
+ assert t.valid?, t.errors.inspect
+ end
+ end
+
+ def test_optionally_validates_length_of_using_within_on_create_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of :title, :within => 5..10, :on => :create, :too_long => "長すぎます: {{count}}"
+
+ t = Topic.create("title" => "一二三四五六七八九十A", "content" => "whatever")
+ assert !t.save
+ assert t.errors[:title].any?
+ assert_equal "長すぎます: 10", t.errors[:title].first
+
+ t.title = "一二三四五六七八九"
+ assert t.save
+
+ t.title = "一二3"
+ assert t.save
+
+ t.content = "一二三四五六七八九十"
+ assert t.save
+
+ t.content = t.title = "一二三四五六"
+ assert t.save
+ end
+ end
+
+ def test_optionally_validates_length_of_using_within_on_update_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of :title, :within => 5..10, :on => :update, :too_short => "短すぎます: {{count}}"
+
+ t = Topic.create("title" => "一二三4", "content" => "whatever")
+ assert !t.save
+ assert t.errors[:title].any?
+
+ t.title = "1二三4"
+ assert !t.save
+ assert t.errors[:title].any?
+ assert_equal ["短すぎます: 5"], t.errors[:title]
+
+ t.title = "一二三四五六七八九十A"
+ assert !t.save
+ assert t.errors[:title].any?
+
+ t.title = "一二345"
+ assert t.save
+ end
+ end
+
+ def test_validates_length_of_using_is_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of :title, :is => 5
+
+ t = Topic.create("title" => "一二345", "content" => "whatever")
+ assert t.valid?
+
+ t.title = "一二345六"
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["is the wrong length (should be 5 characters)"], t.errors["title"]
+ end
+ end
+
+ def test_validates_length_of_with_block
+ Topic.validates_length_of :content, :minimum => 5, :too_short=>"Your essay must be at least {{count}} words.",
+ :tokenizer => lambda {|str| str.scan(/\w+/) }
+ t = Topic.create!(:content => "this content should be long enough")
+ assert t.valid?
+
+ t.content = "not long enough"
+ assert !t.valid?
+ assert t.errors[:content].any?
+ assert_equal ["Your essay must be at least 5 words."], t.errors[:content]
+ end
+
+ def test_validates_length_of_with_custom_too_long_using_quotes
+ repair_validations(Developer) do
+ Developer.validates_length_of :name, :maximum => 4, :too_long=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = "Jeffrey"
+ assert !d.valid?
+ assert_equal ["This string contains 'single' and \"double\" quotes"], d.errors[:name]
+ end
+ end
+
+ def test_validates_length_of_with_custom_too_short_using_quotes
+ repair_validations(Developer) do
+ Developer.validates_length_of :name, :minimum => 4, :too_short=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = "Joe"
+ assert !d.valid?
+ assert_equal ["This string contains 'single' and \"double\" quotes"], d.errors[:name]
+ end
+ end
+
+ def test_validates_length_of_with_custom_message_using_quotes
+ repair_validations(Developer) do
+ Developer.validates_length_of :name, :minimum => 4, :message=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = "Joe"
+ assert !d.valid?
+ assert_equal ["This string contains 'single' and \"double\" quotes"], d.errors[:name]
+ end
+ end
+
+ def test_validates_length_of_for_ruby_class
+ repair_validations(Person) do
+ Person.validates_length_of :karma, :minimum => 5
+
+ p = Person.new
+ p.karma = "Pix"
+ assert p.invalid?
+
+ assert_equal ["is too short (minimum is 5 characters)"], p.errors[:karma]
+
+ p.karma = "The Smiths"
+ assert p.valid?
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
new file mode 100644
index 0000000000..0af6eb69ce
--- /dev/null
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -0,0 +1,179 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+require 'models/developer'
+require 'models/person'
+
+class NumericalityValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ NIL = [nil]
+ BLANK = ["", " ", " \t \r \n"]
+ BIGDECIMAL_STRINGS = %w(12345678901234567890.1234567890) # 30 significent digits
+ FLOAT_STRINGS = %w(0.0 +0.0 -0.0 10.0 10.5 -10.5 -0.0001 -090.1 90.1e1 -90.1e5 -90.1e-5 90e-5)
+ INTEGER_STRINGS = %w(0 +0 -0 10 +10 -10 0090 -090)
+ FLOATS = [0.0, 10.0, 10.5, -10.5, -0.0001] + FLOAT_STRINGS
+ INTEGERS = [0, 10, -10] + INTEGER_STRINGS
+ BIGDECIMAL = BIGDECIMAL_STRINGS.collect! { |bd| BigDecimal.new(bd) }
+ JUNK = ["not a number", "42 not a number", "0xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12", "123\nnot a number"]
+ INFINITY = [1.0/0.0]
+
+ def test_default_validates_numericality_of
+ Topic.validates_numericality_of :approved
+ invalid!(NIL + BLANK + JUNK)
+ valid!(FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
+ end
+
+ def test_validates_numericality_of_with_nil_allowed
+ Topic.validates_numericality_of :approved, :allow_nil => true
+
+ invalid!(JUNK)
+ valid!(NIL + BLANK + FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
+ end
+
+ def test_validates_numericality_of_with_integer_only
+ Topic.validates_numericality_of :approved, :only_integer => true
+
+ invalid!(NIL + BLANK + JUNK + FLOATS + BIGDECIMAL + INFINITY)
+ valid!(INTEGERS)
+ end
+
+ def test_validates_numericality_of_with_integer_only_and_nil_allowed
+ Topic.validates_numericality_of :approved, :only_integer => true, :allow_nil => true
+
+ invalid!(JUNK + FLOATS + BIGDECIMAL + INFINITY)
+ valid!(NIL + BLANK + INTEGERS)
+ end
+
+ def test_validates_numericality_with_greater_than
+ Topic.validates_numericality_of :approved, :greater_than => 10
+
+ invalid!([-10, 10], 'must be greater than 10')
+ valid!([11])
+ end
+
+ def test_validates_numericality_with_greater_than_or_equal
+ Topic.validates_numericality_of :approved, :greater_than_or_equal_to => 10
+
+ invalid!([-9, 9], 'must be greater than or equal to 10')
+ valid!([10])
+ end
+
+ def test_validates_numericality_with_equal_to
+ Topic.validates_numericality_of :approved, :equal_to => 10
+
+ invalid!([-10, 11] + INFINITY, 'must be equal to 10')
+ valid!([10])
+ end
+
+ def test_validates_numericality_with_less_than
+ Topic.validates_numericality_of :approved, :less_than => 10
+
+ invalid!([10], 'must be less than 10')
+ valid!([-9, 9])
+ end
+
+ def test_validates_numericality_with_less_than_or_equal_to
+ Topic.validates_numericality_of :approved, :less_than_or_equal_to => 10
+
+ invalid!([11], 'must be less than or equal to 10')
+ valid!([-10, 10])
+ end
+
+ def test_validates_numericality_with_odd
+ Topic.validates_numericality_of :approved, :odd => true
+
+ invalid!([-2, 2], 'must be odd')
+ valid!([-1, 1])
+ end
+
+ def test_validates_numericality_with_even
+ Topic.validates_numericality_of :approved, :even => true
+
+ invalid!([-1, 1], 'must be even')
+ valid!([-2, 2])
+ end
+
+ def test_validates_numericality_with_greater_than_less_than_and_even
+ Topic.validates_numericality_of :approved, :greater_than => 1, :less_than => 4, :even => true
+
+ invalid!([1, 3, 4])
+ valid!([2])
+ end
+
+ def test_validates_numericality_with_numeric_message
+ Topic.validates_numericality_of :approved, :less_than => 4, :message => "smaller than {{count}}"
+ topic = Topic.new("title" => "numeric test", "approved" => 10)
+
+ assert !topic.valid?
+ assert_equal ["smaller than 4"], topic.errors[:approved]
+
+ Topic.validates_numericality_of :approved, :greater_than => 4, :message => "greater than {{count}}"
+ topic = Topic.new("title" => "numeric test", "approved" => 1)
+
+ assert !topic.valid?
+ assert_equal ["greater than 4"], topic.errors[:approved]
+ end
+
+ def test_numericality_with_getter_method
+ repair_validations(Developer) do
+ Developer.validates_numericality_of( :salary )
+ developer = Developer.new("name" => "michael", "salary" => nil)
+ developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
+ assert developer.valid?
+ end
+ end
+
+ def test_numericality_with_allow_nil_and_getter_method
+ repair_validations(Developer) do
+ Developer.validates_numericality_of( :salary, :allow_nil => true)
+ developer = Developer.new("name" => "michael", "salary" => nil)
+ developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
+ assert developer.valid?
+ end
+ end
+
+ def test_validates_numericality_of_for_ruby_class
+ repair_validations(Person) do
+ Person.validates_numericality_of :karma, :allow_nil => false
+
+ p = Person.new
+ p.karma = "Pix"
+ assert p.invalid?
+
+ assert_equal ["is not a number"], p.errors[:karma]
+
+ p.karma = "1234"
+ assert p.valid?
+ end
+ end
+
+ private
+
+ def invalid!(values, error = nil)
+ with_each_topic_approved_value(values) do |topic, value|
+ assert !topic.valid?, "#{value.inspect} not rejected as a number"
+ assert topic.errors[:approved].any?, "FAILED for #{value.inspect}"
+ assert_equal error, topic.errors[:approved].first if error
+ end
+ end
+
+ def valid!(values)
+ with_each_topic_approved_value(values) do |topic, value|
+ assert topic.valid?, "#{value.inspect} not accepted as a number"
+ end
+ end
+
+ def with_each_topic_approved_value(values)
+ topic = Topic.new(:title => "numeric test", :content => "whatever")
+ values.each do |value|
+ topic.approved = value
+ yield topic, value
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations/presence_validation_test.rb b/activemodel/test/cases/validations/presence_validation_test.rb
new file mode 100644
index 0000000000..f6bed4903a
--- /dev/null
+++ b/activemodel/test/cases/validations/presence_validation_test.rb
@@ -0,0 +1,57 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+require 'models/developer'
+require 'models/person'
+
+class PresenceValidationTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ def test_validate_presences
+ Topic.validates_presence_of(:title, :content)
+
+ t = Topic.create
+ assert !t.save
+ assert_equal ["can't be blank"], t.errors[:title]
+ assert_equal ["can't be blank"], t.errors[:content]
+
+ t.title = "something"
+ t.content = " "
+
+ assert !t.save
+ assert_equal ["can't be blank"], t.errors[:content]
+
+ t.content = "like stuff"
+
+ assert t.save
+ end
+
+ def test_validates_presence_of_with_custom_message_using_quotes
+ repair_validations(Developer) do
+ Developer.validates_presence_of :non_existent, :message=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = "Joe"
+ assert !d.valid?
+ assert_equal ["This string contains 'single' and \"double\" quotes"], d.errors[:non_existent]
+ end
+ end
+
+ def test_validates_presence_of_for_ruby_class
+ repair_validations(Person) do
+ Person.validates_presence_of :karma
+
+ p = Person.new
+ assert p.invalid?
+
+ assert_equal ["can't be blank"], p.errors[:karma]
+
+ p.karma = "Cold"
+ assert p.valid?
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
new file mode 100644
index 0000000000..8c89494247
--- /dev/null
+++ b/activemodel/test/cases/validations_test.rb
@@ -0,0 +1,178 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'cases/tests_database'
+
+require 'models/topic'
+require 'models/reply'
+require 'models/developer'
+
+class ValidationsTest < ActiveModel::TestCase
+ include ActiveModel::TestsDatabase
+ include ActiveModel::ValidationsRepairHelper
+
+ # Most of the tests mess with the validations of Topic, so lets repair it all the time.
+ # Other classes we mess with will be dealt with in the specific tests
+ repair_validations(Topic)
+
+ def test_single_field_validation
+ r = Reply.new
+ r.title = "There's no content!"
+ assert !r.valid?, "A reply without content shouldn't be saveable"
+
+ r.content = "Messa content!"
+ assert r.valid?, "A reply with content should be saveable"
+ end
+
+ def test_single_attr_validation_and_error_msg
+ r = Reply.new
+ r.title = "There's no content!"
+ assert !r.valid?
+ assert r.errors[:content].any?, "A reply without content should mark that attribute as invalid"
+ assert_equal ["Empty"], r.errors["content"], "A reply without content should contain an error"
+ assert_equal 1, r.errors.count
+ end
+
+ def test_double_attr_validation_and_error_msg
+ r = Reply.new
+ assert !r.valid?
+
+ assert r.errors[:title].any?, "A reply without title should mark that attribute as invalid"
+ assert_equal ["Empty"], r.errors["title"], "A reply without title should contain an error"
+
+ assert r.errors[:content].any?, "A reply without content should mark that attribute as invalid"
+ assert_equal ["Empty"], r.errors["content"], "A reply without content should contain an error"
+
+ assert_equal 2, r.errors.count
+ end
+
+ def test_single_error_per_attr_iteration
+ r = Reply.new
+ r.save
+
+ errors = []
+ r.errors.each {|attr, messages| errors << [attr.to_s, messages] }
+
+ assert errors.include?(["title", "Empty"])
+ assert errors.include?(["content", "Empty"])
+ end
+
+ def test_multiple_errors_per_attr_iteration_with_full_error_composition
+ r = Reply.new
+ r.title = "Wrong Create"
+ r.content = "Mismatch"
+ r.save
+
+ errors = r.errors.to_a
+
+ assert_equal "Title is Wrong Create", errors[0]
+ assert_equal "Title is Content Mismatch", errors[1]
+ assert_equal 2, r.errors.count
+ end
+
+ def test_errors_on_base
+ r = Reply.new
+ r.content = "Mismatch"
+ r.save
+ r.errors[:base] << "Reply is not dignifying"
+
+ errors = []
+ r.errors.to_a.each { |error| errors << error }
+
+ assert_equal ["Reply is not dignifying"], r.errors[:base]
+
+ assert errors.include?("Title Empty")
+ assert errors.include?("Reply is not dignifying")
+ assert_equal 2, r.errors.count
+ end
+
+ def test_validates_each
+ hits = 0
+ Topic.validates_each(:title, :content, [:title, :content]) do |record, attr|
+ record.errors.add attr, 'gotcha'
+ hits += 1
+ end
+ t = Topic.new("title" => "valid", "content" => "whatever")
+ assert !t.save
+ assert_equal 4, hits
+ assert_equal %w(gotcha gotcha), t.errors[:title]
+ assert_equal %w(gotcha gotcha), t.errors[:content]
+ end
+
+ def test_validate_block
+ Topic.validate { |topic| topic.errors.add("title", "will never be valid") }
+ t = Topic.create("title" => "Title", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors[:title].any?
+ assert_equal ["will never be valid"], t.errors["title"]
+ end
+
+ def test_invalid_validator
+ Topic.validate 3
+ assert_raise(ArgumentError) { t = Topic.create }
+ end
+
+ def test_errors_to_xml
+ r = Reply.new :title => "Wrong Create"
+ assert !r.valid?
+ xml = r.errors.to_xml(:skip_instruct => true)
+ assert_equal "<errors>", xml.first(8)
+ assert xml.include?("<error>Title is Wrong Create</error>")
+ assert xml.include?("<error>Content Empty</error>")
+ end
+
+ def test_validation_order
+ Topic.validates_presence_of :title
+ Topic.validates_length_of :title, :minimum => 2
+
+ t = Topic.new("title" => "")
+ assert !t.valid?
+ assert_equal "can't be blank", t.errors["title"].first
+ end
+
+ def test_invalid_should_be_the_opposite_of_valid
+ Topic.validates_presence_of :title
+
+ t = Topic.new
+ assert t.invalid?
+ assert t.errors[:title].any?
+
+ t.title = 'Things are going to change'
+ assert !t.invalid?
+ end
+
+ def test_deprecated_error_messages_on
+ Topic.validates_presence_of :title
+
+ t = Topic.new
+ assert t.invalid?
+
+ [:title, "title"].each do |attribute|
+ assert_deprecated { assert_equal "can't be blank", t.errors.on(attribute) }
+ end
+
+ Topic.validates_each(:title) do |record, attribute|
+ record.errors[attribute] << "invalid"
+ end
+
+ assert t.invalid?
+
+ [:title, "title"].each do |attribute|
+ assert_deprecated do
+ assert t.errors.on(attribute).include?("invalid")
+ assert t.errors.on(attribute).include?("can't be blank")
+ end
+ end
+ end
+
+ def test_deprecated_errors_on_base_and_each
+ t = Topic.new
+ assert t.valid?
+
+ assert_deprecated { t.errors.add_to_base "invalid topic" }
+ assert_deprecated { assert_equal "invalid topic", t.errors.on_base }
+ assert_deprecated { assert t.errors.invalid?(:base) }
+
+ all_errors = t.errors.to_a
+ assert_deprecated { assert_equal all_errors, t.errors.each_full{|err| err} }
+ end
+end \ No newline at end of file