blacklight 7.13.0 → 7.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +109 -0
  3. data/README.md +0 -2
  4. data/VERSION +1 -1
  5. data/app/assets/javascripts/blacklight/blacklight.js +4 -2
  6. data/app/assets/stylesheets/blacklight/_header.scss +0 -5
  7. data/app/assets/stylesheets/blacklight/blacklight_defaults.scss +0 -1
  8. data/app/components/blacklight/advanced_search_form_component.html.erb +46 -0
  9. data/app/components/blacklight/advanced_search_form_component.rb +75 -0
  10. data/app/components/blacklight/constraint_component.html.erb +1 -1
  11. data/app/components/blacklight/constraints_component.rb +42 -17
  12. data/app/components/blacklight/document/thumbnail_component.html.erb +2 -2
  13. data/app/components/blacklight/document/thumbnail_component.rb +5 -2
  14. data/app/components/blacklight/document_component.rb +7 -2
  15. data/app/components/blacklight/document_metadata_component.rb +1 -1
  16. data/app/components/blacklight/facet_field_checkboxes_component.html.erb +23 -0
  17. data/app/components/blacklight/facet_field_checkboxes_component.rb +24 -0
  18. data/app/components/blacklight/facet_field_inclusive_constraint_component.html.erb +6 -0
  19. data/app/components/blacklight/facet_field_inclusive_constraint_component.rb +29 -0
  20. data/app/components/blacklight/facet_field_list_component.html.erb +1 -0
  21. data/app/components/blacklight/facet_field_pagination_component.html.erb +4 -4
  22. data/app/components/blacklight/facet_field_pagination_component.rb +0 -4
  23. data/app/components/blacklight/facet_item_component.rb +2 -0
  24. data/app/components/blacklight/hidden_search_state_component.rb +54 -0
  25. data/app/components/blacklight/search_bar_component.html.erb +14 -8
  26. data/app/components/blacklight/search_bar_component.rb +16 -1
  27. data/app/controllers/concerns/blacklight/bookmarks.rb +1 -1
  28. data/app/controllers/concerns/blacklight/catalog.rb +9 -3
  29. data/app/controllers/concerns/blacklight/search_context.rb +1 -1
  30. data/app/helpers/blacklight/component_helper_behavior.rb +1 -1
  31. data/app/helpers/blacklight/configuration_helper_behavior.rb +2 -2
  32. data/app/helpers/blacklight/facets_helper_behavior.rb +1 -1
  33. data/app/helpers/blacklight/hash_as_hidden_fields_helper_behavior.rb +2 -38
  34. data/app/helpers/blacklight/icon_helper_behavior.rb +1 -1
  35. data/app/helpers/blacklight/render_constraints_helper_behavior.rb +2 -2
  36. data/app/helpers/blacklight/render_partials_helper_behavior.rb +2 -2
  37. data/app/javascript/blacklight/button_focus.js +1 -0
  38. data/app/javascript/blacklight/modal.js +10 -4
  39. data/app/models/concerns/blacklight/suggest/response.rb +1 -1
  40. data/app/presenters/blacklight/clause_presenter.rb +37 -0
  41. data/app/presenters/blacklight/document_presenter.rb +5 -1
  42. data/app/presenters/blacklight/facet_field_presenter.rb +4 -0
  43. data/app/presenters/blacklight/facet_grouped_item_presenter.rb +45 -0
  44. data/app/presenters/blacklight/facet_item_presenter.rb +32 -20
  45. data/app/presenters/blacklight/field_presenter.rb +1 -1
  46. data/app/presenters/blacklight/inclusive_facet_item_presenter.rb +16 -0
  47. data/app/presenters/blacklight/search_bar_presenter.rb +4 -0
  48. data/app/values/blacklight/types.rb +1 -1
  49. data/app/views/bookmarks/_tools.html.erb +1 -1
  50. data/app/views/catalog/_advanced_search_form.html.erb +7 -0
  51. data/app/views/catalog/_advanced_search_help.html.erb +24 -0
  52. data/app/views/catalog/_search_form.html.erb +1 -0
  53. data/app/views/catalog/_zero_results.html.erb +1 -1
  54. data/app/views/catalog/advanced_search.html.erb +17 -0
  55. data/blacklight.gemspec +5 -4
  56. data/config/i18n-tasks.yml +1 -0
  57. data/config/locales/blacklight.en.yml +17 -0
  58. data/lib/blacklight/configuration.rb +2 -1
  59. data/lib/blacklight/configuration/field.rb +1 -1
  60. data/lib/blacklight/configuration/fields.rb +1 -1
  61. data/lib/blacklight/configuration/sort_field.rb +1 -1
  62. data/lib/blacklight/routes/searchable.rb +1 -0
  63. data/lib/blacklight/search_builder.rb +2 -0
  64. data/lib/blacklight/search_state.rb +5 -1
  65. data/lib/blacklight/search_state/filter_field.rb +17 -7
  66. data/lib/blacklight/solr/repository.rb +14 -5
  67. data/lib/blacklight/solr/response.rb +1 -1
  68. data/lib/blacklight/solr/search_builder_behavior.rb +87 -23
  69. data/package.json +1 -1
  70. data/spec/components/blacklight/advanced_search_form_component_spec.rb +51 -0
  71. data/spec/components/blacklight/constraint_layout_component_spec.rb +1 -1
  72. data/spec/components/blacklight/document_component_spec.rb +17 -0
  73. data/spec/components/blacklight/facet_field_checkboxes_component_spec.rb +55 -0
  74. data/spec/components/blacklight/facet_field_list_component_spec.rb +39 -4
  75. data/spec/components/blacklight/hidden_search_state_component_spec.rb +24 -0
  76. data/spec/controllers/catalog_controller_spec.rb +9 -0
  77. data/spec/features/advanced_search_spec.rb +67 -0
  78. data/spec/features/bookmarks_spec.rb +1 -9
  79. data/spec/features/facets_spec.rb +2 -17
  80. data/spec/features/search_filters_spec.rb +0 -20
  81. data/spec/helpers/blacklight/hash_as_hidden_fields_behavior_spec.rb +1 -0
  82. data/spec/helpers/blacklight/url_helper_behavior_spec.rb +1 -0
  83. data/spec/lib/blacklight/search_state/filter_field_spec.rb +65 -0
  84. data/spec/models/blacklight/solr/repository_spec.rb +12 -0
  85. data/spec/models/blacklight/solr/response/facets_spec.rb +1 -1
  86. data/spec/models/blacklight/solr/search_builder_spec.rb +28 -0
  87. data/spec/presenters/blacklight/clause_presenter_spec.rb +34 -0
  88. data/spec/presenters/blacklight/document_presenter_spec.rb +13 -0
  89. data/spec/presenters/blacklight/facet_grouped_item_presenter_spec.rb +41 -0
  90. data/spec/spec_helper.rb +8 -3
  91. data/spec/test_app_templates/Gemfile.extra +1 -1
  92. data/spec/views/catalog/_document.html.erb_spec.rb +1 -0
  93. data/spec/views/catalog/_thumbnail.html.erb_spec.rb +2 -0
  94. data/tasks/blacklight.rake +3 -3
  95. metadata +67 -28
  96. data/.travis.yml +0 -40
@@ -44,7 +44,7 @@ module Blacklight
44
44
 
45
45
  def default_label
46
46
  if self.key.respond_to?(:titleize)
47
- self.key.try(:titleize)
47
+ self.key.titleize
48
48
  else
49
49
  self.key.to_s.titleize
50
50
  end
@@ -151,7 +151,7 @@ module Blacklight
151
151
  repository = repository_class.new(self)
152
152
  repository.reflect_fields
153
153
  rescue => e
154
- Blacklight.logger.warn "Error retrieving field metadata: #{e}"
154
+ Blacklight.logger&.warn "Error retrieving field metadata: #{e}"
155
155
  false
156
156
  end
157
157
 
@@ -6,7 +6,7 @@ module Blacklight
6
6
 
7
7
  def normalize! blacklight_config = nil
8
8
  super
9
- self.field ||= label.try(:parameterize)
9
+ self.field ||= label&.parameterize
10
10
  self.field ||= sort
11
11
 
12
12
  self.sort ||= self.field
@@ -8,6 +8,7 @@ module Blacklight
8
8
 
9
9
  def call(mapper, _options = {})
10
10
  mapper.match '/', action: 'index', as: 'search', via: [:get, :post]
11
+ mapper.get '/advanced', action: 'advanced_search', as: 'advanced_search'
11
12
 
12
13
  mapper.post ":id/track", action: 'track', as: 'track'
13
14
  mapper.get ":id/raw", action: 'raw', as: 'raw', defaults: { format: 'json' }
@@ -28,6 +28,7 @@ module Blacklight
28
28
 
29
29
  @blacklight_params = {}
30
30
  @search_state = Blacklight::SearchState.new(@blacklight_params, @scope&.blacklight_config, @scope)
31
+ @additional_filters = {}
31
32
  @merged_params = {}
32
33
  @reverse_merged_params = {}
33
34
  end
@@ -47,6 +48,7 @@ module Blacklight
47
48
  params_will_change!
48
49
  @search_state = @search_state.reset(@search_state.params.merge(q: conditions))
49
50
  @blacklight_params = @search_state.params.dup
51
+ @additional_filters = conditions
50
52
  self
51
53
  end
52
54
 
@@ -82,7 +82,7 @@ module Blacklight
82
82
 
83
83
  def has_constraints?
84
84
  Deprecation.silence(Blacklight::SearchState) do
85
- !(query_param.blank? && filter_params.blank? && filters.blank?)
85
+ !(query_param.blank? && filter_params.blank? && filters.blank? && clause_params.blank?)
86
86
  end
87
87
  end
88
88
 
@@ -90,6 +90,10 @@ module Blacklight
90
90
  params[:q]
91
91
  end
92
92
 
93
+ def clause_params
94
+ params[:clause] || {}
95
+ end
96
+
93
97
  def filter_params
94
98
  params[:f] || {}
95
99
  end
@@ -36,12 +36,16 @@ module Blacklight
36
36
  end
37
37
 
38
38
  params = new_state.params
39
+ param = :f
39
40
  value = as_url_parameter(item)
41
+ param = :f_inclusive if value.is_a?(Array)
40
42
 
41
43
  # value could be a string
42
44
  params[param] = (params[param] || {}).dup
43
45
 
44
- if config.single
46
+ if value.is_a? Array
47
+ params[param][key] = value
48
+ elsif config.single
45
49
  params[param][key] = [value]
46
50
  else
47
51
  params[param][key] = Array(params[param][key] || []).dup
@@ -60,7 +64,10 @@ module Blacklight
60
64
  end
61
65
 
62
66
  params = new_state.params
67
+
68
+ param = :f
63
69
  value = as_url_parameter(item)
70
+ param = :f_inclusive if value.is_a?(Array)
64
71
 
65
72
  # need to dup the facet values too,
66
73
  # if the values aren't dup'd, then the values
@@ -86,7 +93,10 @@ module Blacklight
86
93
  # @return [Array] an array of applied filters
87
94
  def values
88
95
  params = search_state.params
89
- Array(params.dig(param, key)) || []
96
+ f = Array(params.dig(:f, key))
97
+ f_inclusive = [params.dig(:f_inclusive, key)] if params.dig(:f_inclusive, key).present?
98
+
99
+ f + (f_inclusive || [])
90
100
  end
91
101
  delegate :any?, to: :values
92
102
 
@@ -100,15 +110,15 @@ module Blacklight
100
110
  value = as_url_parameter(item)
101
111
  params = search_state.params
102
112
 
103
- (params.dig(param, key) || []).include?(value)
113
+ if value.is_a?(Array)
114
+ (params.dig(:f_inclusive, key) || []).to_set == value.to_set
115
+ else
116
+ (params.dig(:f, key) || []).include?(value)
117
+ end
104
118
  end
105
119
 
106
120
  private
107
121
 
108
- def param
109
- :f
110
- end
111
-
112
122
  # TODO: this code is duplicated in Blacklight::FacetsHelperBehavior
113
123
  def as_url_parameter(item)
114
124
  if item.respond_to? :value
@@ -41,7 +41,7 @@ module Blacklight::Solr
41
41
  # @return [boolean] true if the repository is reachable
42
42
  def ping
43
43
  response = connection.send_and_receive 'admin/ping', {}
44
- Blacklight.logger.info("Ping [#{connection.uri}] returned: '#{response['status']}'")
44
+ Blacklight.logger&.info("Ping [#{connection.uri}] returned: '#{response['status']}'")
45
45
  response['status'] == "OK"
46
46
  end
47
47
 
@@ -58,13 +58,22 @@ module Blacklight::Solr
58
58
  # @return [Blacklight::Solr::Response] the solr response object
59
59
  def send_and_receive(path, solr_params = {})
60
60
  benchmark("Solr fetch", level: :debug) do
61
- key = blacklight_config.http_method == :post ? :data : :params
62
- res = connection.send_and_receive(path, { key => solr_params.to_hash, method: blacklight_config.http_method })
61
+ res = if solr_params[:json].present?
62
+ connection.send_and_receive(
63
+ path,
64
+ data: { params: solr_params.to_hash.except(:json) }.merge(solr_params[:json]).to_json,
65
+ method: :post,
66
+ headers: { 'Content-Type' => 'application/json' }
67
+ )
68
+ else
69
+ key = blacklight_config.http_method == :post ? :data : :params
70
+ connection.send_and_receive(path, { key => solr_params.to_hash, method: blacklight_config.http_method })
71
+ end
63
72
 
64
73
  solr_response = blacklight_config.response_model.new(res, solr_params, document_model: blacklight_config.document_model, blacklight_config: blacklight_config)
65
74
 
66
- Blacklight.logger.debug("Solr query: #{blacklight_config.http_method} #{path} #{solr_params.to_hash.inspect}")
67
- Blacklight.logger.debug("Solr response: #{solr_response.inspect}") if defined?(::BLACKLIGHT_VERBOSE_LOGGING) && ::BLACKLIGHT_VERBOSE_LOGGING
75
+ Blacklight.logger&.debug("Solr query: #{blacklight_config.http_method} #{path} #{solr_params.to_hash.inspect}")
76
+ Blacklight.logger&.debug("Solr response: #{solr_response.inspect}") if defined?(::BLACKLIGHT_VERBOSE_LOGGING) && ::BLACKLIGHT_VERBOSE_LOGGING
68
77
  solr_response
69
78
  end
70
79
  rescue Errno::ECONNREFUSED => e
@@ -93,7 +93,7 @@ class Blacklight::Solr::Response < ActiveSupport::HashWithIndifferentAccess
93
93
  value.each { |v| force_to_utf8(v) }
94
94
  when String
95
95
  if value.encoding != Encoding::UTF_8
96
- Blacklight.logger.warn "Found a non utf-8 value in Blacklight::Solr::Response. \"#{value}\" Encoding is #{value.encoding}"
96
+ Blacklight.logger&.warn "Found a non utf-8 value in Blacklight::Solr::Response. \"#{value}\" Encoding is #{value.encoding}"
97
97
  value.dup.force_encoding('UTF-8')
98
98
  else
99
99
  value
@@ -8,7 +8,8 @@ module Blacklight::Solr
8
8
  :default_solr_parameters, :add_query_to_solr, :add_facet_fq_to_solr,
9
9
  :add_facetting_to_solr, :add_solr_fields_to_query, :add_paging_to_solr,
10
10
  :add_sorting_to_solr, :add_group_config_to_solr,
11
- :add_facet_paging_to_solr
11
+ :add_facet_paging_to_solr, :add_adv_search_clauses,
12
+ :add_additional_filters
12
13
  ]
13
14
  end
14
15
 
@@ -61,12 +62,62 @@ module Blacklight::Solr
61
62
  elsif search_field&.solr_local_parameters.present?
62
63
  add_search_field_with_local_parameters(solr_parameters)
63
64
  elsif search_state.query_param.is_a? Hash
64
- add_multifield_search_query(solr_parameters)
65
- elsif blacklight_params[:q]
65
+ add_additional_filters(solr_parameters, search_state.query_param)
66
+ elsif search_state.query_param
66
67
  solr_parameters[:q] = search_state.query_param
67
68
  end
68
69
  end
69
70
 
71
+ def add_additional_filters(solr_parameters, additional_filters = nil)
72
+ q = additional_filters || @additional_filters
73
+
74
+ return if q.blank?
75
+
76
+ solr_parameters[:q] = if q.values.any?(&:blank?)
77
+ # if any field parameters are empty, exclude _all_ results
78
+ "{!lucene}NOT *:*"
79
+ else
80
+ "{!lucene}" + q.map do |field, values|
81
+ "#{field}:(#{Array(values).map { |x| solr_param_quote(x) }.join(' OR ')})"
82
+ end.join(" AND ")
83
+ end
84
+
85
+ solr_parameters[:defType] = 'lucene'
86
+ solr_parameters[:spellcheck] = 'false'
87
+ end
88
+
89
+ # Transform "clause" parameters into the Solr JSON Query DSL
90
+ def add_adv_search_clauses(solr_parameters)
91
+ return if search_state.clause_params.blank?
92
+
93
+ defaults = { must: [], must_not: [], should: [] }
94
+ bool_query = (solr_parameters.dig(:json, :query, :bool) || {}).reverse_merge(defaults)
95
+
96
+ default_op = blacklight_params[:op]&.to_sym || :must
97
+
98
+ search_state.clause_params.each_value do |clause|
99
+ op, query = adv_search_clause(clause, default_op)
100
+ bool_query[op] << query if defaults.key?(op) && query
101
+ end
102
+
103
+ return if bool_query.values.all?(&:blank?)
104
+
105
+ solr_parameters[:mm] = 1 if default_op == :should
106
+ solr_parameters[:json] ||= { query: { bool: {} } }
107
+ solr_parameters[:json][:query] ||= { bool: {} }
108
+ solr_parameters[:json][:query][:bool] = bool_query.reject { |_k, v| v.blank? }
109
+ end
110
+
111
+ # @return [Array] the first element is the query operator and the second is the value to add
112
+ def adv_search_clause(clause, default_op)
113
+ op = clause[:op]&.to_sym || default_op
114
+ field = (blacklight_config.search_fields || {})[clause[:field]] if clause[:field]
115
+
116
+ return unless field&.clause_params && clause[:query].present?
117
+
118
+ [op, field.clause_params.transform_values { |v| v.merge(query: clause[:query]) }]
119
+ end
120
+
70
121
  ##
71
122
  # Add any existing facet limits, stored in app-level HTTP query
72
123
  # as :f, to solr as appropriate :fq query.
@@ -84,8 +135,13 @@ module Blacklight::Solr
84
135
  solr_parameters.merge!(subqueries) if subqueries
85
136
  else
86
137
  filter.values.reject(&:blank?).each do |value|
87
- filter_query, subqueries = facet_value_to_fq_string(filter.config.key, value)
88
- solr_parameters.append_filter_query(filter_query)
138
+ filter_query, subqueries = if value.is_a?(Array)
139
+ facet_inclusive_value_to_fq_string(filter.key, value.reject(&:blank?))
140
+ else
141
+ facet_value_to_fq_string(filter.config.key, value)
142
+ end
143
+
144
+ solr_parameters.append_filter_query filter_query
89
145
  solr_parameters.merge!(subqueries) if subqueries
90
146
  end
91
147
  end
@@ -250,16 +306,17 @@ module Blacklight::Solr
250
306
 
251
307
  ##
252
308
  # Convert a facet/value pair into a solr fq parameter
253
- def facet_value_to_fq_string(facet_field, value)
309
+ def facet_value_to_fq_string(facet_field, value, use_local_params: true)
254
310
  facet_config = blacklight_config.facet_fields[facet_field]
255
311
 
256
312
  solr_field = facet_config.field if facet_config && !facet_config.query
257
313
  solr_field ||= facet_field
258
314
 
259
315
  local_params = []
260
- local_params << "tag=#{facet_config.tag}" if facet_config && facet_config.tag
261
316
 
262
- prefix = "{!#{local_params.join(' ')}}" unless local_params.empty?
317
+ if use_local_params
318
+ local_params << "tag=#{facet_config.tag}" if facet_config && facet_config.tag
319
+ end
263
320
 
264
321
  if facet_config && facet_config.query
265
322
  if facet_config.query[value]
@@ -269,12 +326,34 @@ module Blacklight::Solr
269
326
  '-*:*'
270
327
  end
271
328
  elsif value.is_a?(Range)
329
+ prefix = "{!#{local_params.join(' ')}}" unless local_params.empty?
272
330
  "#{prefix}#{solr_field}:[#{value.first} TO #{value.last}]"
273
331
  else
274
332
  "{!term f=#{solr_field}#{(' ' + local_params.join(' ')) unless local_params.empty?}}#{convert_to_term_value(value)}"
275
333
  end
276
334
  end
277
335
 
336
+ def facet_inclusive_value_to_fq_string(facet_field, values)
337
+ return if values.blank?
338
+
339
+ return facet_value_to_fq_string(facet_field, values.first) if values.length == 1
340
+
341
+ facet_config = blacklight_config.facet_fields[facet_field]
342
+
343
+ local_params = []
344
+ local_params << "tag=#{facet_config.tag}" if facet_config && facet_config.tag
345
+
346
+ solr_filters = values.each_with_object({}).with_index do |(v, h), index|
347
+ h["f_inclusive.#{facet_field}.#{index}"] = facet_value_to_fq_string(facet_field, v, use_local_params: false)
348
+ end
349
+
350
+ filter_query = solr_filters.keys.map do |k|
351
+ "{!query v=$#{k}}"
352
+ end.join(' OR ')
353
+
354
+ ["{!lucene#{(' ' + local_params.join(' ')) unless local_params.empty?}}#{filter_query}", solr_filters]
355
+ end
356
+
278
357
  def convert_to_term_value(value)
279
358
  if value.is_a?(DateTime) || value.is_a?(Time)
280
359
  value.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
@@ -322,20 +401,5 @@ module Blacklight::Solr
322
401
  # params!
323
402
  solr_parameters["spellcheck.q"] ||= search_state.query_param
324
403
  end
325
-
326
- def add_multifield_search_query(solr_parameters)
327
- q = search_state.query_param
328
- solr_parameters[:q] = if q.values.any?(&:blank?)
329
- # if any field parameters are empty, exclude _all_ results
330
- "{!lucene}NOT *:*"
331
- else
332
- "{!lucene}" + q.map do |field, values|
333
- "#{field}:(#{Array(values).map { |x| solr_param_quote(x) }.join(' OR ')})"
334
- end.join(" AND ")
335
- end
336
-
337
- solr_parameters[:defType] = 'lucene'
338
- solr_parameters[:spellcheck] = 'false'
339
- end
340
404
  end
341
405
  end
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "blacklight-frontend",
3
3
  "version": "7.10.0",
4
- "description": "[![Build Status](https://travis-ci.org/projectblacklight/blacklight.png?branch=master)](https://travis-ci.org/projectblacklight/blacklight) [![Gem Version](https://badge.fury.io/rb/blacklight.png)](http://badge.fury.io/rb/blacklight) [![Coverage Status](https://coveralls.io/repos/github/projectblacklight/blacklight/badge.svg?branch=master)](https://coveralls.io/github/projectblacklight/blacklight?branch=master)",
4
+ "description": "[![Build Status](https://travis-ci.com/projectblacklight/blacklight.png?branch=master)](https://travis-ci.com/projectblacklight/blacklight) [![Gem Version](https://badge.fury.io/rb/blacklight.png)](http://badge.fury.io/rb/blacklight) [![Coverage Status](https://coveralls.io/repos/github/projectblacklight/blacklight/badge.svg?branch=master)](https://coveralls.io/github/projectblacklight/blacklight?branch=master)",
5
5
  "main": "app/assets/javascripts/blacklight",
6
6
  "scripts": {
7
7
  "js-compile-bundle": "shx cat app/javascript/blacklight/core.js app/javascript/blacklight/autocomplete.js app/javascript/blacklight/bookmark_toggle.js app/javascript/blacklight/button_focus.js app/javascript/blacklight/checkbox_submit.js app/javascript/blacklight/facet_load.js app/javascript/blacklight/modal.js app/javascript/blacklight/search_context.js | shx sed \"s/^(import|export).*//\" | babel --filename app/javascript/blacklight/blacklight.js > app/assets/javascripts/blacklight/blacklight.js"
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Blacklight::AdvancedSearchFormComponent, type: :component do
6
+ subject(:render) do
7
+ component.render_in(view_context)
8
+ end
9
+
10
+ let(:component) { described_class.new(url: '/whatever', response: response, params: params) }
11
+ let(:response) { Blacklight::Solr::Response.new({ facet_counts: { facet_fields: { format: { 'Book' => 10, 'CD' => 5 } } } }.with_indifferent_access, {}) }
12
+ let(:params) { {} }
13
+
14
+ let(:rendered) do
15
+ Capybara::Node::Simple.new(render)
16
+ end
17
+
18
+ let(:view_context) { controller.view_context }
19
+
20
+ before do
21
+ allow(view_context).to receive(:facet_limit_for).and_return(nil)
22
+ end
23
+
24
+ context 'with additional parameters' do
25
+ let(:params) { { some: :parameter, an_array: [1, 2] } }
26
+
27
+ it 'adds additional parameters as hidden fields' do
28
+ expect(rendered).to have_field 'some', with: 'parameter', type: :hidden
29
+ expect(rendered).to have_field 'an_array[]', with: '1', type: :hidden
30
+ expect(rendered).to have_field 'an_array[]', with: '2', type: :hidden
31
+ end
32
+ end
33
+
34
+ it 'has text fields for each search field' do
35
+ expect(rendered).to have_selector '.advanced-search-field', count: 4
36
+ expect(rendered).to have_field 'clause_0_field', with: 'all_fields', type: :hidden
37
+ expect(rendered).to have_field 'clause_1_field', with: 'title', type: :hidden
38
+ expect(rendered).to have_field 'clause_2_field', with: 'author', type: :hidden
39
+ expect(rendered).to have_field 'clause_3_field', with: 'subject', type: :hidden
40
+ end
41
+
42
+ it 'has filters' do
43
+ expect(rendered).to have_selector '.blacklight-format'
44
+ expect(rendered).to have_field 'f_inclusive[format][]', with: 'Book'
45
+ expect(rendered).to have_field 'f_inclusive[format][]', with: 'CD'
46
+ end
47
+
48
+ it 'has a sort field' do
49
+ expect(rendered).to have_select 'sort', options: %w[relevance year author title]
50
+ end
51
+ end
@@ -4,7 +4,7 @@ require 'spec_helper'
4
4
 
5
5
  RSpec.describe Blacklight::ConstraintLayoutComponent, type: :component do
6
6
  subject(:render) do
7
- render_inline(described_class.new(params))
7
+ render_inline(described_class.new(**params))
8
8
  end
9
9
 
10
10
  let(:rendered) do
@@ -33,6 +33,8 @@ RSpec.describe Blacklight::DocumentComponent, type: :component do
33
33
  end
34
34
 
35
35
  before do
36
+ # Every call to view_context returns a different object. This ensures it stays stable.
37
+ allow(controller).to receive(:view_context).and_return(view_context)
36
38
  allow(controller).to receive(:current_or_guest_user).and_return(User.new)
37
39
  allow(controller).to receive(:blacklight_config).and_return(blacklight_config)
38
40
  allow(view_context).to receive(:search_session).and_return({})
@@ -149,4 +151,19 @@ RSpec.describe Blacklight::DocumentComponent, type: :component do
149
151
  expect(rendered).to have_selector 'dd', text: 'Title'
150
152
  expect(rendered).not_to have_selector 'dt', text: 'ISBN:'
151
153
  end
154
+
155
+ context 'with a thumbnail component' do
156
+ let(:attr) { { thumbnail_component: thumbnail_component_class } }
157
+ let(:thumbnail_component_class) do
158
+ Class.new(ViewComponent::Base) do
159
+ def render_in(view_context)
160
+ view_context.capture { 'Thumb!' }
161
+ end
162
+ end
163
+ end
164
+
165
+ it 'uses the provided thumbnail component' do
166
+ expect(rendered).to have_content 'Thumb!'
167
+ end
168
+ end
152
169
  end