admin_data 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (159) hide show
  1. data/History.txt +221 -0
  2. data/README.textile +21 -0
  3. data/Rakefile +46 -0
  4. data/app/controllers/admin_data/base_controller.rb +114 -0
  5. data/app/controllers/admin_data/diagnostic_controller.rb +28 -0
  6. data/app/controllers/admin_data/feed_controller.rb +17 -0
  7. data/app/controllers/admin_data/main_controller.rb +132 -0
  8. data/app/controllers/admin_data/migration_controller.rb +19 -0
  9. data/app/controllers/admin_data/search_controller.rb +125 -0
  10. data/app/controllers/admin_data/validate_model_controller.rb +106 -0
  11. data/app/views/admin_data/diagnostic/index.html.erb +17 -0
  12. data/app/views/admin_data/diagnostic/missing_index.html.erb +26 -0
  13. data/app/views/admin_data/feed/index.rss.builder +24 -0
  14. data/app/views/admin_data/main/all_models.html.erb +22 -0
  15. data/app/views/admin_data/main/association/_association_info.html.erb +10 -0
  16. data/app/views/admin_data/main/association/_belongs_to_info.html.erb +7 -0
  17. data/app/views/admin_data/main/association/_has_many_info.html.erb +7 -0
  18. data/app/views/admin_data/main/association/_has_one_info.html.erb +6 -0
  19. data/app/views/admin_data/main/edit.html.erb +38 -0
  20. data/app/views/admin_data/main/misc/_form.html.erb +25 -0
  21. data/app/views/admin_data/main/misc/_modify_record.html.erb +22 -0
  22. data/app/views/admin_data/main/new.html.erb +23 -0
  23. data/app/views/admin_data/main/show.html.erb +41 -0
  24. data/app/views/admin_data/main/table_structure.html.erb +55 -0
  25. data/app/views/admin_data/migration/index.html.erb +18 -0
  26. data/app/views/admin_data/migration/jstest.html.erb +51 -0
  27. data/app/views/admin_data/search/_search_base.html.erb +34 -0
  28. data/app/views/admin_data/search/advance_search.html.erb +3 -0
  29. data/app/views/admin_data/search/quick_search.html.erb +6 -0
  30. data/app/views/admin_data/search/search/_advance_search_form.html.erb +48 -0
  31. data/app/views/admin_data/search/search/_errors.html.erb +5 -0
  32. data/app/views/admin_data/search/search/_listing.html.erb +40 -0
  33. data/app/views/admin_data/search/search/_search_form.html.erb +28 -0
  34. data/app/views/admin_data/search/search/_sortby.html.erb +18 -0
  35. data/app/views/admin_data/search/search/_title.html.erb +34 -0
  36. data/app/views/admin_data/shared/_breadcrum.html.erb +10 -0
  37. data/app/views/admin_data/shared/_drop_down_klasses.html.erb +4 -0
  38. data/app/views/admin_data/shared/_flash_message.html.erb +13 -0
  39. data/app/views/admin_data/shared/_header.html.erb +23 -0
  40. data/app/views/admin_data/shared/_powered_by.html.erb +23 -0
  41. data/app/views/admin_data/shared/_secondary_navigation.html.erb +28 -0
  42. data/app/views/admin_data/validate_model/_bad.html.erb +1 -0
  43. data/app/views/admin_data/validate_model/tid.html.erb +2 -0
  44. data/app/views/admin_data/validate_model/validate.html.erb +67 -0
  45. data/app/views/layouts/admin_data.html.erb +60 -0
  46. data/config/routes.rb +34 -0
  47. data/init.rb +1 -0
  48. data/lib/admin_data.rb +34 -0
  49. data/lib/admin_data/chelper.rb +37 -0
  50. data/lib/admin_data/compatibility.rb +7 -0
  51. data/lib/admin_data/helpers.rb +222 -0
  52. data/lib/admin_data/railtie.rb +21 -0
  53. data/lib/admin_data/search.rb +186 -0
  54. data/lib/admin_data/settings.rb +72 -0
  55. data/lib/admin_data/util.rb +237 -0
  56. data/lib/admin_data/version.rb +3 -0
  57. data/lib/admin_data_date_validation.rb +79 -0
  58. data/lib/css/app.css +224 -0
  59. data/lib/css/base.css +1071 -0
  60. data/lib/css/header.css +65 -0
  61. data/lib/css/rounded.css +18 -0
  62. data/lib/css/themes/drastic-dark/style.css +374 -0
  63. data/lib/css/umbrella.css +34 -0
  64. data/lib/css/vendor/images/ui-bg_diagonals-thick_75_f3d8d8_40x40.png +0 -0
  65. data/lib/css/vendor/images/ui-bg_dots-small_65_a6a6a6_2x2.png +0 -0
  66. data/lib/css/vendor/images/ui-bg_flat_0_333333_40x100.png +0 -0
  67. data/lib/css/vendor/images/ui-bg_flat_65_ffffff_40x100.png +0 -0
  68. data/lib/css/vendor/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  69. data/lib/css/vendor/images/ui-bg_glass_55_fbf8ee_1x400.png +0 -0
  70. data/lib/css/vendor/images/ui-bg_highlight-hard_100_eeeeee_1x100.png +0 -0
  71. data/lib/css/vendor/images/ui-bg_highlight-hard_100_f6f6f6_1x100.png +0 -0
  72. data/lib/css/vendor/images/ui-bg_highlight-soft_15_cc0000_1x100.png +0 -0
  73. data/lib/css/vendor/images/ui-icons_004276_256x240.png +0 -0
  74. data/lib/css/vendor/images/ui-icons_cc0000_256x240.png +0 -0
  75. data/lib/css/vendor/images/ui-icons_ffffff_256x240.png +0 -0
  76. data/lib/css/vendor/jquery-ui-1.7.2.custom.css +406 -0
  77. data/lib/css/vendor/qunit.css +119 -0
  78. data/lib/js/advance_search/act_on_result.js +47 -0
  79. data/lib/js/advance_search/adv_search.js +46 -0
  80. data/lib/js/advance_search/advance_search.js +60 -0
  81. data/lib/js/advance_search/advance_search_structure.js +79 -0
  82. data/lib/js/advance_search/ajaxify_advance_search.js +28 -0
  83. data/lib/js/advance_search/build_first_row.js +12 -0
  84. data/lib/js/advance_search/event_bindings.js +87 -0
  85. data/lib/js/advance_search/global_ajax_setting.js +10 -0
  86. data/lib/js/advance_search/trigger_submit_on_domready.js +6 -0
  87. data/lib/js/misc/drop_down_change.js +8 -0
  88. data/lib/js/misc/js_util.js +42 -0
  89. data/lib/js/misc/quick_search_input_focus.js +6 -0
  90. data/lib/js/test/act_on_result.js +120 -0
  91. data/lib/js/test/advance_search.js +80 -0
  92. data/lib/js/test/ajaxify_advance_search.js +29 -0
  93. data/lib/js/test/build_first_row.js +10 -0
  94. data/lib/js/test/event_bindings.js +100 -0
  95. data/lib/js/validate_model/ajaxify_form.js +66 -0
  96. data/lib/js/validate_model/select_all.js +15 -0
  97. data/lib/js/vendor/jack.js +903 -0
  98. data/lib/js/vendor/jquery-1.4.1.js +6078 -0
  99. data/lib/js/vendor/jquery-ui-1.7.2.custom.min.js +298 -0
  100. data/lib/js/vendor/jquery.ba-isjquery.js +21 -0
  101. data/lib/js/vendor/jquery.form.js +814 -0
  102. data/lib/js/vendor/jquery.lint.js +604 -0
  103. data/lib/js/vendor/log.js +9 -0
  104. data/lib/js/vendor/qunit.js +1043 -0
  105. data/lib/tasks/admin_data_tasks.rake +7 -0
  106. data/lib/tasks/validate_models_bg.rake +23 -0
  107. data/test/factories/article.rb +9 -0
  108. data/test/factories/car.rb +4 -0
  109. data/test/factories/city.rb +4 -0
  110. data/test/factories/comment.rb +6 -0
  111. data/test/factories/door.rb +4 -0
  112. data/test/factories/engine.rb +4 -0
  113. data/test/functional/base_controller_test.rb +5 -0
  114. data/test/functional/feed_controller_test.rb +34 -0
  115. data/test/functional/main_controller_test.rb +421 -0
  116. data/test/functional/migration_controller_test.rb +30 -0
  117. data/test/functional/routes_test.rb +61 -0
  118. data/test/functional/search_controller_test.rb +814 -0
  119. data/test/helper/view_helper_test.rb +177 -0
  120. data/test/misc_tests/date_validation_test.rb +32 -0
  121. data/test/misc_tests/settings_test.rb +29 -0
  122. data/test/misc_tests/util_test.rb +83 -0
  123. data/test/rails_root/Gemfile +22 -0
  124. data/test/rails_root/Gemfile.lock +101 -0
  125. data/test/rails_root/Rakefile +7 -0
  126. data/test/rails_root/app/controllers/application_controller.rb +3 -0
  127. data/test/rails_root/app/helpers/application_helper.rb +2 -0
  128. data/test/rails_root/app/models/article.rb +25 -0
  129. data/test/rails_root/app/models/city.rb +15 -0
  130. data/test/rails_root/app/models/comment.rb +13 -0
  131. data/test/rails_root/app/models/tech_magazine.rb +2 -0
  132. data/test/rails_root/app/models/vehicle/car.rb +4 -0
  133. data/test/rails_root/app/models/vehicle/door.rb +3 -0
  134. data/test/rails_root/app/models/vehicle/engine.rb +3 -0
  135. data/test/rails_root/app/views/layouts/application.html.erb +14 -0
  136. data/test/rails_root/config.ru +4 -0
  137. data/test/rails_root/config/application.rb +42 -0
  138. data/test/rails_root/config/boot.rb +13 -0
  139. data/test/rails_root/config/database.yml +22 -0
  140. data/test/rails_root/config/environment.rb +5 -0
  141. data/test/rails_root/config/environments/development.rb +22 -0
  142. data/test/rails_root/config/environments/production.rb +49 -0
  143. data/test/rails_root/config/environments/test.rb +35 -0
  144. data/test/rails_root/config/initializers/backtrace_silencers.rb +7 -0
  145. data/test/rails_root/config/initializers/inflections.rb +10 -0
  146. data/test/rails_root/config/initializers/mime_types.rb +5 -0
  147. data/test/rails_root/config/initializers/secret_token.rb +7 -0
  148. data/test/rails_root/config/initializers/session_store.rb +8 -0
  149. data/test/rails_root/config/locales/en.yml +5 -0
  150. data/test/rails_root/config/routes.rb +58 -0
  151. data/test/rails_root/db/migrate/20090809061114_create_tables.rb +70 -0
  152. data/test/rails_root/db/schema.rb +74 -0
  153. data/test/rails_root/db/seeds.rb +7 -0
  154. data/test/rails_root/script/rails +6 -0
  155. data/test/rails_root/test/performance/browsing_test.rb +0 -0
  156. data/test/rails_root/test/test_helper.rb +13 -0
  157. data/test/support/assertions.rb +20 -0
  158. data/test/test_helper.rb +74 -0
  159. metadata +321 -0
@@ -0,0 +1,30 @@
1
+ pwd = File.dirname(__FILE__)
2
+ require File.join(pwd, '..', 'test_helper')
3
+ #require 'test_helper'
4
+
5
+ pwd = File.dirname(__FILE__)
6
+ f = File.join(pwd, '..', '..', 'app', 'views')
7
+ AdminData::MainController.prepend_view_path(f)
8
+ AdminData::MigrationController.prepend_view_path(f)
9
+
10
+ class AdminData::MigrationControllerTest < ActionController::TestCase
11
+
12
+ def setup
13
+ @controller = AdminData::MigrationController.new
14
+ @request = ActionController::TestRequest.new
15
+ @response = ActionController::TestResponse.new
16
+ grant_read_only_access
17
+ end
18
+
19
+ context 'GET index' do
20
+ setup do
21
+ get :index
22
+ end
23
+ should_respond_with :success
24
+ should_assign_to :data
25
+ should 'contain title' do
26
+ assert_tag(:tag => 'h2', :content => 'Migration Information from schema_migrations table')
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,61 @@
1
+ pwd = File.dirname(__FILE__)
2
+
3
+ require File.join(pwd, '..', 'test_helper')
4
+
5
+ class AdminData::MainControllerTest < ActionController::TestCase
6
+
7
+ should route(:get, '/admin_data/quick_search/article').to(:controller => 'admin_data/search',
8
+ :action => :quick_search,
9
+ :klass => 'article')
10
+
11
+ should route(:get, '/admin_data/advance_search/article').to(:controller => 'admin_data/search',
12
+ :action => :advance_search,
13
+ :klass => 'article')
14
+
15
+ should route(:get, '/admin_data/migration').to(:controller => 'admin_data/migration',
16
+ :action => :index)
17
+
18
+ should route(:get, '/admin_data').to(:controller => 'admin_data/main',
19
+ :action => :all_models)
20
+
21
+ should route(:get, '/admin_data').to(:controller => 'admin_data/main',
22
+ :action => :all_models)
23
+
24
+ should route(:get, '/admin_data/klass/article/1').to(:controller => 'admin_data/main',
25
+ :action => :show,
26
+ :klass => 'article',
27
+ :id => 1)
28
+
29
+ should route(:delete, '/admin_data/klass/article/1').to(:controller => 'admin_data/main',
30
+ :action => :destroy,
31
+ :klass => 'article',
32
+ :id => 1)
33
+
34
+ should route(:delete, '/admin_data/klass/article/1/del').to(:controller => 'admin_data/main',
35
+ :action => :del,
36
+ :klass => 'article',
37
+ :id => 1)
38
+
39
+ should route(:get, '/admin_data/klass/article/1/edit').to(:controller => 'admin_data/main',
40
+ :action => :edit,
41
+ :klass => 'article',
42
+ :id => 1)
43
+
44
+ should route(:put, '/admin_data/klass/article/1').to(:controller => 'admin_data/main',
45
+ :action => :update,
46
+ :klass => 'article',
47
+ :id => 1)
48
+
49
+ should route(:get, '/admin_data/klass/article/new').to(:controller => 'admin_data/main',
50
+ :action => :new,
51
+ :klass => 'article')
52
+
53
+ should route(:post, '/admin_data/klass/article').to(:controller => 'admin_data/main',
54
+ :action => :create,
55
+ :klass => 'article')
56
+
57
+ should route(:get, '/admin_data/klass/article/table_structure').to(:controller => 'admin_data/main',
58
+ :action => :table_structure,
59
+ :klass => 'article')
60
+
61
+ end
@@ -0,0 +1,814 @@
1
+
2
+ require 'test_helper'
3
+
4
+ pwd = File.dirname(__FILE__)
5
+ f = File.join(pwd, '..', '..', 'app', 'views')
6
+ AdminData::MainController.prepend_view_path(f)
7
+ AdminData::SearchController.prepend_view_path(f)
8
+
9
+ class AdminData::SearchControllerTest < ActionController::TestCase
10
+
11
+ def setup
12
+ @controller = AdminData::SearchController.new
13
+ @request = ActionController::TestRequest.new
14
+ @response = ActionController::TestResponse.new
15
+ @article = Factory(:article)
16
+ @car = Factory(:car, :year => 2000, :brand => 'bmw')
17
+ grant_read_only_access
18
+ end
19
+
20
+ # write filters test
21
+
22
+
23
+ context 'GET quick_search' do
24
+ context 'GET quick_search with wrong children class' do
25
+ setup do
26
+ get :quick_search, { :base => 'article',
27
+ :klass => 'comment',
28
+ :model_id => @article.id,
29
+ :children => 'wrong_children_name' }
30
+ end
31
+ should_respond_with :not_found
32
+ end
33
+
34
+ context 'with no klass param' do
35
+ setup do
36
+ assert_raises ActionController::RoutingError do
37
+ get :quick_search
38
+ end
39
+ end
40
+ end
41
+
42
+ context 'with no search query' do
43
+ setup do
44
+ get :quick_search, {:klass => Article.name.underscore}
45
+ end
46
+ should_respond_with :success
47
+ should_assign_to :records
48
+ end
49
+ context 'with has_many association' do
50
+ context 'for a nested model' do
51
+ setup do
52
+ Vehicle::Door.delete_all
53
+ @door1 = Factory(:door, :color => 'black', :car => @car)
54
+ @door2 = Factory(:door, :color => 'green', :car => @car)
55
+ get :quick_search, { :klass => @door1.class.name.underscore,
56
+ :base => @car.class.name.underscore,
57
+ :model_id => @car.id,
58
+ :children => 'doors'}
59
+ end
60
+ should_respond_with :success
61
+ should_assign_to :records
62
+ should 'have 2 records' do
63
+ assert_equal 2, assigns(:records).size
64
+ end
65
+ should 'have 2 as total number of children' do
66
+ assert_equal 2, assigns(:total_num_of_children)
67
+ end
68
+ should 'contain text' do
69
+ assert_tag(:tag => 'h2', :attributes => {:class => 'title'}, :content => /has 2/m)
70
+ end
71
+ end
72
+
73
+ context 'for a standard model' do
74
+ setup do
75
+ @comment1 = Factory(:comment, :article => @article)
76
+ @comment2 = Factory(:comment, :article => @article)
77
+ get :quick_search, { :klass => Comment.name.underscore,
78
+ :base => 'article',
79
+ :model_id => @article.id.to_s,
80
+ :children => 'comments' }
81
+ end
82
+ should_respond_with :success
83
+ should_assign_to :records
84
+ should 'have 2 records' do
85
+ assert_equal 2, assigns(:records).size
86
+ end
87
+ should 'contain text' do
88
+ assert_tag(:tag => 'h2', :attributes => {:class => 'title'}, :content => /has 2 comments/ )
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+
95
+ context 'GET quick_search' do
96
+ context 'for a standard model' do
97
+ setup do
98
+ @comment = Factory(:comment)
99
+ @comment = Factory(:comment)
100
+ get :quick_search, {:klass => @comment.class.name.underscore}
101
+ end
102
+ should_respond_with :success
103
+ should 'contain valid link at header breadcrum' do
104
+ assert_tag( :tag => 'div', :attributes => {:class => 'breadcrum rounded'},
105
+ :descendant => {:tag => 'a',
106
+ :attributes => {:href => '/admin_data/quick_search/comment'}})
107
+ end
108
+ should 'contain proper link at table listing' do
109
+ url = "/admin_data/klass/comment/#{Comment.last.id}"
110
+ assert_tag( :tag => 'td', :descendant => {:tag => 'a', :attributes => {:href => url}})
111
+ end
112
+ end
113
+
114
+ context 'for a nested model' do
115
+ setup do
116
+ get :quick_search, {:klass => @car.class.name.underscore}
117
+ end
118
+ should_respond_with :success
119
+ should 'contain proper link at header breadcum' do
120
+ s = CGI.escape('vehicle/car')
121
+ assert_tag( :tag => 'div',
122
+ :attributes => {:class => 'breadcrum rounded'},
123
+ :descendant => {:tag => 'a', :attributes => {:href => "/admin_data/quick_search/#{s}" }})
124
+ end
125
+ should 'contain proper link at table listing' do
126
+ s = CGI.escape("vehicle/car")
127
+ url = "/admin_data/klass/#{s}/#{@car.class.last.id}"
128
+ assert_tag(:tag => 'td', :descendant => {:tag => 'a', :attributes => {:href => url}})
129
+ end
130
+ should 'have proper action name for search form' do
131
+ url = admin_data_search_path(:klass=>Vehicle::Car)
132
+ assert_tag( :tag => 'form', :attributes => {:action => url})
133
+ end
134
+ end
135
+ end
136
+
137
+ context 'GET quick_search with search term' do
138
+ setup do
139
+ Article.delete_all
140
+ @python_beginner_book = Factory(:article, :title => 'python for beginners')
141
+ @python_book = Factory(:article, :title => 'python')
142
+ @java_book = Factory(:article, :title => 'java')
143
+ @clojure_book = Factory(:article, :title => 'clojure')
144
+ end
145
+ context 'with default order' do
146
+ setup do
147
+ get :quick_search, {:klass => 'Article', :query => 'python'}
148
+ end
149
+ should_respond_with :success
150
+ should_assign_to :records
151
+ should 'have only two records' do
152
+ assert_equal 2, assigns(:records).size
153
+ end
154
+ should 'have python beginner book as the first book' do
155
+ assert_equal @python_beginner_book.id, assigns(:records).last.id
156
+ end
157
+ should 'have python book as the last book' do
158
+ assert_equal @python_book.id, assigns(:records).first.id
159
+ end
160
+ end
161
+
162
+ context 'with article_id ascending order' do
163
+ setup do
164
+ get :quick_search, { :klass => 'Article', :query => 'python', :sortby => 'article_id asc'}
165
+ end
166
+ should_respond_with :success
167
+ should_assign_to :records
168
+ should 'have only two records' do
169
+ assert_equal 2, assigns(:records).size
170
+ end
171
+ should 'have python beginner book as the first book' do
172
+ assert_equal @python_beginner_book.id, assigns(:records).first.id
173
+ end
174
+ should 'have python book as the last book' do
175
+ assert_equal @python_book.id, assigns(:records).last.id
176
+ end
177
+ end
178
+ end
179
+
180
+ context 'GET advance_search' do
181
+ context 'with no klass param' do
182
+ setup do
183
+ assert_raises ActionController::RoutingError do
184
+ get :advance_search
185
+ end
186
+ end
187
+ end
188
+
189
+ context 'with klass param' do
190
+ setup do
191
+ get :advance_search, {:klass => Article.name.underscore}
192
+ end
193
+ should_respond_with :success
194
+ should_not_assign_to :records
195
+ should 'have proper action for advance search form' do
196
+ url = admin_data_advance_search_path(:klass => Article)
197
+ assert_tag( :tag => 'form', :attributes => {:action => url})
198
+ end
199
+ end
200
+ end
201
+
202
+
203
+ context 'xhr advance_search with does_not_contain first one' do
204
+ setup do
205
+ Article.delete_all
206
+ AdminData::Config.set = ({ :is_allowed_to_update => lambda {|controller| return false} })
207
+ Factory(:article, :short_desc => 'ruby')
208
+ Factory(:article, :short_desc => 'rails')
209
+ Factory(:article, :short_desc => nil)
210
+ xml_http_request :post,
211
+ :advance_search,
212
+ { :klass => Article.name.underscore,
213
+ :sortby => 'article_id desc',
214
+ :adv_search => {'1_row' => {:col1 => 'short_desc',
215
+ :col2 => 'does_not_contain',
216
+ :col3 => 'ruby'} } }
217
+ end
218
+ should_respond_with :success
219
+ should 'contain text' do
220
+ assert_tag(:tag => 'h2', :attributes => {:class => 'title'}, :content => /Search result: 2 records found/ )
221
+ end
222
+ should 'not contain delete all link' do
223
+ assert_no_tag( :tag => 'a', :attributes => {:id => 'advance_search_delete_all'})
224
+ end
225
+ should 'not contain destroy all link' do
226
+ assert_no_tag( :tag => 'a', :attributes => {:id => 'advance_search_destroy_all'})
227
+ end
228
+ end
229
+
230
+ context 'xhr advance_search with delete_all action' do
231
+ setup do
232
+ Article.delete_all
233
+ AdminData::Config.set = ({ :is_allowed_to_update => lambda {|controller| return true} })
234
+ Factory(:article, :short_desc => 'ruby')
235
+ Factory(:article, :short_desc => 'rails')
236
+ so = {'1_row' => {:col1 => 'short_desc', :col2 => 'contains', :col3 => 'ruby'} }
237
+ h = { :klass => Article.name.underscore,
238
+ :sortby => 'article_id desc',
239
+ :admin_data_advance_search_action_type => 'delete',
240
+ :adv_search => so }
241
+ xml_http_request :post, :advance_search, h
242
+ @json = JSON.parse(@response.body)
243
+ end
244
+ should_respond_with :success
245
+ should 'have only one record' do
246
+ assert_equal 1, Article.count
247
+ end
248
+ should 'have success key in the response message' do
249
+ assert @json.has_key?('success')
250
+ end
251
+ should 'have success message in the response message' do
252
+ assert_equal '1 record deleted', @json.fetch('success')
253
+ end
254
+ end
255
+
256
+ context 'xhr advance_search with destroy_all action' do
257
+ setup do
258
+ Article.delete_all
259
+ AdminData::Config.set = ({ :is_allowed_to_update => lambda {|controller| return true} })
260
+ Factory(:article, :short_desc => 'ruby')
261
+ Factory(:article, :short_desc => 'rails')
262
+ xml_http_request :post,
263
+ :advance_search,
264
+ {:klass => Article.name.underscore,
265
+ :sortby => 'article_id desc',
266
+ :admin_data_advance_search_action_type => 'destroy',
267
+ :adv_search => {'1_row' => {:col1 => 'short_desc', :col2 => 'contains', :col3 => 'ruby'} } }
268
+ @json = JSON.parse(@response.body)
269
+ end
270
+ should_respond_with :success
271
+ should 'have only one record' do
272
+ assert_equal 1, Article.count
273
+ end
274
+ should 'have success key in the response message' do
275
+ assert @json.has_key?('success')
276
+ end
277
+ should 'have success message in the response message' do
278
+ assert_equal '1 record destroyed', @json.fetch('success')
279
+ end
280
+ end
281
+
282
+ context 'xhr advance_search with does_not_contain' do
283
+ setup do
284
+ AdminData::Config.set = ({ :is_allowed_to_update => lambda {|controller| return true } })
285
+ Article.delete_all
286
+ Factory(:article, :short_desc => 'ruby')
287
+ Factory(:article, :short_desc => 'rails')
288
+ Factory(:article, :short_desc => nil)
289
+ xml_http_request :post,
290
+ :advance_search,
291
+ {:klass => Article.name.underscore,
292
+ :sortby => 'article_id desc',
293
+ :adv_search => {'1_row' => {:col1 => 'short_desc',
294
+ :col2 => 'does_not_contain',
295
+ :col3 => 'ruby'} } }
296
+ end
297
+ should_respond_with :success
298
+ should 'contain search result' do
299
+ assert_tag(:tag => 'h2', :attributes => {:class => 'title'}, :content => /Search result: 2 records found/ )
300
+ end
301
+ should 'contain delete all link' do
302
+ assert_tag( :tag => 'a', :attributes => {:id => 'advance_search_delete_all'})
303
+ end
304
+ should 'contain destroy all link' do
305
+ assert_tag( :tag => 'a', :attributes => {:id => 'advance_search_destroy_all'})
306
+ end
307
+ end
308
+
309
+ context 'xhr advance_search with contains option with 2 records' do
310
+ setup do
311
+ Article.delete_all
312
+ @python_book = Factory(:article, :title => 'python')
313
+ @python_beginner_book = Factory(:article, :title => 'python for beginners')
314
+ @java_book = Factory(:article, :title => 'java')
315
+ @clojure_book = Factory(:article, :title => 'clojure')
316
+ xml_http_request :post,
317
+ :advance_search,
318
+ {:klass => Article.name.underscore,
319
+ :sortby => 'article_id desc',
320
+ :adv_search => {'1_row' => {:col1 => 'title', :col2 => 'contains', :col3 => 'python'} } }
321
+ end
322
+ should_respond_with :success
323
+ should 'contain text' do
324
+ assert_tag(:tag => 'h2', :attributes => {:class => 'title'}, :content => /Search result: 2 records found/ )
325
+ end
326
+ end
327
+
328
+ context 'xhr advance_search with 1 result' do
329
+ setup do
330
+ Article.delete_all
331
+ @python_book = Factory(:article, :title => 'python')
332
+ @python_beginner_book = Factory(:article, :title => 'python for beginners')
333
+ @java_book = Factory(:article, :title => 'java')
334
+ @clojure_book = Factory(:article, :title => 'clojure')
335
+ xml_http_request :post,
336
+ :advance_search,
337
+ { :klass => Article.name.underscore,
338
+ :sortby => 'article_id desc',
339
+ :adv_search => {'1_row' => {:col1 => 'title', :col2 => 'contains', :col3 => 'clojure'} } }
340
+ end
341
+ should_respond_with :success
342
+ should 'contain text' do
343
+ assert_tag(:tag => 'h2', :attributes => {:class => 'title'}, :content => /Search result: 1 record found/ )
344
+ end
345
+ end
346
+
347
+ context 'xhr advance_search with empty query term with contains option' do
348
+ setup do
349
+ Article.delete_all
350
+ @python_book = Factory(:article, :title => 'python')
351
+ @python_beginner_book = Factory(:article, :title => 'python for beginners')
352
+ @java_book = Factory(:article, :title => 'java')
353
+ @clojure_book = Factory(:article, :title => 'clojure')
354
+ xml_http_request :post,
355
+ :advance_search,
356
+ { :klass => Article.name.underscore,
357
+ :sortby => 'article_id desc',
358
+ :adv_search => {'1_row' => {:col1 => 'title', :col2 => 'contains', :col3 => ''} } }
359
+ end
360
+ should_respond_with :success
361
+ should 'contain text' do
362
+ assert_tag(:tag => 'h2', :attributes => {:class => 'title'}, :content => /Search result: 4 records found/ )
363
+ end
364
+ end
365
+
366
+ context 'xhr advance_search with empty col2' do
367
+ setup do
368
+ Article.delete_all
369
+ @python_book = Factory(:article, :title => 'python')
370
+ @python_beginner_book = Factory(:article, :title => 'python for beginners')
371
+ xml_http_request :post,
372
+ :advance_search,
373
+ { :klass => Article.name.underscore,
374
+ :sortby => 'article_id desc',
375
+ :adv_search => {'1_row' => {:col1 => 'title', :col2 => nil, :col3 => nil} } }
376
+ end
377
+ should_respond_with :success
378
+ should 'contain text' do
379
+ assert_tag(:tag => 'h2', :attributes => {:class => 'title'}, :content => /Search result: 2 records found/ )
380
+ end
381
+ end
382
+
383
+ context 'xhr advance_search with two search terms' do
384
+ setup do
385
+ Article.delete_all
386
+ @python_book = Factory(:article, :title => 'python')
387
+ @python_beginner_book = Factory(:article, :title => 'python for beginners', :body => 'for beginners')
388
+ @java_book = Factory(:article, :title => 'java')
389
+ @clojure_book = Factory(:article, :title => 'clojure', :body => 'not for beginners')
390
+ adv_search = { '1_row' => { :col1 => 'title',
391
+ :col2 => 'contains',
392
+ :col3 => 'python'},
393
+ '2_row' => {:col1 => 'body',
394
+ :col2 => 'contains',
395
+ :col3 => 'beginners'} }
396
+ xml_http_request :post,
397
+ :advance_search,
398
+ { :klass => Article.name.underscore, :sortby => 'article_id desc', :adv_search => adv_search }
399
+ end
400
+ should_respond_with :success
401
+ should 'contain text' do
402
+ assert_tag(:tag => 'h2', :attributes => {:class => 'title'}, :content => /Search result: 1 record found/ )
403
+ end
404
+ end
405
+
406
+ context 'advance search conditions' do
407
+ setup do
408
+ @klass = Object.const_get('Article')
409
+ @proc = Proc.new do
410
+ @controller.send(:build_advance_search_conditions, @klass, { '429440_row' => @hash })
411
+ end
412
+ end
413
+
414
+ context 'with col2 as null' do
415
+ should 'have sql as body is not null' do
416
+ @hash = { :col1 => 'body', :col2 => 'is_not_null'}
417
+ output = @proc.call
418
+ expected = %Q{ SELECT "articles".* FROM "articles" WHERE (articles.body IS NOT NULL) }
419
+ assert_equal_sql expected, @proc.call[:cond].to_sql
420
+ end
421
+ end
422
+
423
+ context 'with col2 contains' do
424
+ should 'have sql with like' do
425
+ @hash = { :col1 => 'body', :col2 => 'contains', :col3 => 'python'}
426
+ expected = %{ SELECT "articles".* FROM "articles" WHERE (articles.body LIKE '%python%') }
427
+ assert_equal_sql expected, @proc.call[:cond].to_sql
428
+ end
429
+ end
430
+
431
+ context 'with col2 as exactly' do
432
+ should 'have sql as body equals' do
433
+ @hash = { :col1 => 'body', :col2 => 'is_exactly', :col3 => 'python'}
434
+ expected = %{ SELECT "articles".* FROM "articles" WHERE (articles.body = 'python') }
435
+ assert_equal_sql expected , @proc.call[:cond].to_sql
436
+ end
437
+ end
438
+
439
+ context 'with does not contain' do
440
+ should 'have sql as body is null or not like' do
441
+ @hash = { :col1 => 'body', :col2 => 'does_not_contain', :col3 => 'python'}
442
+ expected = %Q{SELECT "articles".* FROM "articles" WHERE (articles.body IS NULL OR articles.body NOT LIKE '%python%')}
443
+ assert_equal_sql expected, @proc.call[:cond].to_sql
444
+ end
445
+ end
446
+
447
+ context 'with col2 as false' do
448
+ should 'have sql with body as false' do
449
+ @hash = { :col1 => 'body', :col2 => 'is_false', :col3 => 'python'}
450
+ expected = %{ SELECT "articles".* FROM "articles" WHERE (articles.body = 'f') }
451
+ assert_equal_sql expected, @proc.call[:cond].to_sql
452
+ end
453
+ end
454
+ end
455
+
456
+
457
+ context 'XHR advance_search' do
458
+ setup do
459
+ Article.delete_all
460
+ @proc = Proc.new do
461
+ @hash_big = { :klass => Article.name.underscore, :adv_search => {'2_row' => @hash } }
462
+ end
463
+ end
464
+ context 'with col2 contains' do
465
+ setup do
466
+ Factory(:article, :title => 'python')
467
+ @hash = {:col1 => 'title', :col2 => 'contains', :col3 => 'python'}
468
+ xml_http_request :post, :advance_search, @proc.call
469
+ end
470
+ should_respond_with :success
471
+ should 'contain content' do
472
+ assert_tag( :tag => 'h2', :attributes => { :class => 'title'}, :content => /Search result: 1 record found/ )
473
+ end
474
+ end
475
+
476
+ context 'with col2 contains with no search result' do
477
+ setup do
478
+ Factory(:article, :title => 'ruby')
479
+ @hash = { :col1 => 'title', :col2 => 'contains', :col3 => 'python'}
480
+ xml_http_request :post, :advance_search, @proc.call
481
+ end
482
+ should 'contain text' do
483
+ assert_tag( :tag => 'h2', :attributes => { :class => 'title' }, :content => /Search result: 0 records found/ )
484
+ end
485
+ end
486
+
487
+ context 'with col2 is_exactly' do
488
+ setup do
489
+ Factory(:article, :title => 'python')
490
+ @hash = { :col1 => 'title', :col2 => 'is_exactly', :col3 => 'python'}
491
+ xml_http_request :post, :advance_search, @proc.call
492
+ end
493
+ should 'contain text' do
494
+ assert_tag( :tag => 'h2', :attributes => { :class => 'title'}, :content => /Search result: 1 record found/ )
495
+ end
496
+ end
497
+
498
+ context 'with col2 is_exactly negative case' do
499
+ setup do
500
+ Factory(:article, :title => 'ruby')
501
+ @hash = {:col1 => 'title', :col2 => 'is_exactly', :col3 => 'python'}
502
+ xml_http_request :post, :advance_search, @proc.call
503
+ end
504
+ should 'contain text' do
505
+ assert_tag( :tag => 'h2', :attributes => { :id => 'search_result_title'}, :content => /Search result: 0 records found/ )
506
+ end
507
+ end
508
+
509
+ context 'with col2 does_not_contain' do
510
+ setup do
511
+ Factory(:article, :title => 'python')
512
+ @hash = {:col1 => 'title', :col2 => 'does_not_contain', :col3 => 'ruby'}
513
+ xml_http_request :post, :advance_search, @proc.call
514
+ end
515
+ should 'contain text' do
516
+ assert_tag( :tag => 'h2', :attributes => { :class => 'title'}, :content => /Search result: 1 record found/ )
517
+ end
518
+ end
519
+
520
+ context 'with col2 does_not_conatin negative case' do
521
+ setup do
522
+ Factory(:article, :title => 'ruby')
523
+ @hash = {:col1 => 'title', :col2 => 'does_not_contain', :col3 => 'ruby'}
524
+ xml_http_request :post, :advance_search, @proc.call
525
+ end
526
+ should 'contain text' do
527
+ assert_tag( :tag => 'h2', :attributes =>{ :class => 'title'}, :content => /Search result: 0 records found/ )
528
+ end
529
+ end
530
+
531
+ context 'with col2 is_false' do
532
+ setup do
533
+ Factory(:article, :approved => false)
534
+ @hash = {:col1 => 'approved', :col2 => 'is_false'}
535
+ xml_http_request :post, :advance_search, @proc.call
536
+ end
537
+ should 'contain text' do
538
+ assert_tag( :tag => 'h2', :attributes => {:class => 'title'}, :content => /Search result: 1 record found/ )
539
+ end
540
+ end
541
+
542
+ context 'with col2 is_false negative case' do
543
+ setup do
544
+ Factory(:article, :approved => true)
545
+ @hash = {:col1 => 'approved', :col2 => 'is_false'}
546
+ xml_http_request :post, :advance_search, @proc.call
547
+ end
548
+ should 'contain text' do
549
+ assert_tag(:tag => 'h2', :attributes => {:class => 'title'}, :content => /Search result: 0 records found/ )
550
+ end
551
+ end
552
+
553
+ context 'with col2 is_true' do
554
+ setup do
555
+ Factory(:article, :approved => true)
556
+ @hash = {:col1 => 'approved', :col2 => 'is_true'}
557
+ xml_http_request :post, :advance_search, @proc.call
558
+ end
559
+ should 'contain text' do
560
+ assert_tag( :tag => 'h2', :attributes => { :class => 'title'}, :content => /Search result: 1 record found/ )
561
+ end
562
+ end
563
+
564
+ context 'with col2 is_true negative case' do
565
+ setup do
566
+ Factory(:article, :approved => false)
567
+ @hash = {:col1 => 'approved', :col2 => 'is_true'}
568
+ xml_http_request :post, :advance_search, @proc.call
569
+ end
570
+ should 'contain text' do
571
+ assert_tag(:tag => 'h2', :attributes => {:class => 'title'}, :content => /Search result: 0 records found/ )
572
+ end
573
+ end
574
+
575
+ context 'with col2 is_null' do
576
+ setup do
577
+ Factory(:article, :status => nil)
578
+ @hash = {:col1 => 'status', :col2 => 'is_null'}
579
+ xml_http_request :post, :advance_search, @proc.call
580
+ end
581
+ should 'contain text' do
582
+ assert_tag( :tag => 'h2', :attributes => { :class => 'title' }, :content => /Search result: 1 record found/ )
583
+ end
584
+ end
585
+
586
+ context 'with col2 is_null negative case' do
587
+ setup do
588
+ Factory(:article, :status => 'something')
589
+ @hash = {:col1 => 'status', :col2 => 'is_null'}
590
+ xml_http_request :post, :advance_search, @proc.call
591
+ end
592
+ should 'contain text' do
593
+ assert_tag(:tag => 'h2', :attributes => {:class => 'title'}, :content => /Search result: 0 records found/ )
594
+ end
595
+ end
596
+
597
+ context 'with col2 is_not_null' do
598
+ setup do
599
+ Factory(:article, :status => 'something')
600
+ @hash = {:col1 => 'status', :col2 => 'is_not_null'}
601
+ xml_http_request :post, :advance_search, @proc.call
602
+ end
603
+ should 'contain text' do
604
+ assert_tag( :tag => 'h2', :attributes => { :class => 'title'}, :content => /Search result: 1 record found/ )
605
+ end
606
+ end
607
+
608
+ context 'with col2 is_not_null negative case' do
609
+ setup do
610
+ Factory(:article, :status => nil)
611
+ @hash = {:col1 => 'status', :col2 => 'is_not_null'}
612
+ xml_http_request :post, :advance_search, @proc.call
613
+ end
614
+ should 'contain text' do
615
+ assert_tag( :tag => 'h2', :attributes => { :class => 'title'}, :content => /Search result: 0 records found/ )
616
+ end
617
+ end
618
+
619
+ context 'with col2 is_equal_to' do
620
+ setup do
621
+ Factory(:article, :hits_count => 100)
622
+ @hash = {:col1 => 'hits_count', :col2 => 'is_equal_to', :col3 => 100.to_s}
623
+ xml_http_request :post, :advance_search, @proc.call
624
+ end
625
+ should 'contain text' do
626
+ assert_tag( :tag => 'h2', :attributes => { :class => 'title' }, :content => /Search result: 1 record found/ )
627
+ end
628
+ end
629
+
630
+ context 'with col2 is_equal_to negative case' do
631
+ setup do
632
+ Factory(:article, :hits_count => 100)
633
+ @hash = {:col1 => 'hits_count', :col2 => 'is_equal_to', :col3 => 101.to_s}
634
+ xml_http_request :post, :advance_search, @proc.call
635
+ end
636
+ should 'contain text' do
637
+ assert_tag( :tag => 'h2', :attributes => { :class => 'title' }, :content => /Search result: 0 records found/ )
638
+ end
639
+ end
640
+
641
+ context 'with col2 greater_than' do
642
+ setup do
643
+ Factory(:article, :hits_count => 100)
644
+ @hash = {:col1 => 'hits_count', :col2 => 'greater_than', :col3 => 99.to_s}
645
+ xml_http_request :post, :advance_search, @proc.call
646
+ end
647
+ should 'contain text' do
648
+ assert_tag( :tag => 'h2', :attributes => { :class => 'title'}, :content => /Search result: 1 record found/ )
649
+ end
650
+ end
651
+
652
+ context 'with col2 greater_than negative case' do
653
+ setup do
654
+ Factory(:article, :hits_count => 100)
655
+ @hash = {:col1 => 'hits_count', :col2 => 'greater_than', :col3 => 101.to_s}
656
+ xml_http_request :post, :advance_search, @proc.call
657
+ end
658
+ should 'contain text' do
659
+ assert_tag( :tag => 'h2', :attributes => { :class => 'title'}, :content => /Search result: 0 records found/ )
660
+ end
661
+ end
662
+
663
+ context 'with col2 less_than' do
664
+ setup do
665
+ Factory(:article, :hits_count => 100)
666
+ @hash = {:col1 => 'hits_count', :col2 => 'less_than', :col3 => 101.to_s}
667
+ xml_http_request :post, :advance_search, @proc.call
668
+ end
669
+ should 'contain text' do
670
+ assert_tag( :tag => 'h2', :attributes => { :class => 'title' }, :content => /Search result: 1 record found/ )
671
+ end
672
+ end
673
+
674
+ context 'with col2 less_than negative case' do
675
+ setup do
676
+ Factory(:article, :hits_count => 100)
677
+ @hash = {:col1 => 'hits_count', :col2 => 'less_than', :col3 => 99.to_s}
678
+ xml_http_request :post, :advance_search, @proc.call
679
+ end
680
+ should 'contain text' do
681
+ assert_tag( :tag => 'h2', :attributes => { :class => 'title' }, :content => /Search result: 0 records found/ )
682
+ end
683
+ end
684
+
685
+ context 'with col2 is_on' do
686
+ setup do
687
+ Factory(:article, :published_at => Time.now)
688
+ d = Time.now.strftime('%d-%B-%Y')
689
+ @hash = {:col1 => 'published_at', :col2 => 'is_on', :col3 => d}
690
+ xml_http_request :post, :advance_search, @proc.call
691
+ end
692
+ should 'contain text' do
693
+ assert_tag( :tag => 'h2', :attributes =>{ :class => 'title'}, :content => /Search result: 1 record found/ )
694
+ end
695
+ end
696
+
697
+ context 'with col2 is_on negative case' do
698
+ setup do
699
+ Factory(:article, :published_at => Time.now)
700
+ d = 1.year.ago.strftime('%d-%B-%Y')
701
+ @hash = {:col1 => 'published_at', :col2 => 'is_on', :col3 => d}
702
+ xml_http_request :post, :advance_search, @proc.call
703
+ end
704
+ should 'contain text' do
705
+ assert_tag( :tag => 'h2', :attributes => { :class => 'title'}, :content => /Search result: 0 records found/ )
706
+ end
707
+ end
708
+
709
+ context 'with col2 is_on_or_after_date' do
710
+ setup do
711
+ Factory(:article, :published_at => Time.now)
712
+ @hash = {:col1 => 'published_at', :col2 => 'is_on_or_after_date', :col3 => 1.month.ago.strftime('%d-%B-%Y') }
713
+ xml_http_request :post, :advance_search, @proc.call
714
+ end
715
+ should 'contain text' do
716
+ assert_tag( :tag => 'h2', :attributes => { :class => 'title'}, :descendant => /Search result: 1 record found/ )
717
+ end
718
+ end
719
+
720
+ context 'with col2 is_on_or_after_date negative case' do
721
+ setup do
722
+ Factory(:article, :published_at => Time.now)
723
+ @hash = {:col1 => 'published_at', :col2 => 'is_on_or_after_date', :col3 => 1.month.from_now.strftime('%d-%B-%Y') }
724
+ xml_http_request :post, :advance_search, @proc.call
725
+ end
726
+ should 'contain text' do
727
+ assert_tag( :tag => 'h2', :attributes => { :class => 'title' }, :content => /Search result: 0 records found/ )
728
+ end
729
+ end
730
+
731
+ context 'with col2 is_on_or_before_date' do
732
+ setup do
733
+ Factory(:article, :published_at => Time.now)
734
+ @hash = {:col1 => 'published_at', :col2 => 'is_on_or_before_date', :col3 => 1.month.from_now.strftime('%d-%B-%Y') }
735
+ xml_http_request :post, :advance_search, @proc.call
736
+ end
737
+ should 'contain text' do
738
+ assert_tag( :tag => 'h2', :attributes => { :class => 'title'}, :content => /Search result: 1 record found/ )
739
+ end
740
+ end
741
+
742
+ context 'with col2 is_on_or_before_date negative case' do
743
+ setup do
744
+ @hash = {:col1 => 'published_at', :col2 => 'is_on_or_before_date', :col3 => 1.year.ago.strftime('%d-%B-%Y') }
745
+ xml_http_request :post, :advance_search, @proc.call
746
+ end
747
+ should 'does contain text' do
748
+ assert_tag(:tag => 'h2', :attributes => {:class => 'title'}, :content => /Search result: 0 records found/ )
749
+ end
750
+ end
751
+
752
+ context 'with col2 is_on_or_before_date with invalid_date input' do
753
+ setup do
754
+ @hash = {:col1 => 'published_at', :col2 => 'is_on_or_before_date', :col3 => 'invalid_date'}
755
+ xml_http_request :post, :advance_search, @proc.call
756
+ end
757
+ should 'contain text' do
758
+ assert_tag(:tag => 'p', :attributes => {:class => 'error'}, :content => /is not a valid date/ )
759
+ end
760
+ end
761
+
762
+ context 'with col2 is_on_or_after_date with invalid_date input' do
763
+ setup do
764
+ @hash = {:col1 => 'published_at', :col2 => 'is_on_or_after_date', :col3 => 'invalid_date'}
765
+ xml_http_request :post, :advance_search, @proc.call
766
+ end
767
+ should 'contain text' do
768
+ assert_tag(:tag => 'p', :attributes => {:class => 'error'}, :content => /is not a valid date/ )
769
+ end
770
+ end
771
+
772
+ context 'is_on invalid date' do
773
+ setup do
774
+ @hash = {:col1 => 'published_at', :col2 => 'is_on', :col3 => 'invalid_date'}
775
+ xml_http_request :post, :advance_search, @proc.call
776
+ end
777
+ should 'contain text' do
778
+ assert_tag(:tag => 'p', :attributes => {:class => 'error'}, :content => /is not a valid date/ )
779
+ end
780
+ end
781
+
782
+ context 'with col2 is_equal_to with invalid input' do
783
+ setup do
784
+ @hash = {:col1 => 'hits_count', :col2 => 'is_equal_to', :col3 => 'invalid_integer'}
785
+ xml_http_request :post, :advance_search, @proc.call
786
+ end
787
+ should 'contain text' do
788
+ assert_tag(:tag => 'p', :attributes => {:class => 'error'}, :content => /is not a valid integer/ )
789
+ end
790
+ end
791
+
792
+ context 'with col2 less_than invalid_integer' do
793
+ setup do
794
+ @hash = {:col1 => 'hits_count', :col2 => 'less_than', :col3 => 'invalid_integer'}
795
+ xml_http_request :post, :advance_search, @proc.call
796
+ end
797
+ should 'contain text' do
798
+ assert_tag(:tag => 'p', :attributes => {:class => 'error'}, :content => /is not a valid integer/ )
799
+ end
800
+ end
801
+
802
+ context 'with col2 greater_than invalid integer' do
803
+ setup do
804
+ @hash = {:col1 => 'hits_count', :col2 => 'greater_than', :col3 => 'invalid_integer'}
805
+ xml_http_request :post, :advance_search, @proc.call
806
+ end
807
+ should 'contain text' do
808
+ assert_tag(:tag => 'p', :attributes => {:class => 'error'}, :content => /is not a valid integer/ )
809
+ end
810
+ end
811
+
812
+ end
813
+
814
+ end