blacklight 7.13.2 → 7.15.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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +23 -3
  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/_icons.scss +5 -1
  8. data/app/assets/stylesheets/blacklight/blacklight_defaults.scss +5 -1
  9. data/app/components/blacklight/advanced_search_form_component.html.erb +46 -0
  10. data/app/components/blacklight/advanced_search_form_component.rb +75 -0
  11. data/app/components/blacklight/constraint_component.html.erb +1 -1
  12. data/app/components/blacklight/constraint_layout_component.html.erb +1 -1
  13. data/app/components/blacklight/constraints_component.rb +42 -17
  14. data/app/components/blacklight/document/thumbnail_component.html.erb +2 -2
  15. data/app/components/blacklight/document/thumbnail_component.rb +5 -2
  16. data/app/components/blacklight/document_component.rb +7 -2
  17. data/app/components/blacklight/document_metadata_component.rb +1 -1
  18. data/app/components/blacklight/facet_field_checkboxes_component.html.erb +23 -0
  19. data/app/components/blacklight/facet_field_checkboxes_component.rb +24 -0
  20. data/app/components/blacklight/facet_field_inclusive_constraint_component.html.erb +6 -0
  21. data/app/components/blacklight/facet_field_inclusive_constraint_component.rb +29 -0
  22. data/app/components/blacklight/facet_field_list_component.html.erb +1 -0
  23. data/app/components/blacklight/facet_field_pagination_component.rb +1 -1
  24. data/app/components/blacklight/facet_item_component.rb +4 -2
  25. data/app/components/blacklight/hidden_search_state_component.rb +54 -0
  26. data/app/components/blacklight/search_bar_component.html.erb +4 -0
  27. data/app/components/blacklight/search_bar_component.rb +4 -2
  28. data/app/controllers/concerns/blacklight/bookmarks.rb +1 -1
  29. data/app/controllers/concerns/blacklight/catalog.rb +6 -0
  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/javascript/blacklight/button_focus.js +1 -0
  37. data/app/javascript/blacklight/modal.js +10 -4
  38. data/app/models/concerns/blacklight/suggest/response.rb +1 -1
  39. data/app/presenters/blacklight/clause_presenter.rb +37 -0
  40. data/app/presenters/blacklight/document_presenter.rb +5 -1
  41. data/app/presenters/blacklight/facet_field_presenter.rb +4 -0
  42. data/app/presenters/blacklight/facet_grouped_item_presenter.rb +45 -0
  43. data/app/presenters/blacklight/facet_item_presenter.rb +32 -20
  44. data/app/presenters/blacklight/field_presenter.rb +1 -1
  45. data/app/presenters/blacklight/inclusive_facet_item_presenter.rb +16 -0
  46. data/app/presenters/blacklight/rendering/helper_method.rb +4 -4
  47. data/app/presenters/blacklight/search_bar_presenter.rb +4 -0
  48. data/app/views/bookmarks/_tools.html.erb +1 -1
  49. data/app/views/catalog/_advanced_search_form.html.erb +7 -0
  50. data/app/views/catalog/_advanced_search_help.html.erb +24 -0
  51. data/app/views/catalog/_search_form.html.erb +1 -0
  52. data/app/views/catalog/_zero_results.html.erb +1 -1
  53. data/app/views/catalog/advanced_search.html.erb +17 -0
  54. data/blacklight.gemspec +5 -4
  55. data/config/i18n-tasks.yml +1 -0
  56. data/config/locales/blacklight.en.yml +17 -0
  57. data/lib/blacklight/configuration.rb +2 -1
  58. data/lib/blacklight/configuration/field.rb +1 -1
  59. data/lib/blacklight/configuration/sort_field.rb +1 -1
  60. data/lib/blacklight/open_struct_with_hash_access.rb +18 -1
  61. data/lib/blacklight/routes/searchable.rb +1 -0
  62. data/lib/blacklight/search_builder.rb +2 -0
  63. data/lib/blacklight/search_state.rb +5 -1
  64. data/lib/blacklight/search_state/filter_field.rb +17 -7
  65. data/lib/blacklight/solr/repository.rb +11 -2
  66. data/lib/blacklight/solr/search_builder_behavior.rb +87 -23
  67. data/package.json +1 -1
  68. data/spec/components/blacklight/advanced_search_form_component_spec.rb +51 -0
  69. data/spec/components/blacklight/constraint_layout_component_spec.rb +1 -1
  70. data/spec/components/blacklight/document_component_spec.rb +17 -0
  71. data/spec/components/blacklight/facet_field_checkboxes_component_spec.rb +55 -0
  72. data/spec/components/blacklight/facet_field_list_component_spec.rb +39 -4
  73. data/spec/components/blacklight/hidden_search_state_component_spec.rb +24 -0
  74. data/spec/controllers/catalog_controller_spec.rb +9 -0
  75. data/spec/features/advanced_search_spec.rb +67 -0
  76. data/spec/features/bookmarks_spec.rb +1 -9
  77. data/spec/features/facets_spec.rb +2 -17
  78. data/spec/features/search_filters_spec.rb +0 -20
  79. data/spec/helpers/blacklight/hash_as_hidden_fields_behavior_spec.rb +1 -0
  80. data/spec/helpers/blacklight/url_helper_behavior_spec.rb +1 -0
  81. data/spec/lib/blacklight/open_struct_with_hash_access_spec.rb +8 -0
  82. data/spec/lib/blacklight/search_state/filter_field_spec.rb +65 -0
  83. data/spec/models/blacklight/solr/repository_spec.rb +12 -0
  84. data/spec/models/blacklight/solr/response/facets_spec.rb +1 -1
  85. data/spec/models/blacklight/solr/search_builder_spec.rb +28 -0
  86. data/spec/presenters/blacklight/clause_presenter_spec.rb +34 -0
  87. data/spec/presenters/blacklight/document_presenter_spec.rb +13 -0
  88. data/spec/presenters/blacklight/facet_grouped_item_presenter_spec.rb +41 -0
  89. data/spec/spec_helper.rb +8 -3
  90. data/spec/test_app_templates/Gemfile.extra +1 -1
  91. data/spec/views/catalog/_document.html.erb_spec.rb +1 -0
  92. data/spec/views/catalog/_thumbnail.html.erb_spec.rb +2 -0
  93. metadata +66 -27
@@ -58,8 +58,17 @@ 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
 
@@ -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
data/package.json CHANGED
@@ -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
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Blacklight::FacetFieldCheckboxesComponent, type: :component do
6
+ subject(:render) do
7
+ render_inline(described_class.new(facet_field: facet_field))
8
+ end
9
+
10
+ let(:rendered) do
11
+ Capybara::Node::Simple.new(render)
12
+ end
13
+
14
+ let(:facet_field) do
15
+ instance_double(
16
+ Blacklight::FacetFieldPresenter,
17
+ facet_field: Blacklight::Configuration::NullField.new(key: 'field'),
18
+ paginator: paginator,
19
+ key: 'field',
20
+ label: 'Field',
21
+ active?: false,
22
+ collapsed?: false,
23
+ modal_path: nil,
24
+ html_id: 'facet-field',
25
+ search_state: search_state
26
+ )
27
+ end
28
+
29
+ let(:paginator) do
30
+ instance_double(Blacklight::FacetPaginator, items: [
31
+ double(label: 'a', hits: 10, value: 'a'),
32
+ double(label: 'b', hits: 33, value: 'b'),
33
+ double(label: 'c', hits: 3, value: 'c')
34
+ ])
35
+ end
36
+
37
+ let(:search_state) { Blacklight::SearchState.new(params.with_indifferent_access, Blacklight::Configuration.new) }
38
+ let(:params) { { f: { field: ['a'] } } }
39
+
40
+ it 'renders a collapsible card' do
41
+ expect(rendered).to have_selector '.card'
42
+ expect(rendered).to have_button 'Field'
43
+ expect(rendered).to have_selector 'button[data-target="#facet-field"]'
44
+ expect(rendered).to have_selector '#facet-field.collapse.show'
45
+ end
46
+
47
+ it 'renders the facet items' do
48
+ expect(rendered).to have_selector 'ul.facet-values'
49
+ expect(rendered).to have_selector 'li', count: 3
50
+
51
+ expect(rendered).to have_field 'f_inclusive[field][]', with: 'a'
52
+ expect(rendered).to have_field 'f_inclusive[field][]', with: 'b'
53
+ expect(rendered).to have_field 'f_inclusive[field][]', with: 'c'
54
+ end
55
+ end
@@ -20,7 +20,8 @@ RSpec.describe Blacklight::FacetFieldListComponent, type: :component do
20
20
  active?: false,
21
21
  collapsed?: false,
22
22
  modal_path: nil,
23
- html_id: 'facet-field'
23
+ html_id: 'facet-field',
24
+ values: []
24
25
  )
25
26
  end
26
27
 
@@ -53,7 +54,8 @@ RSpec.describe Blacklight::FacetFieldListComponent, type: :component do
53
54
  active?: true,
54
55
  collapsed?: false,
55
56
  modal_path: nil,
56
- html_id: 'facet-field'
57
+ html_id: 'facet-field',
58
+ values: []
57
59
  )
58
60
  end
59
61
 
@@ -72,7 +74,8 @@ RSpec.describe Blacklight::FacetFieldListComponent, type: :component do
72
74
  active?: false,
73
75
  collapsed?: true,
74
76
  modal_path: nil,
75
- html_id: 'facet-field'
77
+ html_id: 'facet-field',
78
+ values: []
76
79
  )
77
80
  end
78
81
 
@@ -97,7 +100,8 @@ RSpec.describe Blacklight::FacetFieldListComponent, type: :component do
97
100
  active?: false,
98
101
  collapsed?: false,
99
102
  modal_path: '/catalog/facet/modal',
100
- html_id: 'facet-field'
103
+ html_id: 'facet-field',
104
+ values: []
101
105
  )
102
106
  end
103
107
 
@@ -105,4 +109,35 @@ RSpec.describe Blacklight::FacetFieldListComponent, type: :component do
105
109
  expect(rendered).to have_link 'more Field', href: '/catalog/facet/modal'
106
110
  end
107
111
  end
112
+
113
+ context 'with inclusive facets' do
114
+ let(:facet_field) do
115
+ instance_double(
116
+ Blacklight::FacetFieldPresenter,
117
+ paginator: paginator,
118
+ facet_field: Blacklight::Configuration::NullField.new(key: 'field'),
119
+ key: 'field',
120
+ label: 'Field',
121
+ active?: false,
122
+ collapsed?: false,
123
+ modal_path: nil,
124
+ html_id: 'facet-field',
125
+ values: [%w[a b c]],
126
+ search_state: search_state
127
+ )
128
+ end
129
+
130
+ let(:search_state) { Blacklight::SearchState.new(params.with_indifferent_access, Blacklight::Configuration.new) }
131
+ let(:params) { { f_inclusive: { field: %w[a b c] } } }
132
+
133
+ it 'displays the constraint above the list' do
134
+ expect(rendered).to have_content 'Any of:'
135
+ expect(rendered).to have_selector '.inclusive_or .facet-label', text: 'a'
136
+ expect(rendered).to have_link '[remove]', href: 'http://test.host/catalog?f_inclusive%5Bfield%5D%5B%5D=b&f_inclusive%5Bfield%5D%5B%5D=c'
137
+ expect(rendered).to have_selector '.inclusive_or .facet-label', text: 'b'
138
+ expect(rendered).to have_link '[remove]', href: 'http://test.host/catalog?f_inclusive%5Bfield%5D%5B%5D=a&f_inclusive%5Bfield%5D%5B%5D=c'
139
+ expect(rendered).to have_selector '.inclusive_or .facet-label', text: 'c'
140
+ expect(rendered).to have_link '[remove]', href: 'http://test.host/catalog?f_inclusive%5Bfield%5D%5B%5D=a&f_inclusive%5Bfield%5D%5B%5D=b'
141
+ end
142
+ end
108
143
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Blacklight::HiddenSearchStateComponent, type: :component do
4
+ subject(:render) { render_inline(instance) }
5
+
6
+ let(:params) do
7
+ { q: "query",
8
+ search_field: "search_field",
9
+ per_page: 10,
10
+ extra_arbitrary_key: "arbitrary_value",
11
+ f: { field1: %w[a b], field2: ["z"] } }
12
+ end
13
+ let(:instance) { described_class.new(params: params) }
14
+ let(:generated) { Capybara::Node::Simple.new("<div>#{render.to_html}</div>") }
15
+
16
+ it "converts a hash with nested complex data to Rails-style hidden form fields" do
17
+ expect(generated).to have_selector("input[type='hidden'][name='q'][value='query']", visible: :hidden)
18
+ expect(generated).to have_selector("input[type='hidden'][name='per_page'][value='10']", visible: :hidden)
19
+ expect(generated).to have_selector("input[type='hidden'][name='extra_arbitrary_key'][value='arbitrary_value']", visible: :hidden)
20
+ expect(generated).to have_selector("input[type='hidden'][name='f[field2][]'][value='z']", visible: :hidden)
21
+ expect(generated).to have_selector("input[type='hidden'][name='f[field1][]'][value='a']", visible: :hidden)
22
+ expect(generated).to have_selector("input[type='hidden'][name='f[field1][]'][value='b']", visible: :hidden)
23
+ end
24
+ end
@@ -301,6 +301,15 @@ RSpec.describe CatalogController, api: true do
301
301
  end
302
302
  end
303
303
 
304
+ describe 'GET advanced_search' do
305
+ it 'renders an advanced search form' do
306
+ get :advanced_search
307
+ expect(response).to be_successful
308
+
309
+ assert_facets_have_values(assigns(:response).aggregations)
310
+ end
311
+ end
312
+
304
313
  # SHOW ACTION
305
314
  describe "show action" do
306
315
  describe "with format :html" do