blacklight 7.33.0 → 7.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.env +1 -0
  3. data/.github/workflows/ruby.yml +21 -1
  4. data/.rubocop.yml +2 -0
  5. data/Gemfile +4 -0
  6. data/VERSION +1 -1
  7. data/app/assets/javascripts/blacklight/blacklight.js +52 -82
  8. data/app/builders/blacklight/action_builder.rb +1 -1
  9. data/app/components/blacklight/advanced_search_form_component.html.erb +1 -1
  10. data/app/components/blacklight/advanced_search_form_component.rb +5 -5
  11. data/app/components/blacklight/content_areas_shim.rb +1 -1
  12. data/app/components/blacklight/document/action_component.html.erb +2 -9
  13. data/app/components/blacklight/document/action_component.rb +18 -0
  14. data/app/components/blacklight/document/actions_component.rb +1 -1
  15. data/app/components/blacklight/document_component.rb +33 -10
  16. data/app/components/blacklight/document_metadata_component.html.erb +4 -2
  17. data/app/components/blacklight/document_metadata_component.rb +6 -2
  18. data/app/components/blacklight/facet_field_checkboxes_component.html.erb +2 -2
  19. data/app/components/blacklight/facet_field_list_component.html.erb +2 -2
  20. data/app/components/blacklight/header_component.html.erb +2 -0
  21. data/app/components/blacklight/header_component.rb +26 -0
  22. data/app/components/blacklight/metadata_field_component.html.erb +2 -2
  23. data/app/components/blacklight/metadata_field_layout_component.rb +7 -4
  24. data/app/components/blacklight/response/pagination_component.html.erb +1 -1
  25. data/app/components/blacklight/response/pagination_component.rb +5 -1
  26. data/app/components/blacklight/response/view_type_component.rb +1 -1
  27. data/app/components/blacklight/search_navbar_component.html.erb +5 -0
  28. data/app/components/blacklight/search_navbar_component.rb +34 -0
  29. data/app/components/blacklight/system/dropdown_component.rb +2 -2
  30. data/app/components/blacklight/system/flash_message_component.rb +1 -1
  31. data/app/components/blacklight/top_navbar_component.html.erb +12 -0
  32. data/app/components/blacklight/top_navbar_component.rb +17 -0
  33. data/app/controllers/concerns/blacklight/base.rb +5 -0
  34. data/app/controllers/concerns/blacklight/catalog.rb +4 -1
  35. data/app/controllers/concerns/blacklight/controller.rb +3 -2
  36. data/app/helpers/blacklight/blacklight_helper_behavior.rb +2 -2
  37. data/app/helpers/blacklight/render_partials_helper_behavior.rb +1 -0
  38. data/app/models/concerns/blacklight/document/attributes.rb +50 -0
  39. data/app/models/concerns/blacklight/document.rb +12 -20
  40. data/app/presenters/blacklight/rendering/join.rb +1 -1
  41. data/app/values/blacklight/types.rb +99 -11
  42. data/app/views/catalog/_document.html.erb +1 -1
  43. data/app/views/catalog/_facet_layout.html.erb +2 -2
  44. data/app/views/catalog/_search_form.html.erb +1 -1
  45. data/app/views/catalog/_show_main_content.html.erb +2 -2
  46. data/app/views/catalog/_show_sidebar.html.erb +1 -1
  47. data/app/views/catalog/_show_tools.html.erb +4 -3
  48. data/app/views/catalog/facet.html.erb +3 -3
  49. data/app/views/layouts/blacklight/base.html.erb +7 -7
  50. data/app/views/shared/_header_navbar.html.erb +1 -22
  51. data/blacklight.gemspec +1 -2
  52. data/config/locales/blacklight.en.yml +1 -0
  53. data/docker-compose.yml +1 -0
  54. data/lib/blacklight/configuration.rb +44 -3
  55. data/lib/blacklight/engine.rb +4 -0
  56. data/lib/blacklight/solr/repository.rb +14 -2
  57. data/lib/blacklight/solr/request.rb +2 -0
  58. data/lib/blacklight/solr/search_builder_behavior.rb +2 -1
  59. data/lib/blacklight.rb +1 -1
  60. data/lib/generators/blacklight/assets_generator.rb +1 -1
  61. data/lib/generators/blacklight/install_generator.rb +1 -1
  62. data/lib/generators/blacklight/templates/catalog_controller.rb +4 -0
  63. data/lib/generators/blacklight/templates/solr/conf/solrconfig.xml +69 -0
  64. data/lib/generators/blacklight/templates/solr_document.rb +1 -1
  65. data/lib/railties/blacklight.rake +4 -4
  66. data/package.json +2 -2
  67. data/spec/components/blacklight/document_component_spec.rb +60 -11
  68. data/spec/components/blacklight/facet_item_pivot_component_spec.rb +3 -2
  69. data/spec/components/blacklight/header_component_spec.rb +20 -0
  70. data/spec/components/blacklight/search_bar_component_spec.rb +1 -1
  71. data/spec/controllers/blacklight/base_spec.rb +1 -1
  72. data/spec/controllers/catalog_controller_spec.rb +8 -0
  73. data/spec/features/advanced_search_spec.rb +56 -0
  74. data/spec/features/axe_spec.rb +5 -0
  75. data/spec/features/sitelinks_search_box_spec.rb +13 -0
  76. data/spec/helpers/blacklight/search_history_constraints_helper_behavior_spec.rb +8 -15
  77. data/spec/models/blacklight/configuration_spec.rb +22 -0
  78. data/spec/models/blacklight/solr/repository_spec.rb +27 -0
  79. data/spec/models/blacklight/solr/search_builder_spec.rb +16 -0
  80. data/spec/models/solr_document_spec.rb +21 -3
  81. data/spec/presenters/blacklight/show_presenter_spec.rb +4 -10
  82. data/spec/spec_helper.rb +4 -5
  83. data/spec/support/view_component_test_helpers.rb +35 -0
  84. data/spec/views/catalog/_show_tools.html.erb_spec.rb +24 -10
  85. metadata +24 -23
  86. data/spec/features/sitelinks_search_box.rb +0 -13
  87. data/spec/support/view_component_capybara_test_helpers.rb +0 -8
@@ -74,6 +74,10 @@ RSpec.describe Blacklight::DocumentComponent, type: :component do
74
74
  end
75
75
 
76
76
  context 'index view' do
77
+ before do
78
+ controller.action_name = "index"
79
+ end
80
+
77
81
  let(:attr) { { counter: 5 } }
78
82
 
79
83
  it 'has data properties' do
@@ -92,9 +96,12 @@ RSpec.describe Blacklight::DocumentComponent, type: :component do
92
96
  end
93
97
 
94
98
  context 'with a document rendered as part of a collection' do
95
- let(:attr) { { document_counter: 10, counter_offset: 100 } }
99
+ # ViewComponent 3 changes iteration counters to begin at 0 rather than 1
100
+ let(:document_counter) { ViewComponent::VERSION::MAJOR < 3 ? 11 : 10 }
101
+ let(:attr) { { document_counter: document_counter, counter_offset: 100 } }
96
102
 
97
103
  it 'renders a counter with the title' do
104
+ # after ViewComponent 2.5, collection counter params are 1-indexed
98
105
  expect(rendered).to have_selector 'header', text: '111. Title'
99
106
  end
100
107
  end
@@ -106,13 +113,22 @@ RSpec.describe Blacklight::DocumentComponent, type: :component do
106
113
  it 'renders a thumbnail' do
107
114
  expect(rendered).to have_selector 'a[href="/catalog/x"] img[src="http://example.com/image.jpg"]'
108
115
  end
116
+
117
+ context 'with default metadata component' do
118
+ it 'renders metadata' do
119
+ expect(rendered).to have_selector 'dl.document-metadata'
120
+ expect(rendered).to have_selector 'dt', text: 'Title:'
121
+ expect(rendered).to have_selector 'dd', text: 'Title'
122
+ expect(rendered).not_to have_selector 'dt', text: 'ISBN:'
123
+ end
124
+ end
109
125
  end
110
126
 
111
127
  context 'show view' do
112
128
  let(:attr) { { title_component: :h1, show: true } }
113
129
 
114
130
  before do
115
- allow(view_context).to receive(:action_name).and_return('show')
131
+ controller.action_name = "show"
116
132
  end
117
133
 
118
134
  it 'renders with an id' do
@@ -143,13 +159,46 @@ RSpec.describe Blacklight::DocumentComponent, type: :component do
143
159
  blacklight_config.show.embed_component = StubComponent
144
160
  expect(rendered).to have_content 'embed'
145
161
  end
146
- end
147
162
 
148
- it 'renders metadata' do
149
- expect(rendered).to have_selector 'dl.document-metadata'
150
- expect(rendered).to have_selector 'dt', text: 'Title:'
151
- expect(rendered).to have_selector 'dd', text: 'Title'
152
- expect(rendered).not_to have_selector 'dt', text: 'ISBN:'
163
+ context 'with configured metadata component' do
164
+ let(:custom_component_class) do
165
+ Class.new(Blacklight::DocumentMetadataComponent) do
166
+ # Override component rendering with our own value
167
+ def call
168
+ 'blah'
169
+ end
170
+ end
171
+ end
172
+
173
+ before do
174
+ stub_const('MyMetadataComponent', custom_component_class)
175
+ blacklight_config.show.metadata_component = MyMetadataComponent
176
+ end
177
+
178
+ it 'renders custom component' do
179
+ expect(rendered).to have_text 'blah'
180
+ end
181
+ end
182
+
183
+ context 'with configured title component' do
184
+ let(:custom_component_class) do
185
+ Class.new(Blacklight::DocumentTitleComponent) do
186
+ # Override component rendering with our own value
187
+ def call
188
+ 'Titleriffic'
189
+ end
190
+ end
191
+ end
192
+
193
+ before do
194
+ stub_const('MyTitleComponent', custom_component_class)
195
+ blacklight_config.show.title_component = MyTitleComponent
196
+ end
197
+
198
+ it 'renders custom component' do
199
+ expect(rendered).to have_text 'Titleriffic'
200
+ end
201
+ end
153
202
  end
154
203
 
155
204
  context 'with a thumbnail component' do
@@ -167,11 +216,11 @@ RSpec.describe Blacklight::DocumentComponent, type: :component do
167
216
  end
168
217
  end
169
218
 
170
- context 'with before_title' do
219
+ context 'with before_titles' do
171
220
  let(:render) do
172
221
  component.render_in(view_context) do
173
- component.title do |c|
174
- c.before_title { 'Prefix!' }
222
+ component.with_title do |c|
223
+ c.with_before_title { 'Prefix!' }
175
224
  end
176
225
  end
177
226
  end
@@ -33,13 +33,14 @@ RSpec.describe Blacklight::FacetItemPivotComponent, type: :component do
33
33
 
34
34
  it 'links to the facet and shows the number of hits' do
35
35
  expect(rendered).to have_selector 'li'
36
- expect(rendered).to have_link 'x', href: '/catalog?f%5Bz%5D=x'
36
+ expect(rendered).to have_link 'x', href: nokogiri_mediated_href(facet_item.href)
37
37
  expect(rendered).to have_selector '.facet-count', text: '10'
38
38
  end
39
39
 
40
40
  it 'has the facet hierarchy' do
41
+ pending
41
42
  expect(rendered).to have_selector 'li ul.pivot-facet'
42
- expect(rendered).to have_link 'x:1', href: /f%5Bz%5D%5B%5D=x%3A1/
43
+ expect(rendered).to have_link 'x:1', href: nokogiri_mediated_href(facet_item.facet_item_presenters.first.href)
43
44
  end
44
45
 
45
46
  context 'with a selected facet' do
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Blacklight::HeaderComponent, type: :component do
4
+ before do
5
+ with_controller_class(CatalogController) do
6
+ allow(controller).to receive(:current_user).and_return(nil)
7
+ allow(controller).to receive(:search_action_url).and_return('/search')
8
+ render
9
+ end
10
+ end
11
+
12
+ context 'with no slots' do
13
+ let(:render) { render_inline(described_class.new(blacklight_config: CatalogController.blacklight_config)) }
14
+
15
+ it 'draws the topbar' do
16
+ expect(page).to have_css 'nav.topbar'
17
+ expect(page).to have_link 'Blacklight', href: '/'
18
+ end
19
+ end
20
+ end
@@ -29,7 +29,7 @@ RSpec.describe Blacklight::SearchBarComponent, type: :component do
29
29
  context 'when a button is passed in' do
30
30
  subject(:render) do
31
31
  render_inline(instance) do |c|
32
- c.search_button do
32
+ c.with_search_button do
33
33
  controller.view_context.tag.button "hello", id: 'custom_search'
34
34
  end
35
35
  end
@@ -3,7 +3,7 @@
3
3
  RSpec.describe Blacklight::Base do
4
4
  subject { controller }
5
5
 
6
- let(:controller) { (Class.new(ApplicationController) { include Blacklight::Base }).new }
6
+ let(:controller) { Deprecation.silence(described_class) { (Class.new(ApplicationController) { include Blacklight::Base }).new } }
7
7
 
8
8
  describe "#search_state" do
9
9
  subject { controller.send(:search_state) }
@@ -535,6 +535,10 @@ RSpec.describe CatalogController, api: true do
535
535
  post :email, xhr: true, params: { id: doc_id, to: 'test_email@projectblacklight.org' }
536
536
  expect(request).to render_template 'email_success'
537
537
  expect(request.flash[:success]).to eq "Email Sent"
538
+ allow(search_service).to receive(:search_results)
539
+ # When we go to another page, the flash message should no longer display
540
+ get :index
541
+ expect(request.flash[:success]).to be_nil
538
542
  end
539
543
  end
540
544
 
@@ -589,6 +593,10 @@ RSpec.describe CatalogController, api: true do
589
593
  post :sms, xhr: true, params: { id: doc_id, to: '5555555555', carrier: 'txt.att.net' }
590
594
  expect(request).to render_template 'sms_success'
591
595
  expect(request.flash[:success]).to eq "SMS Sent"
596
+ allow(search_service).to receive(:search_results)
597
+ # When we go to another page, the flash message should no longer display
598
+ get :index
599
+ expect(request.flash[:success]).to be_nil
592
600
  end
593
601
  end
594
602
  end
@@ -5,9 +5,28 @@ require 'spec_helper'
5
5
  RSpec.describe "Blacklight Advanced Search Form" do
6
6
  describe "advanced search form" do
7
7
  before do
8
+ CatalogController.blacklight_config.search_fields['all_fields']['clause_params'] = {
9
+ edismax: {}
10
+ }
11
+ CatalogController.blacklight_config.search_fields['author']['clause_params'] = {
12
+ edismax: { qf: '${author_qf}' }
13
+ }
14
+ CatalogController.blacklight_config.search_fields['title']['clause_params'] = {
15
+ edismax: { qf: '${title_qf}' }
16
+ }
17
+ CatalogController.blacklight_config.search_fields['subject']['clause_params'] = {
18
+ edismax: { qf: '${subject_qf}' }
19
+ }
20
+ CatalogController.blacklight_config.json_solr_path = 'advanced'
8
21
  visit '/catalog/advanced?hypothetical_existing_param=true&q=ignore+this+existing+query'
9
22
  end
10
23
 
24
+ after do
25
+ %w[all_fields author title subject].each do |field|
26
+ CatalogController.blacklight_config.search_fields[field].delete(:clause_params)
27
+ end
28
+ end
29
+
11
30
  it "has field and facet blocks" do
12
31
  expect(page).to have_selector('.query-criteria')
13
32
  expect(page).to have_selector('.limit-criteria')
@@ -45,6 +64,43 @@ RSpec.describe "Blacklight Advanced Search Form" do
45
64
  click_on 'advanced-search-submit'
46
65
  expect(page).to have_content 'Remove constraint Title: Medicine'
47
66
  expect(page).to have_content 'Strong Medicine speaks'
67
+ expect(page).to have_selector('article.document', count: 1)
68
+ end
69
+
70
+ it 'can limit to facets' do
71
+ fill_in 'Subject', with: 'Women'
72
+ click_on 'Language'
73
+ check 'Urdu 3'
74
+ click_on 'advanced-search-submit'
75
+ expect(page).to have_content 'Pākistānī ʻaurat dorāhe par'
76
+ expect(page).not_to have_content 'Ajikto kŭrŏk chŏrŏk sasimnikka : and 아직도 그럭 저럭 사십니까'
77
+ expect(page).to have_selector('article.document', count: 1)
78
+ end
79
+
80
+ it 'handles boolean queries' do
81
+ fill_in 'All Fields', with: 'history NOT strong'
82
+ click_on 'advanced-search-submit'
83
+ expect(page).to have_content('Ci an zhou bian')
84
+ expect(page).not_to have_content('Strong Medicine speaks')
85
+ expect(page).to have_selector('article.document', count: 10)
86
+ end
87
+
88
+ it 'handles queries in multiple fields with the ALL operator' do
89
+ fill_in 'All Fields', with: 'history'
90
+ fill_in 'Author', with: 'hearth'
91
+ click_on 'advanced-search-submit'
92
+ expect(page).to have_content('Strong Medicine speaks')
93
+ expect(page).to have_selector('article.document', count: 1)
94
+ end
95
+
96
+ it 'handles queries in multiple fields with the ANY operator' do
97
+ select 'any', from: 'op'
98
+ fill_in 'All Fields', with: 'history'
99
+ fill_in 'Subject', with: 'women'
100
+ click_on 'advanced-search-submit'
101
+ expect(page).to have_content('Ci an zhou bian')
102
+ expect(page).to have_content('Pākistānī ʻaurat dorāhe par')
103
+ expect(page).to have_selector('article.document', count: 10)
48
104
  end
49
105
  end
50
106
 
@@ -21,6 +21,11 @@ RSpec.describe 'Accessibility testing', api: false, js: true do
21
21
  expect(page).to be_accessible
22
22
  end
23
23
 
24
+ it 'validates the advanced search form' do
25
+ visit advanced_search_catalog_path
26
+ expect(page).to be_accessible.excluding('.search-query-form')
27
+ end
28
+
24
29
  it 'validates the single results page' do
25
30
  visit solr_document_path('2007020969')
26
31
  expect(page).to be_accessible
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'Sitelinks search box' do
4
+ it 'is home page' do
5
+ visit root_path
6
+ expect(page).to have_selector 'script[type="application/ld+json"]', visible: :hidden
7
+ end
8
+
9
+ it 'on search page' do
10
+ visit search_catalog_path q: 'book'
11
+ expect(page).not_to have_selector 'script[type="application/ld+json"]', visible: :hidden
12
+ end
13
+ end
@@ -25,8 +25,8 @@ RSpec.describe Blacklight::SearchHistoryConstraintsHelperBehavior do
25
25
  it "renders basic element" do
26
26
  response = helper.render_search_to_s_element("key", "value")
27
27
  expect(response).to have_selector("span.constraint") do |span|
28
- expect(span).to have_selector("span.filter-name", content: "key:")
29
- expect(span).to have_selector("span.filter-value", content: "value")
28
+ expect(span).to have_selector("span.filter-name", text: "key:")
29
+ expect(span).to have_selector("span.filter-values", text: "value")
30
30
  end
31
31
  expect(response).to be_html_safe
32
32
  end
@@ -34,24 +34,17 @@ RSpec.describe Blacklight::SearchHistoryConstraintsHelperBehavior do
34
34
  it "escapes them that need escaping" do
35
35
  response = helper.render_search_to_s_element("key>", "value>")
36
36
  expect(response).to have_selector("span.constraint") do |span|
37
- expect(span).to have_selector("span.filter-name") do |s2|
38
- # Note: nokogiri's gettext will unescape the inner html
39
- # which seems to be what rspecs "contains" method calls on
40
- # text nodes - thus the to_s inserted below.
41
- expect(s2).to match(/key&gt;:/)
42
- end
43
- expect(span).to have_selector("span.filter-value") do |s3|
44
- expect(s3).to match(/value&gt;/)
45
- end
37
+ expect(span).to have_selector("span.filter-name", text: 'key>:')
38
+ expect(span).to have_selector("span.filter-values", text: 'value>')
46
39
  end
47
40
  expect(response).to be_html_safe
48
41
  end
49
42
 
50
- it "does not escape with options set thus" do
51
- response = helper.render_search_to_s_element("key>", "value>", escape_key: false, escape_value: false)
43
+ xit "does not escape with options set thus" do
44
+ response = helper.render_search_to_s_element("<key>", "value>", escape_key: false, escape_value: false)
52
45
  expect(response).to have_selector("span.constraint") do |span|
53
- expect(span).to have_selector("span.filter-name", content: "key>:")
54
- expect(span).to have_selector("span.filter-value", content: "value>")
46
+ expect(span).to have_selector("span.filter-name", text: "key>:")
47
+ expect(span).to have_selector("span.filter-values", text: "value>")
55
48
  end
56
49
  expect(response).to be_html_safe
57
50
  end
@@ -723,4 +723,26 @@ RSpec.describe "Blacklight::Configuration", api: true do
723
723
  expect { config.view.a = '123' }.to raise_error(FrozenError)
724
724
  end
725
725
  end
726
+
727
+ describe '.default_configuration' do
728
+ it 'adds additional default configuration properties' do
729
+ Blacklight::Configuration.default_configuration do
730
+ Blacklight::Configuration.default_values[:a] = '123'
731
+ end
732
+
733
+ Blacklight::Configuration.default_configuration do
734
+ Blacklight::Configuration.default_values[:b] = 'abc'
735
+ end
736
+
737
+ expect(Blacklight::Configuration.default_values[:a]).to eq '123'
738
+ expect(Blacklight::Configuration.default_values[:b]).to eq 'abc'
739
+ ensure
740
+ # reset the default configuration
741
+ Blacklight::Configuration.default_values.delete(:a)
742
+ Blacklight::Configuration.default_values.delete(:b)
743
+
744
+ Blacklight::Configuration.default_configuration.delete_at(1)
745
+ Blacklight::Configuration.default_configuration.delete_at(2)
746
+ end
747
+ end
726
748
  end
@@ -153,6 +153,33 @@ RSpec.describe Blacklight::Solr::Repository, api: true do
153
153
  expect(JSON.parse(actual_params[:data]).with_indifferent_access).to include(query: { bool: {} })
154
154
  expect(actual_params[:headers]).to include({ 'Content-Type' => 'application/json' })
155
155
  end
156
+
157
+ context "without a json solr path configured" do
158
+ before do
159
+ blacklight_config.json_solr_path = nil
160
+ end
161
+
162
+ it "uses the default solr path" do
163
+ blacklight_config.solr_path = 'xyz'
164
+ allow(subject.connection).to receive(:send_and_receive) do |path|
165
+ expect(path).to eq 'xyz'
166
+ end
167
+ subject.search(input_params)
168
+ end
169
+ end
170
+
171
+ context "with a json solr path configured" do
172
+ before do
173
+ blacklight_config.json_solr_path = 'my-great-json'
174
+ end
175
+
176
+ it "uses the configured json_solr_path" do
177
+ allow(subject.connection).to receive(:send_and_receive) do |path|
178
+ expect(path).to eq 'my-great-json'
179
+ end
180
+ subject.search(input_params)
181
+ end
182
+ end
156
183
  end
157
184
  end
158
185
 
@@ -191,6 +191,14 @@ RSpec.describe Blacklight::Solr::SearchBuilderBehavior, api: true do
191
191
  end
192
192
  end
193
193
 
194
+ describe "for a missing string search" do
195
+ let(:user_params) { { q: nil } }
196
+
197
+ it "does not populate the q parameter in solr parameters" do
198
+ expect(subject).not_to have_key :q
199
+ end
200
+ end
201
+
194
202
  describe "for an empty string search" do
195
203
  let(:user_params) { { q: "" } }
196
204
 
@@ -398,6 +406,14 @@ RSpec.describe Blacklight::Solr::SearchBuilderBehavior, api: true do
398
406
  it 'includes addtional clause parameters for the field' do
399
407
  expect(subject.dig(:json, :query, :bool, :must, 0, :edismax)).to include another: :parameter
400
408
  end
409
+
410
+ context 'with an empty search' do
411
+ let(:subject_search_params) { { commit: "search", search_field: "subject", action: "index", controller: "catalog", rows: "10", q: nil } }
412
+
413
+ it 'does not add nil query value clauses to json query' do
414
+ expect(subject).not_to have_key :json
415
+ end
416
+ end
401
417
  end
402
418
 
403
419
  describe "overriding of qt parameter" do
@@ -45,9 +45,11 @@ RSpec.describe SolrDocument, api: true do
45
45
 
46
46
  let(:doc_class) do
47
47
  Class.new(SolrDocument) do
48
- attribute :title, Blacklight::Types::String, 'title_tesim'
49
- attribute :author, Blacklight::Types::Array, 'author_tesim'
50
- attribute :date, Blacklight::Types::Date, 'date_dtsi'
48
+ attribute :title, :string, 'title_tesim'
49
+ attribute :author, :array, 'author_tesim', of: :string
50
+ attribute :first_author, :select, 'author_tesim', by: :min
51
+ attribute :date, :date, field: 'date_dtsi'
52
+ attribute :whatever, :string, default: ->(*) { 'default_value' }
51
53
  end
52
54
  end
53
55
  let(:document) do
@@ -60,7 +62,23 @@ RSpec.describe SolrDocument, api: true do
60
62
  it "casts the attributes" do
61
63
  expect(document.title).to eq 'Good Omens'
62
64
  expect(document.author).to eq ['Neil Gaiman', 'Terry Pratchett']
65
+ expect(document.first_author).to eq 'Neil Gaiman'
63
66
  expect(document.date).to eq Date.new(1990)
67
+ expect(document.whatever).to eq 'default_value'
68
+ end
69
+
70
+ context 'with missing data' do
71
+ let(:document) { doc_class.new(id: '123') }
72
+
73
+ it 'returns nil for scalar values' do
74
+ expect(document.title).to be_nil
75
+ expect(document.first_author).to be_nil
76
+ expect(document.date).to be_nil
77
+ end
78
+
79
+ it 'returns an array for array values' do
80
+ expect(document.author).to eq []
81
+ end
64
82
  end
65
83
  end
66
84
  end
@@ -52,10 +52,6 @@ RSpec.describe Blacklight::ShowPresenter, api: true do
52
52
 
53
53
  MockDocument.use_extension(MockExtension)
54
54
 
55
- def mock_document_app_helper_url *args
56
- solr_document_url(*args)
57
- end
58
-
59
55
  allow(request_context).to receive(:polymorphic_url) do |_, opts|
60
56
  "url.#{opts[:format]}"
61
57
  end
@@ -70,12 +66,10 @@ RSpec.describe Blacklight::ShowPresenter, api: true do
70
66
  tmp_value = Capybara.ignore_hidden_elements
71
67
  Capybara.ignore_hidden_elements = false
72
68
  document.export_formats.each_pair do |format, _spec|
73
- expect(subject).to have_selector("link[href$='.#{format}']") do |matches|
74
- expect(matches).to have(1).match
75
- tag = matches[0]
76
- expect(tag.attributes["rel"].value).to eq "alternate"
77
- expect(tag.attributes["title"].value).to eq format.to_s
78
- expect(tag.attributes["href"].value).to eq mock_document_app_helper_url(document, format: format)
69
+ expect(subject).to have_selector("link[href$='.#{format}']", count: 1) do |tag|
70
+ expect(tag["rel"]).to eq "alternate"
71
+ expect(tag["title"]).to eq format.to_s
72
+ expect(tag["href"]).to eq "url.#{format}"
79
73
  end
80
74
  end
81
75
  Capybara.ignore_hidden_elements = tmp_value
data/spec/spec_helper.rb CHANGED
@@ -16,7 +16,6 @@ EngineCart.load_application!
16
16
  require 'rspec/rails'
17
17
  require 'rspec/collection_matchers'
18
18
  require 'capybara/rails'
19
- require 'webdrivers'
20
19
  require 'selenium-webdriver'
21
20
  require 'equivalent-xml'
22
21
  require 'axe-rspec'
@@ -28,13 +27,13 @@ Capybara.javascript_driver = :headless_chrome
28
27
 
29
28
  Capybara.register_driver :headless_chrome do |app|
30
29
  Capybara::Selenium::Driver.load_selenium
31
- capabilities = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
30
+ options = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
32
31
  opts.args << '--headless'
33
32
  opts.args << '--disable-gpu'
34
33
  opts.args << '--no-sandbox'
35
34
  opts.args << '--window-size=1280,1696'
36
35
  end
37
- Capybara::Selenium::Driver.new(app, browser: :chrome, capabilities: capabilities)
36
+ Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
38
37
  end
39
38
 
40
39
  # Requires supporting ruby files with custom matchers and macros, etc,
@@ -50,7 +49,7 @@ RSpec.configure do |config|
50
49
  config.filter_run api: true if ENV['BLACKLIGHT_API_TEST']
51
50
 
52
51
  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
53
- config.fixture_path = "#{::Rails.root}/spec/fixtures"
52
+ config.fixture_path = Rails.root.join("spec/fixtures")
54
53
 
55
54
  # If you're not using ActiveRecord, or you'd prefer not to run each of your
56
55
  # examples within a transaction, remove the following line or assign false
@@ -67,7 +66,7 @@ RSpec.configure do |config|
67
66
  config.infer_spec_type_from_file_location!
68
67
  config.include PresenterTestHelpers, type: :presenter
69
68
  config.include ViewComponent::TestHelpers, type: :component
70
- config.include ViewComponentCapybaraTestHelpers, type: :component
69
+ config.include ViewComponentTestHelpers, type: :component
71
70
 
72
71
  config.include(ControllerLevelHelpers, type: :helper)
73
72
  config.before(:each, type: :helper) { initialize_controller_helpers(helper) }
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
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
+ # Nokogiri 1.15.0 upgrades the vendored libxml2 from v2.10.4 to v2.11.3
23
+ # libxml2 v2.11.0 introduces a change to parsing HTML href attributes
24
+ # in nokogiri < 1.15, brackets in href attributes are escaped:
25
+ # - <a class="facet-select" rel="nofollow" href="/catalog?f%5Bz%5D%5B%5D=x:1">x:1</a>
26
+ # in nokogiri >= 1.15, brackets in href attributes are not escaped:
27
+ # - <a class="facet-select" rel="nofollow" href="/catalog?f[z][]=x:1">x:1</a>
28
+ # until we can spec a minimum nokogiri version of 1.15.0, we need to see how
29
+ # the installed version parsed the html
30
+ def nokogiri_mediated_href(href)
31
+ start = "<a href=\"".length
32
+ stop = -"\"></a>".length
33
+ Nokogiri::HTML.fragment("<a href=\"#{href}\"></a>").to_s[start...stop]
34
+ end
35
+ end