simple_form_with_client_validation 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. data/CHANGELOG.md +6 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +783 -0
  4. data/lib/generators/simple_form_with_client_validation/USAGE +3 -0
  5. data/lib/generators/simple_form_with_client_validation/install_generator.rb +32 -0
  6. data/lib/generators/simple_form_with_client_validation/templates/README +12 -0
  7. data/lib/generators/simple_form_with_client_validation/templates/_form.html.erb +13 -0
  8. data/lib/generators/simple_form_with_client_validation/templates/_form.html.haml +10 -0
  9. data/lib/generators/simple_form_with_client_validation/templates/_form.html.slim +10 -0
  10. data/lib/generators/simple_form_with_client_validation/templates/config/initializers/simple_form.rb.tt +179 -0
  11. data/lib/generators/simple_form_with_client_validation/templates/config/locales/simple_form.en.yml +26 -0
  12. data/lib/simple_form_with_client_validation/action_view_extensions/builder.rb +331 -0
  13. data/lib/simple_form_with_client_validation/action_view_extensions/form_helper.rb +74 -0
  14. data/lib/simple_form_with_client_validation/components/errors.rb +35 -0
  15. data/lib/simple_form_with_client_validation/components/hints.rb +18 -0
  16. data/lib/simple_form_with_client_validation/components/html5.rb +26 -0
  17. data/lib/simple_form_with_client_validation/components/label_input.rb +15 -0
  18. data/lib/simple_form_with_client_validation/components/labels.rb +79 -0
  19. data/lib/simple_form_with_client_validation/components/maxlength.rb +41 -0
  20. data/lib/simple_form_with_client_validation/components/min_max.rb +50 -0
  21. data/lib/simple_form_with_client_validation/components/minlength.rb +41 -0
  22. data/lib/simple_form_with_client_validation/components/pattern.rb +34 -0
  23. data/lib/simple_form_with_client_validation/components/placeholders.rb +16 -0
  24. data/lib/simple_form_with_client_validation/components/readonly.rb +22 -0
  25. data/lib/simple_form_with_client_validation/components.rb +21 -0
  26. data/lib/simple_form_with_client_validation/core_ext/hash.rb +16 -0
  27. data/lib/simple_form_with_client_validation/error_notification.rb +48 -0
  28. data/lib/simple_form_with_client_validation/form_builder.rb +484 -0
  29. data/lib/simple_form_with_client_validation/helpers/autofocus.rb +11 -0
  30. data/lib/simple_form_with_client_validation/helpers/disabled.rb +15 -0
  31. data/lib/simple_form_with_client_validation/helpers/readonly.rb +15 -0
  32. data/lib/simple_form_with_client_validation/helpers/required.rb +35 -0
  33. data/lib/simple_form_with_client_validation/helpers/validators.rb +44 -0
  34. data/lib/simple_form_with_client_validation/helpers.rb +12 -0
  35. data/lib/simple_form_with_client_validation/i18n_cache.rb +22 -0
  36. data/lib/simple_form_with_client_validation/inputs/base.rb +162 -0
  37. data/lib/simple_form_with_client_validation/inputs/block_input.rb +14 -0
  38. data/lib/simple_form_with_client_validation/inputs/boolean_input.rb +64 -0
  39. data/lib/simple_form_with_client_validation/inputs/collection_check_boxes_input.rb +21 -0
  40. data/lib/simple_form_with_client_validation/inputs/collection_input.rb +101 -0
  41. data/lib/simple_form_with_client_validation/inputs/collection_radio_buttons_input.rb +63 -0
  42. data/lib/simple_form_with_client_validation/inputs/collection_select_input.rb +14 -0
  43. data/lib/simple_form_with_client_validation/inputs/date_time_input.rb +28 -0
  44. data/lib/simple_form_with_client_validation/inputs/file_input.rb +9 -0
  45. data/lib/simple_form_with_client_validation/inputs/grouped_collection_select_input.rb +41 -0
  46. data/lib/simple_form_with_client_validation/inputs/hidden_input.rb +17 -0
  47. data/lib/simple_form_with_client_validation/inputs/numeric_input.rb +24 -0
  48. data/lib/simple_form_with_client_validation/inputs/password_input.rb +12 -0
  49. data/lib/simple_form_with_client_validation/inputs/priority_input.rb +24 -0
  50. data/lib/simple_form_with_client_validation/inputs/range_input.rb +14 -0
  51. data/lib/simple_form_with_client_validation/inputs/string_input.rb +23 -0
  52. data/lib/simple_form_with_client_validation/inputs/text_input.rb +11 -0
  53. data/lib/simple_form_with_client_validation/inputs.rb +21 -0
  54. data/lib/simple_form_with_client_validation/map_type.rb +16 -0
  55. data/lib/simple_form_with_client_validation/version.rb +3 -0
  56. data/lib/simple_form_with_client_validation/wrappers/builder.rb +115 -0
  57. data/lib/simple_form_with_client_validation/wrappers/many.rb +78 -0
  58. data/lib/simple_form_with_client_validation/wrappers/root.rb +34 -0
  59. data/lib/simple_form_with_client_validation/wrappers/single.rb +18 -0
  60. data/lib/simple_form_with_client_validation/wrappers.rb +8 -0
  61. data/lib/simple_form_with_client_validation.rb +218 -0
  62. data/test/action_view_extensions/builder_test.rb +577 -0
  63. data/test/action_view_extensions/form_helper_test.rb +104 -0
  64. data/test/components/label_test.rb +310 -0
  65. data/test/form_builder/association_test.rb +177 -0
  66. data/test/form_builder/button_test.rb +47 -0
  67. data/test/form_builder/error_notification_test.rb +79 -0
  68. data/test/form_builder/error_test.rb +121 -0
  69. data/test/form_builder/general_test.rb +356 -0
  70. data/test/form_builder/hint_test.rb +139 -0
  71. data/test/form_builder/input_field_test.rb +63 -0
  72. data/test/form_builder/label_test.rb +71 -0
  73. data/test/form_builder/wrapper_test.rb +149 -0
  74. data/test/generators/simple_form_generator_test.rb +32 -0
  75. data/test/inputs/boolean_input_test.rb +108 -0
  76. data/test/inputs/collection_check_boxes_input_test.rb +224 -0
  77. data/test/inputs/collection_radio_buttons_input_test.rb +326 -0
  78. data/test/inputs/collection_select_input_test.rb +241 -0
  79. data/test/inputs/datetime_input_test.rb +99 -0
  80. data/test/inputs/disabled_test.rb +38 -0
  81. data/test/inputs/discovery_test.rb +61 -0
  82. data/test/inputs/file_input_test.rb +16 -0
  83. data/test/inputs/general_test.rb +69 -0
  84. data/test/inputs/grouped_collection_select_input_test.rb +118 -0
  85. data/test/inputs/hidden_input_test.rb +30 -0
  86. data/test/inputs/numeric_input_test.rb +173 -0
  87. data/test/inputs/priority_input_test.rb +43 -0
  88. data/test/inputs/readonly_test.rb +61 -0
  89. data/test/inputs/required_test.rb +113 -0
  90. data/test/inputs/string_input_test.rb +140 -0
  91. data/test/inputs/text_input_test.rb +29 -0
  92. data/test/simple_form_test.rb +9 -0
  93. data/test/support/discovery_inputs.rb +21 -0
  94. data/test/support/misc_helpers.rb +102 -0
  95. data/test/support/mock_controller.rb +24 -0
  96. data/test/support/mock_response.rb +14 -0
  97. data/test/support/models.rb +210 -0
  98. data/test/test_helper.rb +95 -0
  99. metadata +227 -0
@@ -0,0 +1,113 @@
1
+ require 'test_helper'
2
+
3
+ class RequiredTest < ActionView::TestCase
4
+ # REQUIRED AND PRESENCE VALIDATION
5
+ test 'builder input should obtain required from ActiveModel::Validations when it is included' do
6
+ with_form_for @validating_user, :name
7
+ assert_select 'input.required[required]#validating_user_name'
8
+ with_form_for @validating_user, :status
9
+ assert_select 'input.optional#validating_user_status'
10
+ end
11
+
12
+ test 'builder input should allow overriding required when ActiveModel::Validations is included' do
13
+ with_form_for @validating_user, :name, :required => false
14
+ assert_select 'input.optional#validating_user_name'
15
+ with_form_for @validating_user, :status, :required => true
16
+ assert_select 'input.required[required]#validating_user_status'
17
+ end
18
+
19
+ test 'builder input should be required by default when ActiveModel::Validations is not included' do
20
+ with_form_for @user, :name
21
+ assert_select 'input.required[required]#user_name'
22
+ end
23
+
24
+ test 'builder input should not be required by default when ActiveModel::Validations is not included if option is set to false' do
25
+ swap SimpleFormWithClientValidation, :required_by_default => false do
26
+ with_form_for @user, :name
27
+ assert_select 'input.optional#user_name'
28
+ assert_no_select 'input[required]'
29
+ end
30
+ end
31
+
32
+ test 'when not using browser validations, input should not generate required html attribute' do
33
+ swap SimpleFormWithClientValidation, :browser_validations => false do
34
+ with_input_for @user, :name, :string
35
+ assert_select 'input[type=text].required'
36
+ assert_no_select 'input[type=text][required]'
37
+ end
38
+ end
39
+
40
+ test 'builder input should allow disabling required when ActiveModel::Validations is not included' do
41
+ with_form_for @user, :name, :required => false
42
+ assert_no_select 'input.required'
43
+ assert_no_select 'input[required]'
44
+ assert_select 'input.optional#user_name'
45
+ end
46
+
47
+ test 'when not the required component the input does not have the required attribute but has the required class' do
48
+ swap_wrapper do
49
+ with_input_for @user, :name, :string
50
+ assert_select 'input[type=text].required'
51
+ assert_no_select 'input[type=text][required]'
52
+ end
53
+ end
54
+
55
+ # VALIDATORS :if :unless
56
+ test 'builder input should not be required when ActiveModel::Validations is included and if option is present' do
57
+ with_form_for @validating_user, :age
58
+ assert_no_select 'input.required'
59
+ assert_no_select 'input[required]'
60
+ assert_select 'input.optional#validating_user_age'
61
+ end
62
+
63
+ test 'builder input should not be required when ActiveModel::Validations is included and unless option is present' do
64
+ with_form_for @validating_user, :amount
65
+ assert_no_select 'input.required'
66
+ assert_no_select 'input[required]'
67
+ assert_select 'input.optional#validating_user_amount'
68
+ end
69
+
70
+ # VALIDATORS :on
71
+ test 'builder input should be required when validation is on create and is not persisted' do
72
+ @validating_user.new_record!
73
+ with_form_for @validating_user, :action
74
+ assert_select 'input.required'
75
+ assert_select 'input[required]'
76
+ assert_select 'input.required[required]#validating_user_action'
77
+ end
78
+
79
+ test 'builder input should not be required when validation is on create and is persisted' do
80
+ with_form_for @validating_user, :action
81
+ assert_no_select 'input.required'
82
+ assert_no_select 'input[required]'
83
+ assert_select 'input.optional#validating_user_action'
84
+ end
85
+
86
+ test 'builder input should be required when validation is on save' do
87
+ with_form_for @validating_user, :credit_limit
88
+ assert_select 'input.required'
89
+ assert_select 'input[required]'
90
+ assert_select 'input.required[required]#validating_user_credit_limit'
91
+
92
+ @validating_user.new_record!
93
+ with_form_for @validating_user, :credit_limit
94
+ assert_select 'input.required'
95
+ assert_select 'input[required]'
96
+ assert_select 'input.required[required]#validating_user_credit_limit'
97
+ end
98
+
99
+ test 'builder input should be required when validation is on update and is persisted' do
100
+ with_form_for @validating_user, :phone_number
101
+ assert_select 'input.required'
102
+ assert_select 'input[required]'
103
+ assert_select 'input.required[required]#validating_user_phone_number'
104
+ end
105
+
106
+ test 'builder input should not be required when validation is on update and is not persisted' do
107
+ @validating_user.new_record!
108
+ with_form_for @validating_user, :phone_number
109
+ assert_no_select 'input.required'
110
+ assert_no_select 'input[required]'
111
+ assert_select 'input.optional#validating_user_phone_number'
112
+ end
113
+ end
@@ -0,0 +1,140 @@
1
+ # encoding: UTF-8
2
+ require 'test_helper'
3
+
4
+ class StringInputTest < ActionView::TestCase
5
+ test 'input should map text field to string attribute' do
6
+ with_input_for @user, :name, :string
7
+ assert_select "input#user_name[type=text][name='user[name]'][value=New in SimpleFormWithClientValidation!]"
8
+ end
9
+
10
+ test 'input should generate a password field for password attributes' do
11
+ with_input_for @user, :password, :password
12
+ assert_select "input#user_password.password[type=password][name='user[password]']"
13
+ end
14
+
15
+ test 'input should not use size attribute for decimal attributes' do
16
+ with_input_for @user, :credit_limit, :decimal
17
+ assert_no_select 'input.decimal[size]'
18
+ end
19
+
20
+ test 'input should get maxlength from column definition for string attributes' do
21
+ with_input_for @user, :name, :string
22
+ assert_select 'input.string[maxlength=100]'
23
+ end
24
+
25
+ test 'input should not get maxlength from column without size definition for string attributes' do
26
+ with_input_for @user, :action, :string
27
+ assert_no_select 'input.string[maxlength]'
28
+ end
29
+
30
+ test 'input should get size from column definition for string attributes respecting maximum value' do
31
+ with_input_for @user, :name, :string
32
+ assert_select 'input.string[size=50]'
33
+ end
34
+
35
+ test 'input should use default text size for password attributes' do
36
+ with_input_for @user, :password, :password
37
+ assert_select 'input.password[type=password][size=50]'
38
+ end
39
+
40
+ test 'input should get maxlength from column definition for password attributes' do
41
+ with_input_for @user, :password, :password
42
+ assert_select 'input.password[type=password][maxlength=100]'
43
+ end
44
+
45
+ test 'input should infer maxlength column definition from validation when present' do
46
+ with_input_for @validating_user, :name, :string
47
+ assert_select 'input.string[maxlength=25]'
48
+ end
49
+
50
+ test 'input should not get maxlength from validation when tokenizer present' do
51
+ with_input_for @validating_user, :action, :string
52
+ assert_no_select 'input.string[maxlength]'
53
+ end
54
+
55
+ test 'input should get maxlength from validation when :is option present' do
56
+ with_input_for @validating_user, :home_picture, :string
57
+ assert_select 'input.string[maxlength=12]'
58
+ end
59
+
60
+ test 'input should not generate placeholder by default' do
61
+ with_input_for @user, :name, :string
62
+ assert_no_select 'input[placeholder]'
63
+ end
64
+
65
+ test 'input should accept the placeholder option' do
66
+ with_input_for @user, :name, :string, :placeholder => 'Put in some text'
67
+ assert_select 'input.string[placeholder=Put in some text]'
68
+ end
69
+
70
+ test 'input should generate a password field for password attributes that accept placeholder' do
71
+ with_input_for @user, :password, :password, :placeholder => 'Password Confirmation'
72
+ assert_select 'input[type=password].password[placeholder=Password Confirmation]#user_password'
73
+ end
74
+
75
+ test 'input should not infer pattern from attributes by default' do
76
+ with_input_for @other_validating_user, :country, :string
77
+ assert_no_select 'input[pattern="\w+"]'
78
+ end
79
+
80
+ test 'input should infer pattern from attributes' do
81
+ with_input_for @other_validating_user, :country, :string, :pattern => true
82
+ assert_select 'input[pattern="\w+"]'
83
+ end
84
+
85
+ test 'input should infer pattern from attributes using proc' do
86
+ with_input_for @other_validating_user, :name, :string, :pattern => true
87
+ assert_select 'input[pattern="\w+"]'
88
+ end
89
+
90
+ test 'input should not infer pattern from attributes if root default is false' do
91
+ swap_wrapper do
92
+ with_input_for @other_validating_user, :country, :string
93
+ assert_no_select 'input[pattern="\w+"]'
94
+ end
95
+ end
96
+
97
+ test 'input should use given pattern from attributes' do
98
+ with_input_for @other_validating_user, :country, :string, :input_html => { :pattern => "\\d+" }
99
+ assert_select 'input[pattern="\d+"]'
100
+ end
101
+
102
+ test 'input should use i18n to translate placeholder text' do
103
+ store_translations(:en, :simple_form => { :placeholders => { :user => {
104
+ :name => 'Name goes here'
105
+ } } }) do
106
+ with_input_for @user, :name, :string
107
+ assert_select 'input.string[placeholder=Name goes here]'
108
+ end
109
+ end
110
+
111
+ [:email, :url, :search, :tel].each do |type|
112
+ test "input should allow type #{type}" do
113
+ with_input_for @user, :name, type
114
+ assert_select "input.string.#{type}"
115
+ assert_select "input[type=#{type}]"
116
+ end
117
+
118
+ test "input should not allow type #{type} if HTML5 compatibility is disabled" do
119
+ swap_wrapper do
120
+ with_input_for @user, :name, type
121
+ assert_select "input[type=text]"
122
+ assert_no_select "input[type=#{type}]"
123
+ end
124
+ end
125
+ end
126
+
127
+ test 'input strips extra spaces from class html attribute with default classes' do
128
+ with_input_for @user, :name, :string
129
+ assert_select "input[class='string required']"
130
+ assert_no_select "input[class='string required ']"
131
+ assert_no_select "input[class=' string required']"
132
+ end
133
+
134
+ test 'input strips extra spaces from class html attribute when giving a custom class' do
135
+ with_input_for @user, :name, :string, :input_html => { :class => "my_input" }
136
+ assert_select "input[class='string required my_input']"
137
+ assert_no_select "input[class='string required my_input ']"
138
+ assert_no_select "input[class=' string required my_input']"
139
+ end
140
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: UTF-8
2
+ require 'test_helper'
3
+
4
+ class TextInputTest < ActionView::TestCase
5
+ test 'input should generate a text area for text attributes' do
6
+ with_input_for @user, :description, :text
7
+ assert_select 'textarea.text#user_description'
8
+ end
9
+
10
+ test 'input should generate a text area for text attributes that accept placeholder' do
11
+ with_input_for @user, :description, :text, :placeholder => 'Put in some text'
12
+ assert_select 'textarea.text[placeholder=Put in some text]'
13
+ end
14
+
15
+ test 'input should get maxlength from column definition for text attributes' do
16
+ with_input_for @user, :description, :text
17
+ assert_select 'textarea.text[maxlength=200]'
18
+ end
19
+
20
+ test 'input should infer maxlength column definition from validation when present for text attributes' do
21
+ with_input_for @validating_user, :description, :text
22
+ assert_select 'textarea.text[maxlength=50]'
23
+ end
24
+
25
+ test 'input should infer minlength column definition from validation when present for text attributes' do
26
+ with_input_for @validating_user, :description, :text
27
+ assert_select 'textarea.text[minlength=5]'
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+
3
+ class SimpleFormWithClientValidationTest < ActiveSupport::TestCase
4
+ test 'setup block yields self' do
5
+ SimpleFormWithClientValidation.setup do |config|
6
+ assert_equal SimpleFormWithClientValidation, config
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ class StringInput < SimpleFormWithClientValidation::Inputs::StringInput
2
+ def input
3
+ "<section>#{super}</section>".html_safe
4
+ end
5
+ end
6
+
7
+ class NumericInput < SimpleFormWithClientValidation::Inputs::NumericInput
8
+ def input
9
+ "<section>#{super}</section>".html_safe
10
+ end
11
+ end
12
+
13
+ class CustomizedInput < SimpleFormWithClientValidation::Inputs::StringInput
14
+ def input
15
+ "<section>#{super}</section>".html_safe
16
+ end
17
+
18
+ def input_method
19
+ :text_field
20
+ end
21
+ end
@@ -0,0 +1,102 @@
1
+ module MiscHelpers
2
+ def store_translations(locale, translations, &block)
3
+ I18n.backend.store_translations locale, translations
4
+ yield
5
+ ensure
6
+ I18n.reload!
7
+ I18n.backend.send :init_translations
8
+ end
9
+
10
+ def assert_no_select(selector, value = nil)
11
+ assert_select(selector, :text => value, :count => 0)
12
+ end
13
+
14
+ def swap(object, new_values)
15
+ old_values = {}
16
+ new_values.each do |key, value|
17
+ old_values[key] = object.send key
18
+ object.send :"#{key}=", value
19
+ end
20
+ yield
21
+ ensure
22
+ old_values.each do |key, value|
23
+ object.send :"#{key}=", value
24
+ end
25
+ end
26
+
27
+ def swap_wrapper(name=:default, wrapper=self.custom_wrapper)
28
+ old = SimpleFormWithClientValidation.wrappers[name]
29
+ SimpleFormWithClientValidation.wrappers[name] = wrapper
30
+ yield
31
+ ensure
32
+ SimpleFormWithClientValidation.wrappers[name] = old
33
+ end
34
+
35
+ def custom_wrapper
36
+ SimpleFormWithClientValidation.build :tag => :section, :class => "custom_wrapper", :pattern => false do |b|
37
+ b.use :pattern
38
+ b.wrapper :another, :class => "another_wrapper" do |ba|
39
+ ba.use :label
40
+ ba.use :input
41
+ end
42
+ b.wrapper :error_wrapper, :tag => :div, :class => "error_wrapper" do |be|
43
+ be.use :error, :wrap_with => { :tag => :span, :class => "omg_error" }
44
+ end
45
+ b.use :hint, :wrap_with => { :class => "omg_hint" }
46
+ end
47
+ end
48
+
49
+ def custom_wrapper_without_top_level
50
+ SimpleFormWithClientValidation.build :tag => false, :class => 'custom_wrapper_without_top_level' do |b|
51
+ b.use :label_input
52
+ b.use :hint, :wrap_with => { :tag => :span, :class => :hint }
53
+ b.use :error, :wrap_with => { :tag => :span, :class => :error }
54
+ end
55
+ end
56
+
57
+ def custom_form_for(object, *args, &block)
58
+ simple_form_for(object, *(args << { :builder => CustomFormBuilder }), &block)
59
+ end
60
+
61
+ def custom_mapping_form_for(object, *args, &block)
62
+ simple_form_for(object, *(args << { :builder => CustomMapTypeFormBuilder }), &block)
63
+ end
64
+
65
+ def with_concat_form_for(*args, &block)
66
+ concat simple_form_for(*args, &(block || proc {}))
67
+ end
68
+
69
+ def with_concat_fields_for(*args, &block)
70
+ concat simple_fields_for(*args, &block)
71
+ end
72
+
73
+ def with_concat_custom_form_for(*args, &block)
74
+ concat custom_form_for(*args, &block)
75
+ end
76
+
77
+ def with_concat_custom_mapping_form_for(*args, &block)
78
+ concat custom_mapping_form_for(*args, &block)
79
+ end
80
+
81
+ def with_form_for(object, *args, &block)
82
+ with_concat_form_for(object) do |f|
83
+ f.input(*args, &block)
84
+ end
85
+ end
86
+
87
+ def with_input_for(object, attribute_name, type, options={})
88
+ with_concat_form_for(object) do |f|
89
+ f.input(attribute_name, options.merge(:as => type))
90
+ end
91
+ end
92
+ end
93
+
94
+ class CustomFormBuilder < SimpleFormWithClientValidation::FormBuilder
95
+ def input(attribute_name, *args, &block)
96
+ super(attribute_name, *(args << { :input_html => { :class => 'custom' } }), &block)
97
+ end
98
+ end
99
+
100
+ class CustomMapTypeFormBuilder < SimpleFormWithClientValidation::FormBuilder
101
+ map_type :custom_type, :to => SimpleFormWithClientValidation::Inputs::StringInput
102
+ end
@@ -0,0 +1,24 @@
1
+ class MockController
2
+ attr_writer :action_name
3
+
4
+ def _routes
5
+ self
6
+ end
7
+
8
+ def action_name
9
+ defined?(@action_name) ? @action_name : "edit"
10
+ end
11
+
12
+ def url_for(*args)
13
+ "http://example.com"
14
+ end
15
+
16
+ def url_helpers
17
+ self
18
+ end
19
+
20
+ def hash_for_user_path(*args); end
21
+ def hash_for_validating_user_path(*args); end
22
+ def hash_for_other_validating_user_path(*args); end
23
+ def hash_for_users_path(*args); end
24
+ end