rs-activeadmin-searchable_select 4.0.5

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 (145) hide show
  1. checksums.yaml +7 -0
  2. data/.actrc +20 -0
  3. data/.claude/commands/fix-tests.md +203 -0
  4. data/.github/workflows/ci.yml +170 -0
  5. data/.github/workflows/npm-publish.yml +47 -0
  6. data/.gitignore +27 -0
  7. data/.npmignore +58 -0
  8. data/.rspec +2 -0
  9. data/.rubocop.yml +67 -0
  10. data/.yardopts +2 -0
  11. data/AGENTS.md +39 -0
  12. data/Appraisals +15 -0
  13. data/CHANGELOG.md +24 -0
  14. data/CLAUDE.md +104 -0
  15. data/Gemfile +11 -0
  16. data/Gemfile.lock +366 -0
  17. data/LICENSE.txt +25 -0
  18. data/README.md +439 -0
  19. data/Rakefile +4 -0
  20. data/bin/rspec +17 -0
  21. data/config/database.yml +16 -0
  22. data/config.ru +28 -0
  23. data/docs/activeadmin-4-detailed-reference.md +932 -0
  24. data/docs/activeadmin-4-gem-migration-guide.md +313 -0
  25. data/docs/combustion.md +213 -0
  26. data/docs/for-next-session.md +199 -0
  27. data/docs/guide-update-your-app.md +213 -0
  28. data/docs/propshaft-readme.md +121 -0
  29. data/docs/propshaft-upgrade.md +267 -0
  30. data/docs/rails-7-asset-pipeline.md +279 -0
  31. data/docs/setup-activeadmin-app.md +552 -0
  32. data/docs/setup-activeadmin-gem.md +535 -0
  33. data/docs/upload-system.md +225 -0
  34. data/gemfiles/rails_7.x_active_admin_4.x.gemfile +11 -0
  35. data/gemfiles/rails_7.x_active_admin_4.x.gemfile.lock +371 -0
  36. data/gemfiles/rails_8.x_active_admin_4.x.gemfile +11 -0
  37. data/gemfiles/rails_8.x_active_admin_4.x.gemfile.lock +366 -0
  38. data/lefthook.yml +17 -0
  39. data/lib/activeadmin/inputs/filters/searchable_select_input.rb +13 -0
  40. data/lib/activeadmin/inputs/searchable_select_input.rb +11 -0
  41. data/lib/activeadmin/searchable_select/engine.rb +17 -0
  42. data/lib/activeadmin/searchable_select/option_collection.rb +119 -0
  43. data/lib/activeadmin/searchable_select/resource_dsl_extension.rb +56 -0
  44. data/lib/activeadmin/searchable_select/resource_extension.rb +10 -0
  45. data/lib/activeadmin/searchable_select/select_input_extension.rb +159 -0
  46. data/lib/activeadmin/searchable_select/version.rb +5 -0
  47. data/lib/activeadmin/searchable_select.rb +20 -0
  48. data/lib/activeadmin-searchable_select.rb +4 -0
  49. data/lib/generators/active_admin/searchable_select/install/install_generator.rb +217 -0
  50. data/package-lock.json +18 -0
  51. data/package.json +45 -0
  52. data/rs-activeadmin-searchable_select.gemspec +38 -0
  53. data/sonar-project.properties +25 -0
  54. data/spec/features/ajax_params_spec.rb +31 -0
  55. data/spec/features/end_to_end_spec.rb +227 -0
  56. data/spec/features/filter_input_spec.rb +137 -0
  57. data/spec/features/form_input_spec.rb +122 -0
  58. data/spec/features/inline_ajax_setting_spec.rb +26 -0
  59. data/spec/features/input_errors_spec.rb +76 -0
  60. data/spec/features/input_html_options_spec.rb +30 -0
  61. data/spec/features/options_dsl_spec.rb +220 -0
  62. data/spec/features/production_build_spec.rb +108 -0
  63. data/spec/internal/app/admin/categories.rb +26 -0
  64. data/spec/internal/app/admin/dashboard.rb +29 -0
  65. data/spec/internal/app/admin/option_types.rb +19 -0
  66. data/spec/internal/app/admin/option_values.rb +30 -0
  67. data/spec/internal/app/admin/posts.rb +27 -0
  68. data/spec/internal/app/admin/products.rb +22 -0
  69. data/spec/internal/app/admin/rgb_colors.rb +25 -0
  70. data/spec/internal/app/admin/tag_names.rb +21 -0
  71. data/spec/internal/app/admin/test_ajax_params_category.rb +10 -0
  72. data/spec/internal/app/admin/test_ajax_params_post.rb +20 -0
  73. data/spec/internal/app/admin/test_form_post_class.rb +7 -0
  74. data/spec/internal/app/admin/test_form_post_custom.rb +11 -0
  75. data/spec/internal/app/admin/test_form_post_resource.rb +11 -0
  76. data/spec/internal/app/admin/test_form_post_resource_custom.rb +12 -0
  77. data/spec/internal/app/admin/test_inline_ajax_post.rb +9 -0
  78. data/spec/internal/app/admin/test_input_html_post.rb +11 -0
  79. data/spec/internal/app/admin/test_posts_display_text.rb +9 -0
  80. data/spec/internal/app/admin/test_posts_filter.rb +9 -0
  81. data/spec/internal/app/admin/test_posts_named.rb +9 -0
  82. data/spec/internal/app/admin/test_posts_pagination.rb +9 -0
  83. data/spec/internal/app/admin/test_posts_payload_lambda.rb +11 -0
  84. data/spec/internal/app/admin/test_posts_payload_proc.rb +9 -0
  85. data/spec/internal/app/admin/test_posts_scope_lambda.rb +8 -0
  86. data/spec/internal/app/admin/test_posts_scope_params.rb +8 -0
  87. data/spec/internal/app/admin/test_posts_scope_user.rb +8 -0
  88. data/spec/internal/app/admin/test_posts_text_attr.rb +5 -0
  89. data/spec/internal/app/admin/users.rb +23 -0
  90. data/spec/internal/app/admin/variants.rb +31 -0
  91. data/spec/internal/app/assets/config/manifest.js +1 -0
  92. data/spec/internal/app/assets/javascripts/active_admin.js +2 -0
  93. data/spec/internal/app/assets/javascripts/searchable_select_test.js +2 -0
  94. data/spec/internal/app/controllers/application_controller.rb +5 -0
  95. data/spec/internal/app/css/active_admin_source.css +81 -0
  96. data/spec/internal/app/js/active_admin.js +17 -0
  97. data/spec/internal/app/models/article.rb +12 -0
  98. data/spec/internal/app/models/category.rb +12 -0
  99. data/spec/internal/app/models/internal/tag_name.rb +14 -0
  100. data/spec/internal/app/models/internal_tag_name.rb +11 -0
  101. data/spec/internal/app/models/option_type.rb +12 -0
  102. data/spec/internal/app/models/option_value.rb +4 -0
  103. data/spec/internal/app/models/post.rb +15 -0
  104. data/spec/internal/app/models/product.rb +12 -0
  105. data/spec/internal/app/models/rgb_color.rb +16 -0
  106. data/spec/internal/app/models/user.rb +12 -0
  107. data/spec/internal/app/models/variant.rb +12 -0
  108. data/spec/internal/build_activeadmin_css.js +115 -0
  109. data/spec/internal/config/database.yml +7 -0
  110. data/spec/internal/config/environment.rb +48 -0
  111. data/spec/internal/config/initializers/active_admin.rb +53 -0
  112. data/spec/internal/config/initializers/assets.rb +9 -0
  113. data/spec/internal/config/initializers/searchable_select.rb +6 -0
  114. data/spec/internal/config/routes.rb +4 -0
  115. data/spec/internal/config.ru +4 -0
  116. data/spec/internal/db/schema.rb +63 -0
  117. data/spec/internal/db/seeds.rb +88 -0
  118. data/spec/internal/esbuild.config.js +30 -0
  119. data/spec/internal/inject-jquery.js +4 -0
  120. data/spec/internal/log/.gitignore +1 -0
  121. data/spec/internal/package/LICENSE.txt +25 -0
  122. data/spec/internal/package/README.md +439 -0
  123. data/spec/internal/package/package.json +45 -0
  124. data/spec/internal/package/src/index.js +1 -0
  125. data/spec/internal/package/src/searchable_select/init.js +1 -0
  126. data/spec/internal/package/src/searchable_select.css +1 -0
  127. data/spec/internal/package/src/searchable_select.scss +1 -0
  128. data/spec/internal/package-lock.json +1385 -0
  129. data/spec/internal/package.json +26 -0
  130. data/spec/internal/public/favicon.ico +0 -0
  131. data/spec/internal/spec/internal/app/css/active_admin_source.css +38 -0
  132. data/spec/internal/spec/internal/log/test.log +0 -0
  133. data/spec/internal/tailwind-active_admin.config.js +53 -0
  134. data/spec/rails_helper.rb +86 -0
  135. data/spec/spec_helper.rb +137 -0
  136. data/spec/support/active_admin_helpers.rb +17 -0
  137. data/spec/support/capybara.rb +8 -0
  138. data/spec/support/models.rb +88 -0
  139. data/spec/support/pluck_polyfill.rb +12 -0
  140. data/spec/support/reset_settings.rb +5 -0
  141. data/src/index.js +77 -0
  142. data/src/searchable_select/init.js +58 -0
  143. data/src/searchable_select.css +5 -0
  144. data/src/searchable_select.css.map +1 -0
  145. metadata +405 -0
@@ -0,0 +1,227 @@
1
+ require 'rails_helper'
2
+
3
+ require 'support/models'
4
+ require 'support/capybara'
5
+
6
+ RSpec.describe 'end to end', type: :feature, js: true do
7
+ context 'class name without namespaces' do
8
+ # Using static admin/posts.rb and admin/categories.rb files
9
+
10
+ describe 'index page with searchable select filter' do
11
+ it 'loads filter input options' do
12
+ Category.create(name: 'Music')
13
+ Category.create(name: 'Travel')
14
+
15
+ visit '/admin/posts'
16
+
17
+ expand_select_box
18
+ wait_for_ajax
19
+
20
+ expect(select_box_items).to eq(%w[Music Travel])
21
+ end
22
+
23
+ it 'allows filtering options by term' do
24
+ Category.create(name: 'Music')
25
+ Category.create(name: 'Travel')
26
+
27
+ visit '/admin/posts'
28
+
29
+ expand_select_box
30
+ enter_search_term('T')
31
+ wait_for_ajax
32
+
33
+ expect(select_box_items).to eq(%w[Travel])
34
+ end
35
+
36
+ it 'loads more items when scrolling down' do
37
+ 15.times { |i| Category.create(name: "Category #{i}") }
38
+ visit '/admin/posts'
39
+
40
+ expand_select_box
41
+ wait_for_ajax
42
+ scroll_select_box_list
43
+ wait_for_ajax
44
+
45
+ expect(select_box_items.size).to eq(15)
46
+ end
47
+ end
48
+ end
49
+
50
+ context 'class name with namespace' do
51
+ # Using static admin/colors.rb and admin/tag_names.rb files
52
+
53
+ describe 'index page with searchable select filter' do
54
+ it 'loads filter input options' do
55
+ RgbColor.create(code: '#eac112', description: 'Orange')
56
+ RgbColor.create(code: '#19bf25', description: 'Green')
57
+
58
+ visit '/admin/tag_names'
59
+
60
+ expand_select_box
61
+ wait_for_ajax
62
+
63
+ expect(select_box_items).to eq(['#eac112 - Orange', '#19bf25 - Green'])
64
+ end
65
+ end
66
+ end
67
+
68
+ context 'class with nested belongs_to association',
69
+ skip: 'Nested routes issue with belongs_to in test environment' do
70
+ # Using static admin files: option_types.rb, products.rb, option_values.rb, variants.rb
71
+
72
+ describe 'new page with searchable select filter' do
73
+ it 'loads filter input options' do
74
+ option_type = OptionType.create(name: 'Color')
75
+ ot = OptionType.create(name: 'Size')
76
+ OptionValue.create(value: 'Black', option_type: option_type)
77
+ OptionValue.create(value: 'Orange', option_type: option_type)
78
+ OptionValue.create(value: 'M', option_type: ot)
79
+ product = Product.create(name: 'Cap', option_type: option_type)
80
+
81
+ visit "/admin/products/#{product.id}/variants/new"
82
+
83
+ # Debug: Check page loaded
84
+ puts "Page title: #{page.title}"
85
+ puts "Current path: #{page.current_path}"
86
+
87
+ # Check for error messages
88
+ error_on_page = page.has_css?('h1', text: 'Action Controller: Exception caught')
89
+ if error_on_page || page.title.include?('Exception')
90
+ puts 'ERROR ON PAGE!'
91
+ puts "Error heading: #{begin
92
+ find('h1').text
93
+ rescue StandardError
94
+ 'Could not find h1'
95
+ end}"
96
+ puts "Error message: #{begin
97
+ find('pre').text
98
+ rescue StandardError
99
+ 'Could not find pre'
100
+ end}"
101
+ # Try to get the full body for debugging
102
+ puts page.body[0..2000] if page.body.length < 2001
103
+ end
104
+
105
+ # Save screenshot for debugging (commented out for CI)
106
+ # begin
107
+ # page.save_screenshot('/tmp/variant_new_page.png')
108
+ # rescue StandardError
109
+ # nil
110
+ # end
111
+
112
+ # Check for any select elements
113
+ puts "Has any select elements: #{page.has_css?('select', visible: :all)}"
114
+ puts "Has form elements: #{page.has_css?('form')}"
115
+
116
+ # Debug: Check if searchable select input exists
117
+ searchable_select_css = '.searchable-select-input'
118
+ expect(page).to have_css(searchable_select_css, wait: 5)
119
+
120
+ # Debug: Check what Select2-related elements are present
121
+ puts "Page HTML includes .select2-container: #{page.has_css?('.select2-container',
122
+ wait: 2)}"
123
+ puts "Page HTML includes .select2: #{page.has_css?('.select2', wait: 2)}"
124
+ has_searchable = page.has_css?(searchable_select_css)
125
+ puts "Page HTML includes searchable-select-input: #{has_searchable}"
126
+
127
+ # Debug: Print the actual HTML around the select
128
+ if page.has_css?(searchable_select_css)
129
+ select_element = find(searchable_select_css, visible: :all)
130
+ parent_html = begin
131
+ select_element.find(:xpath,
132
+ '..')['outerHTML'][0..500]
133
+ rescue StandardError
134
+ 'Could not get parent HTML'
135
+ end
136
+ puts "Parent element HTML: #{parent_html}..."
137
+ end
138
+
139
+ expand_select_box
140
+ wait_for_ajax
141
+
142
+ expect(select_box_items).to eq(%w[Black Orange])
143
+ end
144
+
145
+ it 'allows filtering options by term' do
146
+ option_type = OptionType.create(name: 'Color')
147
+ ot = OptionType.create(name: 'Size')
148
+ OptionValue.create(value: 'Black', option_type: option_type)
149
+ OptionValue.create(value: 'Orange', option_type: option_type)
150
+ OptionValue.create(value: 'M', option_type: ot)
151
+ product = Product.create(name: 'Cap', option_type: option_type)
152
+
153
+ visit "/admin/products/#{product.id}/variants/new"
154
+
155
+ expand_select_box
156
+ enter_search_term('O')
157
+ wait_for_ajax
158
+
159
+ expect(select_box_items).to eq(%w[Orange])
160
+ end
161
+
162
+ it 'loads more items when scrolling down' do
163
+ option_type = OptionType.create(name: 'Color')
164
+ 15.times { |i| OptionValue.create(value: "Black #{i}", option_type: option_type) }
165
+ product = Product.create(name: 'Cap', option_type: option_type)
166
+
167
+ visit "/admin/products/#{product.id}/variants/new"
168
+
169
+ expand_select_box
170
+ wait_for_ajax
171
+ scroll_select_box_list
172
+ wait_for_ajax
173
+
174
+ expect(select_box_items.size).to eq(15)
175
+ end
176
+ end
177
+
178
+ describe 'edit page with searchable select filter' do
179
+ it 'preselects item' do
180
+ option_type = OptionType.create(name: 'Color')
181
+ ot = OptionType.create(name: 'Size')
182
+ option_value = OptionValue.create(value: 'Black', option_type: option_type)
183
+ OptionValue.create(value: 'Orange', option_type: option_type)
184
+ OptionValue.create(value: 'M', option_type: ot)
185
+ product = Product.create(name: 'Cap', option_type: option_type)
186
+ variant = Variant.create(product: product, option_value: option_value)
187
+
188
+ visit "/admin/products/#{product.id}/variants/#{variant.id}/edit"
189
+
190
+ expect(select_box_selected_item_text).to eq('Black')
191
+ end
192
+ end
193
+ end
194
+
195
+ def expand_select_box
196
+ # Just click the first select2 container on the page
197
+ # This works for both filter forms and regular forms
198
+ find('.select2-container', match: :first).click
199
+ end
200
+
201
+ def enter_search_term(term)
202
+ find('.select2-dropdown input').send_keys(term)
203
+ end
204
+
205
+ def scroll_select_box_list
206
+ page.execute_script '$(".select2-container ul").scrollTop(1000)'
207
+ end
208
+
209
+ def select_box_items
210
+ all('.select2-dropdown li').map(&:text)
211
+ end
212
+
213
+ def select_box_selected_item_text
214
+ find('.select2-selection').text
215
+ end
216
+
217
+ def wait_for_ajax
218
+ Timeout.timeout(Capybara.default_max_wait_time) do
219
+ sleep 0.1
220
+ loop until finished_all_ajax_requests?
221
+ end
222
+ end
223
+
224
+ def finished_all_ajax_requests?
225
+ page.evaluate_script('jQuery.active').zero?
226
+ end
227
+ end
@@ -0,0 +1,137 @@
1
+ require 'rails_helper'
2
+
3
+ require 'support/models'
4
+ require 'support/capybara'
5
+ require 'support/active_admin_helpers'
6
+
7
+ RSpec.describe 'filter input', type: :request do
8
+ # Test the existing static configuration (category and user filters with ajax: true)
9
+ describe 'existing ajax-enabled searchable select filters' do
10
+ it 'renders select input with searchable-select-input css class' do
11
+ get '/admin/posts'
12
+
13
+ expect(response.body).to have_selector('select.searchable-select-input')
14
+ end
15
+
16
+ it 'sets data-ajax-url attribute for ajax-enabled filters' do
17
+ get '/admin/posts'
18
+
19
+ expect(response.body).to have_selector('.searchable-select-input[data-ajax-url]')
20
+ end
21
+
22
+ it 'renders selected option for current value' do
23
+ category = Category.create!(name: 'Travel')
24
+
25
+ get "/admin/posts?q[category_id_eq]=#{category.id}"
26
+
27
+ expect(response.body).to have_selector('.searchable-select-input option[selected]',
28
+ text: 'Travel')
29
+ end
30
+
31
+ it 'does not render options statically for ajax-enabled filters' do
32
+ Category.create!(name: 'Travel')
33
+
34
+ get '/admin/posts'
35
+
36
+ # Ajax-enabled filters should not render options inline
37
+ expect(response.body).not_to have_selector('.searchable-select-input option', text: 'Travel')
38
+ end
39
+ end
40
+
41
+ # Test specific behaviors of the searchable select functionality
42
+ describe 'ajax endpoint behavior' do
43
+ it 'generates correct ajax URLs for category filter' do
44
+ get '/admin/posts'
45
+
46
+ expect(response.body).to include('data-ajax-url="/admin/categories/all_options?"')
47
+ end
48
+
49
+ it 'generates correct ajax URLs for user filter' do
50
+ get '/admin/posts'
51
+
52
+ expect(response.body).to include('data-ajax-url="/admin/users/all_options?"')
53
+ end
54
+
55
+ it 'includes empty "Any" option by default' do
56
+ get '/admin/posts'
57
+
58
+ expect(response.body).to have_selector('.searchable-select-input option[value=""]',
59
+ text: 'Any')
60
+ end
61
+ end
62
+
63
+ describe 'selected option rendering' do
64
+ it 'renders selected category option when filtering' do
65
+ category = Category.create!(name: 'Travel')
66
+
67
+ get "/admin/posts?q[category_id_eq]=#{category.id}"
68
+
69
+ expect(response.body).to have_selector('.searchable-select-input option[selected]',
70
+ text: 'Travel')
71
+ end
72
+
73
+ it 'renders selected user option when filtering' do
74
+ user = User.create!(name: 'John Doe')
75
+
76
+ get "/admin/posts?q[user_id_eq]=#{user.id}"
77
+
78
+ expect(response.body).to have_selector('.searchable-select-input option[selected]',
79
+ text: 'John Doe')
80
+ end
81
+ end
82
+
83
+ describe 'edge cases and error handling' do
84
+ it 'handles non-existent category ID gracefully' do
85
+ get '/admin/posts?q[category_id_eq]=99999'
86
+
87
+ # Should not render selected option for non-existent ID
88
+ expect(response.body).not_to have_selector('.searchable-select-input option[selected]')
89
+ end
90
+
91
+ it 'handles multiple filter values correctly' do
92
+ category1 = Category.create!(name: 'Travel')
93
+ category2 = Category.create!(name: 'Leisure')
94
+
95
+ get "/admin/posts?q[category_id_in][]=#{category1.id}&q[category_id_in][]=#{category2.id}"
96
+
97
+ # For 'in' queries, it should handle multiple values
98
+ # Note: The exact behavior depends on how the input handles 'in' vs 'eq' predicates
99
+ expect(response.status).to eq(200)
100
+ end
101
+ end
102
+
103
+ describe 'custom searchable_select_options configurations' do
104
+ it 'uses the configured text_attribute for display' do
105
+ category = Category.create!(name: 'Travel')
106
+
107
+ get "/admin/posts?q[category_id_eq]=#{category.id}"
108
+
109
+ # Should display using the name attribute as configured in the static admin
110
+ expect(response.body).to have_selector('.searchable-select-input option[selected]',
111
+ text: 'Travel')
112
+ end
113
+
114
+ it 'uses the correct scope configuration' do
115
+ # Create a category but test that the scope is working
116
+ Category.create!(name: 'Travel')
117
+
118
+ get '/admin/posts'
119
+
120
+ # Should not render options statically because it's ajax-enabled
121
+ expect(response.body).not_to have_selector('.searchable-select-input option', text: 'Travel')
122
+ # But should have the ajax URL
123
+ url_matcher = '/admin/categories/all_options'
124
+ expect(response.body).to have_selector(
125
+ ".searchable-select-input[data-ajax-url*='#{url_matcher}']"
126
+ )
127
+ end
128
+
129
+ it 'supports custom collection name through admin configuration' do
130
+ # Test that the custom searchable_select_options configuration works
131
+ get '/admin/posts'
132
+
133
+ # Verify that the custom endpoint is accessible (tests the custom collection)
134
+ expect { get '/admin/categories/custom_options' }.not_to raise_error
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,122 @@
1
+ require 'rails_helper'
2
+
3
+ require 'support/models'
4
+ require 'support/capybara'
5
+
6
+ ADMIN_POSTS_PATH = '/admin/posts'.freeze
7
+
8
+ RSpec.describe 'form input', type: :request do
9
+ # The static admin/posts.rb file already configures searchable_select with ajax: true
10
+ # So we'll test with that configuration
11
+ describe 'with ajax option (from static admin file)' do
12
+ before(:each) do
13
+ ActiveAdmin::SearchableSelect.inline_ajax_options = true
14
+ end
15
+
16
+ after(:each) do
17
+ ActiveAdmin::SearchableSelect.inline_ajax_options = false
18
+ end
19
+ it 'renders select input with searchable-select-input css class' do
20
+ get "#{ADMIN_POSTS_PATH}/new"
21
+ expect(response.body).to have_selector('select.searchable-select-input')
22
+ end
23
+
24
+ it 'renders options statically' do
25
+ Category.create!(name: 'Travel')
26
+ Category.create!(name: 'Music')
27
+
28
+ get "#{ADMIN_POSTS_PATH}/new"
29
+
30
+ expect(response.body).to have_selector('.searchable-select-input option', text: 'Travel')
31
+ expect(response.body).to have_selector('.searchable-select-input option', text: 'Music')
32
+ end
33
+
34
+ it 'does not set data-ajax-url attribute' do
35
+ get "#{ADMIN_POSTS_PATH}/new"
36
+
37
+ expect(response.body).not_to have_selector('.searchable-select-input[data-ajax-url]')
38
+ end
39
+ end
40
+
41
+ shared_examples 'renders ajax based searchable select input' do
42
+ it 'renders select input with searchable-select-input css class' do
43
+ get "#{ADMIN_POSTS_PATH}/new"
44
+
45
+ expect(response.body).to have_selector('select.searchable-select-input')
46
+ end
47
+
48
+ it 'does not render options statically' do
49
+ Category.create!(name: 'Travel')
50
+
51
+ get "#{ADMIN_POSTS_PATH}/new"
52
+
53
+ expect(response.body).not_to have_selector('.searchable-select-input option', text: 'Travel')
54
+ end
55
+
56
+ it 'sets data-ajax-url attribute' do
57
+ get "#{ADMIN_POSTS_PATH}/new"
58
+
59
+ expect(response.body).to have_selector('.searchable-select-input[data-ajax-url]')
60
+ end
61
+
62
+ it 'renders selected option for current value' do
63
+ category = Category.create!(name: 'Travel')
64
+ post = Post.create!(title: 'A post', category: category)
65
+
66
+ get "#{ADMIN_POSTS_PATH}/#{post.id}/edit"
67
+
68
+ expect(response.body).to have_selector('.searchable-select-input option[selected]',
69
+ text: 'Travel')
70
+ end
71
+ end
72
+
73
+ describe 'with ajax option set to true' do
74
+ # Using static admin/posts.rb which already has ajax: true configured
75
+ include_examples 'renders ajax based searchable select input'
76
+ end
77
+
78
+ describe 'with options collection name passed in ajax option' do
79
+ # Using static TestFormPostCustom admin and categories.rb which has 'custom' collection
80
+ include_examples 'renders ajax based searchable select input' do
81
+ let(:admin_path_prefix) { 'test_form_post_customs' }
82
+
83
+ def get(path)
84
+ path = path.sub(ADMIN_POSTS_PATH, '/admin/test_form_post_customs')
85
+ super
86
+ end
87
+ end
88
+ end
89
+
90
+ describe 'with options resource passed in ajax option' do
91
+ # Using static TestFormPostResource admin
92
+ include_examples 'renders ajax based searchable select input' do
93
+ let(:admin_path_prefix) { 'test_form_post_resources' }
94
+
95
+ def get(path)
96
+ path = path.sub(ADMIN_POSTS_PATH, '/admin/test_form_post_resources')
97
+ super
98
+ end
99
+ end
100
+ end
101
+
102
+ describe 'with options resource and collection name passed in ajax option' do
103
+ # Using static TestFormPostResourceCustom admin and categories.rb which has 'custom' collection
104
+ include_examples 'renders ajax based searchable select input' do
105
+ let(:admin_path_prefix) { 'test_form_post_resource_customs' }
106
+
107
+ def get(path)
108
+ path = path.sub(ADMIN_POSTS_PATH, '/admin/test_form_post_resource_customs')
109
+ super
110
+ end
111
+ end
112
+ end
113
+
114
+ describe 'with custom class attribute' do
115
+ # Using static TestFormPostClass admin
116
+ it 'adds searchable-select-input css class' do
117
+ get '/admin/test_form_post_classes/new'
118
+
119
+ expect(response.body).to have_selector('select.custom.searchable-select-input')
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,26 @@
1
+ require 'rails_helper'
2
+
3
+ require 'support/models'
4
+ require 'support/capybara'
5
+
6
+ RSpec.describe 'inline_ajax_options setting', type: :request do
7
+ describe 'when ajax option set to true ' do
8
+ # Using static TestInlineAjaxPost and Category admins
9
+
10
+ it 'renders all options statically' do
11
+ Category.create!(name: 'Travel')
12
+ Category.create!(name: 'Music')
13
+ Category.create!(name: 'Cooking')
14
+
15
+ ActiveAdmin::SearchableSelect.inline_ajax_options = true
16
+ get '/admin/test_inline_ajax_posts/new'
17
+
18
+ expect(response.body).to have_selector('.searchable-select-input option',
19
+ text: 'Travel')
20
+ expect(response.body).to have_selector('.searchable-select-input option',
21
+ text: 'Music')
22
+ expect(response.body).to have_selector('.searchable-select-input option',
23
+ text: 'Cooking')
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,76 @@
1
+ require 'rails_helper'
2
+
3
+ require 'support/models'
4
+ require 'support/capybara'
5
+ require 'support/active_admin_helpers'
6
+
7
+ RSpec.describe 'searchable select', type: :feature do
8
+ it 'shows helpful error message if ajax resource cannot be auto detected' do
9
+ ActiveAdminHelpers.setup do
10
+ # Use a unique admin resource name to avoid conflicts
11
+ ActiveAdmin.register(Post, as: 'TestErrorPost1') do
12
+ menu false # Hide from menu to avoid conflicts
13
+
14
+ # Try to use a filter with incorrect naming that can't be auto-detected
15
+ filter(:custom_category_id_eq,
16
+ as: :searchable_select,
17
+ ajax: true)
18
+ end
19
+ end
20
+
21
+ visit '/admin/test_error_post1s'
22
+ expect(page).to have_content('Cannot auto detect resource')
23
+ end
24
+
25
+ it 'shows helpful error message if named option collection does not exist' do
26
+ ActiveAdminHelpers.setup do
27
+ # Register Post that tries to use non-existent 'nonexistent_collection' collection
28
+ ActiveAdmin.register(Post, as: 'TestErrorPost2') do
29
+ menu false # Hide from menu to avoid conflicts
30
+
31
+ filter(:category,
32
+ as: :searchable_select,
33
+ ajax: {
34
+ resource: Category,
35
+ collection_name: 'nonexistent_collection' # This collection doesn't exist
36
+ })
37
+ end
38
+ end
39
+
40
+ visit '/admin/test_error_post2s'
41
+ expect(page).to have_content(
42
+ "No option collection named 'nonexistent_collection' defined in 'Category' admin."
43
+ )
44
+ end
45
+
46
+ it 'shows helpful error message if ajax resource does not have an admin' do
47
+ # Create a model that doesn't have an admin page
48
+ unless defined?(NonAdminModel)
49
+ Object.const_set('NonAdminModel', Class.new(ActiveRecord::Base))
50
+ NonAdminModel.table_name = 'categories' # Use existing table
51
+ NonAdminModel.class_eval do
52
+ def self.ransackable_attributes(_auth_object = nil)
53
+ ['name']
54
+ end
55
+ end
56
+ end
57
+
58
+ ActiveAdminHelpers.setup do
59
+ # Register Post with a filter that references a model without an admin
60
+ ActiveAdmin.register(Post, as: 'TestErrorPost3') do
61
+ menu false # Hide from menu to avoid conflicts
62
+
63
+ filter(:category,
64
+ as: :searchable_select,
65
+ ajax: {
66
+ resource: NonAdminModel
67
+ })
68
+ end
69
+ end
70
+
71
+ visit '/admin/test_error_post3s'
72
+ expect(page).to have_content("No admin found for 'NonAdminModel'")
73
+ ensure
74
+ Object.send(:remove_const, 'NonAdminModel') if defined?(NonAdminModel)
75
+ end
76
+ end
@@ -0,0 +1,30 @@
1
+ require 'rails_helper'
2
+
3
+ require 'support/capybara'
4
+
5
+ RSpec.describe 'input_html options issue', type: :request do
6
+ it 'should pass input_html class option to searchable select element' do
7
+ # Using static TestInputHtmlPost and Category admins
8
+ get '/admin/test_input_html_posts/new'
9
+
10
+ # Parse the HTML response
11
+ doc = Nokogiri::HTML(response.body)
12
+
13
+ # Find the category select element by its ID
14
+ category_select = doc.css('#post_category_id').first
15
+
16
+ # Check if the element has the expected classes
17
+ unless category_select
18
+ raise "Category select element with ID 'post_category_id' not found in response"
19
+ end
20
+
21
+ classes = category_select['class'].to_s.split
22
+
23
+ # The actual assertions
24
+ expect(category_select).not_to be_nil, 'Category select element should exist'
25
+ expect(classes).to include('searchable-select-input'),
26
+ "Select element should have 'searchable-select-input' class"
27
+ expect(classes).to include('custom-class'),
28
+ "Select element should have 'custom-class' from input_html option"
29
+ end
30
+ end