blacklight 7.25.1 → 7.25.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f5591ce042284ddde98c77e6027f09782e681195f31fc919870d3c5efd1dd029
4
- data.tar.gz: ffaf195a0ce3b1ea7a5b2e5958c4cea4daaab81bc34b3075452f44858ff084d9
3
+ metadata.gz: 8c6c1570bf376880fa59c30420730762bf4694b4681856c9b367a6ecd2bb4bb7
4
+ data.tar.gz: 459f8098798a27f3474f2036143adc65d80955f16136cda54a2a244f86223003
5
5
  SHA512:
6
- metadata.gz: 5ae17a8834d21cc7e0229846a7ec37bdeb8ff2f451abf06b9d399580dcffe0d924d80840789d2db9152c440266fb4a8fced5c9fc7f9ef397873af8f5d84a1740
7
- data.tar.gz: 8c4a34218c70c030bc5622ac1e32d84ef5967f7ae3f84fc7b044ff2004922d3cbb78e74ff96ea7d4e0f4a3aedf95f57393bc3815c36c5e27958531d3f0b58ca4
6
+ metadata.gz: 2fee85026a8eff0f283ea7f4d8936248872cbe0e3e87793ecfc13d479f0233723db03fbc01d418f5e242899a909c115b104fd6b9a680a4d21c5132cfabf2d030
7
+ data.tar.gz: e0a50dbea2c14447fb1f43b77e1f36f0b93974bfba525af0a9f677252fa19f064a4efd37ef913cd917e4d0d063df3c1600523e8d98be94c15ad083a75c4d1944
@@ -43,7 +43,7 @@ jobs:
43
43
  - name: Run tests
44
44
  run: bundle exec rake ci
45
45
  env:
46
- ENGINE_CART_RAILS_OPTIONS: '--skip-git --skip-listen --skip-spring --skip-keeps --skip-action-cable --skip-coffee --skip-test'
46
+ ENGINE_CART_RAILS_OPTIONS: '-a propshaft --skip-git --skip-listen --skip-spring --skip-keeps --skip-action-cable --skip-coffee --skip-test'
47
47
  test_bootstrap5:
48
48
  runs-on: ubuntu-latest
49
49
  strategy:
data/.rubocop.yml CHANGED
@@ -39,6 +39,7 @@ Metrics/ClassLength:
39
39
  - "lib/blacklight/configuration.rb"
40
40
  - "lib/blacklight/search_builder.rb"
41
41
  - "lib/blacklight/search_state.rb"
42
+ - "lib/blacklight/search_state/filter_field.rb"
42
43
 
43
44
  Layout/LineLength:
44
45
  Max: 200
data/VERSION CHANGED
@@ -1 +1 @@
1
- 7.25.1
1
+ 7.25.2
@@ -10,7 +10,7 @@ module Blacklight
10
10
  # rubocop:disable Metrics/ParameterLists
11
11
  def initialize(
12
12
  url:, params:,
13
- advanced_search_url: nil,
13
+ advanced_search_url: nil, presenter: nil,
14
14
  classes: ['search-query-form'], prefix: nil,
15
15
  method: 'GET', q: nil, query_param: :q,
16
16
  search_field: nil, search_fields: nil, autocomplete_path: nil,
@@ -29,6 +29,10 @@ module Blacklight
29
29
  @autofocus = autofocus
30
30
  @search_fields = search_fields
31
31
  @i18n = i18n
32
+ return if presenter.nil?
33
+
34
+ Deprecation.warn(self, 'SearchBarComponent no longer uses a SearchBarPresenter, the presenter: param will be removed in 8.0. ' \
35
+ 'Set advanced_search.enabled, autocomplete_enabled, and enable_search_bar_autofocus on BlacklightConfiguration')
32
36
  end
33
37
  # rubocop:enable Metrics/ParameterLists
34
38
 
@@ -75,12 +75,15 @@ module Blacklight::Bookmarks
75
75
 
76
76
  current_or_guest_user.save! unless current_or_guest_user.persisted?
77
77
 
78
- success = @bookmarks.all? do |bookmark|
79
- current_or_guest_user.bookmarks.where(bookmark).exists? || current_or_guest_user.bookmarks.create(bookmark)
78
+ bookmarks_to_add = @bookmarks.reject { |bookmark| current_or_guest_user.bookmarks.where(bookmark).exists? }
79
+ success = ActiveRecord::Base.transaction do
80
+ current_or_guest_user.bookmarks.create!(bookmarks_to_add)
81
+ rescue ActiveRecord::RecordInvalid
82
+ false
80
83
  end
81
84
 
82
85
  if request.xhr?
83
- success ? render(json: { bookmarks: { count: current_or_guest_user.bookmarks.count } }) : render(plain: "", status: "500")
86
+ success ? render(json: { bookmarks: { count: current_or_guest_user.bookmarks.count } }) : render(json: current_or_guest_user.errors.full_messages, status: "500")
84
87
  else
85
88
  if @bookmarks.any? && success
86
89
  flash[:notice] = I18n.t('blacklight.bookmarks.add.success', count: @bookmarks.length)
@@ -79,7 +79,18 @@ module Blacklight::BlacklightHelperBehavior
79
79
  # Render the search navbar
80
80
  # @return [String]
81
81
  def render_search_bar
82
- search_bar_presenter.render
82
+ if search_bar_presenter_class == Blacklight::SearchBarPresenter && partial_from_blacklight?(Blacklight::SearchBarPresenter.partial)
83
+ component_class = blacklight_config&.view_config(document_index_view_type)&.search_bar_component || Blacklight::SearchBarComponent
84
+ component_class.new(
85
+ url: search_action_url,
86
+ advanced_search_url: search_action_url(action: 'advanced_search'),
87
+ params: search_state.params_for_search.except(:qt),
88
+ search_fields: Deprecation.silence(Blacklight::ConfigurationHelperBehavior) { search_fields },
89
+ autocomplete_path: search_action_path(action: :suggest)
90
+ )
91
+ else
92
+ search_bar_presenter.render
93
+ end
83
94
  end
84
95
  deprecation_deprecate render_search_bar: "Call `render Blacklight::SearchBarComponent.new' instead"
85
96
 
@@ -181,6 +181,7 @@ module Blacklight::CatalogHelperBehavior
181
181
  def render_document_main_content_partial(_document = @document)
182
182
  render partial: 'show_main_content'
183
183
  end
184
+ deprecation_deprecate render_document_main_content_partial: "Use \"render 'show_main_content'\" instead"
184
185
 
185
186
  ##
186
187
  # Should we display the sort and per page widget?
@@ -55,7 +55,10 @@ module Blacklight
55
55
  def file_source
56
56
  raise Blacklight::Exceptions::IconNotFound, "Could not find #{path}" if file.blank?
57
57
 
58
- file.source.force_encoding('UTF-8')
58
+ # Handle both Sprockets::Asset and Propshaft::Asset
59
+ data = file.respond_to?(:source) ? file.source : file.path.read
60
+
61
+ data.force_encoding('UTF-8')
59
62
  end
60
63
 
61
64
  def ng_xml
@@ -68,7 +71,10 @@ module Blacklight
68
71
  [icon_name, additional_options[:label_context]].compact.join('_')
69
72
  end
70
73
 
74
+ # @return [Sprockets::Asset,Propshaft::Asset]
71
75
  def file
76
+ return Rails.application.assets.load_path.find(path) if defined? Propshaft
77
+
72
78
  # Rails.application.assets is `nil` in production mode (where compile assets is enabled).
73
79
  # This workaround is based off of this comment: https://github.com/fphilipe/premailer-rails/issues/145#issuecomment-225992564
74
80
  (Rails.application.assets || ::Sprockets::Railtie.build_environment(Rails.application)).find_asset(path)
@@ -17,6 +17,10 @@ module Blacklight
17
17
  search_builder_class.new(self)
18
18
  end
19
19
 
20
+ def search_state_class
21
+ @search_state.class
22
+ end
23
+
20
24
  # a solr query method
21
25
  # @yield [search_builder] optional block yields configured SearchBuilder, caller can modify or create new SearchBuilder to be used. Block should return SearchBuilder to be used.
22
26
  # @return [Blacklight::Solr::Response] the solr response object
@@ -1 +1,2 @@
1
+ <% Deprecation.warn(self, 'The partial _previous_next_doc.html.erb will be removed in 8.0. Render Blacklight::SearchContextComponent instead.') %>
1
2
  <%= render(Blacklight::SearchContextComponent.new(search_context: @search_context, search_session: search_session)) %>
@@ -1,4 +1,4 @@
1
- <%= warn "#{__file__} is a deprecated partial." %>
1
+ <%= warn "#{__FILE__} is a deprecated partial." %>
2
2
  <%= render((blacklight_config&.view_config(document_index_view_type)&.search_bar_component || Blacklight::SearchBarComponent).new(
3
3
  url: search_action_url,
4
4
  advanced_search_url: search_action_url(action: 'advanced_search'),
@@ -1,4 +1,4 @@
1
- <%= render 'previous_next_doc' if @search_context && search_session['document_id'] == @document.id %>
1
+ <%= render(Blacklight::SearchContextComponent.new(search_context: @search_context, search_session: search_session)) if search_session['document_id'] == @document.id %>
2
2
 
3
3
  <% @page_title = t('blacklight.search.show.title', document_title: Deprecation.silence(Blacklight::BlacklightHelperBehavior) { document_show_html_title }, application_name: application_name).html_safe %>
4
4
  <% content_for(:head) { render_link_rel_alternates } %>
@@ -5,7 +5,7 @@
5
5
  </div>
6
6
  <% end %>
7
7
 
8
- <%= render_document_main_content_partial %>
8
+ <%= render 'show_main_content' %>
9
9
 
10
10
  <% content_for(:sidebar) do %>
11
11
  <%= render_document_sidebar_partial @document %>
data/blacklight.gemspec CHANGED
@@ -33,6 +33,7 @@ Gem::Specification.new do |s|
33
33
  s.add_dependency "i18n", '>= 1.7.0' # added named parameters
34
34
  s.add_dependency "ostruct", '>= 0.3.2'
35
35
  s.add_dependency "view_component", '~> 2.43'
36
+ s.add_dependency 'hashdiff'
36
37
 
37
38
  s.add_development_dependency "rsolr", ">= 1.0.6", "< 3" # Library for interacting with rSolr.
38
39
  s.add_development_dependency "rspec-rails", "~> 5.0"
@@ -296,7 +296,7 @@ module Blacklight
296
296
  property :enable_search_bar_autofocus, default: false
297
297
 
298
298
  BASIC_SEARCH_PARAMETERS = [:q, :qt, :page, :per_page, :search_field, :sort, :controller, :action, :'facet.page', :'facet.prefix', :'facet.sort', :rows, :format].freeze
299
- ADVANCED_SEARCH_PARAMETERS = [:clause, :op].freeze
299
+ ADVANCED_SEARCH_PARAMETERS = [{ clause: {} }, :op].freeze
300
300
  # List the request parameters that compose the SearchState.
301
301
  # If you use a plugin that adds to the search state, then you can add the parameters
302
302
  # by modifiying this field.
@@ -305,6 +305,13 @@ module Blacklight
305
305
  # @return [Array<Symbol>]
306
306
  property :search_state_fields, default: BASIC_SEARCH_PARAMETERS + ADVANCED_SEARCH_PARAMETERS
307
307
 
308
+ # Have SearchState filter out unknown request parameters
309
+ #
310
+ # @!attribute filter_search_state_fields
311
+ # @since v8.0.0
312
+ # @return [Boolean]
313
+ property :filter_search_state_fields, default: false
314
+
308
315
  ##
309
316
  # Create collections of solr field configurations.
310
317
  # This will create array-like accessor methods for
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require 'hashdiff'
4
+
2
5
  module Blacklight
3
- module Parameters
6
+ class Parameters
7
+ extend Deprecation
8
+
4
9
  ##
5
10
  # Sanitize the search parameters by removing unnecessary parameters
6
11
  # from the provided parameters.
@@ -9,5 +14,96 @@ module Blacklight
9
14
  params.reject { |_k, v| v.nil? }
10
15
  .except(:action, :controller, :id, :commit, :utf8)
11
16
  end
17
+
18
+ # rubocop:disable Naming/MethodParameterName
19
+ # Merge two Rails strong_params-style permissions into a single list of permitted parameters,
20
+ # deep-merging complex values as needed.
21
+ # @param [Array<Symbol, Hash>] a
22
+ # @param [Array<Symbol, Hash>] b
23
+ # @return [Array<Symbol, Hash>]
24
+ def self.deep_merge_permitted_params(a, b)
25
+ a = [a] if a.is_a? Hash
26
+ b = [b] if b.is_a? Hash
27
+
28
+ complex_params_from_a, scalar_params_from_a = a.flatten.uniq.partition { |x| x.is_a? Hash }
29
+ complex_params_from_a = complex_params_from_a.inject({}) { |tmp, h| _deep_merge_permitted_param_hashes(h, tmp) }
30
+ complex_params_from_b, scalar_params_from_b = b.flatten.uniq.partition { |x| x.is_a? Hash }
31
+ complex_params_from_b = complex_params_from_b.inject({}) { |tmp, h| _deep_merge_permitted_param_hashes(h, tmp) }
32
+
33
+ (scalar_params_from_a + scalar_params_from_b + [_deep_merge_permitted_param_hashes(complex_params_from_a, complex_params_from_b)]).reject(&:blank?).uniq
34
+ end
35
+
36
+ private_class_method def self._deep_merge_permitted_param_hashes(h1, h2)
37
+ h1.merge(h2) do |_key, old_value, new_value|
38
+ if (old_value.is_a?(Hash) && old_value.empty?) || (new_value.is_a?(Hash) && new_value.empty?)
39
+ {}
40
+ elsif old_value.is_a?(Hash) && new_value.is_a?(Hash)
41
+ _deep_merge_permitted_param_hashes(old_value, new_value)
42
+ elsif old_value.is_a?(Array) || new_value.is_a?(Array)
43
+ deep_merge_permitted_params(old_value, new_value)
44
+ else
45
+ new_value
46
+ end
47
+ end
48
+ end
49
+ # rubocop:enable Naming/MethodParameterName
50
+
51
+ attr_reader :params, :search_state
52
+
53
+ delegate :blacklight_config, :filter_fields, to: :search_state
54
+
55
+ def initialize(params, search_state)
56
+ @params = params.is_a?(Hash) ? params.with_indifferent_access : params
57
+ @search_state = search_state
58
+ end
59
+
60
+ # @param [Hash] params with unknown structure (not declared in the blacklight config or filters) stripped out
61
+ def permit_search_params
62
+ # if the parameters were generated internally, we can (probably) trust that they're fine
63
+ return params unless params.is_a?(ActionController::Parameters)
64
+
65
+ # if the parameters were permitted already, we should be able to trust them
66
+ return params if params.permitted?
67
+
68
+ permitted_params = filter_fields.inject(blacklight_config.search_state_fields) do |allowlist, filter|
69
+ Blacklight::Parameters.deep_merge_permitted_params(allowlist, filter.permitted_params)
70
+ end
71
+
72
+ deep_unmangle_params!(params, permitted_params)
73
+
74
+ if blacklight_config.filter_search_state_fields
75
+ params.permit(*permitted_params)
76
+ else
77
+ warn_about_deprecated_parameter_handling(params, permitted_params)
78
+ params.deep_dup.permit!
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def warn_about_deprecated_parameter_handling(params, permitted_params)
85
+ diff = Hashdiff.diff(params.to_unsafe_h, params.permit(*permitted_params).to_h)
86
+ return if diff.empty?
87
+
88
+ Deprecation.warn(Blacklight::Parameters, "Blacklight 8 will filter out non-search parameter, including: #{diff.map { |_op, key, *| key }.to_sentence}")
89
+ end
90
+
91
+ # Facebook's crawler turns array query parameters into a hash with numeric keys. Once we know
92
+ # the expected parameter structure, we can unmangle those parameters to match our expected values.
93
+ def deep_unmangle_params!(params, permitted_params)
94
+ permitted_params.select { |p| p.is_a?(Hash) }.each do |permission|
95
+ permission.each do |key, permitted_value|
96
+ if params[key].is_a?(ActionController::Parameters) && permitted_value.is_a?(Hash)
97
+ deep_unmangle_params!(params[key], [permitted_value])
98
+ elsif permitted_value.is_a?(Array) && permitted_value.empty?
99
+ if params[key].is_a?(ActionController::Parameters) && params[key]&.keys&.all? { |k| k.to_s =~ /\A\d+\z/ }
100
+ params[key] = params[key].values
101
+ elsif params[key].is_a?(String)
102
+ params[key] = Array(params[key])
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
12
108
  end
13
109
  end
@@ -27,7 +27,8 @@ module Blacklight
27
27
  end
28
28
 
29
29
  @blacklight_params = {}
30
- @search_state = Blacklight::SearchState.new(@blacklight_params, @scope&.blacklight_config, @scope)
30
+ search_state_class = @scope&.search_state_class || Blacklight::SearchState
31
+ @search_state = search_state_class.new(@blacklight_params, @scope&.blacklight_config, @scope)
31
32
  @additional_filters = {}
32
33
  @merged_params = {}
33
34
  @reverse_merged_params = {}
@@ -48,7 +49,7 @@ module Blacklight
48
49
  Deprecation.warn(Blacklight::SearchBuilder, "SearchBuilder#where must be called with a hash, received #{conditions.inspect}.") unless conditions.is_a? Hash
49
50
  params_will_change!
50
51
  @search_state = @search_state.reset(@search_state.params.merge(q: conditions))
51
- @blacklight_params = @search_state.params.dup
52
+ @blacklight_params = @search_state.params
52
53
  @additional_filters = conditions
53
54
  self
54
55
  end
@@ -159,12 +159,18 @@ module Blacklight
159
159
  end
160
160
  end
161
161
 
162
- def needs_normalization?(value_params)
163
- value_params&.is_a?(Hash) && value_params != Blacklight::SearchState::FilterField::MISSING
164
- end
165
-
166
- def normalize(value_params)
167
- needs_normalization?(value_params) ? value_params.values : value_params
162
+ def permitted_params
163
+ if config.pivot
164
+ {
165
+ filters_key => config.pivot.each_with_object({}) { |key, filter| filter.merge(key => [], "-#{key}" => []) },
166
+ inclusive_filters_key => config.pivot.each_with_object({}) { |key, filter| filter.merge(key => []) }
167
+ }
168
+ else
169
+ {
170
+ filters_key => { config.key => [], "-#{config.key}" => [] },
171
+ inclusive_filters_key => { config.key => [] }
172
+ }
173
+ end
168
174
  end
169
175
 
170
176
  private
@@ -19,71 +19,17 @@ module Blacklight
19
19
 
20
20
  delegate :facet_configuration_for_field, to: :blacklight_config
21
21
 
22
- def self.modifiable_params(params)
23
- if params.respond_to?(:to_unsafe_h)
24
- # This is the typical (not-ActionView::TestCase) code path.
25
- params = params.to_unsafe_h
26
- # In Rails 5 to_unsafe_h returns a HashWithIndifferentAccess, in Rails 4 it returns Hash
27
- params = params.with_indifferent_access if params.instance_of? Hash
28
- elsif params.is_a? Hash
29
- # This is an ActionView::TestCase workaround for Rails 4.2.
30
- params = params.dup.with_indifferent_access
31
- else
32
- params = params.dup.to_h.with_indifferent_access
33
- end
34
- params
35
- end
36
-
37
22
  # @param [ActionController::Parameters] params
38
23
  # @param [Blacklight::Config] blacklight_config
39
24
  # @param [ApplicationController] controller used for the routing helpers
40
25
  def initialize(params, blacklight_config, controller = nil)
41
26
  @blacklight_config = blacklight_config
42
27
  @controller = controller
43
- @params = SearchState.modifiable_params(params)
44
- normalize_params! if needs_normalization?
45
- end
46
-
47
- def needs_normalization?
48
- return false if params.blank?
49
- return true if (params.keys.map(&:to_s) - permitted_fields.map(&:to_s)).present?
50
-
51
- !!filters.detect { |filter| filter.values.detect { |value| filter.needs_normalization?(value) } }
52
- end
53
-
54
- def normalize_params!
55
- @params = normalize_params
56
- end
57
-
58
- def normalize_params
59
- return params unless needs_normalization?
60
-
61
- base_params = params.slice(*blacklight_config.search_state_fields)
62
- normal_state = blacklight_config.facet_fields.each_value.inject(reset(base_params)) do |working_state, filter_key|
63
- f = filter(filter_key)
64
- next working_state unless f.any?
65
-
66
- filter_values = f.values(except: [:inclusive_filters]).inject([]) do |memo, filter_value|
67
- # flatten arrays that had been mangled into integer-indexed hashes
68
- memo.concat([f.normalize(filter_value)].flatten)
69
- end
70
- filter_values = f.values(except: [:filters, :missing]).inject(filter_values) do |memo, filter_value|
71
- memo << f.normalize(filter_value)
72
- end
73
- filter_values.inject(working_state) do |memo, filter_value|
74
- memo.filter(filter_key).add(filter_value)
75
- end
76
- end
77
- normal_state.params
78
- end
79
-
80
- def permitted_fields
81
- filter_keys = filter_fields.inject(Set.new) { |memo, filter| memo.merge [filter.filters_key, filter.inclusive_filters_key] }
82
- blacklight_config.search_state_fields + filter_keys.subtract([nil, '']).to_a
28
+ @params = Blacklight::Parameters.new(params, self).permit_search_params.to_h.with_indifferent_access
83
29
  end
84
30
 
85
31
  def to_hash
86
- @params.deep_dup
32
+ params.deep_dup
87
33
  end
88
34
  alias to_h to_hash
89
35
 
@@ -132,7 +78,7 @@ module Blacklight
132
78
 
133
79
  # @return [Blacklight::SearchState]
134
80
  def reset(params = nil)
135
- self.class.new(params || ActionController::Parameters.new, blacklight_config, controller)
81
+ self.class.new(params || {}, blacklight_config, controller)
136
82
  end
137
83
 
138
84
  # @return [Blacklight::SearchState]
@@ -162,6 +108,10 @@ module Blacklight
162
108
  p
163
109
  end
164
110
 
111
+ def filter_fields
112
+ blacklight_config.facet_fields.each_value.map { |value| filter(value) }
113
+ end
114
+
165
115
  def filters
166
116
  @filters ||= filter_fields.select(&:any?)
167
117
  end
@@ -192,7 +142,7 @@ module Blacklight
192
142
  # catalog/index with their new facet choice.
193
143
  def add_facet_params_and_redirect(field, item)
194
144
  new_params = Deprecation.silence(self.class) do
195
- add_facet_params(field, item)
145
+ add_facet_params(field, item).to_h.with_indifferent_access
196
146
  end
197
147
 
198
148
  # Delete any request params from facet-specific action, needed
@@ -229,7 +179,7 @@ module Blacklight
229
179
  # @yield [params] The merged parameters hash before being sanitized
230
180
  def params_for_search(params_to_merge = {})
231
181
  # params hash we'll return
232
- my_params = params.dup.merge(self.class.new(params_to_merge, blacklight_config, controller))
182
+ my_params = to_h.merge(self.class.new(params_to_merge, blacklight_config, controller))
233
183
 
234
184
  if block_given?
235
185
  yield my_params
@@ -297,11 +247,7 @@ module Blacklight
297
247
  # and need to be reset when e.g. constraints change
298
248
  # @return [ActionController::Parameters]
299
249
  def reset_search_params
300
- Parameters.sanitize(params).except(:page, :counter)
301
- end
302
-
303
- def filter_fields
304
- blacklight_config.facet_fields.each_value.map { |value| filter(value) }
250
+ Parameters.sanitize(to_h).except(:page, :counter)
305
251
  end
306
252
  end
307
253
  end
@@ -24,7 +24,8 @@ RSpec.describe BookmarksController do
24
24
  allow(@controller).to receive_message_chain(:current_or_guest_user, :existing_bookmark_for).and_return(false)
25
25
  allow(@controller).to receive_message_chain(:current_or_guest_user, :persisted?).and_return(true)
26
26
  allow(@controller).to receive_message_chain(:current_or_guest_user, :bookmarks, :where, :exists?).and_return(false)
27
- allow(@controller).to receive_message_chain(:current_or_guest_user, :bookmarks, :create).and_return(false)
27
+ allow(@controller).to receive_message_chain(:current_or_guest_user, :bookmarks, :create!).and_raise(ActiveRecord::RecordInvalid)
28
+ allow(@controller).to receive_message_chain(:current_or_guest_user, :errors, :full_messages).and_return([1])
28
29
  put :update, xhr: true, params: { id: 'iamabooboo', format: :js }
29
30
  expect(response.code).to eq "500"
30
31
  end
@@ -27,7 +27,7 @@ RSpec.describe Blacklight::RenderConstraintsHelperBehavior do
27
27
  let(:params) { ActionController::Parameters.new(q: 'foobar', f: { type: 'journal' }) }
28
28
 
29
29
  it "has a link relative to the current url" do
30
- expect(subject).to have_link 'Remove constraint', href: '/catalog?f%5Btype%5D=journal'
30
+ expect(subject).to have_link 'Remove constraint', href: '/catalog?f%5Btype%5D%5B%5D=journal'
31
31
  end
32
32
  end
33
33
 
@@ -21,4 +21,91 @@ RSpec.describe Blacklight::Parameters do
21
21
  end
22
22
  end
23
23
  end
24
+
25
+ describe '.deep_merge_permitted_params' do
26
+ it 'merges scalar values' do
27
+ expect(described_class.deep_merge_permitted_params([:a], [:b])).to eq [:a, :b]
28
+ end
29
+
30
+ it 'appends complex values' do
31
+ expect(described_class.deep_merge_permitted_params([:a], { b: [] })).to eq [:a, { b: [] }]
32
+ end
33
+
34
+ it 'merges lists of scalar values' do
35
+ expect(described_class.deep_merge_permitted_params({ f: [:a, :b] }, { f: [:b, :c] })).to eq [{ f: [:a, :b, :c] }]
36
+ end
37
+
38
+ it 'merges complex value data structures' do
39
+ expect(described_class.deep_merge_permitted_params([{ f: { field1: [] } }], { f: { field2: [] } })).to eq [{ f: { field1: [], field2: [] } }]
40
+ end
41
+
42
+ it 'takes the most permissive value' do
43
+ expect(described_class.deep_merge_permitted_params([{ f: {} }], { f: { field2: [] } })).to eq [{ f: {} }]
44
+ expect(described_class.deep_merge_permitted_params([{ f: {} }], { f: [:some_value] })).to eq [{ f: {} }]
45
+ end
46
+ end
47
+
48
+ describe '#permit_search_params' do
49
+ subject(:params) { described_class.new(query_params, search_state) }
50
+
51
+ let(:query_params) { ActionController::Parameters.new(a: 1, b: 2, c: []) }
52
+ let(:search_state) { Blacklight::SearchState.new(query_params, blacklight_config) }
53
+ let(:blacklight_config) { Blacklight::Configuration.new }
54
+
55
+ context 'with facebooks badly mangled query parameters' do
56
+ let(:query_params) do
57
+ ActionController::Parameters.new(
58
+ f: { field: { '0': 'first', '1': 'second' } },
59
+ f_inclusive: { field: { '0': 'first', '1': 'second' } }
60
+ )
61
+ end
62
+
63
+ before do
64
+ blacklight_config.add_facet_field 'field'
65
+ end
66
+
67
+ it 'normalizes the facets to the expected format' do
68
+ expect(params.permit_search_params.to_h.with_indifferent_access).to include f: { field: %w[first second] }, f_inclusive: { field: %w[first second] }
69
+ end
70
+ end
71
+
72
+ context 'with filter_search_state_fields set to false' do
73
+ let(:blacklight_config) { Blacklight::Configuration.new(filter_search_state_fields: false) }
74
+
75
+ it 'allows all params, but warns about the behavior' do
76
+ allow(Deprecation).to receive(:warn)
77
+ expect(params.permit_search_params.to_h.with_indifferent_access).to include(a: 1, b: 2, c: [])
78
+
79
+ expect(Deprecation).to have_received(:warn).with(described_class, /including: a, b, and c/).at_least(:once)
80
+ end
81
+ end
82
+
83
+ context 'with filter_search_state_fields set to true' do
84
+ let(:blacklight_config) { Blacklight::Configuration.new(filter_search_state_fields: true) }
85
+
86
+ it 'rejects unknown params' do
87
+ expect(params.permit_search_params.to_h).to be_empty
88
+ end
89
+
90
+ context 'with some search parameters' do
91
+ let(:query_params) { ActionController::Parameters.new(q: 'abc', page: 5, f: { facet_field: %w[a b], unknown_field: ['a'] }) }
92
+
93
+ before do
94
+ blacklight_config.add_facet_field 'facet_field'
95
+ end
96
+
97
+ it 'allows scalar params' do
98
+ expect(params.permit_search_params.to_h.with_indifferent_access).to include(q: 'abc', page: 5)
99
+ end
100
+
101
+ it 'allows facet params' do
102
+ expect(params.permit_search_params.to_h.with_indifferent_access).to include(f: { facet_field: %w[a b] })
103
+ end
104
+
105
+ it 'removes unknown facet fields parameters' do
106
+ expect(params.permit_search_params.to_h.with_indifferent_access[:f]).not_to include(:unknown_field)
107
+ end
108
+ end
109
+ end
110
+ end
24
111
  end
@@ -6,6 +6,7 @@ RSpec.describe Blacklight::SearchState::FilterField do
6
6
  let(:params) { { f: { some_field: %w[1 2], another_field: ['3'] } } }
7
7
  let(:blacklight_config) do
8
8
  Blacklight::Configuration.new.configure do |config|
9
+ config.add_facet_field 'new_field'
9
10
  config.add_facet_field 'another_field', single: true
10
11
  simple_facet_fields.each { |simple_facet_field| config.add_facet_field simple_facet_field }
11
12
  config.search_state_fields = config.search_state_fields + additional_search_fields
@@ -23,14 +24,6 @@ RSpec.describe Blacklight::SearchState::FilterField do
23
24
  expect(new_state.filter('some_field').values).to eq %w[1 2 4]
24
25
  end
25
26
 
26
- it 'creates new parameter as needed' do
27
- filter = search_state.filter('unknown_field')
28
- new_state = filter.add('4')
29
-
30
- expect(new_state.filter('unknown_field').values).to eq %w[4]
31
- expect(new_state.params[:f]).to include(:unknown_field)
32
- end
33
-
34
27
  context 'without any parameters in the url' do
35
28
  let(:params) { {} }
36
29
 
@@ -205,10 +198,4 @@ RSpec.describe Blacklight::SearchState::FilterField do
205
198
  expect(search_state.filter('some_field').include?(OpenStruct.new(value: '1'))).to eq true
206
199
  end
207
200
  end
208
-
209
- describe '#needs_normalization?' do
210
- it 'returns false for Blacklight::SearchState::FilterField::MISSING' do
211
- expect(search_state.filter('some_field').needs_normalization?(Blacklight::SearchState::FilterField::MISSING)).to be false
212
- end
213
- end
214
201
  end
@@ -54,19 +54,6 @@ RSpec.describe Blacklight::SearchState do
54
54
  end
55
55
  end
56
56
 
57
- context 'with facebooks badly mangled query parameters' do
58
- let(:simple_facet_fields) { [:field] }
59
- let(:params) do
60
- { f: { field: { '0': 'first', '1': 'second' } },
61
- f_inclusive: { field: { '0': 'first', '1': 'second' } } }
62
- end
63
-
64
- it 'normalizes the facets to the expected format' do
65
- expect(search_state.to_h).to include f: { field: %w[first second] }
66
- expect(search_state.to_h).to include f_inclusive: { field: %w[first second] }
67
- end
68
- end
69
-
70
57
  context 'deleting item from to_h' do
71
58
  let(:additional_search_fields) { [:q_1] }
72
59
  let(:params) { { q: 'foo', q_1: 'bar' } }
@@ -81,7 +68,7 @@ RSpec.describe Blacklight::SearchState do
81
68
  end
82
69
 
83
70
  context 'deleting deep item from to_h' do
84
- let(:additional_search_fields) { [:foo] }
71
+ let(:additional_search_fields) { [{ foo: {} }] }
85
72
  let(:params) { { foo: { bar: [] } } }
86
73
 
87
74
  it 'does not mutate search_state to deep mutate search_state.to_h' do
@@ -5,7 +5,7 @@ RSpec.describe Blacklight::SearchBuilder, api: true do
5
5
 
6
6
  let(:processor_chain) { [] }
7
7
  let(:blacklight_config) { Blacklight::Configuration.new }
8
- let(:scope) { double blacklight_config: blacklight_config }
8
+ let(:scope) { double blacklight_config: blacklight_config, search_state_class: nil }
9
9
 
10
10
  context "with default processor chain" do
11
11
  subject { described_class.new scope }
@@ -15,6 +15,15 @@ RSpec.describe Blacklight::SearchBuilder, api: true do
15
15
  end
16
16
  end
17
17
 
18
+ context "with scope search_state_class" do
19
+ let(:state_class) { Class.new(Blacklight::SearchState) }
20
+ let(:scope) { double blacklight_config: blacklight_config, search_state_class: state_class }
21
+
22
+ it "uses the class-level default_processor_chain" do
23
+ expect(subject.search_state).to be_a state_class
24
+ end
25
+ end
26
+
18
27
  describe "#with" do
19
28
  it "sets the blacklight params" do
20
29
  params = {}
@@ -216,7 +216,9 @@ RSpec.describe Blacklight::Solr::SearchBuilderBehavior, api: true do
216
216
  expect(subject["spellcheck.q"]).to be_blank
217
217
 
218
218
  single_facet.each_value do |value|
219
- expect(subject[:fq]).to include("{!term f=#{single_facet.keys[0]}}#{value}")
219
+ Array(value).each do |v|
220
+ expect(subject[:fq]).to include("{!term f=#{single_facet.keys[0]}}#{v}")
221
+ end
220
222
  end
221
223
  end
222
224
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blacklight
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.25.1
4
+ version: 7.25.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Rochkind
@@ -17,7 +17,7 @@ authors:
17
17
  autorequire:
18
18
  bindir: exe
19
19
  cert_chain: []
20
- date: 2022-05-05 00:00:00.000000000 Z
20
+ date: 2022-05-19 00:00:00.000000000 Z
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
23
23
  name: rails
@@ -137,6 +137,20 @@ dependencies:
137
137
  - - "~>"
138
138
  - !ruby/object:Gem::Version
139
139
  version: '2.43'
140
+ - !ruby/object:Gem::Dependency
141
+ name: hashdiff
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ type: :runtime
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
140
154
  - !ruby/object:Gem::Dependency
141
155
  name: rsolr
142
156
  requirement: !ruby/object:Gem::Requirement