blacklight 7.19.2 → 7.20.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/.env +1 -1
  3. data/.github/workflows/ruby.yml +19 -1
  4. data/.rubocop.yml +4 -0
  5. data/README.md +1 -1
  6. data/VERSION +1 -1
  7. data/app/assets/javascripts/blacklight/blacklight.js +8 -3
  8. data/app/assets/stylesheets/blacklight/_constraints.scss +7 -4
  9. data/app/assets/stylesheets/blacklight/_controls.scss +7 -0
  10. data/app/assets/stylesheets/blacklight/_facets.scss +2 -2
  11. data/app/assets/stylesheets/blacklight/_pagination.scss +1 -1
  12. data/app/assets/stylesheets/blacklight/_twitter_typeahead.scss +1 -0
  13. data/app/components/blacklight/constraints_component.rb +6 -2
  14. data/app/components/blacklight/document_component.rb +4 -4
  15. data/app/components/blacklight/facet_item_component.rb +2 -2
  16. data/app/components/blacklight/metadata_field_layout_component.rb +1 -1
  17. data/app/components/blacklight/response/pagination_component.html.erb +1 -1
  18. data/app/controllers/concerns/blacklight/bookmarks.rb +0 -3
  19. data/app/controllers/concerns/blacklight/catalog.rb +3 -0
  20. data/app/controllers/concerns/blacklight/controller.rb +9 -5
  21. data/app/controllers/concerns/blacklight/search_context.rb +1 -1
  22. data/app/helpers/blacklight/catalog_helper_behavior.rb +1 -3
  23. data/app/javascript/blacklight/core.js +5 -1
  24. data/app/javascript/blacklight/search_context.js +5 -2
  25. data/app/views/catalog/_home_text.html.erb +2 -2
  26. data/app/views/catalog/_paginate_compact.html.erb +1 -0
  27. data/app/views/shared/_header_navbar.html.erb +2 -2
  28. data/blacklight.gemspec +4 -2
  29. data/config/locales/blacklight.de.yml +4 -2
  30. data/config/locales/blacklight.en.yml +6 -2
  31. data/config/locales/blacklight.es.yml +3 -1
  32. data/config/locales/blacklight.fr.yml +3 -1
  33. data/config/locales/blacklight.it.yml +3 -1
  34. data/lib/blacklight/search_state/filter_field.rb +9 -0
  35. data/lib/blacklight/solr/response/group_response.rb +3 -2
  36. data/lib/blacklight/solr/response/pagination_methods.rb +1 -1
  37. data/lib/blacklight/solr/search_builder_behavior.rb +14 -0
  38. data/lib/blacklight.rb +5 -1
  39. data/lib/generators/blacklight/assets_generator.rb +4 -2
  40. data/lib/generators/blacklight/install_generator.rb +4 -1
  41. data/lib/generators/blacklight/user_generator.rb +1 -1
  42. data/package.json +2 -2
  43. data/spec/components/blacklight/facet_item_component_spec.rb +6 -2
  44. data/spec/features/axe_spec.rb +34 -0
  45. data/spec/features/facet_missing_spec.rb +59 -0
  46. data/spec/features/facets_spec.rb +1 -1
  47. data/spec/helpers/blacklight/facets_helper_behavior_spec.rb +2 -2
  48. data/spec/lib/blacklight/search_state/filter_field_spec.rb +27 -0
  49. data/spec/models/blacklight/solr/response/group_response_spec.rb +3 -2
  50. data/spec/models/blacklight/solr/search_builder_spec.rb +13 -0
  51. data/spec/spec_helper.rb +15 -9
  52. data/spec/test_app_templates/lib/generators/test_app_generator.rb +0 -3
  53. metadata +43 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f48da02e969e0c163d09760d711066ce9c0b4d5078636c8f50e19699ec58e8e
4
- data.tar.gz: 77da2b86854e93f08a79ba9e28c86c7c91116b9f6934229eb6936cc6872bfdc6
3
+ metadata.gz: 514bb2df051f28fda2f7b1a2f4b309bd20cf79843dbaa74b519232543f36bc70
4
+ data.tar.gz: 0bbb3de22863a1e3b9e0aa55d0b9e501550f955fcc2630685e78572b7baf3d05
5
5
  SHA512:
6
- metadata.gz: a42155551ab05fa25e433a7aad6e40078ce7cbc2918de03f6ede94aef53ade03b4b16f4411a915b737b1233a631d9cb2baddc9d560aa70b67df237776c01e7f6
7
- data.tar.gz: 6430192899c4db45277e988217aecc04e954d856645941773176e36ecefd3ddda3c2a9988caffb7bde50f8aa76702a10cd00da67216f17c6fb49a2802a678dfe
6
+ metadata.gz: c8a023e934c9d2b1365f71104047b53d60d51073f53fecf80d8e159a66edf9dbb5148db8be095b522d04e9d5939cf623fe824fa9607bfc6ed123c3e5cb08dfb7
7
+ data.tar.gz: e04a8a63ca946e9d31696ecc76c8da4fca3495687174dba352f613f64388a7802df446d2c902507d99f767b163acea71d2dd021f90f60f2aebe97ae8b115e0d4
data/.env CHANGED
@@ -1,5 +1,5 @@
1
1
  ALPINE_RUBY_VERSION=2.6.5
2
- RAILS_VERSION=5.2.4.1
2
+ RAILS_VERSION=5.2.5
3
3
  SOLR_PORT=8983
4
4
  SOLR_URL=http://solr:8983/solr/blacklight-core
5
5
  SOLR_VERSION=latest
@@ -43,6 +43,24 @@ jobs:
43
43
  run: bundle exec rake ci
44
44
  env:
45
45
  ENGINE_CART_RAILS_OPTIONS: '--skip-git --skip-listen --skip-spring --skip-keeps --skip-action-cable --skip-coffee --skip-test'
46
+ test_bootstrap5:
47
+ runs-on: ubuntu-latest
48
+ strategy:
49
+ matrix:
50
+ ruby: [3.0]
51
+ steps:
52
+ - uses: actions/checkout@v2
53
+ - name: Set up Ruby
54
+ uses: ruby/setup-ruby@v1
55
+ with:
56
+ ruby-version: ${{ matrix.ruby }}
57
+ - name: Install dependencies
58
+ run: bundle install
59
+ - name: Run tests
60
+ run: bundle exec rake ci
61
+ env:
62
+ BOOTSTRAP_VERSION: '~> 5.0'
63
+ ENGINE_CART_RAILS_OPTIONS: '--skip-git --skip-listen --skip-spring --skip-keeps --skip-action-cable --skip-coffee --skip-test'
46
64
  test_rails6_0:
47
65
  runs-on: ubuntu-latest
48
66
  strategy:
@@ -87,7 +105,7 @@ jobs:
87
105
  runs-on: ubuntu-latest
88
106
  strategy:
89
107
  matrix:
90
- ruby: [2.7]
108
+ ruby: [2.7, 3.0]
91
109
  steps:
92
110
  - uses: actions/checkout@v2
93
111
  - name: Set up Ruby
data/.rubocop.yml CHANGED
@@ -50,6 +50,10 @@ Metrics/ModuleLength:
50
50
  - 'app/controllers/concerns/blacklight/catalog.rb'
51
51
  - 'lib/blacklight/solr/search_builder_behavior.rb'
52
52
 
53
+ Metrics/AbcSize:
54
+ Exclude:
55
+ - "lib/blacklight/search_state/filter_field.rb"
56
+
53
57
  Naming/HeredocDelimiterNaming:
54
58
  Enabled: false
55
59
 
data/README.md CHANGED
@@ -35,7 +35,7 @@ rails generate blacklight:install
35
35
 
36
36
  * Ruby 2.2+
37
37
  * Bundler
38
- * Rails 5.1+
38
+ * Rails 5.2+
39
39
 
40
40
  ## Contributing Code
41
41
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 7.19.2
1
+ 7.20.0
@@ -36,7 +36,11 @@ Blacklight.listeners().forEach(function (listener) {
36
36
  Blacklight.activate();
37
37
  });
38
38
  });
39
- $('.no-js').removeClass('no-js').addClass('js');
39
+ Blacklight.onLoad(function () {
40
+ const elem = document.querySelector('.no-js');
41
+ elem.classList.remove('no-js');
42
+ elem.classList.add('js');
43
+ });
40
44
  /*global Bloodhound */
41
45
 
42
46
  Blacklight.onLoad(function () {
@@ -460,8 +464,8 @@ Blacklight.handleSearchContextMethod = function (event) {
460
464
 
461
465
  let href = link.getAttribute('data-context-href');
462
466
  let target = link.getAttribute('target');
463
- let csrfToken = Rails.csrfToken();
464
- let csrfParam = Rails.csrfParam();
467
+ let csrfToken = document.querySelector('meta[name=csrf-token]')?.content
468
+ let csrfParam = document.querySelector('meta[name=csrf-param]')?.content
465
469
  let form = document.createElement('form');
466
470
  form.method = 'post';
467
471
  form.action = href;
@@ -495,3 +499,4 @@ Blacklight.handleSearchContextMethod = function (event) {
495
499
  Blacklight.onLoad(function () {
496
500
  Blacklight.doSearchContextBehavior();
497
501
  });
502
+
@@ -1,27 +1,30 @@
1
1
  .constraints-container {
2
2
  @extend .mb-2;
3
+ @extend .d-flex;
3
4
  }
4
5
 
5
6
  .applied-filter {
7
+ @extend .mx-1;
8
+
6
9
  .constraint-value {
7
10
  cursor: default;
8
11
  text-overflow: ellipsis;
9
12
  overflow: hidden;
10
13
 
11
14
  @media (max-width: breakpoint-min(sm)) {
12
- max-width: breakpoint-min(sm) / 2;
15
+ max-width: breakpoint-min(sm) * .5;
13
16
  }
14
17
 
15
18
  @media (min-width: breakpoint-min(sm)) and (max-width: breakpoint-max(sm)) {
16
- max-width: breakpoint-min(sm) / 2;
19
+ max-width: breakpoint-min(sm) * .5;
17
20
  }
18
21
 
19
22
  @media (min-width: breakpoint-min(md)) and (max-width: breakpoint-max(md)) {
20
- max-width: breakpoint-min(md) / 2;
23
+ max-width: breakpoint-min(md) * .5;
21
24
  }
22
25
 
23
26
  @media (min-width: breakpoint-min(lg)) {
24
- max-width: breakpoint-min(lg) / 2;
27
+ max-width: breakpoint-min(lg) * .5;
25
28
  }
26
29
 
27
30
  &:hover, &:active {
@@ -1,3 +1,10 @@
1
+ .search-widgets {
2
+ @extend .d-flex;
3
+ > * {
4
+ @extend .mx-1;
5
+ }
6
+ }
7
+
1
8
  .sort-pagination,
2
9
  .pagination-search-widgets {
3
10
  border-bottom: $pagination-border-width solid $pagination-border-color;
@@ -83,7 +83,7 @@
83
83
  .remove {
84
84
  color: $text-muted;
85
85
  font-weight: bold;
86
- padding-left: $spacer / 2;
86
+ padding-left: $spacer * .5;
87
87
  text-decoration: none;
88
88
 
89
89
  &:hover {
@@ -104,7 +104,7 @@
104
104
  padding-right: 1em;
105
105
  text-indent: -15px;
106
106
  padding-left: 15px;
107
- padding-bottom: $spacer / 2;
107
+ padding-bottom: $spacer * .5;
108
108
  @include hyphens-auto;
109
109
  }
110
110
 
@@ -6,7 +6,7 @@
6
6
  }
7
7
 
8
8
  .record-padding {
9
- margin-top: $spacer / 2;
9
+ margin-top: $spacer * .5;
10
10
  }
11
11
 
12
12
  .pagination {
@@ -1,4 +1,5 @@
1
1
  .twitter-typeahead {
2
+ display: inline-flex !important;
2
3
  flex-grow: 2;
3
4
  z-index: $zindex-typeahead;
4
5
 
@@ -63,10 +63,14 @@ module Blacklight
63
63
 
64
64
  Deprecation.silence(Blacklight::SearchState) do
65
65
  @search_state.filters.map do |facet|
66
+ missing_facet = @search_state.params.dig("f", "-#{facet.key}:").present?
66
67
  facet.values.map do |val|
67
- next if val.blank? # skip empty string
68
+ next if val.blank? && !missing_facet
68
69
 
69
- if val.is_a?(Array)
70
+ if missing_facet && val.blank?
71
+ missing = I18n.t("blacklight.search.facets.missing")
72
+ yield facet_item_presenter(facet.config, missing, facet.key)
73
+ elsif val.is_a?(Array)
70
74
  yield inclusive_facet_item_presenter(facet.config, val, facet.key) if val.any?(&:present?)
71
75
  else
72
76
  yield facet_item_presenter(facet.config, val, facet.key)
@@ -121,10 +121,10 @@ module Blacklight
121
121
  end
122
122
 
123
123
  def before_render
124
- set_slot(:title) unless title
125
- set_slot(:thumbnail, component: @thumbnail_component || presenter.view_config&.thumbnail_component) unless thumbnail || show?
126
- set_slot(:metadata, component: @metadata_component, fields: presenter.field_presenters) unless metadata
127
- set_slot(:embed, component: @embed_component || presenter.view_config&.embed_component) unless embed
124
+ set_slot(:title, nil) unless title
125
+ set_slot(:thumbnail, nil, component: @thumbnail_component || presenter.view_config&.thumbnail_component) unless thumbnail || show?
126
+ set_slot(:metadata, nil, component: @metadata_component, fields: presenter.field_presenters) unless metadata
127
+ set_slot(:embed, nil, component: @embed_component || presenter.view_config&.embed_component) unless embed
128
128
  end
129
129
 
130
130
  private
@@ -72,7 +72,7 @@ module Blacklight
72
72
  # @private
73
73
  def render_facet_value
74
74
  tag.span(class: "facet-label") do
75
- link_to_unless(@suppress_link, @label, @href, class: "facet-select")
75
+ link_to_unless(@suppress_link, @label, @href, class: "facet-select", rel: "nofollow")
76
76
  end + render_facet_count
77
77
  end
78
78
 
@@ -85,7 +85,7 @@ module Blacklight
85
85
  tag.span(class: "facet-label") do
86
86
  tag.span(@label, class: "selected") +
87
87
  # remove link
88
- link_to(@href, class: "remove") do
88
+ link_to(@href, class: "remove", rel: "nofollow") do
89
89
  tag.span('✖', class: "remove-icon", aria: { hidden: true }) +
90
90
  tag.span(@view_context.t(:'blacklight.search.facets.selected.remove'), class: 'sr-only visually-hidden')
91
91
  end
@@ -23,7 +23,7 @@ module Blacklight
23
23
  end
24
24
 
25
25
  def value(*args, **kwargs, &block)
26
- return set_slot(:values, *args, **kwargs, &block) if block_given?
26
+ return set_slot(:values, nil, *args, **kwargs, &block) if block_given?
27
27
 
28
28
  Deprecation.warn(Blacklight::MetadataFieldLayoutComponent, 'The `value` content area is deprecated; render from the values slot instead')
29
29
 
@@ -1,3 +1,3 @@
1
- <%= content_tag :nav, class: 'pagination', role: 'region', **@html_attr do %>
1
+ <%= content_tag :section, class: 'pagination', **@html_attr do %>
2
2
  <%= pagination %>
3
3
  <% end %>
@@ -48,9 +48,6 @@ module Blacklight::Bookmarks
48
48
  format.html {}
49
49
  format.rss { render layout: false }
50
50
  format.atom { render layout: false }
51
- format.json do
52
- render json: render_search_results_as_json
53
- end
54
51
 
55
52
  additional_response_formats(format)
56
53
  document_export_formats(format)
@@ -2,6 +2,9 @@
2
2
  module Blacklight::Catalog
3
3
  extend ActiveSupport::Concern
4
4
 
5
+ # MimeResponds is part of ActionController::Base, but not ActionController::API
6
+ include ActionController::MimeResponds
7
+
5
8
  include Blacklight::Base
6
9
  include Blacklight::Facet
7
10
  include Blacklight::Searchable
@@ -3,6 +3,7 @@
3
3
  # as this module is mixed-in to the application controller in the hosting app on installation.
4
4
  module Blacklight::Controller
5
5
  extend ActiveSupport::Concern
6
+ extend Deprecation
6
7
 
7
8
  included do
8
9
  include Blacklight::SearchFields
@@ -38,11 +39,6 @@ module Blacklight::Controller
38
39
  # TODO: move to Searchable
39
40
  class_attribute :search_service_class
40
41
  self.search_service_class = Blacklight::SearchService
41
-
42
- # This callback runs when a user first logs in
43
-
44
- define_callbacks :logging_in_user
45
- set_callback :logging_in_user, :before, :transfer_guest_user_actions_to_current_user
46
42
  end
47
43
 
48
44
  # @private
@@ -136,6 +132,13 @@ module Blacklight::Controller
136
132
 
137
133
  ##
138
134
  # When a user logs in, transfer any saved searches or bookmarks to the current_user
135
+ def transfer_guest_to_user
136
+ Deprecation.silence(Blacklight::Controller) do
137
+ transfer_guest_user_actions_to_current_user
138
+ end
139
+ end
140
+
141
+ # @deprecated use canonical `transfer_guest_to_user` method instead
139
142
  def transfer_guest_user_actions_to_current_user
140
143
  return unless respond_to?(:current_user) && respond_to?(:guest_user) && current_user && guest_user
141
144
 
@@ -155,6 +158,7 @@ module Blacklight::Controller
155
158
  # let guest_user know we've moved some bookmarks from under it
156
159
  guest_user.reload if guest_user.persisted?
157
160
  end
161
+ deprecation_deprecate :transfer_guest_user_actions_to_current_user
158
162
 
159
163
  ##
160
164
  # To handle failed authorization attempts, redirect the user to the
@@ -73,7 +73,7 @@ module Blacklight::SearchContext
73
73
  #
74
74
  def agent_is_crawler?
75
75
  crawler_proc = blacklight_config.crawler_detector
76
- return false if crawler_proc.nil? || current_user.present?
76
+ return false if crawler_proc.nil? || (defined?(current_user) && current_user.present?)
77
77
 
78
78
  crawler_proc.call(request)
79
79
  end
@@ -47,11 +47,9 @@ module Blacklight::CatalogHelperBehavior
47
47
  entry_name = if entry_name
48
48
  entry_name.pluralize(collection.size, I18n.locale)
49
49
  else
50
- collection.entry_name(count: collection.size).to_s.downcase
50
+ collection.entry_name(count: collection.size).to_s
51
51
  end
52
52
 
53
- entry_name = entry_name.pluralize unless collection.total_count == 1
54
-
55
53
  # grouped response objects need special handling
56
54
  end_num = if collection.respond_to?(:groups) && render_grouped_response?(collection)
57
55
  collection.groups.length
@@ -38,4 +38,8 @@ Blacklight.listeners().forEach(function(listener) {
38
38
  })
39
39
  })
40
40
 
41
- $('.no-js').removeClass('no-js').addClass('js');
41
+ Blacklight.onLoad(function () {
42
+ const elem = document.querySelector('.no-js')
43
+ elem.classList.remove('no-js')
44
+ elem.classList.add('js')
45
+ })
@@ -15,6 +15,9 @@ Blacklight.doSearchContextBehavior = function() {
15
15
  })
16
16
  };
17
17
 
18
+ Blacklight.csrfToken = () => document.querySelector('meta[name=csrf-token]')?.content
19
+ Blacklight.csrfParam = () => document.querySelector('meta[name=csrf-param]')?.content
20
+
18
21
  // this is the Rails.handleMethod with a couple adjustments, described inline:
19
22
  // first, we're attaching this directly to the event handler, so we can check for meta-keys
20
23
  Blacklight.handleSearchContextMethod = function(event) {
@@ -27,8 +30,8 @@ Blacklight.handleSearchContextMethod = function(event) {
27
30
  // instead of using the normal href, we need to use the context href instead
28
31
  let href = link.getAttribute('data-context-href')
29
32
  let target = link.getAttribute('target')
30
- let csrfToken = Rails.csrfToken()
31
- let csrfParam = Rails.csrfParam()
33
+ let csrfToken = Blacklight.csrfToken()
34
+ let csrfParam = Blacklight.csrfParam()
32
35
  let form = document.createElement('form')
33
36
  form.method = 'post'
34
37
  form.action = href
@@ -1,11 +1,11 @@
1
- <div class="jumbotron text-center">
1
+ <div class="jumbotron text-center p-5 mb-4 bg-light rounded-3">
2
2
  <h1 class="jumbotron-heading"><%= t('blacklight.welcome') %></h1>
3
3
 
4
4
  <p class="lead">Blacklight is a multi-institutional open-source collaboration building a better discovery platform framework.</p>
5
5
 
6
6
  <p>
7
7
  <%= link_to 'Read the Documentation', 'https://github.com/projectblacklight/blacklight/wiki', class: 'btn btn-primary' %>
8
- <%= link_to 'See Examples', 'http://projectblacklight.org', class: 'btn btn-outline-secondary' %>
8
+ <%= link_to 'See Examples', 'http://projectblacklight.org', class: 'btn btn-light' %>
9
9
  </p>
10
10
  </div>
11
11
 
@@ -2,5 +2,6 @@
2
2
  response: paginate_compact,
3
3
  theme: :blacklight_compact,
4
4
  page_entries_info: page_entries_info(paginate_compact),
5
+ role: nil,
5
6
  html: { aria: {} })
6
7
  %>
@@ -11,8 +11,8 @@
11
11
  </div>
12
12
  </nav>
13
13
 
14
- <div class="navbar-search navbar navbar-light bg-light" role="navigation">
14
+ <%= content_tag :div, class: 'navbar-search navbar navbar-light bg-light', role: 'navigation', aria: { label: t('blacklight.search.header') } do %>
15
15
  <div class="<%= container_classes %>">
16
16
  <%= render_search_bar %>
17
17
  </div>
18
- </div>
18
+ <% end %>
data/blacklight.gemspec CHANGED
@@ -32,14 +32,16 @@ Gem::Specification.new do |s|
32
32
  s.add_dependency "deprecation"
33
33
  s.add_dependency "i18n", '>= 1.7.0' # added named parameters
34
34
  s.add_dependency "ostruct", '>= 0.3.2'
35
- s.add_dependency "view_component", '>= 2.28.0'
35
+ s.add_dependency "view_component", '~> 2.42.0'
36
36
 
37
37
  s.add_development_dependency "rsolr", ">= 1.0.6", "< 3" # Library for interacting with rSolr.
38
38
  s.add_development_dependency "rspec-rails", "~> 4.0.0.beta2"
39
39
  s.add_development_dependency "rspec-its"
40
40
  s.add_development_dependency "rspec-collection_matchers", ">= 1.0"
41
+ s.add_development_dependency 'axe-core-rspec'
41
42
  s.add_development_dependency "capybara", '~> 3'
42
- s.add_development_dependency 'apparition'
43
+ s.add_development_dependency 'webdrivers'
44
+ s.add_development_dependency 'selenium-webdriver'
43
45
  s.add_development_dependency 'engine_cart', '~> 2.1'
44
46
  s.add_development_dependency "equivalent-xml"
45
47
  s.add_development_dependency "simplecov"
@@ -169,7 +169,7 @@ de:
169
169
  pagination:
170
170
  title: 'Ergebnisse Navigation'
171
171
  pagination_info:
172
- no_items_found: 'Kein %{entry_name} gefunden'
172
+ no_items_found: 'Keine %{entry_name} gefunden'
173
173
  single_item_found: '<strong>1</strong> %{entry_name} gefunden'
174
174
  pages:
175
175
  one: '<strong>%{start_num}</strong> - <strong>%{end_num}</strong> von <strong>%{total_num}</strong>'
@@ -230,7 +230,9 @@ de:
230
230
  list: "Liste"
231
231
 
232
232
  entry_name:
233
- default: 'Eintrag'
233
+ default:
234
+ one: Eintrag
235
+ other: Enträge
234
236
  grouped:
235
237
  default: 'gruppiertes Ergebnis'
236
238
 
@@ -230,9 +230,13 @@ en:
230
230
  list: "List"
231
231
 
232
232
  entry_name:
233
- default: 'entry'
233
+ default:
234
+ one: 'entry'
235
+ other: 'entries'
234
236
  grouped:
235
- default: 'grouped result'
237
+ default:
238
+ one: 'grouped result'
239
+ other: 'grouped results'
236
240
 
237
241
  did_you_mean: 'Did you mean to type: %{options}?'
238
242
 
@@ -230,7 +230,9 @@ es:
230
230
  list: "Lista"
231
231
 
232
232
  entry_name:
233
- default: 'entrada'
233
+ default:
234
+ one: 'entrada'
235
+ other: 'entradas'
234
236
  grouped:
235
237
  default: 'resultado agrupado'
236
238
 
@@ -234,7 +234,9 @@ fr:
234
234
 
235
235
 
236
236
  entry_name:
237
- default: 'résultat'
237
+ default:
238
+ one: 'résultat'
239
+ other: 'résultats'
238
240
  grouped:
239
241
  default: 'résultat groupé'
240
242
 
@@ -230,7 +230,9 @@ it:
230
230
  list: "Lista"
231
231
 
232
232
  entry_name:
233
- default: 'termine di ricerca'
233
+ default:
234
+ one: 'termine di ricerca'
235
+ other: 'termini di ricerca'
234
236
  grouped:
235
237
  default: 'risultato raggruppato'
236
238
 
@@ -83,10 +83,19 @@ module Blacklight
83
83
  Deprecation.warn(self, 'Normalizing parameters in FilterField#remove is deprecated')
84
84
  collection = collection.values
85
85
  end
86
+
86
87
  params[param][key] = collection - Array(value)
87
88
  params[param].delete(key) if params[param][key].empty?
88
89
  params.delete(param) if params[param].empty?
89
90
 
91
+ # Handle missing field queries.
92
+ missing = I18n.t("blacklight.search.facets.missing")
93
+ if (item.respond_to?(:fq) && item.fq == "-#{key}:[* TO *]") ||
94
+ item == missing
95
+ params[param].delete("-#{key}:")
96
+ params[param].delete(key) if params[param][key] == [""]
97
+ end
98
+
90
99
  new_state.reset(params)
91
100
  end
92
101
 
@@ -45,8 +45,9 @@ class Blacklight::Solr::Response::GroupResponse
45
45
  def entry_name(options)
46
46
  I18n.t(
47
47
  "blacklight.entry_name.grouped.#{key}",
48
- default: :'blacklight.entry_name.grouped.default'
49
- ).pluralize(options[:count])
48
+ default: :'blacklight.entry_name.grouped.default',
49
+ count: options[:count]
50
+ )
50
51
  end
51
52
 
52
53
  def method_missing meth, *args, &block
@@ -24,6 +24,6 @@ module Blacklight::Solr::Response::PaginationMethods
24
24
  ##
25
25
  # Meant to have the same signature as Kaminari::PaginatableArray#entry_name
26
26
  def entry_name(options)
27
- I18n.t('blacklight.entry_name.default').pluralize(options[:count])
27
+ I18n.t('blacklight.entry_name.default', count: options[:count])
28
28
  end
29
29
  end
@@ -10,6 +10,7 @@ module Blacklight::Solr
10
10
  :add_facetting_to_solr, :add_solr_fields_to_query, :add_paging_to_solr,
11
11
  :add_sorting_to_solr, :add_group_config_to_solr,
12
12
  :add_facet_paging_to_solr, :add_adv_search_clauses,
13
+ :add_missing_field_query,
13
14
  :add_additional_filters
14
15
  ]
15
16
  end
@@ -81,6 +82,19 @@ module Blacklight::Solr
81
82
  end
82
83
  end
83
84
 
85
+ ##
86
+ # Build and append a missing field query.
87
+ ##
88
+ def add_missing_field_query(solr_parameters)
89
+ return unless solr_parameters["facet.missing"]
90
+
91
+ solr_parameters[:fq] = [] if solr_parameters[:fq].blank?
92
+
93
+ solr_parameters[:fq].append(*(blacklight_params["f"] || [])
94
+ .select { |f| f.match(/^-/) }
95
+ .map { |k, _v| "#{k}[* TO *]" })
96
+ end
97
+
84
98
  def add_additional_filters(solr_parameters, additional_filters = nil)
85
99
  q = additional_filters || @additional_filters
86
100
 
data/lib/blacklight.rb CHANGED
@@ -96,7 +96,11 @@ module Blacklight
96
96
  end
97
97
 
98
98
  begin
99
- @blacklight_yml = YAML.safe_load(blacklight_erb)
99
+ @blacklight_yml = if RUBY_VERSION > '2.6'
100
+ YAML.safe_load(blacklight_erb, aliases: true)
101
+ else
102
+ YAML.safe_load(blacklight_erb, [], [], true)
103
+ end
100
104
  rescue => e
101
105
  raise("#{blacklight_config_file} was found, but could not be parsed.\n#{e.inspect}")
102
106
  end
@@ -3,9 +3,11 @@ module Blacklight
3
3
  class Assets < Rails::Generators::Base
4
4
  source_root File.expand_path('../templates', __FILE__)
5
5
 
6
+ class_option :'bootstrap-version', type: :string, default: ENV.fetch('BOOTSTRAP_VERSION', '~> 4.0'), desc: "Set the generated app's bootstrap version"
7
+
6
8
  # This could be skipped if you want to use webpacker
7
9
  def add_javascript_dependencies
8
- gem 'bootstrap', '~> 4.0'
10
+ gem 'bootstrap', options[:'bootstrap-version']
9
11
  gem 'twitter-typeahead-rails', '0.11.1.pre.corejavascript'
10
12
  end
11
13
 
@@ -15,7 +17,7 @@ module Blacklight
15
17
 
16
18
  create_file 'app/assets/javascripts/application.js' do
17
19
  <<~CONTENT
18
- //= require jquery
20
+ //= require jquery3
19
21
  //= require rails-ujs
20
22
  //= require turbolinks
21
23
  CONTENT
@@ -10,6 +10,7 @@ module Blacklight
10
10
 
11
11
  class_option :devise, type: :boolean, default: false, aliases: "-d", desc: "Use Devise as authentication logic."
12
12
  class_option :marc, type: :boolean, default: false, aliases: "-m", desc: "Generate MARC-based demo."
13
+ class_option :'bootstrap-version', type: :string, default: nil, desc: "Set the generated app's bootstrap version"
13
14
  class_option :'skip-assets', type: :boolean, default: !defined?(Sprockets), desc: "Skip generating javascript and css assets into the application"
14
15
  class_option :'skip-solr', type: :boolean, default: false, desc: "Skip generating solr configurations."
15
16
 
@@ -33,7 +34,9 @@ module Blacklight
33
34
  # Call external generator in AssetsGenerator, so we can
34
35
  # leave that callable seperately too.
35
36
  def copy_public_assets
36
- generate "blacklight:assets" unless options[:'skip-assets']
37
+ generated_options = "--bootstrap-version #{options[:'bootstrap-version']}" if options[:'bootstrap-version']
38
+
39
+ generate "blacklight:assets", generated_options unless options[:'skip-assets']
37
40
  end
38
41
 
39
42
  def bundle_install
@@ -22,7 +22,7 @@ module Blacklight
22
22
  return unless options[:devise]
23
23
 
24
24
  gem "devise"
25
- gem "devise-guests", "~> 0.6"
25
+ gem "devise-guests", "~> 0.8"
26
26
 
27
27
  inside destination_root do
28
28
  Bundler.with_clean_env do
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blacklight-frontend",
3
- "version": "7.10.0",
3
+ "version": "7.20.0",
4
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": {
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "bloodhound-js": "^1.2.3",
30
- "bootstrap": "^4.3.1",
30
+ "bootstrap": ">=4.3.1 <6.0.0",
31
31
  "jquery": "^3.5.1",
32
32
  "typeahead.js": "^0.11.1"
33
33
  }
@@ -20,7 +20,9 @@ RSpec.describe Blacklight::FacetItemComponent, type: :component do
20
20
 
21
21
  it 'links to the facet and shows the number of hits' do
22
22
  expect(rendered).to have_selector 'li'
23
- expect(rendered).to have_link 'x', href: '/catalog?f=x'
23
+ expect(rendered).to have_link 'x', href: '/catalog?f=x' do |link|
24
+ link['rel'] == 'nofollow'
25
+ end
24
26
  expect(rendered).to have_selector '.facet-count', text: '10'
25
27
  end
26
28
 
@@ -39,7 +41,9 @@ RSpec.describe Blacklight::FacetItemComponent, type: :component do
39
41
  it 'links to the facet and shows the number of hits' do
40
42
  expect(rendered).to have_selector 'li'
41
43
  expect(rendered).to have_selector '.selected', text: 'x'
42
- expect(rendered).to have_link '[remove]', href: '/catalog'
44
+ expect(rendered).to have_link '[remove]', href: '/catalog' do |link|
45
+ link['rel'] == 'nofollow'
46
+ end
43
47
  expect(rendered).to have_selector '.selected.facet-count', text: '10'
44
48
  end
45
49
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'Accessibility testing', api: false, js: true do
4
+ xit 'validates the home page' do
5
+ visit root_path
6
+ expect(page).to be_accessible
7
+ end
8
+
9
+ xit 'validates the catalog page' do
10
+ visit root_path
11
+ fill_in "q", with: 'history'
12
+ click_button 'search'
13
+
14
+ # aria-allowed-role doesn't like nav[role="region"]
15
+ expect(page).to be_accessible(skipping: ['aria-allowed-role'])
16
+
17
+ within '.card.blacklight-language_ssim' do
18
+ click_button 'Language'
19
+ click_link "Tibetan"
20
+ end
21
+
22
+ expect(page).to be_accessible(skipping: ['aria-allowed-role'])
23
+ end
24
+
25
+ xit 'validates the single results page' do
26
+ visit solr_document_path('2007020969')
27
+ expect(page).to be_accessible
28
+ end
29
+
30
+ def be_accessible(skipping: [])
31
+ # typeahead does funny things with the search bar
32
+ be_axe_clean.excluding('.tt-hint').skipping(skipping + [('color-contrast' if Bootstrap::VERSION < '5')])
33
+ end
34
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "Facet missing" do
4
+ before do
5
+ CatalogController.blacklight_config[:default_solr_params]["facet.missing"] = true
6
+ end
7
+
8
+ after do
9
+ CatalogController.blacklight_config[:default_solr_params].delete("facet.missing")
10
+ end
11
+
12
+ context "selecting missing field in facets" do
13
+ it "adds facet missing query and constraints" do
14
+ visit root_path
15
+
16
+ within "#facet-subject_geo_ssim" do
17
+ click_link "[Missing]"
18
+ end
19
+
20
+ within "#facet-subject_geo_ssim" do
21
+ expect(page).to have_selector("span.selected", text: "[Missing")
22
+ expect(page).to have_selector("span.facet-count.selected", text: "13")
23
+ end
24
+
25
+ within "#sortAndPerPage" do
26
+ expect(page).to have_content "1 - 10 of 13"
27
+ end
28
+
29
+ expect(page).to have_css(".constraint-value", text: "Region")
30
+ expect(page).to have_css(".constraint-value", text: "[Missing]")
31
+ end
32
+ end
33
+
34
+ context "unselecting the facet missing facet" do
35
+ it "unselects the missig field facet" do
36
+ visit root_path + "?f[-subject_geo_ssim:[* TO *]][]=&f[subject_geo_ssim][]="
37
+
38
+ within "#facet-subject_geo_ssim" do
39
+ click_link "remove"
40
+ end
41
+
42
+ expect(page).not_to have_link "remove"
43
+ expect(page).to have_content("Welcome!")
44
+ end
45
+ end
46
+
47
+ context "unselecting the facet missing constraint" do
48
+ it "unselects the missig field facet" do
49
+ visit root_path + "?f[-subject_geo_ssim:[* TO *]][]=&f[subject_geo_ssim][]="
50
+
51
+ within ".filter-subject_geo_ssim" do
52
+ click_link "Remove constraint Region: [Missing]"
53
+ end
54
+
55
+ expect(page).not_to have_link "remove"
56
+ expect(page).to have_content("Welcome!")
57
+ end
58
+ end
59
+ end
@@ -69,7 +69,7 @@ RSpec.describe "Facets" do
69
69
  page.find('h3.facet-field-heading button', text: 'Pivot Field').click
70
70
 
71
71
  within '#facet-example_pivot_field' do
72
- expect(page).to have_css('.facet-leaf-node', text: "Book\t30")
72
+ expect(page).to have_css('.facet-leaf-node', text: "Book 30")
73
73
  expect(page).not_to have_css('.facet-select', text: 'Tibetan')
74
74
  page.find('.facet-toggle-handle').click
75
75
  click_link 'Tibetan'
@@ -314,7 +314,7 @@ RSpec.describe Blacklight::FacetsHelperBehavior do
314
314
  end
315
315
 
316
316
  describe "simple case" do
317
- let(:expected_html) { '<span class="facet-label"><a class="facet-select" href="/catalog">Z</a></span><span class="facet-count">10</span>' }
317
+ let(:expected_html) { '<span class="facet-label"><a class="facet-select" rel="nofollow" href="/catalog">Z</a></span><span class="facet-count">10</span>' }
318
318
 
319
319
  it "uses facet_display_value" do
320
320
  result = helper.render_facet_value('simple_field', item)
@@ -323,7 +323,7 @@ RSpec.describe Blacklight::FacetsHelperBehavior do
323
323
  end
324
324
 
325
325
  describe "when :url_method is set" do
326
- let(:expected_html) { '<span class="facet-label"><a class="facet-select" href="/blabla">Z</a></span><span class="facet-count">10</span>' }
326
+ let(:expected_html) { '<span class="facet-label"><a class="facet-select" rel="nofollow" href="/blabla">Z</a></span><span class="facet-count">10</span>' }
327
327
 
328
328
  it "uses that method" do
329
329
  allow(helper).to receive(:facet_configuration_for_field).with('simple_field').and_return(Blacklight::Configuration::FacetField.new(key: 'simple_field', query: nil, date: nil, helper_method: nil, single: false, url_method: :test_method))
@@ -159,6 +159,33 @@ RSpec.describe Blacklight::SearchState::FilterField do
159
159
  expect(new_state.filter('another_field').values).to eq ['3']
160
160
  end
161
161
  end
162
+
163
+ context "With facet.missing field" do
164
+ let(:params) do
165
+ { f: { some_field: [""], "-some_field:": [""] } }
166
+ end
167
+
168
+ it "removes facet.missing facet params" do
169
+ filter = search_state.filter("some_field")
170
+ new_state = filter.remove(OpenStruct.new(fq: "-some_field:[* TO *]"))
171
+
172
+ expect(new_state.params).to eq("f" => {})
173
+ end
174
+ end
175
+
176
+ context "With facet.missing field value" do
177
+ let(:params) do
178
+ { f: { some_field: [""], "-some_field:": [""] } }
179
+ end
180
+
181
+ it "removes facet.missing facet params" do
182
+ missing = I18n.t("blacklight.search.facets.missing")
183
+ filter = search_state.filter("some_field")
184
+ new_state = filter.remove(missing)
185
+
186
+ expect(new_state.params).to eq("f" => {})
187
+ end
188
+ end
162
189
  end
163
190
 
164
191
  describe '#values' do
@@ -69,8 +69,9 @@ RSpec.describe Blacklight::Solr::Response::GroupResponse, api: true do
69
69
  it "accesses a custom field grouped i18n key" do
70
70
  allow(I18n).to receive(:t).with(
71
71
  'blacklight.entry_name.grouped.result_group_ssi',
72
- default: :'blacklight.entry_name.grouped.default'
73
- ).and_return('cool group')
72
+ default: :'blacklight.entry_name.grouped.default',
73
+ count: 2
74
+ ).and_return('cool groups')
74
75
  expect(group.entry_name(count: 2)).to eq 'cool groups'
75
76
  end
76
77
 
@@ -27,6 +27,10 @@ RSpec.describe Blacklight::Solr::SearchBuilderBehavior, api: true do
27
27
  it "uses the class-level default_processor_chain" do
28
28
  expect(subject.processor_chain).to eq search_builder_class.default_processor_chain
29
29
  end
30
+
31
+ it "appends the :add_missing_field_query processor" do
32
+ expect(subject.processor_chain).to include(:add_missing_field_query)
33
+ end
30
34
  end
31
35
 
32
36
  context 'with merged parameters from the defaults + the search field' do
@@ -822,4 +826,13 @@ RSpec.describe Blacklight::Solr::SearchBuilderBehavior, api: true do
822
826
  expect(subject.to_hash).to include q: '{!lucene}id:(1 OR 2 OR 3)'
823
827
  end
824
828
  end
829
+
830
+ describe "#add_missing_field_query" do
831
+ it "precesses facet.missing query" do
832
+ subject.with("f" => { "-hello:" => [""] })
833
+ solr_params = { "facet.missing" => true, fq: [] }
834
+
835
+ expect(subject.add_missing_field_query(solr_params)).to eq(["-hello:[* TO *]"])
836
+ end
837
+ end
825
838
  end
data/spec/spec_helper.rb CHANGED
@@ -19,18 +19,24 @@ EngineCart.load_application!
19
19
  require 'rspec/rails'
20
20
  require 'rspec/its'
21
21
  require 'rspec/collection_matchers'
22
- require 'capybara/rspec'
23
- require 'capybara/apparition'
22
+ require 'capybara/rails'
23
+ require 'webdrivers'
24
+ require 'selenium-webdriver'
24
25
  require 'equivalent-xml'
26
+ require 'axe-rspec'
25
27
 
26
- Capybara.javascript_driver = :apparition
27
- Capybara.disable_animation = true
28
- # Capybara.enable_aria_label = true
28
+ Capybara.javascript_driver = :headless_chrome
29
29
 
30
- # Uncomment for a headed browser
31
- # Capybara.register_driver :apparition do |app|
32
- # Capybara::Apparition::Driver.new(app, headless: false)
33
- # end
30
+ Capybara.register_driver :headless_chrome do |app|
31
+ Capybara::Selenium::Driver.load_selenium
32
+ browser_options = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
33
+ opts.args << '--headless'
34
+ opts.args << '--disable-gpu'
35
+ opts.args << '--no-sandbox'
36
+ opts.args << '--window-size=1280,1696'
37
+ end
38
+ Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options)
39
+ end
34
40
 
35
41
  # Requires supporting ruby files with custom matchers and macros, etc,
36
42
  # in spec/support/ and its subdirectories.
@@ -17,9 +17,6 @@ class TestAppGenerator < Rails::Generators::Base
17
17
  options = '--devise'
18
18
  if ENV['BLACKLIGHT_API_TEST']
19
19
  options += ' --skip-assets'
20
- inject_into_class 'app/controllers/application_controller.rb', 'ApplicationController' do
21
- " include ActionController::MimeResponds\n" # see https://github.com/projectblacklight/blacklight/issues/1894
22
- end
23
20
  end
24
21
 
25
22
  generate 'blacklight:install', options
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blacklight
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.19.2
4
+ version: 7.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Rochkind
@@ -14,10 +14,10 @@ authors:
14
14
  - Dan Funk
15
15
  - Naomi Dushay
16
16
  - Justin Coyne
17
- autorequire:
17
+ autorequire:
18
18
  bindir: exe
19
19
  cert_chain: []
20
- date: 2021-05-25 00:00:00.000000000 Z
20
+ date: 2021-11-01 00:00:00.000000000 Z
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
23
23
  name: rails
@@ -127,16 +127,16 @@ dependencies:
127
127
  name: view_component
128
128
  requirement: !ruby/object:Gem::Requirement
129
129
  requirements:
130
- - - ">="
130
+ - - "~>"
131
131
  - !ruby/object:Gem::Version
132
- version: 2.28.0
132
+ version: 2.42.0
133
133
  type: :runtime
134
134
  prerelease: false
135
135
  version_requirements: !ruby/object:Gem::Requirement
136
136
  requirements:
137
- - - ">="
137
+ - - "~>"
138
138
  - !ruby/object:Gem::Version
139
- version: 2.28.0
139
+ version: 2.42.0
140
140
  - !ruby/object:Gem::Dependency
141
141
  name: rsolr
142
142
  requirement: !ruby/object:Gem::Requirement
@@ -199,6 +199,20 @@ dependencies:
199
199
  - - ">="
200
200
  - !ruby/object:Gem::Version
201
201
  version: '1.0'
202
+ - !ruby/object:Gem::Dependency
203
+ name: axe-core-rspec
204
+ requirement: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
209
+ type: :development
210
+ prerelease: false
211
+ version_requirements: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: '0'
202
216
  - !ruby/object:Gem::Dependency
203
217
  name: capybara
204
218
  requirement: !ruby/object:Gem::Requirement
@@ -214,7 +228,21 @@ dependencies:
214
228
  - !ruby/object:Gem::Version
215
229
  version: '3'
216
230
  - !ruby/object:Gem::Dependency
217
- name: apparition
231
+ name: webdrivers
232
+ requirement: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: '0'
237
+ type: :development
238
+ prerelease: false
239
+ version_requirements: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - ">="
242
+ - !ruby/object:Gem::Version
243
+ version: '0'
244
+ - !ruby/object:Gem::Dependency
245
+ name: selenium-webdriver
218
246
  requirement: !ruby/object:Gem::Requirement
219
247
  requirements:
220
248
  - - ">="
@@ -762,8 +790,10 @@ files:
762
790
  - spec/features/advanced_search_spec.rb
763
791
  - spec/features/alternate_controller_spec.rb
764
792
  - spec/features/autocomplete_spec.rb
793
+ - spec/features/axe_spec.rb
765
794
  - spec/features/bookmarks_spec.rb
766
795
  - spec/features/did_you_mean_spec.rb
796
+ - spec/features/facet_missing_spec.rb
767
797
  - spec/features/facets_spec.rb
768
798
  - spec/features/record_view_spec.rb
769
799
  - spec/features/search_context_spec.rb
@@ -888,7 +918,7 @@ homepage: http://projectblacklight.org/
888
918
  licenses:
889
919
  - Apache 2.0
890
920
  metadata: {}
891
- post_install_message:
921
+ post_install_message:
892
922
  rdoc_options: []
893
923
  require_paths:
894
924
  - lib
@@ -903,8 +933,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
903
933
  - !ruby/object:Gem::Version
904
934
  version: '0'
905
935
  requirements: []
906
- rubygems_version: 3.2.3
907
- signing_key:
936
+ rubygems_version: 3.1.2
937
+ signing_key:
908
938
  specification_version: 4
909
939
  summary: Blacklight provides a discovery interface for any Solr (http://lucene.apache.org/solr)
910
940
  index.
@@ -934,8 +964,10 @@ test_files:
934
964
  - spec/features/advanced_search_spec.rb
935
965
  - spec/features/alternate_controller_spec.rb
936
966
  - spec/features/autocomplete_spec.rb
967
+ - spec/features/axe_spec.rb
937
968
  - spec/features/bookmarks_spec.rb
938
969
  - spec/features/did_you_mean_spec.rb
970
+ - spec/features/facet_missing_spec.rb
939
971
  - spec/features/facets_spec.rb
940
972
  - spec/features/record_view_spec.rb
941
973
  - spec/features/search_context_spec.rb