diff options
Diffstat (limited to 'activemodel/test/cases')
24 files changed, 560 insertions, 191 deletions
diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb index 67471ed497..34298d31c2 100644 --- a/activemodel/test/cases/attribute_methods_test.rb +++ b/activemodel/test/cases/attribute_methods_test.rb @@ -76,7 +76,15 @@ private end end +class ModelWithoutAttributesMethod + include ActiveModel::AttributeMethods +end + class AttributeMethodsTest < ActiveModel::TestCase + test 'method missing works correctly even if attributes method is not defined' do + assert_raises(NoMethodError) { ModelWithoutAttributesMethod.new.foo } + end + test 'unrelated classes should not share attribute method matchers' do assert_not_equal ModelWithAttributes.send(:attribute_method_matchers), ModelWithAttributes2.send(:attribute_method_matchers) @@ -133,24 +141,6 @@ class AttributeMethodsTest < ActiveModel::TestCase assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.send(:'foo bar') end - test '#define_attr_method generates attribute method' do - ModelWithAttributes.define_attr_method(:bar, 'bar') - - assert_respond_to ModelWithAttributes, :bar - assert_equal "original bar", ModelWithAttributes.original_bar - assert_equal "bar", ModelWithAttributes.bar - ModelWithAttributes.define_attr_method(:bar) - assert !ModelWithAttributes.bar - end - - test '#define_attr_method generates attribute method with invalid identifier characters' do - ModelWithWeirdNamesAttributes.define_attr_method(:'c?d', 'c?d') - - assert_respond_to ModelWithWeirdNamesAttributes, :'c?d' - assert_equal "original c?d", ModelWithWeirdNamesAttributes.send('original_c?d') - assert_equal "c?d", ModelWithWeirdNamesAttributes.send('c?d') - end - test '#alias_attribute works with attributes with spaces in their names' do ModelWithAttributesWithSpaces.define_attribute_methods([:'foo bar']) ModelWithAttributesWithSpaces.alias_attribute(:'foo_bar', :'foo bar') @@ -198,6 +188,12 @@ class AttributeMethodsTest < ActiveModel::TestCase assert_raises(NoMethodError) { m.protected_method } end + class ClassWithProtected + protected + def protected_method + end + end + test 'should not interfere with respond_to? if the attribute has a private/protected method' do m = ModelWithAttributes2.new m.attributes = { 'private_method' => '<3', 'protected_method' => 'O_o' } @@ -205,9 +201,11 @@ class AttributeMethodsTest < ActiveModel::TestCase assert !m.respond_to?(:private_method) assert m.respond_to?(:private_method, true) + c = ClassWithProtected.new + # This is messed up, but it's how Ruby works at the moment. Apparently it will be changed # in the future. - assert m.respond_to?(:protected_method) + assert_equal c.respond_to?(:protected_method), m.respond_to?(:protected_method) assert m.respond_to?(:protected_method, true) end diff --git a/activemodel/test/cases/configuration_test.rb b/activemodel/test/cases/configuration_test.rb new file mode 100644 index 0000000000..a172fa26a3 --- /dev/null +++ b/activemodel/test/cases/configuration_test.rb @@ -0,0 +1,154 @@ +require 'cases/helper' + +class ConfigurationOnModuleTest < ActiveModel::TestCase + def setup + @mod = mod = Module.new do + extend ActiveSupport::Concern + extend ActiveModel::Configuration + + config_attribute :omg + self.omg = "default" + + config_attribute :wtf, global: true + self.wtf = "default" + + config_attribute :boolean + + config_attribute :lol, instance_writer: true + end + + @klass = Class.new do + include mod + end + + @subklass = Class.new(@klass) + end + + test "default" do + assert_equal "default", @mod.omg + assert_equal "default", @klass.omg + assert_equal "default", @klass.new.omg + end + + test "setting" do + @mod.omg = "lol" + assert_equal "lol", @mod.omg + end + + test "setting on class including the module" do + @klass.omg = "lol" + assert_equal "lol", @klass.omg + assert_equal "lol", @klass.new.omg + assert_equal "default", @mod.omg + end + + test "setting on subclass of class including the module" do + @subklass.omg = "lol" + assert_equal "lol", @subklass.omg + assert_equal "default", @klass.omg + assert_equal "default", @mod.omg + end + + test "setting on instance" do + assert !@klass.new.respond_to?(:omg=) + + @klass.lol = "lol" + obj = @klass.new + assert_equal "lol", obj.lol + obj.lol = "omg" + assert_equal "omg", obj.lol + assert_equal "lol", @klass.lol + assert_equal "lol", @klass.new.lol + obj.lol = false + assert !obj.lol? + end + + test "global attribute" do + assert_equal "default", @mod.wtf + assert_equal "default", @klass.wtf + + @mod.wtf = "wtf" + + assert_equal "wtf", @mod.wtf + assert_equal "wtf", @klass.wtf + + @klass.wtf = "lol" + + assert_equal "lol", @mod.wtf + assert_equal "lol", @klass.wtf + end + + test "boolean" do + assert_equal false, @mod.boolean? + assert_equal false, @klass.new.boolean? + @mod.boolean = true + assert_equal true, @mod.boolean? + assert_equal true, @klass.new.boolean? + end +end + +class ConfigurationOnClassTest < ActiveModel::TestCase + def setup + @klass = Class.new do + extend ActiveModel::Configuration + + config_attribute :omg + self.omg = "default" + + config_attribute :wtf, global: true + self.wtf = "default" + + config_attribute :omg2, instance_writer: true + config_attribute :wtf2, instance_writer: true, global: true + end + + @subklass = Class.new(@klass) + end + + test "defaults" do + assert_equal "default", @klass.omg + assert_equal "default", @klass.wtf + assert_equal "default", @subklass.omg + assert_equal "default", @subklass.wtf + end + + test "changing" do + @klass.omg = "lol" + assert_equal "lol", @klass.omg + assert_equal "lol", @subklass.omg + end + + test "changing in subclass" do + @subklass.omg = "lol" + assert_equal "lol", @subklass.omg + assert_equal "default", @klass.omg + end + + test "changing global" do + @klass.wtf = "wtf" + assert_equal "wtf", @klass.wtf + assert_equal "wtf", @subklass.wtf + + @subklass.wtf = "lol" + assert_equal "lol", @klass.wtf + assert_equal "lol", @subklass.wtf + end + + test "instance_writer" do + obj = @klass.new + + @klass.omg2 = "omg" + @klass.wtf2 = "wtf" + + assert_equal "omg", obj.omg2 + assert_equal "wtf", obj.wtf2 + + obj.omg2 = "lol" + obj.wtf2 = "lol" + + assert_equal "lol", obj.omg2 + assert_equal "lol", obj.wtf2 + assert_equal "omg", @klass.omg2 + assert_equal "lol", @klass.wtf2 + end +end diff --git a/activemodel/test/cases/conversion_test.rb b/activemodel/test/cases/conversion_test.rb index 24552bcaf2..d679ad41aa 100644 --- a/activemodel/test/cases/conversion_test.rb +++ b/activemodel/test/cases/conversion_test.rb @@ -24,7 +24,7 @@ class ConversionTest < ActiveModel::TestCase assert_equal "1", Contact.new(:id => 1).to_param end - test "to_path default implementation returns a string giving a relative path" do + test "to_partial_path default implementation returns a string giving a relative path" do assert_equal "contacts/contact", Contact.new.to_partial_path assert_equal "helicopters/helicopter", Helicopter.new.to_partial_path, "ActiveModel::Conversion#to_partial_path caching should be class-specific" diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index 8ddedb160a..3bc0d58351 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -27,12 +27,27 @@ class ErrorsTest < ActiveModel::TestCase end end + def test_delete + errors = ActiveModel::Errors.new(self) + errors[:foo] = 'omg' + errors.delete(:foo) + assert_empty errors[:foo] + end + def test_include? errors = ActiveModel::Errors.new(self) errors[:foo] = 'omg' assert errors.include?(:foo), 'errors should include :foo' end + def test_dup + errors = ActiveModel::Errors.new(self) + errors[:foo] = 'bar' + errors_dup = errors.dup + errors_dup[:bar] = 'omg' + assert_not_same errors_dup.messages, errors.messages + end + def test_has_key? errors = ActiveModel::Errors.new(self) errors[:foo] = 'omg' @@ -136,10 +151,10 @@ class ErrorsTest < ActiveModel::TestCase assert_equal ["name can not be blank", "name can not be nil"], person.errors.to_a end - test 'to_hash should return an ordered hash' do + test 'to_hash should return a hash' do person = Person.new person.errors.add(:name, "can not be blank") - assert_instance_of ActiveSupport::OrderedHash, person.errors.to_hash + assert_instance_of ::Hash, person.errors.to_hash end test 'full_messages should return an array of error messages, with the attribute name included' do @@ -169,6 +184,16 @@ class ErrorsTest < ActiveModel::TestCase assert_equal ["is invalid"], hash[:email] end + test 'should return a JSON hash representation of the errors with full messages' do + person = Person.new + person.errors.add(:name, "can not be blank") + person.errors.add(:name, "can not be nil") + person.errors.add(:email, "is invalid") + hash = person.errors.as_json(:full_messages => true) + assert_equal ["name can not be blank", "name can not be nil"], hash[:name] + assert_equal ["email is invalid"], hash[:email] + end + test "generate_message should work without i18n_scope" do person = Person.new assert !Person.respond_to?(:i18n_scope) diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb index 2e860272a4..4347b17cbc 100644 --- a/activemodel/test/cases/helper.rb +++ b/activemodel/test/cases/helper.rb @@ -10,4 +10,4 @@ require 'active_support/core_ext/string/access' # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true -require 'test/unit' +require 'minitest/autorun' diff --git a/activemodel/test/cases/lint_test.rb b/activemodel/test/cases/lint_test.rb index 68372160cd..8faf93c056 100644 --- a/activemodel/test/cases/lint_test.rb +++ b/activemodel/test/cases/lint_test.rb @@ -7,14 +7,10 @@ class LintTest < ActiveModel::TestCase extend ActiveModel::Naming include ActiveModel::Conversion - def valid?() true end def persisted?() false end def errors - obj = Object.new - def obj.[](key) [] end - def obj.full_messages() [] end - obj + Hash.new([]) end end diff --git a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb index 676937b5e1..3660b9b1e5 100644 --- a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb +++ b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'logger' +require 'active_support/logger' require 'active_support/core_ext/object/inclusion' class SanitizerTest < ActiveModel::TestCase @@ -28,7 +28,7 @@ class SanitizerTest < ActiveModel::TestCase test "debug mass assignment removal with LoggerSanitizer" do original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' } log = StringIO.new - self.logger = Logger.new(log) + self.logger = ActiveSupport::Logger.new(log) @logger_sanitizer.sanitize(original_attributes, @authorizer) assert_match(/admin/, log.string, "Should log removed attributes: #{log.string}") end diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb index be07e59a2f..a197dbe748 100644 --- a/activemodel/test/cases/mass_assignment_security_test.rb +++ b/activemodel/test/cases/mass_assignment_security_test.rb @@ -19,6 +19,13 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase assert_equal expected, sanitized end + def test_attribute_protection_when_role_is_nil + user = User.new + expected = { "name" => "John Smith", "email" => "john@smith.com" } + sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true), nil) + assert_equal expected, sanitized + end + def test_only_moderator_role_attribute_accessible user = SpecialUser.new expected = { "name" => "John Smith", "email" => "john@smith.com" } diff --git a/activemodel/test/cases/model_test.rb b/activemodel/test/cases/model_test.rb new file mode 100644 index 0000000000..d93fd96b88 --- /dev/null +++ b/activemodel/test/cases/model_test.rb @@ -0,0 +1,26 @@ +require 'cases/helper' + +class ModelTest < ActiveModel::TestCase + include ActiveModel::Lint::Tests + + class BasicModel + include ActiveModel::Model + attr_accessor :attr + end + + def setup + @model = BasicModel.new + end + + def test_initialize_with_params + object = BasicModel.new(:attr => "value") + assert_equal object.attr, "value" + end + + def test_initialize_with_nil_or_empty_hash_params_does_not_explode + assert_nothing_raised do + BasicModel.new() + BasicModel.new({}) + end + end +end diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb index 5f943729dd..49d8706ac2 100644 --- a/activemodel/test/cases/naming_test.rb +++ b/activemodel/test/cases/naming_test.rb @@ -25,15 +25,13 @@ class NamingTest < ActiveModel::TestCase assert_equal 'post/track_backs', @model_name.collection end - def test_partial_path - assert_deprecated(/#partial_path.*#to_partial_path/) do - assert_equal 'post/track_backs/track_back', @model_name.partial_path - end - end - def test_human assert_equal 'Track back', @model_name.human end + + def test_i18n_key + assert_equal :"post/track_back", @model_name.i18n_key + end end class NamingWithNamespacedModelInIsolatedNamespaceTest < ActiveModel::TestCase @@ -57,12 +55,6 @@ class NamingWithNamespacedModelInIsolatedNamespaceTest < ActiveModel::TestCase assert_equal 'blog/posts', @model_name.collection end - def test_partial_path - assert_deprecated(/#partial_path.*#to_partial_path/) do - assert_equal 'blog/posts/post', @model_name.partial_path - end - end - def test_human assert_equal 'Post', @model_name.human end @@ -75,8 +67,8 @@ class NamingWithNamespacedModelInIsolatedNamespaceTest < ActiveModel::TestCase assert_equal 'post', @model_name.param_key end - def test_recognizing_namespace - assert_equal 'Post', Blog::Post.model_name.instance_variable_get("@unnamespaced") + def test_i18n_key + assert_equal :"blog/post", @model_name.i18n_key end end @@ -101,12 +93,6 @@ class NamingWithNamespacedModelInSharedNamespaceTest < ActiveModel::TestCase assert_equal 'blog/posts', @model_name.collection end - def test_partial_path - assert_deprecated(/#partial_path.*#to_partial_path/) do - assert_equal 'blog/posts/post', @model_name.partial_path - end - end - def test_human assert_equal 'Post', @model_name.human end @@ -118,6 +104,10 @@ class NamingWithNamespacedModelInSharedNamespaceTest < ActiveModel::TestCase def test_param_key assert_equal 'blog_post', @model_name.param_key end + + def test_i18n_key + assert_equal :"blog/post", @model_name.i18n_key + end end class NamingWithSuppliedModelNameTest < ActiveModel::TestCase @@ -141,12 +131,6 @@ class NamingWithSuppliedModelNameTest < ActiveModel::TestCase assert_equal 'articles', @model_name.collection end - def test_partial_path - assert_deprecated(/#partial_path.*#to_partial_path/) do - assert_equal 'articles/article', @model_name.partial_path - end - end - def test_human assert_equal 'Article', @model_name.human end @@ -158,15 +142,58 @@ class NamingWithSuppliedModelNameTest < ActiveModel::TestCase def test_param_key assert_equal 'article', @model_name.param_key end + + def test_i18n_key + assert_equal :"article", @model_name.i18n_key + end +end + +class NamingUsingRelativeModelNameTest < ActiveModel::TestCase + def setup + @model_name = Blog::Post.model_name + end + + def test_singular + assert_equal 'blog_post', @model_name.singular + end + + def test_plural + assert_equal 'blog_posts', @model_name.plural + end + + def test_element + assert_equal 'post', @model_name.element + end + + def test_collection + assert_equal 'blog/posts', @model_name.collection + end + + def test_human + assert_equal 'Post', @model_name.human + end + + def test_route_key + assert_equal 'posts', @model_name.route_key + end + + def test_param_key + assert_equal 'post', @model_name.param_key + end + + def test_i18n_key + assert_equal :"blog/post", @model_name.i18n_key + end end -class NamingHelpersTest < Test::Unit::TestCase +class NamingHelpersTest < ActiveModel::TestCase def setup @klass = Contact @record = @klass.new @singular = 'contact' @plural = 'contacts' @uncountable = Sheep + @singular_route_key = 'contact' @route_key = 'contacts' @param_key = 'contact' end @@ -193,10 +220,12 @@ class NamingHelpersTest < Test::Unit::TestCase def test_route_key assert_equal @route_key, route_key(@record) + assert_equal @singular_route_key, singular_route_key(@record) end def test_route_key_for_class assert_equal @route_key, route_key(@klass) + assert_equal @singular_route_key, singular_route_key(@klass) end def test_param_key @@ -212,8 +241,26 @@ class NamingHelpersTest < Test::Unit::TestCase assert !uncountable?(@klass), "Expected 'contact' to be countable" end + def test_uncountable_route_key + assert_equal "sheep", singular_route_key(@uncountable) + assert_equal "sheep_index", route_key(@uncountable) + end + private def method_missing(method, *args) ActiveModel::Naming.send(method, *args) end end + +class NameWithAnonymousClassTest < ActiveModel::TestCase + def test_anonymous_class_without_name_argument + assert_raises(ArgumentError) do + ActiveModel::Name.new(Class.new) + end + end + + def test_anonymous_class_with_name_argument + model_name = ActiveModel::Name.new(Class.new, nil, "Anonymous") + assert_equal "Anonymous", model_name + end +end diff --git a/activemodel/test/cases/observing_test.rb b/activemodel/test/cases/observing_test.rb index f6ec24ae57..ade6026602 100644 --- a/activemodel/test/cases/observing_test.rb +++ b/activemodel/test/cases/observing_test.rb @@ -14,8 +14,8 @@ class FooObserver < ActiveModel::Observer attr_accessor :stub - def on_spec(record) - stub.event_with(record) if stub + def on_spec(record, *args) + stub.event_with(record, *args) if stub end def around_save(record) @@ -70,23 +70,38 @@ class ObservingTest < ActiveModel::TestCase ObservedModel.instantiate_observers end + test "raises an appropriate error when a developer accidentally adds the wrong class (i.e. Widget instead of WidgetObserver)" do + assert_raise ArgumentError do + ObservedModel.observers = ['string'] + ObservedModel.instantiate_observers + end + assert_raise ArgumentError do + ObservedModel.observers = [:string] + ObservedModel.instantiate_observers + end + assert_raise ArgumentError do + ObservedModel.observers = [String] + ObservedModel.instantiate_observers + end + end + test "passes observers to subclasses" do FooObserver.instance bar = Class.new(Foo) - assert_equal Foo.count_observers, bar.count_observers + assert_equal Foo.observers_count, bar.observers_count end end class ObserverTest < ActiveModel::TestCase def setup ObservedModel.observers = :foo_observer - FooObserver.instance_eval do + FooObserver.singleton_class.instance_eval do alias_method :original_observed_classes, :observed_classes end end def teardown - FooObserver.instance_eval do + FooObserver.singleton_class.instance_eval do undef_method :observed_classes alias_method :observed_classes, :original_observed_classes end @@ -98,44 +113,51 @@ class ObserverTest < ActiveModel::TestCase 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}" + assert_equal [Foo], instance.observed_classes 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}" + assert_equal [ObservedModel], instance.observed_classes 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}" + assert_equal [ObservedModel], instance.observed_classes 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}" + assert_equal [ObservedModel], instance.observed_classes end test "calls existing observer event" do foo = Foo.new FooObserver.instance.stub = stub FooObserver.instance.stub.expects(:event_with).with(foo) - Foo.send(:notify_observers, :on_spec, foo) + Foo.notify_observers(:on_spec, foo) + end + + test "calls existing observer event from the instance" do + foo = Foo.new + FooObserver.instance.stub = stub + FooObserver.instance.stub.expects(:event_with).with(foo) + foo.notify_observers(:on_spec) + end + + test "passes extra arguments" do + foo = Foo.new + FooObserver.instance.stub = stub + FooObserver.instance.stub.expects(:event_with).with(foo, :bar) + Foo.send(:notify_observers, :on_spec, foo, :bar) end test "skips nonexistent observer event" do foo = Foo.new - Foo.send(:notify_observers, :whatever, foo) + Foo.notify_observers(:whatever, foo) end test "update passes a block on to the observer" do @@ -145,4 +167,15 @@ class ObserverTest < ActiveModel::TestCase end assert_equal :in_around_save, yielded_value end + + test "observe redefines observed_classes class method" do + class BarObserver < ActiveModel::Observer + observe :foo + end + + assert_equal [Foo], BarObserver.observed_classes + + BarObserver.observe(ObservedModel) + assert_equal [ObservedModel], BarObserver.observed_classes + end end diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb index 4338a3fc53..c451cc1aca 100644 --- a/activemodel/test/cases/secure_password_test.rb +++ b/activemodel/test/cases/secure_password_test.rb @@ -19,6 +19,12 @@ class SecurePasswordTest < ActiveModel::TestCase assert !@user.valid?, 'user should be invalid' end + test "blank password doesn't override previous password" do + @user.password = 'test' + @user.password = '' + assert_equal @user.password, 'test' + end + test "password must be present" do assert !@user.valid? assert_equal 1, @user.errors.size diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serialization_test.rb index b8dad9d51f..66b18d65e5 100644 --- a/activemodel/test/cases/serialization_test.rb +++ b/activemodel/test/cases/serialization_test.rb @@ -43,38 +43,38 @@ class SerializationTest < ActiveModel::TestCase end def test_method_serializable_hash_should_work - expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"} - assert_equal expected , @user.serializable_hash + expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"} + assert_equal expected, @user.serializable_hash end def test_method_serializable_hash_should_work_with_only_option - expected = {"name"=>"David"} - assert_equal expected , @user.serializable_hash(:only => [:name]) + expected = {"name"=>"David"} + assert_equal expected, @user.serializable_hash(:only => [:name]) end def test_method_serializable_hash_should_work_with_except_option - expected = {"gender"=>"male", "email"=>"david@example.com"} - assert_equal expected , @user.serializable_hash(:except => [:name]) + expected = {"gender"=>"male", "email"=>"david@example.com"} + assert_equal expected, @user.serializable_hash(:except => [:name]) end def test_method_serializable_hash_should_work_with_methods_option - expected = {"name"=>"David", "gender"=>"male", :foo=>"i_am_foo", "email"=>"david@example.com"} - assert_equal expected , @user.serializable_hash(:methods => [:foo]) + expected = {"name"=>"David", "gender"=>"male", "foo"=>"i_am_foo", "email"=>"david@example.com"} + assert_equal expected, @user.serializable_hash(:methods => [:foo]) end def test_method_serializable_hash_should_work_with_only_and_methods - expected = {:foo=>"i_am_foo"} - assert_equal expected , @user.serializable_hash(:only => [], :methods => [:foo]) + expected = {"foo"=>"i_am_foo"} + assert_equal expected, @user.serializable_hash(:only => [], :methods => [:foo]) end def test_method_serializable_hash_should_work_with_except_and_methods - expected = {"gender"=>"male", :foo=>"i_am_foo"} - assert_equal expected , @user.serializable_hash(:except => [:name, :email], :methods => [:foo]) + expected = {"gender"=>"male", "foo"=>"i_am_foo"} + assert_equal expected, @user.serializable_hash(:except => [:name, :email], :methods => [:foo]) end def test_should_not_call_methods_that_dont_respond - expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"} - assert_equal expected , @user.serializable_hash(:methods => [:bar]) + expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"} + assert_equal expected, @user.serializable_hash(:methods => [:bar]) end def test_should_use_read_attribute_for_serialization @@ -87,65 +87,64 @@ class SerializationTest < ActiveModel::TestCase end def test_include_option_with_singular_association - expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com", - :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}} - assert_equal expected , @user.serializable_hash(:include => :address) + expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com", + "address"=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}} + assert_equal expected, @user.serializable_hash(:include => :address) end def test_include_option_with_plural_association - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, - {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} - assert_equal expected , @user.serializable_hash(:include => :friends) + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, + {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} + assert_equal expected, @user.serializable_hash(:include => :friends) end def test_include_option_with_empty_association @user.friends = [] - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", :friends=>[]} - assert_equal expected , @user.serializable_hash(:include => :friends) + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", "friends"=>[]} + assert_equal expected, @user.serializable_hash(:include => :friends) end def test_multiple_includes - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}, - :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, - {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} - assert_equal expected , @user.serializable_hash(:include => [:address, :friends]) + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + "address"=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}, + "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, + {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} + assert_equal expected, @user.serializable_hash(:include => [:address, :friends]) end def test_include_with_options - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :address=>{"street"=>"123 Lane"}} - assert_equal expected , @user.serializable_hash(:include => {:address => {:only => "street"}}) + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + "address"=>{"street"=>"123 Lane"}} + assert_equal expected, @user.serializable_hash(:include => {:address => {:only => "street"}}) end def test_nested_include @user.friends.first.friends = [@user] - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male', - :friends => [{"email"=>"david@example.com", "gender"=>"male", "name"=>"David"}]}, - {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female', :friends => []}]} - assert_equal expected , @user.serializable_hash(:include => {:friends => {:include => :friends}}) + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male', + "friends"=> [{"email"=>"david@example.com", "gender"=>"male", "name"=>"David"}]}, + {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female', "friends"=> []}]} + assert_equal expected, @user.serializable_hash(:include => {:friends => {:include => :friends}}) end def test_only_include - expected = {"name"=>"David", :friends => [{"name" => "Joe"}, {"name" => "Sue"}]} - assert_equal expected , @user.serializable_hash(:only => :name, :include => {:friends => {:only => :name}}) + expected = {"name"=>"David", "friends" => [{"name" => "Joe"}, {"name" => "Sue"}]} + assert_equal expected, @user.serializable_hash(:only => :name, :include => {:friends => {:only => :name}}) end def test_except_include expected = {"name"=>"David", "email"=>"david@example.com", - :friends => [{"name" => 'Joe', "email" => 'joe@example.com'}, - {"name" => "Sue", "email" => 'sue@example.com'}]} - assert_equal expected , @user.serializable_hash(:except => :gender, :include => {:friends => {:except => :gender}}) + "friends"=> [{"name" => 'Joe', "email" => 'joe@example.com'}, + {"name" => "Sue", "email" => 'sue@example.com'}]} + assert_equal expected, @user.serializable_hash(:except => :gender, :include => {:friends => {:except => :gender}}) end def test_multiple_includes_with_options - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :address=>{"street"=>"123 Lane"}, - :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, - {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} - assert_equal expected , @user.serializable_hash(:include => [{:address => {:only => "street"}}, :friends]) + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + "address"=>{"street"=>"123 Lane"}, + "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, + {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} + assert_equal expected, @user.serializable_hash(:include => [{:address => {:only => "street"}}, :friends]) end - end diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb index a754d610b9..7160635eb4 100644 --- a/activemodel/test/cases/serializers/json_serialization_test.rb +++ b/activemodel/test/cases/serializers/json_serialization_test.rb @@ -14,9 +14,11 @@ class Contact end end + remove_method :attributes if method_defined?(:attributes) + def attributes instance_values - end unless method_defined?(:attributes) + end end class JsonSerializationTest < ActiveModel::TestCase @@ -128,13 +130,13 @@ class JsonSerializationTest < ActiveModel::TestCase assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json end - test "should return OrderedHash for errors" do + test "should return Hash for errors" do contact = Contact.new contact.errors.add :name, "can't be blank" contact.errors.add :name, "is too short (minimum is 2 characters)" contact.errors.add :age, "must be 16 or over" - hash = ActiveSupport::OrderedHash.new + hash = {} hash[:name] = ["can't be blank", "is too short (minimum is 2 characters)"] hash[:age] = ["must be 16 or over"] assert_equal hash.to_json, contact.errors.to_json diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb index fc73d9dcd8..38aecf51ff 100644 --- a/activemodel/test/cases/serializers/xml_serialization_test.rb +++ b/activemodel/test/cases/serializers/xml_serialization_test.rb @@ -9,6 +9,8 @@ class Contact attr_accessor :address, :friends + remove_method :attributes if method_defined?(:attributes) + def attributes instance_values.except("address", "friends") end diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb index 1b1d972d5c..4999583802 100644 --- a/activemodel/test/cases/translation_test.rb +++ b/activemodel/test/cases/translation_test.rb @@ -56,6 +56,16 @@ class ActiveModelI18nTests < ActiveModel::TestCase assert_equal 'person gender attribute', Person::Gender.human_attribute_name('attribute') end + def test_translated_nested_model_attributes + I18n.backend.store_translations 'en', :activemodel => {:attributes => {:"person/addresses" => {:street => 'Person Address Street'}}} + assert_equal 'Person Address Street', Person.human_attribute_name('addresses.street') + end + + def test_translated_nested_model_attributes_with_namespace_fallback + I18n.backend.store_translations 'en', :activemodel => {:attributes => {:addresses => {:street => 'Cool Address Street'}}} + assert_equal 'Cool Address Street', Person.human_attribute_name('addresses.street') + end + def test_translated_model_names I18n.backend.store_translations 'en', :activemodel => {:models => {:person => 'person model'} } assert_equal 'person model', Person.model_name.human @@ -72,9 +82,15 @@ class ActiveModelI18nTests < ActiveModel::TestCase end def test_human_does_not_modify_options - options = {:default => 'person model'} + options = { :default => 'person model' } Person.model_name.human(options) - assert_equal({:default => 'person model'}, options) + assert_equal({ :default => 'person model' }, options) + end + + def test_human_attribute_name_does_not_modify_options + options = { :default => 'Cool gender' } + Person.human_attribute_name('gender', options) + assert_equal({ :default => 'Cool gender' }, options) end end diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb index 1cf09758f9..e4f602bd80 100644 --- a/activemodel/test/cases/validations/callbacks_test.rb +++ b/activemodel/test/cases/validations/callbacks_test.rb @@ -5,11 +5,10 @@ class Dog include ActiveModel::Validations include ActiveModel::Validations::Callbacks - attr_accessor :name - attr_writer :history + attr_accessor :name, :history - def history - @history ||= [] + def initialize + @history = [] end end diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb index d0418170fa..f7556a249f 100644 --- a/activemodel/test/cases/validations/confirmation_validation_test.rb +++ b/activemodel/test/cases/validations/confirmation_validation_test.rb @@ -44,7 +44,7 @@ class ConfirmationValidationTest < ActiveModel::TestCase p.karma_confirmation = "None" assert p.invalid? - assert_equal ["doesn't match confirmation"], p.errors[:karma] + assert_equal ["doesn't match Karma"], p.errors[:karma_confirmation] p.karma = "None" assert p.valid? @@ -52,4 +52,23 @@ class ConfirmationValidationTest < ActiveModel::TestCase Person.reset_callbacks(:validate) end + def test_title_confirmation_with_i18n_attribute + @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend + I18n.load_path.clear + I18n.backend = I18n::Backend::Simple.new + I18n.backend.store_translations('en', { + :errors => {:messages => {:confirmation => "doesn't match %{attribute}"}}, + :activemodel => {:attributes => {:topic => {:title => 'Test Title'}}} + }) + + Topic.validates_confirmation_of(:title) + + t = Topic.new("title" => "We should be confirmed","title_confirmation" => "") + assert t.invalid? + assert_equal ["doesn't match Test Title"], t.errors[:title_confirmation] + + I18n.load_path.replace @old_load_path + I18n.backend = @old_backend + 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 index 0679e67f84..df0fcd243a 100644 --- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb @@ -37,7 +37,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase # validates_confirmation_of: generate_message(attr_name, :confirmation, :message => custom_message) def test_generate_message_confirmation_with_default_message - assert_equal "doesn't match confirmation", @person.errors.generate_message(:title, :confirmation) + assert_equal "doesn't match Title", @person.errors.generate_message(:title, :confirmation) end def test_generate_message_confirmation_with_custom_message diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb index e9f0e430fe..6b6aad3bd1 100644 --- a/activemodel/test/cases/validations/i18n_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_validation_test.rb @@ -81,7 +81,7 @@ class I18nValidationTest < ActiveModel::TestCase test "validates_confirmation_of on generated message #{name}" do Person.validates_confirmation_of :title, validation_options @person.title_confirmation = 'foo' - @person.errors.expects(:generate_message).with(:title, :confirmation, generate_message_options) + @person.errors.expects(:generate_message).with(:title_confirmation, :confirmation, generate_message_options.merge(:attribute => 'Title')) @person.valid? end end @@ -217,24 +217,29 @@ class I18nValidationTest < ActiveModel::TestCase # To make things DRY this macro is defined to define 3 tests for every validation case. def self.set_expectations_for_validation(validation, error_type, &block_that_sets_validation) + if error_type == :confirmation + attribute = :title_confirmation + else + attribute = :title + end # test "validates_confirmation_of finds custom model key translation when blank" test "#{validation} finds custom model key translation when #{error_type}" do - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {error_type => 'custom message'}}}}}} + I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {attribute => {error_type => 'custom message'}}}}}} I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}} yield(@person, {}) @person.valid? - assert_equal ['custom message'], @person.errors[:title] + assert_equal ['custom message'], @person.errors[attribute] end # test "validates_confirmation_of finds custom model key translation with interpolation when blank" test "#{validation} finds custom model key translation with interpolation when #{error_type}" do - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {error_type => 'custom message with %{extra}'}}}}}} + I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {attribute => {error_type => 'custom message with %{extra}'}}}}}} I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}} yield(@person, {:extra => "extra information"}) @person.valid? - assert_equal ['custom message with extra information'], @person.errors[:title] + assert_equal ['custom message with extra information'], @person.errors[attribute] end # test "validates_confirmation_of finds global default key translation when blank" @@ -243,7 +248,7 @@ class I18nValidationTest < ActiveModel::TestCase yield(@person, {}) @person.valid? - assert_equal ['global message'], @person.errors[:title] + assert_equal ['global message'], @person.errors[attribute] end end diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb index 44048a9c1d..113bfd6337 100644 --- a/activemodel/test/cases/validations/length_validation_test.rb +++ b/activemodel/test/cases/validations/length_validation_test.rb @@ -260,74 +260,64 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_minimum_utf8 - with_kcode('UTF8') do - Topic.validates_length_of :title, :minimum => 5 + Topic.validates_length_of :title, :minimum => 5 - t = Topic.new("title" => "一二三四五", "content" => "whatever") - assert t.valid? + t = Topic.new("title" => "一二三四五", "content" => "whatever") + assert t.valid? - t.title = "一二三四" - assert t.invalid? - assert t.errors[:title].any? - assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"] - end + t.title = "一二三四" + assert t.invalid? + assert t.errors[:title].any? + assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"] end def test_validates_length_of_using_maximum_utf8 - with_kcode('UTF8') do - Topic.validates_length_of :title, :maximum => 5 + Topic.validates_length_of :title, :maximum => 5 - t = Topic.new("title" => "一二三四五", "content" => "whatever") - assert t.valid? + t = Topic.new("title" => "一二三四五", "content" => "whatever") + assert t.valid? - t.title = "一二34五六" - assert t.invalid? - assert t.errors[:title].any? - assert_equal ["is too long (maximum is 5 characters)"], t.errors["title"] - end + t.title = "一二34五六" + assert t.invalid? + assert t.errors[:title].any? + assert_equal ["is too long (maximum is 5 characters)"], t.errors["title"] 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.invalid? - 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 + Topic.validates_length_of(:title, :content, :within => 3..5) + + t = Topic.new("title" => "一二", "content" => "12三四五六七") + assert t.invalid? + 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 def test_optionally_validates_length_of_using_within_utf8 - with_kcode('UTF8') do - Topic.validates_length_of :title, :within => 3..5, :allow_nil => true + Topic.validates_length_of :title, :within => 3..5, :allow_nil => true - t = Topic.new(:title => "一二三四五") - assert t.valid?, t.errors.inspect + t = Topic.new(:title => "一二三四五") + assert t.valid?, t.errors.inspect - t = Topic.new(:title => "一二三") - assert t.valid?, t.errors.inspect + t = Topic.new(:title => "一二三") + assert t.valid?, t.errors.inspect - t.title = nil - assert t.valid?, t.errors.inspect - end + t.title = nil + assert t.valid?, t.errors.inspect end def test_validates_length_of_using_is_utf8 - with_kcode('UTF8') do - Topic.validates_length_of :title, :is => 5 + Topic.validates_length_of :title, :is => 5 - t = Topic.new("title" => "一二345", "content" => "whatever") - assert t.valid? + t = Topic.new("title" => "一二345", "content" => "whatever") + assert t.valid? - t.title = "一二345六" - assert t.invalid? - assert t.errors[:title].any? - assert_equal ["is the wrong length (should be 5 characters)"], t.errors["title"] - end + t.title = "一二345六" + assert t.invalid? + assert t.errors[:title].any? + assert_equal ["is the wrong length (should be 5 characters)"], t.errors["title"] end def test_validates_length_of_with_block @@ -367,4 +357,22 @@ class LengthValidationTest < ActiveModel::TestCase ensure Person.reset_callbacks(:validate) end + + def test_validates_length_of_for_infinite_maxima + Topic.validates_length_of(:title, :within => 5..Float::INFINITY) + + t = Topic.new("title" => "1234") + assert t.invalid? + assert t.errors[:title].any? + + t.title = "12345" + assert t.valid? + + Topic.validates_length_of(:author_name, :maximum => Float::INFINITY) + + assert t.valid? + + t.author_name = "A very long author name that should still be valid." * 100 + assert t.valid? + end end diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb index 08f6169ca5..6742a4bab0 100644 --- a/activemodel/test/cases/validations/numericality_validation_test.rb +++ b/activemodel/test/cases/validations/numericality_validation_test.rb @@ -106,6 +106,13 @@ class NumericalityValidationTest < ActiveModel::TestCase valid!([2]) end + def test_validates_numericality_with_other_than + Topic.validates_numericality_of :approved, :other_than => 0 + + invalid!([0, 0.0]) + valid!([-1, 42]) + end + def test_validates_numericality_with_proc Topic.send(:define_method, :min_approved, lambda { 5 }) Topic.validates_numericality_of :approved, :greater_than_or_equal_to => Proc.new {|topic| topic.min_approved } diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb index 779f6c8448..90bc018ae1 100644 --- a/activemodel/test/cases/validations/validates_test.rb +++ b/activemodel/test/cases/validations/validates_test.rb @@ -16,6 +16,12 @@ class ValidatesTest < ActiveModel::TestCase PersonWithValidator.reset_callbacks(:validate) end + def test_validates_with_messages_empty + Person.validates :title, :presence => {:message => "" } + person = Person.new + assert !person.valid?, 'person should not be valid.' + end + def test_validates_with_built_in_validation Person.validates :title, :numericality => true person = Person.new @@ -148,6 +154,6 @@ class ValidatesTest < ActiveModel::TestCase topic.title = "What's happening" topic.title_confirmation = "Not this" assert !topic.valid? - assert_equal ['Y U NO CONFIRM'], topic.errors[:title] + assert_equal ['Y U NO CONFIRM'], topic.errors[:title_confirmation] end end diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index 2f4376bd41..a716d0896e 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -58,8 +58,7 @@ class ValidationsTest < ActiveModel::TestCase r = Reply.new r.valid? - errors = [] - r.errors.each {|attr, messages| errors << [attr.to_s, messages] } + errors = r.errors.collect {|attr, messages| [attr.to_s, messages]} assert errors.include?(["title", "is Empty"]) assert errors.include?(["content", "is Empty"]) @@ -181,7 +180,7 @@ class ValidationsTest < ActiveModel::TestCase assert_match %r{<error>Title can't be blank</error>}, xml assert_match %r{<error>Content can't be blank</error>}, xml - hash = ActiveSupport::OrderedHash.new + hash = {} hash[:title] = ["can't be blank"] hash[:content] = ["can't be blank"] assert_equal t.errors.to_json, hash.to_json @@ -311,7 +310,7 @@ class ValidationsTest < ActiveModel::TestCase end def test_strict_validation_particular_validator - Topic.validates :title, :presence => {:strict => true} + Topic.validates :title, :presence => { :strict => true } assert_raises ActiveModel::StrictValidationFailed do Topic.new.valid? end @@ -330,4 +329,19 @@ class ValidationsTest < ActiveModel::TestCase Topic.new.valid? end end + + def test_strict_validation_error_message + Topic.validates :title, :strict => true, :presence => true + + exception = assert_raises(ActiveModel::StrictValidationFailed) do + Topic.new.valid? + end + assert_equal "Title can't be blank", exception.message + end + + def test_does_not_modify_options_argument + options = { :presence => true } + Topic.validates :title, options + assert_equal({ :presence => true }, options) + end end |