blacklight 9.0.0.beta1 → 9.0.0

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 (263) hide show
  1. checksums.yaml +4 -4
  2. data/.docker/app/Dockerfile +2 -1
  3. data/.github/matrix.json +55 -0
  4. data/.github/workflows/build.yml +16 -0
  5. data/.github/workflows/lint.yml +25 -0
  6. data/.github/workflows/main.yml +22 -0
  7. data/.github/workflows/release_7_x_scheduled.yml +39 -0
  8. data/.github/workflows/release_8_x_scheduled.yml +39 -0
  9. data/.github/workflows/test.yml +53 -0
  10. data/.rubocop.yml +81 -3
  11. data/.rubocop_todo.yml +46 -71
  12. data/.solr_wrapper.yml +3 -1
  13. data/README.md +2 -2
  14. data/VERSION +1 -1
  15. data/app/assets/builds/blacklight.css +51 -38
  16. data/app/assets/javascripts/blacklight/blacklight.esm.js +62 -77
  17. data/app/assets/javascripts/blacklight/blacklight.esm.js.map +1 -1
  18. data/app/assets/javascripts/blacklight/blacklight.js +62 -77
  19. data/app/assets/javascripts/blacklight/blacklight.js.map +1 -1
  20. data/app/assets/stylesheets/blacklight/_blacklight_base.scss +0 -1
  21. data/app/assets/stylesheets/blacklight/_bootstrap_overrides.scss +4 -0
  22. data/app/assets/stylesheets/blacklight/_facets.scss +37 -29
  23. data/app/assets/stylesheets/blacklight/_header.scss +4 -0
  24. data/app/assets/stylesheets/blacklight/_modal.scss +9 -8
  25. data/app/assets/stylesheets/blacklight/_pagination.scss +1 -3
  26. data/app/assets/stylesheets/blacklight/_search_history.scss +0 -4
  27. data/app/assets/stylesheets/blacklight/blacklight_defaults.scss +3 -0
  28. data/app/components/blacklight/advanced_search_form_component.html.erb +4 -4
  29. data/app/components/blacklight/advanced_search_form_component.rb +6 -5
  30. data/app/components/blacklight/constraint_component.html.erb +1 -5
  31. data/app/components/blacklight/constraint_component.rb +11 -0
  32. data/app/components/blacklight/constraint_layout_component.html.erb +1 -6
  33. data/app/components/blacklight/constraint_layout_component.rb +9 -0
  34. data/app/components/blacklight/constraints_component.html.erb +3 -8
  35. data/app/components/blacklight/constraints_component.rb +59 -14
  36. data/app/components/blacklight/document/action_component.rb +1 -3
  37. data/app/components/blacklight/document/bookmark_component.html.erb +3 -3
  38. data/app/components/blacklight/document/bookmark_component.rb +10 -4
  39. data/app/components/blacklight/document/group_component.html.erb +1 -1
  40. data/app/components/blacklight/document/more_like_this_component.rb +2 -2
  41. data/app/components/blacklight/document/page_header_component.rb +3 -3
  42. data/app/components/blacklight/document/thumbnail_component.html.erb +3 -7
  43. data/app/components/blacklight/document/thumbnail_component.rb +7 -6
  44. data/app/components/blacklight/document_component.html.erb +3 -3
  45. data/app/components/blacklight/document_component.rb +22 -26
  46. data/app/components/blacklight/document_title_component.rb +6 -10
  47. data/app/components/blacklight/facet_field_checkboxes_component.rb +4 -18
  48. data/app/components/blacklight/facet_field_component.rb +4 -15
  49. data/app/components/blacklight/facet_field_filter_component.rb +4 -19
  50. data/app/components/blacklight/facet_field_inclusive_constraint_component.rb +4 -23
  51. data/app/components/blacklight/facet_field_list_component.rb +4 -30
  52. data/app/components/blacklight/facet_field_no_layout_component.rb +4 -8
  53. data/app/components/blacklight/facet_field_pagination_component.html.erb +2 -2
  54. data/app/components/blacklight/facet_item_component.rb +4 -72
  55. data/app/components/blacklight/facet_item_pivot_component.rb +8 -4
  56. data/app/components/blacklight/{facet_field_checkboxes_component.html.erb → facets/checkboxes_component.html.erb} +4 -4
  57. data/app/components/blacklight/facets/checkboxes_component.rb +26 -0
  58. data/app/components/blacklight/facets/count_component.rb +23 -0
  59. data/app/components/blacklight/{facet_field_component.html.erb → facets/field_component.html.erb} +1 -3
  60. data/app/components/blacklight/facets/field_component.rb +23 -0
  61. data/app/components/blacklight/facets/filters_component.html.erb +4 -0
  62. data/app/components/blacklight/facets/filters_component.rb +39 -0
  63. data/app/components/blacklight/{facet_field_inclusive_constraint_component.html.erb → facets/inclusive_constraint_component.html.erb} +1 -1
  64. data/app/components/blacklight/facets/inclusive_constraint_component.rb +31 -0
  65. data/app/components/blacklight/{facet_field_filter_component.html.erb → facets/index_navigation_component.html.erb} +1 -1
  66. data/app/components/blacklight/facets/index_navigation_component.rb +32 -0
  67. data/app/components/blacklight/facets/item_component.rb +76 -0
  68. data/app/components/blacklight/facets/list_component.html.erb +11 -0
  69. data/app/components/blacklight/facets/list_component.rb +46 -0
  70. data/app/components/blacklight/facets/no_layout_component.rb +16 -0
  71. data/app/components/blacklight/facets/pivot_list_component.rb +14 -0
  72. data/app/components/blacklight/facets/selected_value_component.rb +29 -0
  73. data/app/components/blacklight/facets/suggest_component.html.erb +17 -0
  74. data/app/components/blacklight/facets/suggest_component.rb +21 -0
  75. data/app/components/blacklight/header_component.rb +2 -2
  76. data/app/components/blacklight/icons/remove_component.rb +2 -2
  77. data/app/components/blacklight/metadata_field_component.html.erb +2 -2
  78. data/app/components/blacklight/metadata_field_component.rb +2 -1
  79. data/app/components/blacklight/metadata_field_layout_component.rb +9 -4
  80. data/app/components/blacklight/metadata_field_plain_text_layout_component.rb +2 -2
  81. data/app/components/blacklight/response/facet_group_component.html.erb +3 -3
  82. data/app/components/blacklight/response/facet_group_component.rb +9 -1
  83. data/app/components/blacklight/search_bar_component.html.erb +2 -2
  84. data/app/components/blacklight/system/dropdown_component.html.erb +1 -1
  85. data/app/components/blacklight/system/dropdown_component.rb +1 -1
  86. data/app/components/blacklight/system/flash_message_component.html.erb +1 -1
  87. data/app/components/blacklight/system/flash_message_component.rb +0 -9
  88. data/app/components/blacklight/system/modal_component.html.erb +1 -2
  89. data/app/components/blacklight/top_navbar_component.html.erb +2 -2
  90. data/app/controllers/concerns/blacklight/bookmarks.rb +13 -3
  91. data/app/controllers/concerns/blacklight/catalog.rb +27 -30
  92. data/app/controllers/concerns/blacklight/controller.rb +1 -1
  93. data/app/controllers/concerns/blacklight/search_context.rb +1 -1
  94. data/app/controllers/concerns/blacklight/searchable.rb +1 -1
  95. data/app/helpers/blacklight/catalog_helper_behavior.rb +5 -3
  96. data/app/helpers/blacklight/configuration_helper_behavior.rb +2 -2
  97. data/app/helpers/blacklight/document_helper_behavior.rb +3 -1
  98. data/app/helpers/blacklight/facets_helper_behavior.rb +7 -12
  99. data/app/helpers/blacklight/icon_helper_behavior.rb +2 -2
  100. data/app/javascript/blacklight-frontend/bookmark_toggle.js +7 -2
  101. data/app/javascript/blacklight-frontend/button_focus.js +1 -1
  102. data/app/javascript/blacklight-frontend/checkbox_submit.js +9 -2
  103. data/app/javascript/blacklight-frontend/debounce.js +1 -1
  104. data/app/javascript/blacklight-frontend/facet_suggest.js +27 -3
  105. data/app/javascript/blacklight-frontend/index.js +0 -2
  106. data/app/javascript/blacklight-frontend/modal.js +15 -8
  107. data/app/javascript/blacklight-frontend/search_context.js +3 -2
  108. data/app/presenters/blacklight/constraint_presenter.rb +22 -0
  109. data/app/presenters/blacklight/document_presenter.rb +6 -5
  110. data/app/presenters/blacklight/facet_field_presenter.rb +18 -2
  111. data/app/presenters/blacklight/facet_grouped_item_presenter.rb +6 -0
  112. data/app/presenters/blacklight/facet_item_pivot_presenter.rb +1 -5
  113. data/app/presenters/blacklight/facet_item_presenter.rb +5 -18
  114. data/app/presenters/blacklight/field_presenter.rb +4 -2
  115. data/app/presenters/blacklight/json_presenter.rb +8 -4
  116. data/app/presenters/blacklight/rendering/abstract_step.rb +7 -1
  117. data/app/presenters/blacklight/rendering/helper_method.rb +4 -4
  118. data/app/presenters/blacklight/rendering/join.rb +11 -7
  119. data/app/presenters/blacklight/rendering/terminator.rb +1 -1
  120. data/app/services/blacklight/field_retriever.rb +1 -1
  121. data/app/services/blacklight/search_service.rb +10 -8
  122. data/app/values/blacklight/types.rb +2 -2
  123. data/app/views/blacklight/nav/_bookmark.html.erb +1 -1
  124. data/app/views/catalog/_document.atom.builder +1 -1
  125. data/app/views/catalog/_document.html.erb +1 -1
  126. data/app/views/catalog/_email_form.html.erb +4 -4
  127. data/app/views/catalog/_home_text.html.erb +2 -2
  128. data/app/views/catalog/_show_main_content.html.erb +9 -5
  129. data/app/views/catalog/_sms_form.html.erb +4 -4
  130. data/app/views/catalog/_sort_and_per_page.html.erb +1 -1
  131. data/app/views/catalog/advanced_search.html.erb +1 -1
  132. data/app/views/catalog/facet.html.erb +8 -10
  133. data/app/views/catalog/index.html.erb +0 -1
  134. data/app/views/catalog/index.json.jbuilder +5 -3
  135. data/app/views/catalog/show.html.erb +2 -2
  136. data/app/views/layouts/blacklight/base.html.erb +1 -1
  137. data/blacklight.gemspec +1 -1
  138. data/config/locales/blacklight.ar.yml +14 -14
  139. data/config/locales/blacklight.ca.yml +2 -2
  140. data/config/locales/blacklight.de.yml +13 -13
  141. data/config/locales/blacklight.en.yml +3 -3
  142. data/config/locales/blacklight.es.yml +13 -13
  143. data/config/locales/blacklight.fr.yml +14 -14
  144. data/config/locales/blacklight.hu.yml +15 -15
  145. data/config/locales/blacklight.it.yml +16 -16
  146. data/config/locales/blacklight.nl.yml +13 -13
  147. data/config/locales/blacklight.pt-BR.yml +15 -15
  148. data/config/locales/blacklight.sq.yml +14 -14
  149. data/config/locales/blacklight.zh.yml +12 -12
  150. data/lib/blacklight/abstract_repository.rb +2 -2
  151. data/lib/blacklight/component.rb +7 -1
  152. data/lib/blacklight/configuration/context.rb +3 -3
  153. data/lib/blacklight/configuration/facet_field.rb +11 -6
  154. data/lib/blacklight/configuration/field.rb +4 -4
  155. data/lib/blacklight/configuration/fields.rb +0 -1
  156. data/lib/blacklight/configuration/search_field.rb +1 -1
  157. data/lib/blacklight/configuration/view_config.rb +32 -18
  158. data/lib/blacklight/configuration.rb +64 -10
  159. data/lib/blacklight/nested_open_struct_with_hash_access.rb +1 -1
  160. data/lib/blacklight/open_struct_with_hash_access.rb +2 -2
  161. data/lib/blacklight/routes/searchable.rb +1 -1
  162. data/lib/blacklight/search_builder.rb +2 -1
  163. data/lib/blacklight/search_state/filter_field.rb +4 -4
  164. data/lib/blacklight/search_state/pivot_filter_field.rb +5 -5
  165. data/lib/blacklight/search_state.rb +2 -0
  166. data/lib/blacklight/solr/abstract_filter_query_builder.rb +77 -0
  167. data/lib/blacklight/solr/default_filter_query_builder.rb +20 -0
  168. data/lib/blacklight/solr/field_reflection_search_builder.rb +11 -0
  169. data/lib/blacklight/solr/repository.rb +17 -15
  170. data/lib/blacklight/solr/response/facets.rb +2 -2
  171. data/lib/blacklight/solr/response/params.rb +0 -4
  172. data/lib/blacklight/solr/response.rb +5 -1
  173. data/lib/blacklight/solr/search_builder_behavior.rb +48 -97
  174. data/lib/blacklight/solr/single_doc_search_builder.rb +25 -0
  175. data/lib/blacklight.rb +6 -4
  176. data/lib/generators/blacklight/assets/importmap_generator.rb +3 -5
  177. data/lib/generators/blacklight/assets_generator.rb +1 -1
  178. data/lib/generators/blacklight/search_builder_generator.rb +1 -1
  179. data/lib/generators/blacklight/templates/.solr_wrapper.yml +3 -1
  180. data/lib/generators/blacklight/templates/catalog_controller.rb +34 -8
  181. data/lib/generators/blacklight/templates/solr/conf/solrconfig.xml +0 -71
  182. data/package.json +3 -3
  183. data/spec/components/blacklight/advanced_search_form_component_spec.rb +18 -22
  184. data/spec/components/blacklight/constraint_layout_component_spec.rb +8 -8
  185. data/spec/components/blacklight/constraints_component_spec.rb +11 -11
  186. data/spec/components/blacklight/document/action_component_spec.rb +23 -15
  187. data/spec/components/blacklight/document/group_component_spec.rb +10 -15
  188. data/spec/components/blacklight/document/page_header_component_spec.rb +35 -28
  189. data/spec/components/blacklight/document/sidebar_component_spec.rb +5 -11
  190. data/spec/components/blacklight/document_component_spec.rb +105 -79
  191. data/spec/components/blacklight/facet_component_spec.rb +12 -8
  192. data/spec/components/blacklight/facet_item_pivot_component_spec.rb +12 -12
  193. data/spec/components/blacklight/{facet_field_checkboxes_component_spec.rb → facets/checkboxes_component_spec.rb} +13 -13
  194. data/spec/components/blacklight/facets/filters_component_spec.rb +36 -0
  195. data/spec/components/blacklight/facets/index_navigation_component_spec.rb +41 -0
  196. data/spec/components/blacklight/{facet_item_component_spec.rb → facets/item_component_spec.rb} +10 -10
  197. data/spec/components/blacklight/{facet_field_list_component_spec.rb → facets/list_component_spec.rb} +34 -23
  198. data/spec/components/blacklight/facets/pivot_list_component_spec.rb +53 -0
  199. data/spec/components/blacklight/facets/suggest_component_spec.rb +82 -0
  200. data/spec/components/blacklight/header_component_spec.rb +2 -4
  201. data/spec/components/blacklight/hidden_search_state_component_spec.rb +7 -7
  202. data/spec/components/blacklight/metadata_field_component_spec.rb +17 -15
  203. data/spec/components/blacklight/response/facet_group_component_spec.rb +37 -0
  204. data/spec/components/blacklight/response/pagination_component_spec.rb +1 -1
  205. data/spec/components/blacklight/response/spellcheck_component_spec.rb +1 -1
  206. data/spec/components/blacklight/search_bar_component_spec.rb +28 -5
  207. data/spec/components/blacklight/search_context/server_applied_params_component_spec.rb +2 -2
  208. data/spec/components/blacklight/search_context/server_item_pagination_component_spec.rb +3 -5
  209. data/spec/components/blacklight/skip_link_component_spec.rb +8 -11
  210. data/spec/components/blacklight/start_over_button_component_spec.rb +4 -4
  211. data/spec/components/blacklight/system/dropdown_component_spec.rb +26 -0
  212. data/spec/components/blacklight/system/flash_message_component_spec.rb +7 -11
  213. data/spec/controllers/blacklight/catalog_spec.rb +1 -1
  214. data/spec/controllers/catalog_controller_spec.rb +12 -20
  215. data/spec/features/advanced_search_spec.rb +42 -21
  216. data/spec/features/facets_spec.rb +84 -12
  217. data/spec/features/modal_spec.rb +1 -1
  218. data/spec/features/search_filters_spec.rb +6 -4
  219. data/spec/features/search_spec.rb +3 -3
  220. data/spec/helpers/blacklight/facets_helper_behavior_spec.rb +10 -0
  221. data/spec/lib/blacklight/configuration/facet_field_spec.rb +2 -2
  222. data/spec/lib/blacklight/parameters_spec.rb +12 -1
  223. data/spec/lib/blacklight/search_state/filter_field_spec.rb +18 -0
  224. data/spec/models/blacklight/configuration_spec.rb +178 -28
  225. data/spec/models/blacklight/solr/default_filter_query_builder_spec.rb +72 -0
  226. data/spec/models/blacklight/solr/document_spec.rb +0 -4
  227. data/spec/models/blacklight/solr/repository_spec.rb +37 -29
  228. data/spec/models/blacklight/solr/response/facets_spec.rb +86 -40
  229. data/spec/models/blacklight/solr/response/group_response_spec.rb +8 -5
  230. data/spec/models/blacklight/solr/response/group_spec.rb +9 -5
  231. data/spec/models/blacklight/solr/response_spec.rb +96 -64
  232. data/spec/models/blacklight/solr/search_builder_behavior_spec.rb +38 -57
  233. data/spec/models/solr_document_spec.rb +5 -1
  234. data/spec/presenters/blacklight/constraint_presenter_spec.rb +32 -0
  235. data/spec/presenters/blacklight/document_presenter_spec.rb +3 -3
  236. data/spec/presenters/blacklight/facet_item_presenter_spec.rb +0 -7
  237. data/spec/presenters/blacklight/field_presenter_spec.rb +103 -22
  238. data/spec/presenters/blacklight/json_presenter_spec.rb +17 -0
  239. data/spec/presenters/blacklight/rendering/pipeline_spec.rb +130 -14
  240. data/spec/services/blacklight/search_service_spec.rb +6 -27
  241. data/spec/spec_helper.rb +0 -1
  242. data/spec/support/presenter_test_helpers.rb +1 -1
  243. data/spec/support/view_component_test_helpers.rb +0 -18
  244. data/spec/views/catalog/facet.html.erb_spec.rb +10 -3
  245. data/spec/views/catalog/index.atom.builder_spec.rb +8 -3
  246. data/spec/views/catalog/index.html.erb_spec.rb +3 -1
  247. metadata +69 -38
  248. data/.github/workflows/ruby.yml +0 -98
  249. data/app/assets/stylesheets/blacklight/_icons.scss +0 -4
  250. data/app/components/blacklight/facet_field_list_component.html.erb +0 -19
  251. data/app/components/blacklight/search/facet_suggest_input.html.erb +0 -9
  252. data/app/components/blacklight/search/facet_suggest_input.rb +0 -16
  253. data/app/javascript/blacklight-frontend/modalForm.js +0 -60
  254. data/app/views/catalog/_facet_index_navigation.html.erb +0 -1
  255. data/app/views/catalog/_facet_layout.html.erb +0 -8
  256. data/app/views/catalog/_facet_pagination.html.erb +0 -1
  257. data/app/views/catalog/_facet_pivot.html.erb +0 -3
  258. data/app/views/shared/_sitelinks_search_box.html.erb +0 -12
  259. data/spec/components/blacklight/document_metadata_component_spec.rb +0 -0
  260. data/spec/components/blacklight/search/facet_suggest_input_spec.rb +0 -33
  261. data/spec/features/sitelinks_search_box_spec.rb +0 -13
  262. data/spec/views/catalog/_facet_index_navigation.html.erb_spec.rb +0 -43
  263. data/spec/views/catalog/_facet_layout.html.erb_spec.rb +0 -41
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Blacklight::ConstraintPresenter, type: :presenter do
6
+ subject(:presenter) do
7
+ described_class.new(facet_item_presenter: facet_item_presenter, field_label: field_label)
8
+ end
9
+
10
+ let(:facet_item_presenter) do
11
+ instance_double(Blacklight::FacetItemPresenter, label: 'item 1', remove_href: '/catalog')
12
+ end
13
+ let(:field_label) { 'field 1' }
14
+
15
+ describe '#constraint_label' do
16
+ subject { presenter.constraint_label }
17
+
18
+ it { is_expected.to eq 'item 1' }
19
+ end
20
+
21
+ describe '#field_label' do
22
+ subject { presenter.field_label }
23
+
24
+ it { is_expected.to eq 'field 1' }
25
+ end
26
+
27
+ describe '#remove_href' do
28
+ subject { presenter.remove_href }
29
+
30
+ it { is_expected.to eq '/catalog' }
31
+ end
32
+ end
@@ -29,13 +29,13 @@ RSpec.describe Blacklight::DocumentPresenter do
29
29
  end
30
30
 
31
31
  describe '#field_value' do
32
- let(:field_presenter) { instance_double(Blacklight::FieldPresenter, render: 'xyz') }
32
+ let(:field_presenter) { instance_double(Blacklight::FieldPresenter, render: ['xyz']) }
33
33
  let(:field_config) { Blacklight::Configuration::DisplayField.new }
34
34
  let(:options) { { a: 1 } }
35
35
 
36
36
  it 'calls the field presenter' do
37
37
  allow(Blacklight::FieldPresenter).to receive(:new).with(request_context, doc, field_config, options).and_return(field_presenter)
38
- expect(presenter.field_value(field_config, options)).to eq 'xyz'
38
+ expect(presenter.field_value(field_config, options)).to eq ['xyz']
39
39
  end
40
40
 
41
41
  it 'can be configured to use an alternate presenter' do
@@ -44,7 +44,7 @@ RSpec.describe Blacklight::DocumentPresenter do
44
44
  field_config.presenter = SomePresenter
45
45
  allow(SomePresenter).to receive(:new).and_return(instance)
46
46
 
47
- expect(presenter.field_value(field_config, options)).to eq 'abc'
47
+ expect(presenter.field_value(field_config, options)).to eq ['abc']
48
48
  end
49
49
  end
50
50
 
@@ -56,13 +56,6 @@ RSpec.describe Blacklight::FacetItemPresenter, type: :presenter do
56
56
  end
57
57
  end
58
58
 
59
- describe '#constraint_label' do
60
- it 'provides the label for the constraint' do
61
- allow(facet_config).to receive_messages(query: nil, date: nil, helper_method: nil, url_method: nil)
62
- expect(presenter.constraint_label).to eq presenter.label
63
- end
64
- end
65
-
66
59
  describe '#href' do
67
60
  let(:filter_field) { instance_double(Blacklight::SearchState::FilterField, include?: false) }
68
61
 
@@ -6,6 +6,8 @@ RSpec.describe Blacklight::FieldPresenter, :api do
6
6
  let(:request_context) { double('View context', params: { x: '1' }, search_state: search_state, should_render_field?: true, blacklight_config: config) }
7
7
  let(:document) do
8
8
  SolrDocument.new(id: 1,
9
+ 'join_true' => %w[a b c],
10
+ 'separator_options' => %w[d e f],
9
11
  'link_to_facet_true' => 'x',
10
12
  'link_to_facet_named' => 'x',
11
13
  'qwer' => 'document qwer value',
@@ -30,6 +32,8 @@ RSpec.describe Blacklight::FieldPresenter, :api do
30
32
  Blacklight::Configuration.new.configure do |config|
31
33
  config.add_index_field 'qwer'
32
34
  config.add_index_field 'asdf', helper_method: :render_asdf_index_field
35
+ config.add_index_field 'join_true', join: true
36
+ config.add_index_field 'separator_options', separator_options: { words_connector: '<br/>', last_word_connector: '<br/>' }
33
37
  config.add_index_field 'link_to_facet_true', link_to_facet: true
34
38
  config.add_index_field 'link_to_facet_named', link_to_facet: :some_field
35
39
  config.add_index_field 'highlight', highlight: true
@@ -40,6 +44,7 @@ RSpec.describe Blacklight::FieldPresenter, :api do
40
44
  config.add_index_field 'alias', field: 'qwer'
41
45
  config.add_index_field 'with_default', default: 'value'
42
46
  config.add_index_field 'with_steps', steps: [custom_step]
47
+ config.add_index_field :some_field
43
48
  config.add_facet_field :link_to_facet_true
44
49
  config.add_facet_field :some_field
45
50
  end
@@ -51,25 +56,33 @@ RSpec.describe Blacklight::FieldPresenter, :api do
51
56
  context 'when an explicit html value is provided' do
52
57
  let(:options) { { value: '<b>val1</b>' } }
53
58
 
54
- it { is_expected.not_to be_html_safe }
59
+ it { is_expected.to eq(['<b>val1</b>']) }
60
+
61
+ it 'does not mark value as html_safe' do
62
+ expect(result.first).not_to be_html_safe
63
+ end
55
64
  end
56
65
 
57
66
  context 'when an explicit array value with unsafe characters is provided' do
58
67
  let(:options) { { value: ['<a', 'b'] } }
59
68
 
60
- it { is_expected.to eq('&lt;a and b').and be_html_safe }
69
+ it { is_expected.to eq(%w[<a b]) }
70
+
71
+ it 'does not mark values as html_safe' do
72
+ expect(subject.all?(&:html_safe?)).not_to be true
73
+ end
61
74
  end
62
75
 
63
76
  context 'when an explicit array value is provided' do
64
77
  let(:options) { { value: %w[a b c] } }
65
78
 
66
- it { is_expected.to eq 'a, b, and c' }
79
+ it { is_expected.to eq %w[a b c] }
67
80
  end
68
81
 
69
82
  context 'when an explicit value is provided' do
70
83
  let(:options) { { value: 'asdf' } }
71
84
 
72
- it { is_expected.to eq 'asdf' }
85
+ it { is_expected.to eq ['asdf'] }
73
86
  end
74
87
 
75
88
  context 'when field has a helper method' do
@@ -77,7 +90,7 @@ RSpec.describe Blacklight::FieldPresenter, :api do
77
90
  allow(request_context).to receive(:render_asdf_index_field).and_return('custom asdf value')
78
91
  end
79
92
 
80
- it { is_expected.to eq 'custom asdf value' }
93
+ it { is_expected.to eq ['custom asdf value'] }
81
94
  end
82
95
 
83
96
  context 'when field has link_to_facet with true' do
@@ -88,7 +101,7 @@ RSpec.describe Blacklight::FieldPresenter, :api do
88
101
 
89
102
  let(:field_name) { 'link_to_facet_true' }
90
103
 
91
- it { is_expected.to eq 'bar' }
104
+ it { is_expected.to eq ['bar'] }
92
105
  end
93
106
 
94
107
  context 'when field has link_to_facet with a field name' do
@@ -99,7 +112,7 @@ RSpec.describe Blacklight::FieldPresenter, :api do
99
112
 
100
113
  let(:field_name) { 'link_to_facet_named' }
101
114
 
102
- it { is_expected.to eq 'bar' }
115
+ it { is_expected.to eq ['bar'] }
103
116
  end
104
117
 
105
118
  context 'when no highlight field is available' do
@@ -120,14 +133,14 @@ RSpec.describe Blacklight::FieldPresenter, :api do
120
133
 
121
134
  let(:field_name) { 'highlight' }
122
135
 
123
- it { is_expected.to eq '<em>highlight</em>' }
136
+ it { is_expected.to eq ['<em>highlight</em>'] }
124
137
  end
125
138
 
126
139
  context 'when no options are provided' do
127
140
  let(:field_name) { 'qwer' }
128
141
 
129
142
  it "checks the document field value" do
130
- expect(subject).to eq 'document qwer value'
143
+ expect(subject).to eq ['document qwer value']
131
144
  end
132
145
  end
133
146
 
@@ -138,7 +151,7 @@ RSpec.describe Blacklight::FieldPresenter, :api do
138
151
 
139
152
  let(:field_name) { 'solr_doc_accessor' }
140
153
 
141
- it { is_expected.to eq '123' }
154
+ it { is_expected.to eq ['123'] }
142
155
  end
143
156
 
144
157
  context 'when accessor is set to a value' do
@@ -147,7 +160,7 @@ RSpec.describe Blacklight::FieldPresenter, :api do
147
160
  it 'calls the accessor with the field_name as the argument' do
148
161
  expect(document).to receive(:solr_doc_accessor).with('explicit_accessor').and_return("123")
149
162
 
150
- expect(subject).to eq '123'
163
+ expect(subject).to eq ['123']
151
164
  end
152
165
  end
153
166
 
@@ -157,7 +170,7 @@ RSpec.describe Blacklight::FieldPresenter, :api do
157
170
  it 'calls the accessors on the return of the preceeding' do
158
171
  allow(document).to receive_message_chain(:solr_doc_accessor, some_method: "123")
159
172
 
160
- expect(subject).to eq '123'
173
+ expect(subject).to eq ['123']
161
174
  end
162
175
  end
163
176
 
@@ -165,26 +178,26 @@ RSpec.describe Blacklight::FieldPresenter, :api do
165
178
  let(:field_name) { 'explicit_values_with_context' }
166
179
 
167
180
  it 'calls the accessors on the return of the preceeding' do
168
- expect(subject).to eq '1'
181
+ expect(subject).to eq ['1']
169
182
  end
170
183
  end
171
184
 
172
185
  context 'when the field is an alias' do
173
186
  let(:field_name) { 'alias' }
174
187
 
175
- it { is_expected.to eq 'document qwer value' }
188
+ it { is_expected.to eq ['document qwer value'] }
176
189
  end
177
190
 
178
191
  context 'when the field has a default' do
179
192
  let(:field_name) { 'with_default' }
180
193
 
181
- it { is_expected.to eq 'value' }
194
+ it { is_expected.to eq ['value'] }
182
195
  end
183
196
 
184
197
  context 'with steps' do
185
198
  let(:field_name) { 'with_steps' }
186
199
 
187
- it { is_expected.to eq 'Static step' }
200
+ it { is_expected.to eq ['Static step'] }
188
201
  end
189
202
 
190
203
  context 'for a field with the helper_method option' do
@@ -200,12 +213,80 @@ RSpec.describe Blacklight::FieldPresenter, :api do
200
213
  args.first
201
214
  end
202
215
 
203
- expect(result).to include :document, :field, :value, :config, :a
204
- expect(result[:document]).to eq document
205
- expect(result[:field]).to eq 'field_with_helper'
206
- expect(result[:value]).to eq ['value']
207
- expect(result[:config]).to eq field_config
208
- expect(result[:a]).to eq 1
216
+ expect(result).to contain_exactly({ document: document, field: field_name,
217
+ value: ['value'], config: field_config, a: 1 })
218
+ end
219
+ end
220
+
221
+ context 'when joining a field' do
222
+ context 'with join config' do
223
+ let(:field_name) { :join_true }
224
+
225
+ it { is_expected.to eq ['a, b, and c'] }
226
+
227
+ it 'marks the value as html_safe' do
228
+ expect(result.first).to be_html_safe
229
+ end
230
+ end
231
+
232
+ context 'with separator_options' do
233
+ let(:field_name) { :separator_options }
234
+
235
+ it { is_expected.to eq ['d<br/>e<br/>f'] }
236
+
237
+ it 'marks the value as html_safe' do
238
+ expect(result.first).to be_html_safe
239
+ end
240
+ end
241
+
242
+ context 'with join option' do
243
+ let(:options) { { join: true } }
244
+ let(:field_name) { 'some_field' }
245
+
246
+ it { is_expected.to eq ['1 and 2'] }
247
+
248
+ it 'marks the value as html_safe' do
249
+ expect(result.first).to be_html_safe
250
+ end
251
+ end
252
+ end
253
+
254
+ context 'with a single value' do
255
+ let(:options) { { join: true, value: 1 } }
256
+
257
+ it 'is not joined' do
258
+ expect(result).to eq [1]
259
+ end
260
+ end
261
+
262
+ context 'with a single html value' do
263
+ let(:options) { { join: true, value: '<b>value</b>' } }
264
+
265
+ it { is_expected.to eq ['<b>value</b>'] }
266
+
267
+ it 'does not mark the value as html_safe' do
268
+ expect(result.first).not_to be_html_safe
269
+ end
270
+ end
271
+
272
+ context 'when an array containing unsafe characters provided' do
273
+ let(:options) { { join: true, value: ['<a', 'b'] } }
274
+
275
+ it 'html escapes the unsafe characters' do
276
+ expect(result).to eq ["&lt;a and b"]
277
+ end
278
+
279
+ it 'marks the value as html_safe' do
280
+ expect(result.first).to be_html_safe
281
+ end
282
+ end
283
+
284
+ context 'when outside the html context' do
285
+ let(:field_name) { :join_true }
286
+ let(:options) { { format: 'json' } }
287
+
288
+ it 'does not join the values' do
289
+ expect(result).to eq %w[a b c]
209
290
  end
210
291
  end
211
292
  end
@@ -56,5 +56,22 @@ RSpec.describe Blacklight::JsonPresenter, :api do
56
56
  expect(search_facets.map(&:name)).to eq ['format_si']
57
57
  end
58
58
  end
59
+
60
+ context 'when facets are in multiple groups' do
61
+ before do
62
+ config.add_facet_field 'author_tsim', label: 'author', group: 'other-group'
63
+ end
64
+
65
+ let(:aggregations) do
66
+ {
67
+ 'format_si' => Blacklight::Solr::Response::Facets::FacetField.new("format_si", [{ label: "Book", value: 'Book', hits: 20 }]),
68
+ 'author_tsim' => Blacklight::Solr::Response::Facets::FacetField.new("author_tsim", [{ label: "Julie", value: 'Julie', hits: 1 }])
69
+ }
70
+ end
71
+
72
+ it 'filters out the facets that are not defined' do
73
+ expect(search_facets.map(&:name)).to eq %w[format_si author_tsim]
74
+ end
75
+ end
59
76
  end
60
77
  end
@@ -12,20 +12,15 @@ RSpec.describe Blacklight::Rendering::Pipeline do
12
12
  let(:values) { %w[a b] }
13
13
  let(:field_config) { Blacklight::Configuration::NullField.new }
14
14
 
15
- it { is_expected.to eq "a and b" }
16
-
17
- context "when separator_options are in the config" do
18
- let(:values) { %w[c d] }
19
- let(:field_config) { Blacklight::Configuration::NullField.new(itemprop: nil, separator_options: { two_words_connector: '; ' }) }
20
-
21
- it { is_expected.to eq "c; d" }
22
- end
15
+ it { is_expected.to eq %w[a b] }
23
16
 
24
17
  context "when itemprop is in the config" do
25
18
  let(:values) { ['a'] }
26
19
  let(:field_config) { Blacklight::Configuration::NullField.new(itemprop: 'some-prop', separator_options: nil) }
27
20
 
28
- it { is_expected.to have_css("span[@itemprop='some-prop']", text: "a") }
21
+ it 'renders the expected markup' do
22
+ expect(rendered.first).to have_css("span[@itemprop='some-prop']", text: "a")
23
+ end
29
24
  end
30
25
 
31
26
  it 'sets the operations on the instance as equal to the class variable' do
@@ -37,13 +32,134 @@ RSpec.describe Blacklight::Rendering::Pipeline do
37
32
  end
38
33
 
39
34
  context 'outside of an HTML context' do
40
- let(:options) { { format: 'text' } }
35
+ context 'when options determines format' do
36
+ let(:options) { { format: 'text' } }
37
+
38
+ let(:values) { ['"blah"', "<notatag>"] }
39
+ let(:field_config) { Blacklight::Configuration::NullField.new itemprop: 'some-prop' }
40
+
41
+ it 'does not HTML escape values or inject HTML tags' do
42
+ expect(rendered).to eq ['"blah"', "<notatag>"]
43
+ end
44
+ end
45
+
46
+ context 'when context determines format' do
47
+ let(:values) { ['"blah"', "<notatag>"] }
48
+ let(:field_config) { Blacklight::Configuration::NullField.new itemprop: 'some-prop' }
49
+ let(:controller) { CatalogController.new }
50
+ let(:search_state) { Blacklight::SearchState.new({ format: 'text' }, controller.blacklight_config, controller) }
51
+
52
+ before { allow(context).to receive(:search_state).and_return(search_state) }
53
+
54
+ it 'does not HTML escape values or inject HTML tags' do
55
+ expect(rendered).to eq ['"blah"', '<notatag>']
56
+ end
57
+ end
58
+ end
59
+
60
+ context 'when link_to_facet is in the config' do
61
+ let(:values) { %w[book manuscript] }
62
+ let(:field_config) { Blacklight::Configuration::Field.new(field: 'format', key: 'format', link_to_facet: true) }
63
+ let(:controller) { CatalogController.new }
64
+ let(:search_state) { Blacklight::SearchState.new({}, controller.blacklight_config, controller) }
65
+
66
+ before do
67
+ allow(context).to receive(:search_state).and_return(search_state)
68
+ allow(context).to receive(:search_action_path) { |f| "/catalog?f[format][]=#{f['f']['format'].first}" }
69
+ allow(context).to receive(:link_to) { |value, link| ActiveSupport::SafeBuffer.new("<a href=#{link}>#{value}</a>") }
70
+ end
71
+
72
+ it 'renders html' do
73
+ expect(rendered).to eq ["<a href=/catalog?f[format][]=book>book</a>", "<a href=/catalog?f[format][]=manuscript>manuscript</a>"]
74
+ end
75
+
76
+ context 'outside html context' do
77
+ let(:values) { %w[book manuscript] }
78
+ let(:field_config) { Blacklight::Configuration::Field.new(field: 'format', link_to_facet: true) }
79
+ let(:search_state) { Blacklight::SearchState.new({ format: 'json' }, CatalogController.blacklight_config, CatalogController.new) }
80
+
81
+ it 'does not render html' do
82
+ expect(rendered).to eq %w[book manuscript]
83
+ end
84
+ end
85
+ end
86
+
87
+ context 'when joining values' do
88
+ context 'with join in the config' do
89
+ let(:field_config) { Blacklight::Configuration::NullField.new(join: true) }
90
+
91
+ it 'joins the values' do
92
+ expect(rendered).to eq ['a and b']
93
+ end
94
+ end
95
+
96
+ context 'with join is in the options' do
97
+ let(:options) { { join: true } }
98
+ let(:field_config) { Blacklight::Configuration::NullField.new }
99
+
100
+ it 'joins the values' do
101
+ expect(rendered).to eq ['a and b']
102
+ end
103
+ end
104
+
105
+ context 'with separator_options in the config' do
106
+ let(:values) { %w[c d] }
107
+ let(:field_config) { Blacklight::Configuration::NullField.new(separator_options: { two_words_connector: '; ' }) }
108
+
109
+ it { is_expected.to eq ["c; d"] }
110
+ end
111
+
112
+ context 'with a single value' do
113
+ let(:values) { [1] }
114
+ let(:field_config) { Blacklight::Configuration::NullField.new(join: true) }
115
+
116
+ it 'does not run the join step' do
117
+ expect(rendered).to eq [1]
118
+ end
119
+ end
120
+
121
+ context 'with a single html value' do
122
+ let(:values) { ['<b>value</b>'] }
123
+ let(:field_config) { Blacklight::Configuration::NullField.new(join: true) }
124
+
125
+ it 'does not escape the html' do
126
+ expect(rendered).to eq ['<b>value</b>']
127
+ end
128
+
129
+ it 'does not mark the value as html_safe' do
130
+ expect(rendered.first).not_to be_html_safe
131
+ end
132
+ end
133
+
134
+ context 'with an array of values containing unsafe characters' do
135
+ let(:values) { ['<a', 'b'] }
136
+ let(:field_config) { Blacklight::Configuration::NullField.new(join: true) }
137
+
138
+ it 'escapes the unsafe characters' do
139
+ expect(rendered).to eq ["&lt;a and b"]
140
+ end
141
+
142
+ it 'marks the joined value as html_safe' do
143
+ expect(rendered.first).to be_html_safe
144
+ end
145
+ end
146
+
147
+ context 'outside the html context' do
148
+ let(:values) { %w[a b <c] }
149
+ let(:field_config) { Blacklight::Configuration::Field.new(join: true) }
150
+ let(:options) { { format: 'json' } }
151
+
152
+ it 'does not run the join step' do
153
+ expect(rendered).to eq %w[a b <c]
154
+ end
41
155
 
42
- let(:values) { ['"blah"', "<notatag>"] }
43
- let(:field_config) { Blacklight::Configuration::NullField.new itemprop: 'some-prop' }
156
+ it 'does not escape unsafe html characters' do
157
+ expect(rendered.last).to eq '<c'
158
+ end
44
159
 
45
- it 'does not HTML escape values or inject HTML tags' do
46
- expect(rendered).to eq '"blah" and <notatag>'
160
+ it 'does not mark the values as html_safe' do
161
+ expect(rendered.none?(&:html_safe?)).to be true
162
+ end
47
163
  end
48
164
  end
49
165
  end
@@ -12,22 +12,19 @@ RSpec.describe Blacklight::SearchService, :api do
12
12
  subject { service }
13
13
 
14
14
  let(:context) { { whatever: :value } }
15
- let(:service) { described_class.new(config: blacklight_config, user_params: user_params, **context) }
15
+ let(:search_state) { Blacklight::SearchState.new(user_params, blacklight_config) }
16
+ let(:service) { described_class.new(config: blacklight_config, search_state: search_state, **context) }
16
17
  let(:repository) { Blacklight::Solr::Repository.new(blacklight_config) }
17
18
  let(:user_params) { {} }
18
19
 
19
- let(:blacklight_config) { Blacklight::Configuration.new }
20
- let(:copy_of_catalog_config) { CatalogController.blacklight_config.deep_copy }
21
- let(:blacklight_solr) { RSolr.connect(Blacklight.connection_config.except(:adapter)) }
20
+ let(:blacklight_config) { CatalogController.blacklight_config.deep_copy }
22
21
 
23
22
  let(:all_docs_query) { '' }
24
23
  let(:no_docs_query) { 'zzzzzzzzzzzz' }
25
- # f[format][]=Book&f[language_facet][]=English
26
24
  let(:single_facet) { { format: 'Book' } }
27
25
 
28
26
  before do
29
27
  allow(service).to receive(:repository).and_return(repository)
30
- service.repository.connection = blacklight_solr
31
28
  end
32
29
 
33
30
  describe '#search_builder_class' do
@@ -39,8 +36,7 @@ RSpec.describe Blacklight::SearchService, :api do
39
36
 
40
37
  context 'when the search_builder_class is passed in' do
41
38
  let(:klass) { double("Search builder") }
42
-
43
- let(:service) { described_class.new(config: blacklight_config, user_params: user_params, search_builder_class: klass) }
39
+ let(:context) { { search_builder_class: klass } }
44
40
 
45
41
  it 'uses the passed value' do
46
42
  expect(subject).to eq klass
@@ -50,16 +46,15 @@ RSpec.describe Blacklight::SearchService, :api do
50
46
 
51
47
  # SPECS FOR SEARCH RESULTS FOR QUERY
52
48
  describe 'Search Results', :integration do
53
- let(:blacklight_config) { copy_of_catalog_config }
54
-
55
49
  describe 'for a sample query returning results' do
56
50
  let(:user_params) { { q: all_docs_query } }
51
+ let(:blacklight_solr) { service.repository.connection }
57
52
 
58
53
  it "uses the configured request handler" do
59
54
  allow(blacklight_config).to receive(:default_solr_params).and_return(qt: 'custom_request_handler')
60
55
  allow(blacklight_solr).to receive(:send_and_receive) do |path, params|
61
56
  expect(path).to eq 'select'
62
- expect(params[:params]['facet.field']).to eq ["format", "{!ex=pub_date_ssim_single}pub_date_ssim", "subject_ssim", "language_ssim", "lc_1letter_ssim", "subject_geo_ssim", "subject_era_ssim"]
57
+ expect(params[:params]['facet.field']).to contain_exactly "format", "{!ex=pub_date_ssim_single}pub_date_ssim", "subject_ssim", "language_ssim", "lc_1letter_ssim", "subject_geo_ssim", "subject_era_ssim"
63
58
  expect(params[:params]["facet.query"]).to eq ["pub_date_ssim:[#{5.years.ago.year} TO *]", "pub_date_ssim:[#{10.years.ago.year} TO *]", "pub_date_ssim:[#{25.years.ago.year} TO *]"]
64
59
  expect(params[:params]).to include('rows' => 10, 'qt' => "custom_request_handler", 'q' => "", "f.subject_ssim.facet.limit" => 21, 'sort' => "score desc, pub_date_si desc, title_si asc")
65
60
  end.and_return('response' => { 'docs' => [] })
@@ -68,7 +63,6 @@ RSpec.describe Blacklight::SearchService, :api do
68
63
  end
69
64
 
70
65
  describe "for a query returning a grouped response" do
71
- let(:blacklight_config) { copy_of_catalog_config }
72
66
  let(:user_params) { { q: all_docs_query } }
73
67
 
74
68
  before do
@@ -82,7 +76,6 @@ RSpec.describe Blacklight::SearchService, :api do
82
76
  end
83
77
 
84
78
  describe "for a query returning multiple groups", :integration do
85
- let(:blacklight_config) { copy_of_catalog_config }
86
79
  let(:user_params) { { q: all_docs_query } }
87
80
 
88
81
  before do
@@ -140,7 +133,6 @@ RSpec.describe Blacklight::SearchService, :api do
140
133
 
141
134
  # SPECS FOR SEARCH RESULTS FOR FACETS
142
135
  describe 'Facets in Search Results for All Docs Query', :integration do
143
- let(:blacklight_config) { copy_of_catalog_config }
144
136
  let(:user_params) { { q: all_docs_query } }
145
137
 
146
138
  before do
@@ -185,7 +177,6 @@ RSpec.describe Blacklight::SearchService, :api do
185
177
 
186
178
  # SPECS FOR SEARCH RESULTS FOR PAGING
187
179
  describe 'Paging', :integration do
188
- let(:blacklight_config) { copy_of_catalog_config }
189
180
  let(:user_params) { { q: all_docs_query } }
190
181
 
191
182
  it 'starts with first results by default' do
@@ -379,18 +370,6 @@ RSpec.describe Blacklight::SearchService, :api do
379
370
  # TODO: maybe eventually check other types of solr requests
380
371
  # more like this
381
372
  # nearby on shelf
382
- it "raises a Blacklight exception if RSolr can't connect to the Solr instance" do
383
- allow(blacklight_solr).to receive(:send_and_receive).and_raise(Errno::ECONNREFUSED)
384
- expect { service.repository.search }.to raise_exception(/Unable to connect to Solr instance/)
385
- end
386
-
387
- it "raises a Blacklight exception if RSolr raises a timeout error connecting to Solr instance" do
388
- rsolr_timeout = RSolr::Error::Timeout.new(nil, nil)
389
- allow(rsolr_timeout).to receive(:to_s).and_return("mocked RSolr timeout")
390
-
391
- allow(blacklight_solr).to receive(:send_and_receive).and_raise(rsolr_timeout)
392
- expect { service.repository.search }.to raise_exception(Blacklight::Exceptions::RepositoryTimeout, /Timeout connecting to Solr instance/)
393
- end
394
373
 
395
374
  describe "#previous_and_next_documents_for_search" do
396
375
  let(:user_params) { { q: '', per_page: 100 } }
data/spec/spec_helper.rb CHANGED
@@ -21,7 +21,6 @@ require 'selenium-webdriver'
21
21
  require 'equivalent-xml'
22
22
  require 'axe-rspec'
23
23
 
24
- require 'rsolr'
25
24
  require 'blacklight'
26
25
 
27
26
  Capybara.javascript_driver = :headless_chrome
@@ -2,7 +2,7 @@
2
2
 
3
3
  module PresenterTestHelpers
4
4
  def controller
5
- @controller ||= ViewComponent::Base.test_controller.constantize.new.tap { |c| c.request = request }.extend(Rails.application.routes.url_helpers)
5
+ @controller ||= ApplicationController.new.tap { |c| c.request = request }.extend(Rails.application.routes.url_helpers)
6
6
  end
7
7
 
8
8
  def request
@@ -1,24 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ViewComponentTestHelpers
4
- # Work around for https://github.com/teamcapybara/capybara/issues/2466
5
- def render_inline_to_capybara_node(component)
6
- Capybara::Node::Simple.new(render_inline(component).to_s)
7
- end
8
-
9
- # Work-around for https://github.com/ViewComponent/view_component/pull/1661
10
- # which made the component test's controller method (more) private. This makes
11
- # it available so we can set up controller-level state for our tests.
12
- def controller
13
- # ViewComponent 2.x
14
- return super if defined?(super)
15
-
16
- # ViewComponent 3.x
17
- return vc_test_controller if defined?(vc_test_controller)
18
-
19
- ApplicationController.new.extend(Rails.application.routes.url_helpers)
20
- end
21
-
22
4
  # Nokogiri 1.15.0 upgrades the vendored libxml2 from v2.10.4 to v2.11.3
23
5
  # libxml2 v2.11.0 introduces a change to parsing HTML href attributes
24
6
  # in nokogiri < 1.15, brackets in href attributes are escaped: