blacklight 7.25.1 → 7.25.2

Sign up to get free protection for your applications and to get access to all the features.
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