blacklight 9.0.0 → 9.1.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 (170) hide show
  1. checksums.yaml +4 -4
  2. data/.env +3 -3
  3. data/.github/matrix.json +12 -18
  4. data/.github/workflows/lint.yml +1 -1
  5. data/.github/workflows/test.yml +4 -3
  6. data/.rubocop.yml +72 -7
  7. data/.rubocop_todo.yml +142 -268
  8. data/Gemfile +1 -1
  9. data/README.md +2 -9
  10. data/VERSION +1 -1
  11. data/app/assets/builds/blacklight.css +37 -23
  12. data/app/assets/images/blacklight/logo-dark.svg +184 -0
  13. data/app/assets/images/blacklight/logo.svg +184 -0
  14. data/app/assets/javascripts/blacklight/blacklight.esm.js +128 -45
  15. data/app/assets/javascripts/blacklight/blacklight.esm.js.map +1 -1
  16. data/app/assets/javascripts/blacklight/blacklight.js +128 -45
  17. data/app/assets/javascripts/blacklight/blacklight.js.map +1 -1
  18. data/app/assets/stylesheets/blacklight/_balanced_list.scss +1 -1
  19. data/app/assets/stylesheets/blacklight/_bootstrap_overrides.scss +17 -0
  20. data/app/assets/stylesheets/blacklight/_constraints.scss +2 -2
  21. data/app/assets/stylesheets/blacklight/_facets.scss +14 -10
  22. data/app/assets/stylesheets/blacklight/_header.scss +0 -4
  23. data/app/assets/stylesheets/blacklight/blacklight_defaults.scss +5 -9
  24. data/app/components/blacklight/facets/inclusive_constraint_component.html.erb +1 -1
  25. data/app/components/blacklight/icons/checkmark_component.rb +16 -0
  26. data/app/components/blacklight/icons/circle_half_component.rb +16 -0
  27. data/app/components/blacklight/icons/moon_stars_component.rb +17 -0
  28. data/app/components/blacklight/icons/sun_component.rb +16 -0
  29. data/app/components/blacklight/metadata_field_component.rb +1 -1
  30. data/app/components/blacklight/search_navbar_component.html.erb +1 -1
  31. data/app/components/blacklight/theme_switcher_component.html.erb +31 -0
  32. data/app/components/blacklight/theme_switcher_component.rb +6 -0
  33. data/app/components/blacklight/top_navbar_component.html.erb +1 -1
  34. data/app/controllers/concerns/blacklight/bookmarks.rb +8 -14
  35. data/app/controllers/concerns/blacklight/catalog.rb +9 -10
  36. data/app/controllers/concerns/blacklight/controller.rb +54 -11
  37. data/app/controllers/concerns/blacklight/guest_user.rb +51 -0
  38. data/app/controllers/concerns/blacklight/search_context.rb +1 -1
  39. data/app/controllers/concerns/blacklight/search_history.rb +3 -1
  40. data/app/controllers/concerns/blacklight/searchable.rb +18 -0
  41. data/app/helpers/blacklight/layout_helper_behavior.rb +7 -0
  42. data/app/javascript/blacklight-frontend/color_theme_switcher.js +85 -0
  43. data/app/javascript/blacklight-frontend/index.js +2 -0
  44. data/app/models/concerns/blacklight/document/dublin_core.rb +2 -2
  45. data/app/models/concerns/blacklight/document.rb +1 -1
  46. data/app/models/concerns/blacklight/user.rb +36 -0
  47. data/app/presenters/blacklight/field_presenter.rb +1 -1
  48. data/app/presenters/blacklight/rendering/microdata.rb +1 -0
  49. data/app/services/blacklight/bookmarks_search_builder.rb +4 -4
  50. data/app/services/blacklight/search_service.rb +46 -33
  51. data/app/values/blacklight/types.rb +1 -1
  52. data/app/views/catalog/_email_form.html.erb +3 -3
  53. data/app/views/catalog/_home_text.html.erb +1 -1
  54. data/app/views/catalog/_sms_form.html.erb +4 -4
  55. data/app/views/catalog/index.rss.builder +4 -4
  56. data/app/views/catalog/opensearch.json.jbuilder +3 -0
  57. data/app/views/catalog/opensearch.xml.builder +2 -2
  58. data/app/views/layouts/blacklight/base.html.erb +13 -1
  59. data/app/views/shared/_user_util_links.html.erb +18 -5
  60. data/blacklight.gemspec +3 -3
  61. data/config/locales/blacklight.ar.yml +5 -0
  62. data/config/locales/blacklight.ca.yml +5 -0
  63. data/config/locales/blacklight.de.yml +6 -2
  64. data/config/locales/blacklight.en.yml +5 -0
  65. data/config/locales/blacklight.es.yml +5 -0
  66. data/config/locales/blacklight.fr.yml +5 -0
  67. data/config/locales/blacklight.hu.yml +5 -0
  68. data/config/locales/blacklight.it.yml +6 -2
  69. data/config/locales/blacklight.nl.yml +5 -0
  70. data/config/locales/blacklight.pt-BR.yml +6 -2
  71. data/config/locales/blacklight.sq.yml +5 -0
  72. data/config/locales/blacklight.zh.yml +5 -0
  73. data/lib/blacklight/component.rb +1 -1
  74. data/lib/blacklight/configuration/action_config_map_entry.rb +31 -0
  75. data/lib/blacklight/configuration/display_field.rb +1 -0
  76. data/lib/blacklight/configuration/field.rb +6 -6
  77. data/lib/blacklight/configuration/fields.rb +1 -1
  78. data/lib/blacklight/configuration/sort_field.rb +2 -2
  79. data/lib/blacklight/configuration.rb +30 -19
  80. data/lib/blacklight/parameters.rb +1 -1
  81. data/lib/blacklight/search_builder.rb +58 -19
  82. data/lib/blacklight/search_state.rb +4 -0
  83. data/lib/blacklight/solr/request.rb +8 -1
  84. data/lib/blacklight/solr/response/group_response.rb +1 -1
  85. data/lib/blacklight/solr/response/spelling.rb +8 -8
  86. data/lib/blacklight/solr/response.rb +2 -21
  87. data/lib/blacklight/solr/search_builder_behavior.rb +3 -9
  88. data/lib/blacklight.rb +2 -2
  89. data/lib/generators/blacklight/assets/importmap_generator.rb +2 -2
  90. data/lib/generators/blacklight/assets_generator.rb +1 -1
  91. data/lib/generators/blacklight/controller_generator.rb +1 -1
  92. data/lib/generators/blacklight/document_generator.rb +1 -1
  93. data/lib/generators/blacklight/install_generator.rb +6 -4
  94. data/lib/generators/blacklight/models_generator.rb +1 -1
  95. data/lib/generators/blacklight/search_builder_generator.rb +1 -1
  96. data/lib/generators/blacklight/solr_generator.rb +1 -1
  97. data/lib/generators/blacklight/templates/catalog_controller.rb +20 -1
  98. data/lib/generators/blacklight/templates/solr/conf/schema.xml +264 -178
  99. data/lib/generators/blacklight/templates/solr/conf/solrconfig.xml +6 -14
  100. data/lib/generators/blacklight/test_support_generator.rb +1 -1
  101. data/lib/generators/blacklight/user_generator.rb +66 -5
  102. data/lib/railties/blacklight.rake +4 -4
  103. data/package.json +2 -2
  104. data/spec/components/blacklight/document/action_component_spec.rb +1 -1
  105. data/spec/components/blacklight/document/group_component_spec.rb +1 -1
  106. data/spec/components/blacklight/document_component_spec.rb +9 -9
  107. data/spec/components/blacklight/facet_component_spec.rb +2 -2
  108. data/spec/components/blacklight/facets/index_navigation_component_spec.rb +1 -2
  109. data/spec/components/blacklight/facets/list_component_spec.rb +1 -1
  110. data/spec/components/blacklight/facets/suggest_component_spec.rb +1 -2
  111. data/spec/components/blacklight/metadata_field_component_spec.rb +26 -0
  112. data/spec/components/blacklight/skip_link_component_spec.rb +2 -2
  113. data/spec/controllers/application_controller_spec.rb +71 -0
  114. data/spec/controllers/bookmarks_controller_spec.rb +18 -5
  115. data/spec/controllers/catalog_controller_spec.rb +7 -5
  116. data/spec/features/advanced_search_spec.rb +29 -16
  117. data/spec/features/alternate_controller_spec.rb +4 -4
  118. data/spec/features/axe_spec.rb +34 -22
  119. data/spec/features/bookmarks_spec.rb +13 -13
  120. data/spec/features/dark_mode_spec.rb +42 -0
  121. data/spec/features/did_you_mean_spec.rb +27 -27
  122. data/spec/features/facet_missing_spec.rb +3 -3
  123. data/spec/features/record_view_spec.rb +14 -14
  124. data/spec/features/search_context_spec.rb +8 -8
  125. data/spec/features/search_filters_spec.rb +16 -16
  126. data/spec/features/search_formats_spec.rb +2 -2
  127. data/spec/features/search_history_spec.rb +11 -11
  128. data/spec/features/search_pagination_spec.rb +17 -17
  129. data/spec/features/search_results_spec.rb +3 -3
  130. data/spec/features/search_sort_spec.rb +4 -4
  131. data/spec/features/search_spec.rb +20 -20
  132. data/spec/helpers/blacklight/url_helper_behavior_spec.rb +15 -15
  133. data/spec/helpers/blacklight_helper_spec.rb +1 -1
  134. data/spec/helpers/catalog_helper_spec.rb +7 -7
  135. data/spec/integration/generators/blacklight/user_generator_spec.rb +60 -0
  136. data/spec/lib/blacklight/search_state/pivot_filter_field_spec.rb +1 -1
  137. data/spec/models/blacklight/configurable_spec.rb +1 -1
  138. data/spec/models/blacklight/configuration_spec.rb +0 -19
  139. data/spec/models/blacklight/document/dublin_core_spec.rb +2 -2
  140. data/spec/models/blacklight/facet_paginator_spec.rb +1 -1
  141. data/spec/models/blacklight/search_builder_spec.rb +15 -3
  142. data/spec/models/blacklight/solr/repository_spec.rb +10 -2
  143. data/spec/models/blacklight/solr/search_builder_behavior_spec.rb +3 -5
  144. data/spec/models/blacklight/user_spec.rb +32 -5
  145. data/spec/models/bookmark_spec.rb +1 -1
  146. data/spec/models/record_mailer_spec.rb +8 -8
  147. data/spec/models/search_spec.rb +1 -1
  148. data/spec/models/solr_document_spec.rb +4 -0
  149. data/spec/presenters/blacklight/index_presenter_spec.rb +1 -0
  150. data/spec/presenters/blacklight/rendering/pipeline_spec.rb +1 -0
  151. data/spec/presenters/blacklight/show_presenter_spec.rb +1 -0
  152. data/spec/presenters/blacklight/thumbnail_presenter_spec.rb +1 -0
  153. data/spec/services/blacklight/search_service_spec.rb +7 -7
  154. data/spec/spec_helper.rb +3 -12
  155. data/spec/support/features/session_helpers.rb +13 -4
  156. data/spec/support/features/turbo_helpers.rb +13 -0
  157. data/spec/support/features.rb +2 -0
  158. data/spec/support/user_helpers.rb +24 -0
  159. data/spec/test_app_templates/lib/generators/test_app_generator.rb +1 -1
  160. data/spec/views/catalog/_document.html.erb_spec.rb +4 -4
  161. data/spec/views/catalog/_paginate_compact.html.erb_spec.rb +2 -2
  162. data/spec/views/catalog/_sort_and_per_page.html.erb_spec.rb +5 -5
  163. data/spec/views/catalog/index.html.erb_spec.rb +2 -2
  164. data/spec/views/catalog/show.html.erb_spec.rb +3 -4
  165. data/spec/views/shared/_user_util_links.html.erb_spec.rb +56 -0
  166. data/template.demo.rb +2 -1
  167. metadata +30 -22
  168. data/.hound.yml +0 -4
  169. data/.jshintrc +0 -22
  170. data/app/assets/images/blacklight/logo.png +0 -0
@@ -4,11 +4,9 @@
4
4
  from this file. Please refer to http://wiki.apache.org/solr/SolrConfigXml,
5
5
  as well as the default solrconfig file included with Solr -->
6
6
 
7
- <abortOnConfigurationError>${solr.abortOnConfigurationError:true}</abortOnConfigurationError>
7
+ <luceneMatchVersion>9.0.0</luceneMatchVersion>
8
8
 
9
- <luceneMatchVersion>6.1.0</luceneMatchVersion>
10
-
11
- <directoryFactory name="DirectoryFactory" class="${solr.directoryFactory:solr.StandardDirectoryFactory}"/>
9
+ <directoryFactory name="DirectoryFactory" class="${solr.directoryFactory:solr.StandardDirectoryFactory}" />
12
10
 
13
11
  <updateHandler class="solr.DirectUpdateHandler2">
14
12
  <updateLog>
@@ -16,17 +14,11 @@
16
14
  </updateLog>
17
15
  </updateHandler>
18
16
 
19
- <!-- solr lib dirs, which are needed for Solr 8 compatibility but ignored in solr 9.8 and above -->
20
17
  <lib dir="${solr.install.dir:../../../..}/modules/analysis-extras/lib" />
21
- <lib dir="${solr.install.dir:../../../..}/contrib/analysis-extras/lib" />
22
- <lib dir="${solr.install.dir:../../../..}/contrib/analysis-extras/lucene-libs" />
23
18
 
24
19
  <dataDir>${solr.data.dir:}</dataDir>
25
20
 
26
- <requestHandler name="search" class="solr.SearchHandler" default="true">
27
- <!-- default values for query parameters can be specified, these
28
- will be overridden by parameters in the request
29
- -->
21
+ <requestHandler name="/select" class="solr.SearchHandler">
30
22
  <lst name="defaults">
31
23
  <str name="defType">edismax</str>
32
24
  <str name="echoParams">explicit</str>
@@ -98,7 +90,7 @@
98
90
  </arr>
99
91
  </requestHandler>
100
92
 
101
- <requestHandler name="permissions" class="solr.SearchHandler" >
93
+ <requestHandler name="permissions" class="solr.SearchHandler">
102
94
  <lst name="defaults">
103
95
  <str name="facet">off</str>
104
96
  <str name="echoParams">all</str>
@@ -186,8 +178,8 @@
186
178
 
187
179
  <requestHandler name="/replication" class="solr.ReplicationHandler" startup="lazy" />
188
180
 
189
- <requestDispatcher handleSelect="true" >
190
- <requestParsers enableRemoteStreaming="true" multipartUploadLimitInKB="2048" />
181
+ <requestDispatcher handleSelect="true">
182
+ <requestParsers multipartUploadLimitInKB="2048" />
191
183
  </requestDispatcher>
192
184
 
193
185
  <requestHandler name="/analysis/field" startup="lazy" class="solr.FieldAnalysisRequestHandler" />
@@ -8,7 +8,7 @@ require 'rails/generators'
8
8
  require 'rails/generators/base'
9
9
  module Blacklight
10
10
  class TestSupport < Rails::Generators::Base
11
- source_root File.expand_path('../templates', __FILE__)
11
+ source_root File.expand_path('templates', __dir__)
12
12
 
13
13
  desc <<-EOS
14
14
  Generate blacklight testing configurations for blacklight's own tests, or for blacklight plugins to use for testing
@@ -7,16 +7,22 @@ module Blacklight
7
7
  class UserGenerator < Rails::Generators::Base
8
8
  include Rails::Generators::Migration
9
9
 
10
- source_root File.expand_path('../templates', __FILE__)
10
+ source_root File.expand_path('templates', __dir__)
11
11
 
12
12
  argument :model_name, type: :string, default: "user"
13
- class_option :devise, type: :boolean, default: false, aliases: "-d", desc: "Use Devise as authentication logic (this is default)."
13
+ class_option :devise, type: :boolean, default: false, aliases: "-d", desc: "Use Devise as authentication logic."
14
+ class_option :authentication, type: :boolean, default: false, desc: "Use Rails' built-in authentication generator."
14
15
 
15
16
  desc <<-EOS
16
17
  This generator makes the following changes to your application:
17
- 1. Creates a devise-based user model
18
+ 1. Optionally creates an authenticated user model
18
19
  2. Injects blacklight-specific behavior into your user model
19
20
  EOS
21
+
22
+ def validate_authentication_options
23
+ raise Thor::Error, "Choose either --devise or --authentication, not both." if options[:devise] && options[:authentication]
24
+ end
25
+
20
26
  # Install Devise?
21
27
  def generate_devise_assets
22
28
  return unless options[:devise]
@@ -39,6 +45,18 @@ module Blacklight
39
45
  gsub_file("config/initializers/devise.rb", "# config.reload_routes = true", "config.reload_routes = false")
40
46
  end
41
47
 
48
+ # Install Rails' built-in authentication generator?
49
+ def generate_rails_authentication_assets
50
+ return unless options[:authentication]
51
+
52
+ raise Thor::Error, "Rails' authentication generator is only available in Rails 8.0 and newer." unless rails_authentication_generator_available?
53
+ raise Thor::Error, "Rails' authentication generator creates a User model; omit the model name when using --authentication." unless model_name.underscore == "user"
54
+
55
+ generate "authentication", rails_authentication_generator_args
56
+ inject_rails_authentication_blacklight_behavior
57
+ inject_rails_authentication_guest_transfer
58
+ end
59
+
42
60
  # Add Blacklight to the user model
43
61
  def inject_blacklight_user_behavior
44
62
  file_path = "app/models/#{model_name.underscore}.rb"
@@ -49,8 +67,8 @@ module Blacklight
49
67
  include Blacklight::User
50
68
 
51
69
  # Blacklight::User uses a method on your User class to get a user-displayable
52
- # label (e.g. login or identifier) for the account. Blacklight uses `email' by default.
53
- # self.string_display_key = :email
70
+ # label (e.g. login or identifier) for the account.
71
+ #{blacklight_user_display_key_configuration}
54
72
  EOS
55
73
  end
56
74
  else
@@ -63,5 +81,48 @@ module Blacklight
63
81
  EOS
64
82
  end
65
83
  end
84
+
85
+ private
86
+
87
+ def rails_authentication_generator_available?
88
+ Rails.gem_version >= Gem::Version.new("8.0.0")
89
+ end
90
+
91
+ def rails_authentication_generator_args
92
+ Rails.application.config.api_only ? "--api" : ""
93
+ end
94
+
95
+ def inject_rails_authentication_blacklight_behavior
96
+ inject_into_class "app/controllers/application_controller.rb", "ApplicationController" do
97
+ [
98
+ " # Makes Rails' generated authentication available through Blacklight's auth hooks.",
99
+ " helper_method :current_user",
100
+ "",
101
+ " def current_user",
102
+ " resume_session&.user",
103
+ " end",
104
+ ""
105
+ ].join("\n")
106
+ end
107
+ end
108
+
109
+ # Move bookmarks/searches accumulated by the anonymous guest user over to the
110
+ # real account when they sign in.
111
+ def inject_rails_authentication_guest_transfer
112
+ sessions_path = "app/controllers/sessions_controller.rb"
113
+ return unless File.exist?(File.expand_path(sessions_path, destination_root))
114
+
115
+ inject_into_file sessions_path, after: " start_new_session_for user\n" do
116
+ " transfer_guest_to_user\n"
117
+ end
118
+ end
119
+
120
+ def blacklight_user_display_key_configuration
121
+ if options[:authentication]
122
+ "self.string_display_key = :email_address"
123
+ else
124
+ "# self.string_display_key = :email"
125
+ end
126
+ end
66
127
  end
67
128
  end
@@ -39,7 +39,7 @@ namespace :blacklight do
39
39
  puts "Unable to reach: #{conn.uri}"
40
40
  exit 1
41
41
  end
42
- rescue => e
42
+ rescue StandardError => e
43
43
  puts e
44
44
  exit 1
45
45
  end
@@ -68,7 +68,7 @@ namespace :blacklight do
68
68
  puts "\tdoc count: #{response.docs.length}"
69
69
  puts "\tfacet fields: #{response.facets.length}"
70
70
  end
71
- rescue => e
71
+ rescue StandardError => e
72
72
  errors += 1
73
73
  puts e
74
74
  end
@@ -90,7 +90,7 @@ namespace :blacklight do
90
90
  puts "\tdoc count: #{docs.length}"
91
91
  puts "\tfacet fields: #{response.facets.length}"
92
92
  end
93
- rescue => e
93
+ rescue StandardError => e
94
94
  errors += 1
95
95
  puts e
96
96
  end
@@ -110,7 +110,7 @@ namespace :blacklight do
110
110
  if verbose
111
111
  puts "\tstatus: #{response.header['status']}"
112
112
  end
113
- rescue => e
113
+ rescue StandardError => e
114
114
  errors += 1
115
115
  puts e
116
116
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blacklight-frontend",
3
- "version": "9.0.0",
3
+ "version": "9.1.0",
4
4
  "description": "The frontend code and styles for Blacklight",
5
5
  "exports": {
6
6
  "./blacklight.esm.js": "./app/assets/javascripts/blacklight/blacklight.esm.js",
@@ -34,7 +34,7 @@
34
34
  "rollup": "^4.24.0",
35
35
  "rollup-plugin-includepaths": "^0.2.4",
36
36
  "sass": "^1.80.3",
37
- "bootstrap": "^5.3.5"
37
+ "bootstrap": "^5.3.8"
38
38
  },
39
39
  "browserslist": [
40
40
  "defaults",
@@ -42,7 +42,7 @@ RSpec.describe Blacklight::Document::ActionComponent, type: :component do
42
42
  end
43
43
 
44
44
  it 'render the partial' do
45
- expect(page).to have_content 'tool'
45
+ expect(page).to have_text 'tool'
46
46
  end
47
47
  end
48
48
  end
@@ -27,7 +27,7 @@ RSpec.describe Blacklight::Document::GroupComponent, type: :component do
27
27
  end
28
28
 
29
29
  it 'renders the group documents' do
30
- expect(page).to have_content 'results'
30
+ expect(page).to have_text 'results'
31
31
  end
32
32
 
33
33
  context 'with a limit applied' do
@@ -41,11 +41,11 @@ RSpec.describe Blacklight::DocumentComponent, type: :component do
41
41
  component.with_actions { 'Actions' }
42
42
  render_inline component
43
43
 
44
- expect(page).to have_content 'Title'
45
- expect(page).to have_content 'Embed'
46
- expect(page).to have_content 'Metadata'
47
- expect(page).to have_content 'Thumbnail'
48
- expect(page).to have_content 'Actions'
44
+ expect(page).to have_text 'Title'
45
+ expect(page).to have_text 'Embed'
46
+ expect(page).to have_text 'Metadata'
47
+ expect(page).to have_text 'Thumbnail'
48
+ expect(page).to have_text 'Actions'
49
49
  end
50
50
 
51
51
  it 'has schema.org properties' do
@@ -61,7 +61,7 @@ RSpec.describe Blacklight::DocumentComponent, type: :component do
61
61
  component.with_body { 'Body content' }
62
62
  render_inline component
63
63
 
64
- expect(page).to have_content 'Body content'
64
+ expect(page).to have_text 'Body content'
65
65
  expect(page).to have_no_css 'header'
66
66
  expect(page).to have_no_css 'dl'
67
67
  end
@@ -167,7 +167,7 @@ RSpec.describe Blacklight::DocumentComponent, type: :component do
167
167
  end
168
168
 
169
169
  it 'renders an embed' do
170
- expect(page).to have_content 'embed'
170
+ expect(page).to have_text 'embed'
171
171
  end
172
172
  end
173
173
 
@@ -242,7 +242,7 @@ RSpec.describe Blacklight::DocumentComponent, type: :component do
242
242
  end
243
243
 
244
244
  it 'renders partials' do
245
- expect(page).to have_content 'Partials'
245
+ expect(page).to have_text 'Partials'
246
246
  end
247
247
  end
248
248
 
@@ -266,7 +266,7 @@ RSpec.describe Blacklight::DocumentComponent, type: :component do
266
266
  end
267
267
 
268
268
  it 'shows the prefix' do
269
- expect(page).to have_content "Prefix!"
269
+ expect(page).to have_text "Prefix!"
270
270
  end
271
271
  end
272
272
  end
@@ -39,7 +39,7 @@ RSpec.describe Blacklight::FacetComponent, type: :component do
39
39
  before { rendered }
40
40
 
41
41
  it 'renders the provided component' do
42
- expect(page).to have_content 'Custom facet rendering'
42
+ expect(page).to have_text 'Custom facet rendering'
43
43
  end
44
44
  end
45
45
 
@@ -64,7 +64,7 @@ RSpec.describe Blacklight::FacetComponent, type: :component do
64
64
  end
65
65
 
66
66
  it 'renders the partial' do
67
- expect(page).to have_content 'facet partial'
67
+ expect(page).to have_text 'facet partial'
68
68
  end
69
69
  end
70
70
 
@@ -16,8 +16,7 @@ RSpec.describe Blacklight::Facets::IndexNavigationComponent, type: :component do
16
16
 
17
17
  it 'renders the facet index navigation range' do
18
18
  expect(page).to have_css '.pagination'
19
- facet_path = ViewComponent::VERSION::MAJOR == 3 ? '/catalog/facet/language.html' : '/catalog/facet/language'
20
- expect(page).to have_link '0', href: "#{facet_path}?facet.prefix=0&facet.sort=index"
19
+ expect(page).to have_link '0', href: "/catalog/facet/language?facet.prefix=0&facet.sort=index"
21
20
  expect(page).to have_link '1'
22
21
  expect(page).to have_link '8'
23
22
  expect(page).to have_link '9'
@@ -144,7 +144,7 @@ RSpec.describe Blacklight::Facets::ListComponent, type: :component do
144
144
  let(:params) { { f_inclusive: { field: %w[a b c] } } }
145
145
 
146
146
  it 'displays the constraint above the list' do
147
- expect(page).to have_content 'Any of:'
147
+ expect(page).to have_text 'Any of:'
148
148
  expect(page).to have_css '.inclusive_or .facet-label', text: 'a'
149
149
  expect(page).to have_link '[remove]', href: 'http://test.host/catalog?f_inclusive%5Bfield%5D%5B%5D=b&f_inclusive%5Bfield%5D%5B%5D=c'
150
150
  expect(page).to have_css '.inclusive_or .facet-label', text: 'b'
@@ -28,8 +28,7 @@ RSpec.describe Blacklight::Facets::SuggestComponent, type: :component do
28
28
  it 'has an input with the data-facet-search-context attribute, which the javascript needs to determine the current search context' do
29
29
  with_request_url "/catalog/facet/language_facet?f%5Bformat%5D%5B%5D=Book&facet.prefix=R&facet.sort=index&q=tibet&search_field=all_fields" do
30
30
  rendered = render_inline component
31
- facet_path = ViewComponent::VERSION::MAJOR == 3 ? '/catalog/facet/language_facet.html' : '/catalog/facet/language_facet'
32
- expect(rendered.css("input[data-facet-search-context=\"#{facet_path}?f%5Bformat%5D%5B%5D=Book&facet.prefix=R&facet.sort=index&q=tibet&search_field=all_fields\"]").count).to eq 1
31
+ expect(rendered.css("input[data-facet-search-context=\"/catalog/facet/language_facet?f%5Bformat%5D%5B%5D=Book&facet.prefix=R&facet.sort=index&q=tibet&search_field=all_fields\"]").count).to eq 1
33
32
  end
34
33
  end
35
34
 
@@ -2,6 +2,14 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
+ class CustomLayoutComponent < ViewComponent::Base
6
+ def initialize(field:); end
7
+
8
+ def call
9
+ 'Hello from the custom layout component!'
10
+ end
11
+ end
12
+
5
13
  RSpec.describe Blacklight::MetadataFieldComponent, type: :component do
6
14
  let(:view_context) { vc_test_controller.view_context }
7
15
  let(:document) { SolrDocument.new('field' => ['Value']) }
@@ -35,5 +43,23 @@ RSpec.describe Blacklight::MetadataFieldComponent, type: :component do
35
43
  it 'renders the right field label' do
36
44
  expect(page).to have_css 'dt.blacklight-field', text: 'custom label'
37
45
  end
46
+
47
+ context 'with a custom layout component specified in the field config' do
48
+ let(:field_config) { Blacklight::Configuration::Field.new(layout_component: CustomLayoutComponent, key: 'field', field: 'field', label: 'Field label') }
49
+
50
+ it 'displays the custom layout component' do
51
+ expect(page.text).to include 'Hello from the custom layout component!'
52
+ end
53
+ end
54
+ end
55
+
56
+ context 'with a custom layout component passed in through args' do
57
+ before do
58
+ render_inline(described_class.new(field: field, layout: CustomLayoutComponent))
59
+ end
60
+
61
+ it 'displays the custom layout component' do
62
+ expect(page.text).to include 'Hello from the custom layout component!'
63
+ end
38
64
  end
39
65
  end
@@ -24,7 +24,7 @@ RSpec.describe Blacklight::SkipLinkComponent, type: :component do
24
24
  context 'with one search field' do
25
25
  let(:blacklight_config) do
26
26
  Blacklight::Configuration.new.configure do |config|
27
- config.search_fields = { "all_fields" => "" }
27
+ config.search_fields = { "all_fields" => "" }
28
28
  end
29
29
  end
30
30
 
@@ -37,7 +37,7 @@ RSpec.describe Blacklight::SkipLinkComponent, type: :component do
37
37
  context 'with two search field' do
38
38
  let(:blacklight_config) do
39
39
  Blacklight::Configuration.new.configure do |config|
40
- config.search_fields = { "all_fields" => "", "title_field" => "" }
40
+ config.search_fields = { "all_fields" => "", "title_field" => "" }
41
41
  end
42
42
  end
43
43
 
@@ -1,9 +1,80 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Blacklight manages its own guest users only when no other provider (such as
4
+ # the devise-guests gem) is present. Under Devise, current_or_guest_user defers
5
+ # to devise-guests, so the guest-fallback specs below run on the Rails
6
+ # authentication test app only.
7
+ BLACKLIGHT_MANAGES_GUESTS = !defined?(DeviseGuests)
8
+
3
9
  RSpec.describe ApplicationController do
4
10
  describe "#blacklight_config" do
5
11
  it "provides a default blacklight_config everywhere" do
6
12
  expect(controller.blacklight_config).to eq CatalogController.blacklight_config
7
13
  end
8
14
  end
15
+
16
+ describe "authentication path helpers" do
17
+ it "prefers Devise routes when available" do
18
+ allow(controller).to receive(:respond_to?).and_call_original
19
+ allow(controller).to receive(:respond_to?).with(:new_user_session_path).and_return(true)
20
+ allow(controller).to receive(:new_user_session_path).and_return("/users/sign_in")
21
+
22
+ expect(controller.send(:blacklight_login_path)).to eq "/users/sign_in"
23
+ end
24
+
25
+ it "falls back to Rails authentication routes" do
26
+ allow(controller).to receive(:respond_to?).and_call_original
27
+ allow(controller).to receive(:respond_to?).with(:new_user_session_path).and_return(false)
28
+ allow(controller).to receive(:respond_to?).with(:new_session_path).and_return(true)
29
+ allow(controller).to receive(:new_session_path).and_return("/session/new")
30
+
31
+ expect(controller.send(:blacklight_login_path)).to eq "/session/new"
32
+ end
33
+
34
+ it "uses a DELETE logout link for Rails authentication" do
35
+ allow(controller).to receive(:respond_to?).and_call_original
36
+ allow(controller).to receive(:respond_to?).with(:destroy_user_session_path).and_return(false)
37
+ allow(controller).to receive(:respond_to?).with(:session_path).and_return(true)
38
+ allow(controller).to receive(:session_path).and_return("/session")
39
+
40
+ expect(controller.send(:blacklight_logout_path)).to eq "/session"
41
+ expect(controller.send(:blacklight_logout_link_options)).to eq(data: { turbo_method: :delete })
42
+ end
43
+ end
44
+
45
+ describe "#current_or_guest_user and #guest_user" do
46
+ it "returns the current user when one is signed in" do
47
+ user = create_user(email: "signed_in@example.com", password: "password12345")
48
+ allow(controller).to receive(:current_user).and_return(user)
49
+
50
+ expect(controller.send(:current_or_guest_user)).to eq user
51
+ expect { controller.send(:current_or_guest_user) }.not_to(change { User.where("#{User.blacklight_email_column} LIKE 'guest_%'").count })
52
+ end
53
+
54
+ # Blacklight only manages its own guest users when no other provider (such
55
+ # as the devise-guests gem) is doing so. Under Devise, current_or_guest_user
56
+ # defers to devise-guests, so these fallback behaviors are exercised on the
57
+ # Rails authentication test app instead.
58
+ context "when Blacklight manages guest users", if: BLACKLIGHT_MANAGES_GUESTS do
59
+ it "creates and memoizes a persisted guest user for anonymous visitors" do
60
+ session = {}
61
+ allow(controller).to receive_messages(current_user: nil, session: session)
62
+
63
+ guest = controller.send(:current_or_guest_user)
64
+
65
+ expect(guest).to be_persisted
66
+ expect(session[:blacklight_guest_user_id]).to eq guest.id
67
+ expect(guest.public_send(User.blacklight_email_column)).to match(/^guest_.*@example\.com$/)
68
+
69
+ expect(controller.send(:current_or_guest_user)).to eq guest
70
+ end
71
+
72
+ it "reuses an existing guest across requests via the session" do
73
+ existing = User.create_guest_user!
74
+ allow(controller).to receive_messages(current_user: nil, session: { blacklight_guest_user_id: existing.id })
75
+
76
+ expect(controller.send(:current_or_guest_user)).to eq existing
77
+ end
78
+ end
79
+ end
9
80
  end
@@ -12,6 +12,12 @@ RSpec.describe BookmarksController do
12
12
  end
13
13
 
14
14
  describe "update" do
15
+ let(:user) { create_user }
16
+
17
+ before do
18
+ allow(controller).to receive(:current_or_guest_user).and_return(user)
19
+ end
20
+
15
21
  it "has a 200 status code when creating a new one" do
16
22
  put :update, xhr: true, params: { id: '2007020969', format: :js }
17
23
  expect(response).to be_successful
@@ -31,8 +37,13 @@ RSpec.describe BookmarksController do
31
37
  end
32
38
 
33
39
  describe "create" do
40
+ let(:user) { create_user }
41
+
42
+ before do
43
+ allow(controller).to receive(:current_or_guest_user).and_return(user)
44
+ end
45
+
34
46
  it "can create bookmarks via params bookmarks attribute" do
35
- @controller.send(:current_or_guest_user).save
36
47
  put :create, xhr: true, params: {
37
48
  id: "notused",
38
49
  bookmarks: [
@@ -50,9 +61,11 @@ RSpec.describe BookmarksController do
50
61
  end
51
62
 
52
63
  describe "delete" do
64
+ let(:user) { create_user }
65
+
53
66
  before do
54
- @controller.send(:current_or_guest_user).save
55
- @controller.send(:current_or_guest_user).bookmarks.create! document_id: '2007020969', document_type: "SolrDocument"
67
+ allow(controller).to receive(:current_or_guest_user).and_return(user)
68
+ user.bookmarks.create! document_id: '2007020969', document_type: "SolrDocument"
56
69
  end
57
70
 
58
71
  it "has a 200 status code when delete is success" do
@@ -90,12 +103,12 @@ RSpec.describe BookmarksController do
90
103
  end
91
104
 
92
105
  describe 'token based users' do
93
- let(:user) { User.find_or_create_by(email: 'user1@example.com') { |u| u.password = 'password' } }
106
+ let(:user) { create_user email: 'user1@example.com', password: 'password' }
94
107
  let(:current_time) { nil }
95
108
  let(:token) { controller.send(:encrypt_user_id, user.id, current_time) }
96
109
 
97
110
  before do
98
- allow(controller).to receive(:fetch).and_return([])
111
+ allow(controller).to receive_messages(fetch: [], retrieve_search_results: instance_double(Blacklight::Solr::Response))
99
112
  end
100
113
 
101
114
  it 'finds the user from the encrypted token' do
@@ -440,7 +440,7 @@ RSpec.describe CatalogController, :api do
440
440
  it "responds to an extension-registered format properly" do
441
441
  get :show, params: { id: doc_id, format: 'mock' }
442
442
  expect(response).to be_successful
443
- expect(response.body).to match /mock_export/
443
+ expect(response.body).to include('mock_export')
444
444
  end
445
445
  end # dynamic export formats
446
446
  end # describe show action
@@ -452,16 +452,18 @@ RSpec.describe CatalogController, :api do
452
452
  end
453
453
 
454
454
  context 'when searching for something' do
455
+ render_views
455
456
  before do
456
457
  allow(controller).to receive(:search_service).and_return(search_service)
457
- expect(search_service).to receive(:opensearch_response)
458
- .and_return(['a', [SolrDocument.new(id: 'my_fake_doc'),
459
- SolrDocument.new(id: 'my_other_doc')]])
458
+ expect(search_service).to receive(:search_results)
459
+ .and_return(instance_double(Blacklight::Solr::Response, documents: [SolrDocument.new(id: 'my_fake_doc'),
460
+ SolrDocument.new(id: 'my_other_doc')]))
460
461
  end
461
462
 
462
463
  it "returns valid JSON" do
463
464
  get :opensearch, params: { format: 'json', q: 'a' }
464
465
  expect(response).to be_successful
466
+ expect(response.parsed_body).to eq %w[a my_fake_doc my_other_doc]
465
467
  end
466
468
  end
467
469
  end
@@ -594,7 +596,7 @@ RSpec.describe CatalogController, :api do
594
596
 
595
597
  it "does not show user util links" do
596
598
  get :index
597
- expect(response.body).not_to match /Login/
599
+ expect(response.body).not_to include('Login')
598
600
  end
599
601
  end
600
602