blacklight 7.12.1 → 7.13.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/VERSION +1 -1
- data/app/components/blacklight/constraints_component.rb +7 -5
- data/app/components/blacklight/document_component.html.erb +1 -0
- data/app/components/blacklight/document_component.rb +14 -1
- data/app/components/blacklight/facet_field_component.html.erb +1 -0
- data/app/controllers/concerns/blacklight/search_context.rb +1 -1
- data/app/controllers/concerns/blacklight/searchable.rb +1 -1
- data/app/helpers/blacklight/configuration_helper_behavior.rb +3 -9
- data/app/helpers/blacklight/facets_helper_behavior.rb +8 -2
- data/app/helpers/blacklight/render_constraints_helper_behavior.rb +7 -5
- data/app/presenters/blacklight/document_presenter.rb +4 -0
- data/app/presenters/blacklight/facet_item_presenter.rb +6 -2
- data/app/presenters/blacklight/index_presenter.rb +2 -2
- data/app/presenters/blacklight/rendering/link_to_facet.rb +3 -1
- data/app/presenters/blacklight/show_presenter.rb +0 -4
- data/app/services/blacklight/search_service.rb +13 -11
- data/app/views/catalog/_search_form.html.erb +1 -1
- data/app/views/catalog/index.json.jbuilder +3 -1
- data/lib/blacklight/configuration/facet_field.rb +7 -0
- data/lib/blacklight/configuration/search_field.rb +5 -0
- data/lib/blacklight/configuration/tool_config.rb +4 -0
- data/lib/blacklight/configuration/view_config.rb +12 -0
- data/lib/blacklight/nested_open_struct_with_hash_access.rb +1 -1
- data/lib/blacklight/search_builder.rb +13 -23
- data/lib/blacklight/search_state.rb +82 -70
- data/lib/blacklight/search_state/filter_field.rb +122 -0
- data/lib/blacklight/solr/search_builder_behavior.rb +71 -51
- data/package.json +4 -0
- data/spec/components/blacklight/document_component_spec.rb +15 -0
- data/spec/features/search_spec.rb +0 -5
- data/spec/helpers/blacklight/configuration_helper_behavior_spec.rb +1 -2
- data/spec/lib/blacklight/configuration/view_config_spec.rb +15 -0
- data/spec/lib/blacklight/nested_open_struct_with_hash_access_spec.rb +9 -0
- data/spec/lib/blacklight/search_state/filter_field_spec.rb +125 -0
- data/spec/lib/blacklight/search_state_spec.rb +132 -3
- data/spec/models/blacklight/configuration_spec.rb +8 -0
- data/spec/models/blacklight/solr/search_builder_spec.rb +32 -2
- metadata +7 -3
- data/.npmignore +0 -23
data/package.json
CHANGED
@@ -42,11 +42,13 @@ RSpec.describe Blacklight::DocumentComponent, type: :component do
|
|
42
42
|
|
43
43
|
it 'has some defined content areas' do
|
44
44
|
component.with(:title, 'Title')
|
45
|
+
component.with(:embed, 'Embed')
|
45
46
|
component.with(:metadata, 'Metadata')
|
46
47
|
component.with(:thumbnail, 'Thumbnail')
|
47
48
|
component.with(:actions, 'Actions')
|
48
49
|
|
49
50
|
expect(rendered).to have_content 'Title'
|
51
|
+
expect(rendered).to have_content 'Embed'
|
50
52
|
expect(rendered).to have_content 'Metadata'
|
51
53
|
expect(rendered).to have_content 'Thumbnail'
|
52
54
|
expect(rendered).to have_content 'Actions'
|
@@ -126,6 +128,19 @@ RSpec.describe Blacklight::DocumentComponent, type: :component do
|
|
126
128
|
expect(rendered).to have_selector 'dt', text: 'ISBN:'
|
127
129
|
expect(rendered).to have_selector 'dd', text: 'Value'
|
128
130
|
end
|
131
|
+
|
132
|
+
it 'renders an embed' do
|
133
|
+
stub_const('StubComponent', Class.new(ViewComponent::Base) do
|
134
|
+
def initialize(**); end
|
135
|
+
|
136
|
+
def call
|
137
|
+
'embed'
|
138
|
+
end
|
139
|
+
end)
|
140
|
+
|
141
|
+
blacklight_config.show.embed_component = StubComponent
|
142
|
+
expect(rendered).to have_content 'embed'
|
143
|
+
end
|
129
144
|
end
|
130
145
|
|
131
146
|
it 'renders metadata' do
|
@@ -115,9 +115,4 @@ RSpec.describe "Search Page" do
|
|
115
115
|
expect(page).to have_content "Welcome!"
|
116
116
|
expect(page).not_to have_selector "#q[value='history']"
|
117
117
|
end
|
118
|
-
|
119
|
-
it "handles searches with invalid facet parameters" do
|
120
|
-
visit root_path f: { missing_s: [1] }
|
121
|
-
expect(page).to have_content "No results found for your search"
|
122
|
-
end
|
123
118
|
end
|
@@ -122,8 +122,7 @@ RSpec.describe Blacklight::ConfigurationHelperBehavior do
|
|
122
122
|
|
123
123
|
describe "#view_label" do
|
124
124
|
it "looks up the label to display for the view" do
|
125
|
-
allow(blacklight_config).to receive(:view).and_return("my_view" => double(
|
126
|
-
allow(helper).to receive(:field_label).with(:"blacklight.search.view_title.my_view", :"blacklight.search.view.my_view", "some label", nil, "My view")
|
125
|
+
allow(blacklight_config).to receive(:view).and_return("my_view" => double(display_label: "some label"))
|
127
126
|
|
128
127
|
helper.view_label "my_view"
|
129
128
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Blacklight::Configuration::ViewConfig do
|
4
|
+
subject { described_class.new(key: key, label: label) }
|
5
|
+
|
6
|
+
let(:key) { 'my_view' }
|
7
|
+
let(:label) { 'some label' }
|
8
|
+
|
9
|
+
describe '#display_label' do
|
10
|
+
it "looks up the label to display for the given document and field" do
|
11
|
+
allow(I18n).to receive(:t).with(:"blacklight.search.view_title.my_view", default: [:"blacklight.search.view.my_view", label, nil, "My view"]).and_return('x')
|
12
|
+
expect(subject.display_label(key)).to eq 'x'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -14,4 +14,13 @@ RSpec.describe Blacklight::NestedOpenStructWithHashAccess do
|
|
14
14
|
expect(copy.a[:b]).to eq 1
|
15
15
|
end
|
16
16
|
end
|
17
|
+
|
18
|
+
describe '#<<' do
|
19
|
+
subject { described_class.new(Blacklight::Configuration::Field) }
|
20
|
+
|
21
|
+
it 'includes the key in the hash' do
|
22
|
+
subject << :blah
|
23
|
+
expect(subject.blah).to have_attributes(key: :blah)
|
24
|
+
end
|
25
|
+
end
|
17
26
|
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Blacklight::SearchState::FilterField do
|
4
|
+
let(:search_state) { Blacklight::SearchState.new(params.with_indifferent_access, blacklight_config, controller) }
|
5
|
+
|
6
|
+
let(:params) { { f: { some_field: %w[1 2], another_field: ['3'] } } }
|
7
|
+
let(:blacklight_config) do
|
8
|
+
Blacklight::Configuration.new.configure do |config|
|
9
|
+
config.add_facet_field 'some_field'
|
10
|
+
config.add_facet_field 'another_field', single: true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
let(:controller) { double }
|
14
|
+
|
15
|
+
describe '#add' do
|
16
|
+
it 'adds the parameter to the filter list' do
|
17
|
+
filter = search_state.filter('some_field')
|
18
|
+
new_state = filter.add('4')
|
19
|
+
|
20
|
+
expect(new_state.filter('some_field').values).to eq %w[1 2 4]
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'creates new parameter as needed' do
|
24
|
+
filter = search_state.filter('unknown_field')
|
25
|
+
new_state = filter.add('4')
|
26
|
+
|
27
|
+
expect(new_state.filter('unknown_field').values).to eq %w[4]
|
28
|
+
expect(new_state.params[:f]).to include(:unknown_field)
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'without any parameters in the url' do
|
32
|
+
let(:params) { {} }
|
33
|
+
|
34
|
+
it 'adds the necessary structure' do
|
35
|
+
filter = search_state.filter('some_field')
|
36
|
+
new_state = filter.add('1')
|
37
|
+
|
38
|
+
expect(new_state.filter('some_field').values).to eq %w[1]
|
39
|
+
expect(new_state.params).to include(:f)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'with a single-valued field' do
|
44
|
+
it 'replaces any existing parameter from the filter list' do
|
45
|
+
filter = search_state.filter('another_field')
|
46
|
+
new_state = filter.add('5')
|
47
|
+
|
48
|
+
expect(new_state.filter('another_field').values).to eq %w[5]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'with a pivot facet-type item' do
|
53
|
+
it 'includes the pivot facet fqs' do
|
54
|
+
filter = search_state.filter('some_field')
|
55
|
+
new_state = filter.add(OpenStruct.new(fq: { some_other_field: '5' }, value: '4'))
|
56
|
+
|
57
|
+
expect(new_state.filter('some_field').values).to eq %w[1 2 4]
|
58
|
+
expect(new_state.filter('some_other_field').values).to eq %w[5]
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'handles field indirection' do
|
62
|
+
filter = search_state.filter('some_field')
|
63
|
+
new_state = filter.add(OpenStruct.new(field: 'some_other_field', value: '4'))
|
64
|
+
|
65
|
+
expect(new_state.filter('some_other_field').values).to eq %w[4]
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'handles value indirection' do
|
69
|
+
filter = search_state.filter('some_field')
|
70
|
+
new_state = filter.add(OpenStruct.new(value: '4'))
|
71
|
+
|
72
|
+
expect(new_state.filter('some_field').values).to eq %w[1 2 4]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#remove' do
|
78
|
+
it 'returns a search state without the given filter applied' do
|
79
|
+
filter = search_state.filter('some_field')
|
80
|
+
new_state = filter.remove('1')
|
81
|
+
|
82
|
+
expect(new_state.filter('some_field').values).to eq ['2']
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'removes the whole field if there are no filter left for the field' do
|
86
|
+
filter = search_state.filter('another_field')
|
87
|
+
new_state = filter.remove('3')
|
88
|
+
|
89
|
+
expect(new_state.filter('another_field').values).to eq []
|
90
|
+
expect(new_state.params[:f]).not_to include :another_field
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'removes the filter parameter entirely if there are no filters left' do
|
94
|
+
new_state = search_state.filter('some_field').remove('1')
|
95
|
+
new_state = new_state.filter('some_field').remove('2')
|
96
|
+
new_state = new_state.filter('another_field').remove('3')
|
97
|
+
|
98
|
+
expect(new_state.params).not_to include :f
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'handles value indirection' do
|
102
|
+
filter = search_state.filter('some_field')
|
103
|
+
new_state = filter.remove(OpenStruct.new(value: '1'))
|
104
|
+
|
105
|
+
expect(new_state.filter('some_field').values).to eq ['2']
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe '#values' do
|
110
|
+
it 'returns the currently selected values of the filter' do
|
111
|
+
expect(search_state.filter('some_field').values).to eq %w[1 2]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe '#include?' do
|
116
|
+
it 'checks whether the value is currently selected' do
|
117
|
+
expect(search_state.filter('some_field').include?('1')).to eq true
|
118
|
+
expect(search_state.filter('some_field').include?('3')).to eq false
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'handles value indirection' do
|
122
|
+
expect(search_state.filter('some_field').include?(OpenStruct.new(value: '1'))).to eq true
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
RSpec.describe Blacklight::SearchState do
|
4
4
|
subject(:search_state) { described_class.new(params, blacklight_config, controller) }
|
5
5
|
|
6
|
+
around { |test| Deprecation.silence(described_class) { test.call } }
|
7
|
+
|
6
8
|
let(:blacklight_config) do
|
7
9
|
Blacklight::Configuration.new.configure do |config|
|
8
10
|
config.index.title_field = 'title_tsim'
|
@@ -242,9 +244,9 @@ RSpec.describe Blacklight::SearchState do
|
|
242
244
|
end
|
243
245
|
|
244
246
|
it "uses the facet's key in the url" do
|
245
|
-
|
247
|
+
blacklight_config.add_facet_field 'some_key', single: true, field: "a_solr_field"
|
246
248
|
|
247
|
-
result_params = search_state.add_facet_params('
|
249
|
+
result_params = search_state.add_facet_params('some_key', 'my_value')
|
248
250
|
|
249
251
|
expect(result_params[:f]['some_key']).to have(1).item
|
250
252
|
expect(result_params[:f]['some_key'].first).to eq 'my_value'
|
@@ -284,7 +286,7 @@ RSpec.describe Blacklight::SearchState do
|
|
284
286
|
let(:params) { parameter_class.new f: { 'single_value_facet_field' => 'other_value' } }
|
285
287
|
|
286
288
|
it "replaces facets configured as single" do
|
287
|
-
|
289
|
+
blacklight_config.add_facet_field 'single_value_facet_field', single: true
|
288
290
|
result_params = search_state.add_facet_params('single_value_facet_field', 'my_value')
|
289
291
|
|
290
292
|
expect(result_params[:f]['single_value_facet_field']).to have(1).item
|
@@ -415,4 +417,131 @@ RSpec.describe Blacklight::SearchState do
|
|
415
417
|
expect(new_state.to_hash).to eq('a' => 1)
|
416
418
|
end
|
417
419
|
end
|
420
|
+
|
421
|
+
describe '#page' do
|
422
|
+
context 'with a page' do
|
423
|
+
let(:params) { { 'page' => '3' } }
|
424
|
+
|
425
|
+
it 'is mapped from page' do
|
426
|
+
expect(search_state.page).to eq 3
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
context 'without a page' do
|
431
|
+
let(:params) { {} }
|
432
|
+
|
433
|
+
it 'is defaults to page 1' do
|
434
|
+
expect(search_state.page).to eq 1
|
435
|
+
end
|
436
|
+
|
437
|
+
context 'with negative numbers or other bad data' do
|
438
|
+
let(:params) { { 'page' => '-3' } }
|
439
|
+
|
440
|
+
it 'is defaults to page 1' do
|
441
|
+
expect(search_state.page).to eq 1
|
442
|
+
end
|
443
|
+
end
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
describe '#per_page' do
|
448
|
+
context 'with rows' do
|
449
|
+
let(:params) { { rows: '30' } }
|
450
|
+
|
451
|
+
it 'maps from rows' do
|
452
|
+
expect(search_state.per_page).to eq 30
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
context 'with per_page' do
|
457
|
+
let(:params) { { per_page: '14' } }
|
458
|
+
|
459
|
+
it 'maps from rows' do
|
460
|
+
expect(search_state.per_page).to eq 14
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
context 'it defaults to the configured value' do
|
465
|
+
let(:params) { {} }
|
466
|
+
|
467
|
+
it 'maps from rows' do
|
468
|
+
expect(search_state.per_page).to eq 10
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
describe '#sort_field' do
|
474
|
+
let(:params) { { 'sort' => 'author' } }
|
475
|
+
|
476
|
+
before do
|
477
|
+
blacklight_config.add_sort_field 'relevancy', label: 'relevance'
|
478
|
+
blacklight_config.add_sort_field 'author', label: 'asd'
|
479
|
+
end
|
480
|
+
|
481
|
+
it 'returns the current search field' do
|
482
|
+
expect(search_state.sort_field).to have_attributes(key: 'author')
|
483
|
+
end
|
484
|
+
|
485
|
+
context 'without a search field' do
|
486
|
+
let(:params) { {} }
|
487
|
+
|
488
|
+
it 'returns the current search field' do
|
489
|
+
expect(search_state.sort_field).to have_attributes(key: 'relevancy')
|
490
|
+
end
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
describe '#search_field' do
|
495
|
+
let(:params) { { 'search_field' => 'author' } }
|
496
|
+
|
497
|
+
before do
|
498
|
+
blacklight_config.add_search_field 'author', label: 'asd'
|
499
|
+
end
|
500
|
+
|
501
|
+
it 'returns the current search field' do
|
502
|
+
expect(search_state.search_field).to have_attributes(key: 'author')
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
describe '#facet_page' do
|
507
|
+
context 'with a page' do
|
508
|
+
let(:params) { { 'facet.page' => '3' } }
|
509
|
+
|
510
|
+
it 'is mapped from facet.page' do
|
511
|
+
expect(search_state.facet_page).to eq 3
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
context 'without a page' do
|
516
|
+
let(:params) { {} }
|
517
|
+
|
518
|
+
it 'is defaults to page 1' do
|
519
|
+
expect(search_state.facet_page).to eq 1
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
context 'with negative numbers or other bad data' do
|
524
|
+
let(:params) { { 'facet.page' => '-3' } }
|
525
|
+
|
526
|
+
it 'is defaults to page 1' do
|
527
|
+
expect(search_state.facet_page).to eq 1
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
describe '#facet_sort' do
|
533
|
+
let(:params) { { 'facet.sort' => 'index' } }
|
534
|
+
|
535
|
+
it 'is mapped from facet.sort' do
|
536
|
+
expect(search_state.facet_sort).to eq 'index'
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
describe '#facet_prefix' do
|
541
|
+
let(:params) { { 'facet.prefix' => 'A' } }
|
542
|
+
|
543
|
+
it 'is mapped from facet.prefix' do
|
544
|
+
expect(search_state.facet_prefix).to eq 'A'
|
545
|
+
end
|
546
|
+
end
|
418
547
|
end
|
@@ -62,6 +62,14 @@ RSpec.describe "Blacklight::Configuration", api: true do
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
+
describe 'config.index.document_actions' do
|
66
|
+
it 'allows you to use the << operator' do
|
67
|
+
config.index.document_actions << :blah
|
68
|
+
expect(config.index.document_actions.blah).to have_attributes key: :blah
|
69
|
+
expect(config.index.document_actions.blah.name).to eq :blah
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
65
73
|
describe "config.index.respond_to" do
|
66
74
|
it "has a list of additional formats for index requests to respond to" do
|
67
75
|
config.index.respond_to.xml = true
|
@@ -186,9 +186,9 @@ RSpec.describe Blacklight::Solr::SearchBuilderBehavior, api: true do
|
|
186
186
|
end
|
187
187
|
|
188
188
|
describe "for request params also passed in as argument" do
|
189
|
-
let(:user_params) { {
|
189
|
+
let(:user_params) { { 'q' => 'another value', q: "some query" } }
|
190
190
|
|
191
|
-
it "
|
191
|
+
it "only has one value for the key 'q' regardless if a symbol or string" do
|
192
192
|
expect(subject[:q]).to eq 'some query'
|
193
193
|
expect(subject['q']).to eq 'some query'
|
194
194
|
end
|
@@ -244,6 +244,21 @@ RSpec.describe Blacklight::Solr::SearchBuilderBehavior, api: true do
|
|
244
244
|
end
|
245
245
|
end
|
246
246
|
|
247
|
+
describe 'with a facet with a custom filter query builder' do
|
248
|
+
let(:user_params) { { f: { some: ['value'] } }.with_indifferent_access }
|
249
|
+
|
250
|
+
before do
|
251
|
+
blacklight_config.add_facet_field 'some', filter_query_builder: (lambda do |*_args|
|
252
|
+
['some:filter', { qq1: 'abc' }]
|
253
|
+
end)
|
254
|
+
end
|
255
|
+
|
256
|
+
it "has proper solr parameters" do
|
257
|
+
expect(subject[:fq]).to include('some:filter')
|
258
|
+
expect(subject[:qq1]).to include('abc')
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
247
262
|
describe "solr parameters for a field search from config (subject)" do
|
248
263
|
let(:user_params) { subject_search_params }
|
249
264
|
|
@@ -384,6 +399,21 @@ RSpec.describe Blacklight::Solr::SearchBuilderBehavior, api: true do
|
|
384
399
|
end
|
385
400
|
end
|
386
401
|
|
402
|
+
describe 'the search field query_builder config' do
|
403
|
+
let(:blacklight_config) do
|
404
|
+
Blacklight::Configuration.new do |config|
|
405
|
+
config.add_search_field('built_query', query_builder: ->(builder, *_args) { [builder.blacklight_params[:q].reverse, qq1: 'xyz'] })
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
let(:user_params) { { search_field: 'built_query', q: 'value' } }
|
410
|
+
|
411
|
+
it 'uses the provided query builder' do
|
412
|
+
expect(subject[:q]).to eq 'eulav'
|
413
|
+
expect(subject[:qq1]).to eq 'xyz'
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
387
417
|
describe "mapping facet.field" do
|
388
418
|
let(:blacklight_config) do
|
389
419
|
Blacklight::Configuration.new do |config|
|