geoblacklight 6.0.0.pre.alpha.7 → 6.0.0.pre.beta.2

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 (231) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +14 -3
  3. data/README.md +2 -2
  4. data/Rakefile +1 -17
  5. data/app/assets/images/geoblacklight/logo-dark.svg +31 -0
  6. data/app/assets/images/geoblacklight/logo.svg +10 -10
  7. data/app/assets/stylesheets/geoblacklight/global.css +44 -7
  8. data/app/assets/stylesheets/geoblacklight/index_maps.css +0 -7
  9. data/app/assets/stylesheets/geoblacklight/record.css +33 -9
  10. data/app/assets/stylesheets/geoblacklight/search.css +66 -17
  11. data/app/assets/stylesheets/geoblacklight/viewers.css +18 -111
  12. data/app/assets/stylesheets/geoblacklight.css +0 -5
  13. data/app/components/blacklight/icons/georeferenced_component.rb +23 -0
  14. data/app/components/geoblacklight/document/download_link_component.html.erb +1 -1
  15. data/app/components/geoblacklight/document/preview_component.html.erb +0 -2
  16. data/app/components/geoblacklight/document_component.html.erb +0 -4
  17. data/app/components/geoblacklight/facets/bbox_field_component.html.erb +2 -1
  18. data/app/components/geoblacklight/georeferenced_header_badge_component.rb +16 -0
  19. data/app/components/geoblacklight/header_badge_component.html.erb +4 -2
  20. data/app/components/geoblacklight/header_badge_component.rb +1 -1
  21. data/app/components/geoblacklight/header_badges_component.html.erb +5 -0
  22. data/app/components/geoblacklight/header_badges_component.rb +34 -0
  23. data/app/components/geoblacklight/iiif_drag_drop_component.html.erb +1 -1
  24. data/app/components/geoblacklight/item_map_viewer_component.html.erb +0 -58
  25. data/app/components/geoblacklight/item_map_viewer_component.rb +19 -70
  26. data/app/components/geoblacklight/locator_map_component.rb +31 -0
  27. data/app/components/geoblacklight/login_link_component.html.erb +1 -1
  28. data/app/components/geoblacklight/login_link_component.rb +17 -1
  29. data/app/components/geoblacklight/metadata_description_markdown_component.rb +3 -9
  30. data/app/components/geoblacklight/overview_map_component.html.erb +2 -0
  31. data/app/components/geoblacklight/overview_map_component.rb +86 -0
  32. data/app/components/geoblacklight/relations/relation_component.html.erb +12 -0
  33. data/app/components/geoblacklight/relations/relation_component.rb +30 -0
  34. data/app/components/geoblacklight/relations/relations_component.html.erb +15 -0
  35. data/app/components/geoblacklight/relations/relations_component.rb +59 -0
  36. data/app/components/geoblacklight/resource_header_badge_component.rb +1 -1
  37. data/app/components/geoblacklight/search_result_component.html.erb +14 -33
  38. data/app/components/geoblacklight/search_result_component.rb +2 -14
  39. data/app/components/geoblacklight/static_map_component.rb +3 -14
  40. data/app/components/geoblacklight/truncatable_metadata_field_layout_component.html.erb +7 -0
  41. data/app/components/geoblacklight/truncatable_metadata_field_layout_component.rb +45 -0
  42. data/app/controllers/{relation_controller.rb → relations_controller.rb} +4 -2
  43. data/app/controllers/viewer_record_controller.rb +28 -0
  44. data/app/helpers/geoblacklight_helper.rb +57 -6
  45. data/app/javascript/geoblacklight/controllers/overview_map_controller.js +143 -0
  46. data/app/javascript/geoblacklight/core.js +8 -10
  47. data/app/javascript/geoblacklight/index.js +9 -12
  48. data/app/javascript/geoblacklight/initializers/field_truncation.js +62 -0
  49. data/app/javascript/geoblacklight/initializers/metadata_download.js +3 -8
  50. data/app/javascript/geoblacklight/initializers/truncation.js +7 -3
  51. data/app/javascript/geoblacklight/initializers/viewer_requests.js +61 -0
  52. data/app/javascript/geoblacklight/initializers/viewer_theme.js +28 -0
  53. data/app/models/concerns/geoblacklight/solr_document/thumbnail.rb +38 -0
  54. data/app/models/concerns/geoblacklight/solr_document.rb +2 -0
  55. data/app/models/concerns/geoblacklight/suppressed_records_search_behavior.rb +1 -1
  56. data/app/views/catalog/_document_split.html.erb +10 -10
  57. data/app/views/catalog/_home_text.html.erb +1 -1
  58. data/app/views/catalog/metadata.html.erb +1 -1
  59. data/app/views/{relation → relations}/index.html.erb +1 -1
  60. data/app/views/relations/index.json.jbuilder +9 -0
  61. data/config/importmap.rb +8 -8
  62. data/config/locales/geoblacklight.en.yml +48 -99
  63. data/geoblacklight.gemspec +3 -3
  64. data/lib/generators/geoblacklight/assets_generator.rb +2 -2
  65. data/lib/generators/geoblacklight/install_generator.rb +3 -6
  66. data/lib/generators/geoblacklight/templates/assets/customizations.css +5 -1
  67. data/lib/generators/geoblacklight/templates/catalog_controller.rb +17 -27
  68. data/lib/generators/geoblacklight/templates/settings.yml +15 -95
  69. data/lib/geoblacklight/configuration/display_note_shown_config.rb +3 -0
  70. data/lib/geoblacklight/configuration/relationship_config.rb +7 -1
  71. data/lib/geoblacklight/configuration/relationships_config.rb +15 -15
  72. data/lib/geoblacklight/configuration/settings_attributes.rb +85 -0
  73. data/lib/geoblacklight/configuration/settings_builder.rb +3 -14
  74. data/lib/geoblacklight/configuration.rb +23 -62
  75. data/lib/geoblacklight/exceptions.rb +4 -0
  76. data/lib/geoblacklight/reference.rb +0 -2
  77. data/lib/geoblacklight/references.rb +17 -5
  78. data/lib/geoblacklight/relations/ancestors.rb +12 -0
  79. data/lib/geoblacklight/relations/descendants.rb +11 -0
  80. data/lib/geoblacklight/relations/query.rb +39 -0
  81. data/lib/geoblacklight/{relation → relations}/relation_response.rb +5 -4
  82. data/lib/geoblacklight/routes/exportable.rb +2 -1
  83. data/lib/geoblacklight/version.rb +1 -1
  84. data/lib/geoblacklight/viewer_record.rb +39 -0
  85. data/lib/geoblacklight.rb +1 -1
  86. data/lib/tasks/geoblacklight.rake +1 -17
  87. data/lib/tasks/solr.rb +37 -0
  88. data/schema/readme.md +1 -1
  89. data/script/wait-for-solr +36 -0
  90. data/solr/conf/solrconfig.xml +10 -16
  91. data/spec/components/geoblacklight/document/sidebar_component_spec.rb +3 -3
  92. data/spec/components/geoblacklight/document_component_spec.rb +8 -34
  93. data/spec/components/geoblacklight/{header_icons_component_spec.rb → header_badges_component_spec.rb} +20 -2
  94. data/spec/components/geoblacklight/item_map_viewer_component_spec.rb +65 -15
  95. data/spec/components/geoblacklight/locator_map_component_spec.rb +54 -0
  96. data/spec/components/geoblacklight/login_link_component_spec.rb +20 -12
  97. data/spec/components/geoblacklight/metadata_description_markdown_component_spec.rb +1 -6
  98. data/spec/components/geoblacklight/overview_map_component_spec.rb +136 -0
  99. data/spec/components/geoblacklight/relations/relation_component_spec.rb +98 -0
  100. data/spec/components/geoblacklight/relations/relations_component_spec.rb +146 -0
  101. data/spec/components/geoblacklight/search_result_component_spec.rb +66 -39
  102. data/spec/components/geoblacklight/static_map_component_spec.rb +8 -2
  103. data/spec/components/geoblacklight/truncatable_metadata_field_layout_component_spec.rb +83 -0
  104. data/spec/controllers/catalog_controller_spec.rb +22 -7
  105. data/spec/controllers/{relation_controller_spec.rb → relations_controller_spec.rb} +1 -1
  106. data/spec/factories/user.rb +6 -1
  107. data/spec/features/axe_spec.rb +5 -3
  108. data/spec/features/home_page_spec.rb +8 -7
  109. data/spec/features/index_view_spec.rb +6 -20
  110. data/spec/features/layer_preview_spec.rb +4 -11
  111. data/spec/features/locator_map_spec.rb +31 -0
  112. data/spec/features/relations_spec.rb +22 -11
  113. data/spec/features/restricted_viewer_spec.rb +14 -0
  114. data/spec/features/search_results_map_spec.rb +89 -13
  115. data/spec/features/search_spec.rb +6 -2
  116. data/spec/features/sign_in_spec.rb +26 -0
  117. data/spec/features/tilejson_spec.rb +1 -3
  118. data/spec/features/tms_spec.rb +2 -2
  119. data/spec/features/truncated_metadata_spec.rb +77 -0
  120. data/spec/features/viewer_requests_spec.rb +40 -0
  121. data/spec/features/viewer_theme_spec.rb +51 -0
  122. data/spec/features/wmts_spec.rb +6 -6
  123. data/spec/features/xyz_spec.rb +3 -3
  124. data/spec/fixtures/solr_documents/README.md +18 -14
  125. data/spec/fixtures/solr_documents/actual-polygon1.json +22 -18
  126. data/spec/fixtures/solr_documents/b1g_wabash_child_15.json +14 -15
  127. data/spec/fixtures/solr_documents/b1g_wabash_child_16.json +14 -15
  128. data/spec/fixtures/solr_documents/b1g_wabash_child_17.json +14 -15
  129. data/spec/fixtures/solr_documents/b1g_wabash_child_18.json +14 -15
  130. data/spec/fixtures/solr_documents/b1g_wabash_parent.json +14 -15
  131. data/spec/fixtures/solr_documents/esri-dynamic-layer-single-layer.json +2 -2
  132. data/spec/fixtures/solr_documents/esri-feature-layer.json +1 -1
  133. data/spec/fixtures/solr_documents/esri-image-map-layer.json +20 -20
  134. data/spec/fixtures/solr_documents/esri-wms-layer.json +22 -24
  135. data/spec/fixtures/solr_documents/index_map_point.json +3 -3
  136. data/spec/fixtures/solr_documents/public_polygon.json +28 -0
  137. data/spec/helpers/geoblacklight_helper_spec.rb +103 -25
  138. data/spec/lib/geoblacklight/bounding_box_spec.rb +16 -0
  139. data/spec/lib/geoblacklight/configuration/relationships_config_spec.rb +121 -0
  140. data/spec/lib/geoblacklight/configuration/settings_attributes_spec.rb +113 -0
  141. data/spec/lib/geoblacklight/configuration/settings_builder_spec.rb +33 -4
  142. data/spec/lib/geoblacklight/references_spec.rb +35 -0
  143. data/spec/lib/geoblacklight/{relation → relations}/ancestors_spec.rb +17 -6
  144. data/spec/lib/geoblacklight/{relation → relations}/descendants_spec.rb +14 -7
  145. data/spec/lib/geoblacklight/{relation → relations}/relation_response_spec.rb +31 -17
  146. data/spec/lib/geoblacklight/viewer_record_spec.rb +69 -0
  147. data/spec/models/concerns/geoblacklight/solr_document/thumbnail_spec.rb +149 -0
  148. data/spec/models/concerns/geoblacklight/solr_document_spec.rb +1 -1
  149. data/spec/requests/bookmarks_spec.rb +2 -1
  150. data/spec/requests/catalog_show_spec.rb +24 -2
  151. data/spec/requests/index_view_spec.rb +50 -2
  152. data/spec/requests/missing_metadata_spec.rb +1 -1
  153. data/spec/requests/relations_spec.rb +9 -2
  154. data/spec/requests/search_results_spec.rb +6 -6
  155. data/spec/requests/viewer_record_spec.rb +37 -0
  156. data/spec/requests/viewer_requests_spec.rb +52 -0
  157. data/spec/solr_config_spec.rb +41 -0
  158. data/spec/solr_fixtures_spec.rb +23 -0
  159. data/spec/spec_helper.rb +13 -3
  160. data/spec/support/authentication.rb +37 -0
  161. data/spec/support/features/map_helpers.rb +68 -0
  162. data/spec/support/features/session_helpers.rb +18 -13
  163. data/spec/support/features/viewer_helpers.rb +20 -0
  164. data/spec/support/features.rb +4 -0
  165. data/spec/test_app_templates/lib/generators/test_app_generator.rb +1 -1
  166. data/template.rb +1 -1
  167. metadata +72 -84
  168. data/app/components/geoblacklight/accordion_component.html.erb +0 -16
  169. data/app/components/geoblacklight/accordion_component.rb +0 -15
  170. data/app/components/geoblacklight/attribute_table_component.html.erb +0 -15
  171. data/app/components/geoblacklight/attribute_table_component.rb +0 -17
  172. data/app/components/geoblacklight/header_icons_component.html.erb +0 -3
  173. data/app/components/geoblacklight/header_icons_component.rb +0 -27
  174. data/app/components/geoblacklight/index_map_inspect_component.html.erb +0 -12
  175. data/app/components/geoblacklight/index_map_inspect_component.rb +0 -15
  176. data/app/components/geoblacklight/index_map_legend_component.html.erb +0 -14
  177. data/app/components/geoblacklight/index_map_legend_component.rb +0 -24
  178. data/app/components/geoblacklight/location_leaflet_map_component.rb +0 -68
  179. data/app/components/geoblacklight/relations_component.html.erb +0 -23
  180. data/app/components/geoblacklight/relations_component.rb +0 -25
  181. data/app/components/geoblacklight/viewer_help_text_component.html.erb +0 -1
  182. data/app/components/geoblacklight/viewer_help_text_component.rb +0 -46
  183. data/app/controllers/wms_controller.rb +0 -17
  184. data/app/javascript/geoblacklight/controllers/leaflet_viewer_controller.js +0 -240
  185. data/app/javascript/geoblacklight/controllers/openlayers_viewer_controller.js +0 -74
  186. data/app/javascript/geoblacklight/controllers/search_results_controller.js +0 -50
  187. data/app/javascript/geoblacklight/initializers/popovers.js +0 -11
  188. data/app/javascript/geoblacklight/initializers/tooltips.js +0 -51
  189. data/app/javascript/geoblacklight/leaflet/basemaps.js +0 -73
  190. data/app/javascript/geoblacklight/leaflet/constants.js +0 -18
  191. data/app/javascript/geoblacklight/leaflet/controls/geosearch.js +0 -168
  192. data/app/javascript/geoblacklight/leaflet/controls/layer_opacity.js +0 -121
  193. data/app/javascript/geoblacklight/leaflet/controls/sidebar.js +0 -49
  194. data/app/javascript/geoblacklight/leaflet/controls/sleep.js +0 -101
  195. data/app/javascript/geoblacklight/leaflet/inspection.js +0 -210
  196. data/app/javascript/geoblacklight/leaflet/layer_index_map.js +0 -128
  197. data/app/javascript/geoblacklight/leaflet/layer_wmts.js +0 -57
  198. data/app/javascript/geoblacklight/leaflet/layers.js +0 -146
  199. data/app/javascript/geoblacklight/leaflet/utils.js +0 -83
  200. data/app/javascript/geoblacklight/openlayers/basemaps.js +0 -55
  201. data/app/javascript/geoblacklight/openlayers/inspection.js +0 -42
  202. data/app/javascript/geoblacklight/openlayers/layers.js +0 -44
  203. data/app/views/relation/index.json.jbuilder +0 -8
  204. data/lib/geoblacklight/configuration/layer_config.rb +0 -28
  205. data/lib/geoblacklight/configuration/leaflet_config.rb +0 -73
  206. data/lib/geoblacklight/configuration/leaflet_layers_config.rb +0 -33
  207. data/lib/geoblacklight/configuration/leaflet_sleep_config.rb +0 -20
  208. data/lib/geoblacklight/relation/ancestors.rb +0 -31
  209. data/lib/geoblacklight/relation/descendants.rb +0 -31
  210. data/lib/geoblacklight/routes/wms.rb +0 -15
  211. data/lib/geoblacklight/wms_layer/feature_info_response.rb +0 -37
  212. data/lib/geoblacklight/wms_layer.rb +0 -34
  213. data/spec/components/geoblacklight/attribute_table_component_spec.rb +0 -36
  214. data/spec/components/geoblacklight/index_map_legend_component_spec.rb +0 -29
  215. data/spec/components/geoblacklight/viewer_help_text_component_spec.rb +0 -27
  216. data/spec/controllers/wms_controller_spec.rb +0 -34
  217. data/spec/features/configurable_basemap_spec.rb +0 -40
  218. data/spec/features/full_screen_control_spec.rb +0 -15
  219. data/spec/features/layer_inspection_spec.rb +0 -12
  220. data/spec/features/layer_opacity_spec.rb +0 -16
  221. data/spec/fixtures/solr_documents/harvard_raster.json +0 -39
  222. data/spec/fixtures/solr_documents/public_polygon_mit.json +0 -23
  223. data/spec/fixtures/solr_documents/uva_slug_colon.json +0 -29
  224. data/spec/javascripts/geoblacklight_spec.js +0 -11
  225. data/spec/lib/geoblacklight/configuration/layer_config_spec.rb +0 -71
  226. data/spec/lib/geoblacklight/configuration/leaflet_config_spec.rb +0 -129
  227. data/spec/lib/geoblacklight/configuration/leaflet_layers_config_spec.rb +0 -63
  228. data/spec/lib/geoblacklight/configuration_spec.rb +0 -48
  229. data/spec/lib/geoblacklight/wms_layer/feature_info_response_spec.rb +0 -54
  230. data/spec/lib/geoblacklight/wms_layer_spec.rb +0 -115
  231. /data/app/components/geoblacklight/{location_leaflet_map_component.html.erb → locator_map_component.html.erb} +0 -0
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "Viewer record", type: :request do
6
+ let(:references) { JSON.parse(response.parsed_body[Geoblacklight.configuration.fields.references]) }
7
+
8
+ # Restricted, and provided by this app's own institution, so signing in makes the difference
9
+ let(:restricted) { "stanford-dp018hs9766" }
10
+
11
+ it "withholds a restricted record's data from a reader who isn't signed in" do
12
+ get viewer_solr_document_path(restricted)
13
+
14
+ expect(response).to have_http_status(:ok)
15
+ expect(references.keys).not_to include("https://github.com/cogeotiff/cog-spec", "http://schema.org/downloadUrl")
16
+ expect(references.keys).to include("http://www.isotc211.org/schemas/2005/gmd/")
17
+ end
18
+
19
+ it "hands over the same record once that reader signs in" do
20
+ sign_in
21
+ get viewer_solr_document_path(restricted)
22
+
23
+ expect(references.keys).to include("https://github.com/cogeotiff/cog-spec", "http://schema.org/downloadUrl")
24
+ end
25
+
26
+ it "hands over a public record in full" do
27
+ get viewer_solr_document_path("berkeley-s7pq31")
28
+
29
+ expect(references.keys).to include("http://www.opengis.net/def/serviceType/ogc/wms")
30
+ end
31
+
32
+ it "reports a record that isn't there as missing" do
33
+ get viewer_solr_document_path("no-such-record")
34
+
35
+ expect(response).to have_http_status(:not_found)
36
+ end
37
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "Viewer requests", type: :request do
6
+ let(:origins) { ["https://stacks.stanford.edu/"] }
7
+
8
+ # Restricted, and provided by this app's own institution, so signing in makes the difference
9
+ let(:restricted) { "stanford-dp018hs9766" }
10
+
11
+ around do |example|
12
+ configured = Geoblacklight.configuration.restricted_origins
13
+ Geoblacklight.configuration.restricted_origins = origins
14
+ example.run
15
+ Geoblacklight.configuration.restricted_origins = configured
16
+ end
17
+
18
+ def restricted_origins
19
+ Capybara.string(response.body).find("ogm-viewer")["data-restricted-origins"]
20
+ end
21
+
22
+ it "names the services a signed-in reader's requests need to carry cookies to" do
23
+ sign_in
24
+ get solr_document_path(restricted)
25
+
26
+ expect(JSON.parse(restricted_origins)).to eq origins
27
+ end
28
+
29
+ it "names none for a reader who isn't signed in" do
30
+ get solr_document_path(restricted)
31
+
32
+ expect(restricted_origins).to be_nil
33
+ end
34
+
35
+ it "names none for a public record, which has nothing that needs authorizing" do
36
+ sign_in
37
+ get solr_document_path("berkeley-s7pq31")
38
+
39
+ expect(restricted_origins).to be_nil
40
+ end
41
+
42
+ context "when no services are configured" do
43
+ let(:origins) { [] }
44
+
45
+ it "names none" do
46
+ sign_in
47
+ get solr_document_path(restricted)
48
+
49
+ expect(restricted_origins).to be_nil
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "Solr configuration" do
6
+ def parse(name)
7
+ Nokogiri::XML(File.read(File.expand_path("../solr/conf/#{name}", __dir__)))
8
+ end
9
+
10
+ let(:schema) { parse("schema.xml") }
11
+ let(:solrconfig) { parse("solrconfig.xml") }
12
+
13
+ # Every *_ti and *_tmi name matches a dynamicField, so boosting a field that no
14
+ # copyField populates is legal, queryable, and always empty -- Solr never
15
+ # complains, the boost just silently contributes nothing to scoring. That has
16
+ # happened twice now: renaming a copyField destination from *_ti to *_tmi
17
+ # (multiValued sources need multiValued destinations) left the matching qf/pf
18
+ # entry behind. Deliberately ignores dynamicFields, since resolving through one
19
+ # is exactly the bug being guarded against.
20
+ def populated_fields
21
+ schema.xpath("//fields/field/@name").map(&:value) +
22
+ schema.xpath("//copyField/@dest").map(&:value)
23
+ end
24
+
25
+ # Reads the /text() children rather than the <str> node's own text, so the XML
26
+ # comment inside <str name="pf"> cannot leak words into the field list.
27
+ def boosted_fields(param)
28
+ solrconfig.xpath("//requestHandler[@name='/select']//str[@name='#{param}']/text()")
29
+ .map(&:text).join(" ")
30
+ .split.map { |entry| entry.split("^").first }
31
+ end
32
+
33
+ %w[qf pf].each do |param|
34
+ it "only boosts #{param} fields that something populates" do
35
+ dead = boosted_fields(param) - populated_fields
36
+
37
+ expect(dead).to be_empty,
38
+ "#{param} boosts fields no copyField or <field> populates: #{dead.join(", ")}"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "Solr fixture documents" do
6
+ def documents
7
+ Dir[File.expand_path("fixtures/solr_documents/*.json", __dir__)].flat_map do |path|
8
+ parsed = JSON.parse(File.read(path))
9
+ Array.wrap(parsed).map { |document| [File.basename(path), document] }
10
+ end
11
+ end
12
+
13
+ # geoblacklight:index:seed adds these documents to Solr, which upserts on id. Two
14
+ # fixtures sharing an id overwrite one another silently, so the index comes up
15
+ # short and the losing record is simply absent from every spec that expects it.
16
+ it "gives every document a unique id" do
17
+ duplicates = documents.group_by { |_file, document| document["id"] }
18
+ .select { |_id, entries| entries.length > 1 }
19
+ .transform_values { |entries| entries.map(&:first).sort }
20
+
21
+ expect(duplicates).to be_empty, "fixtures share an id: #{duplicates.inspect}"
22
+ end
23
+ end
data/spec/spec_helper.rb CHANGED
@@ -16,7 +16,7 @@ SimpleCov.start "rails" do
16
16
  add_filter "lib/geoblacklight/version.rb"
17
17
  add_filter "lib/geoblacklight/engine.rb"
18
18
  add_filter "lib/generators"
19
- add_filter "lib/tasks/geoblacklight.rake"
19
+ add_filter "lib/tasks"
20
20
  add_filter "/spec"
21
21
  add_filter ".internal_test_app/"
22
22
  minimum_coverage 100
@@ -40,10 +40,20 @@ WebMock.allow_net_connect!(net_http_connect_on_start: true)
40
40
  Capybara.register_driver :chrome_headless do |app|
41
41
  # Set up Chrome options
42
42
  options = Selenium::WebDriver::Chrome::Options.new
43
- options.add_argument("--headless")
43
+ options.add_argument("--headless=new")
44
44
  options.add_argument("--disable-gpu")
45
45
  options.add_argument("--no-sandbox")
46
46
  options.add_argument("--window-size=1280,1024")
47
+ # Scroll without animating. Bootstrap puts `scroll-behavior: smooth` on :root, so when
48
+ # ChromeDriver sets scrollTop to bring an element into view before clicking it, the page only
49
+ # starts to move and ChromeDriver reads back the element's old, off-screen position. The click
50
+ # then fails and is left to Capybara's retry, which a busy page can outlast.
51
+ options.add_argument("--disable-smooth-scrolling")
52
+ # Software WebGL, which the viewer and the overview map both need: they draw with MapLibre, and
53
+ # headless Chrome has no GPU to give them. SwiftShader is Chrome's own CPU renderer, and since
54
+ # Chrome 128 it has to be asked for by name before WebGL will fall back to it at all.
55
+ options.add_argument("--enable-unsafe-swiftshader")
56
+ options.add_argument("--use-angle=swiftshader")
47
57
  # Return from #visit at DOMContentLoaded instead of waiting for the full
48
58
  # window `load` event. GeoBlacklight pages pull basemap tiles and WMS/TMS
49
59
  # layers from external hosts; in CI those requests can hang and prevent
@@ -72,6 +82,6 @@ RSpec.configure do |config|
72
82
 
73
83
  config.use_transactional_fixtures = true
74
84
 
75
- config.include Devise::Test::ControllerHelpers, type: :controller
85
+ config.include Devise::Test::ControllerHelpers, type: :controller if defined?(Devise)
76
86
  config.include ViewComponent::TestHelpers, type: :component
77
87
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The app can be generated using Devise auth, Rails auth, or no auth. These
4
+ # are for signing in and/or skipping tests depending on which is the case.
5
+ module AuthenticationHelpers
6
+ # :devise, :rails, or nil if the app has no authentication
7
+ def self.provider
8
+ if defined?(Devise)
9
+ :devise
10
+ elsif ApplicationController.respond_to?(:allow_unauthenticated_access)
11
+ :rails
12
+ end
13
+ end
14
+
15
+ def skip_without_authentication
16
+ skip "the test app has no authentication" unless AuthenticationHelpers.provider
17
+ end
18
+
19
+ module Requests
20
+ # Sign a new user in by posting the sign-in form
21
+ def sign_in
22
+ skip_without_authentication
23
+ user = FactoryBot.create(:user)
24
+
25
+ if AuthenticationHelpers.provider == :devise
26
+ post user_session_path, params: {user: {email: user.email, password: user.password}}
27
+ else
28
+ post session_path, params: {email_address: user.email_address, password: user.password}
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ RSpec.configure do |config|
35
+ config.include AuthenticationHelpers
36
+ config.include AuthenticationHelpers::Requests, type: :request
37
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Features
4
+ module MapHelpers
5
+ # An overview or locator map, once it has something on it. Both are handed what to draw a moment
6
+ # after the page arrives - a locator fetches its own record, and an overview's controller imports
7
+ # the library it draws with - so anything that reads or points at a map has to wait for that
8
+ # rather than for the page.
9
+ def map_ready(selector = "#overview-map", wait: 20)
10
+ map = find(selector)
11
+ expect(map.shadow_root).to have_css("canvas.maplibregl-canvas", wait: wait)
12
+
13
+ # And until there is something to draw, which may well be nothing: a map with no results to
14
+ # put on it - the home page's - has still been given its records.
15
+ page.document.synchronize(wait) do
16
+ given = page.evaluate_script(
17
+ "arguments[0].record !== undefined || arguments[0].previewers !== undefined || arguments[0].previewer !== undefined", map
18
+ )
19
+ raise(Capybara::ElementNotFound) unless given
20
+ end
21
+
22
+ map
23
+ end
24
+
25
+ # What the map has been given to draw, as a west/south, east/north pair per result, in the order
26
+ # their numbers put them, with nil where a result had nowhere to be drawn. A locator holds one
27
+ # record rather than a list, and reports the same shape around it: a single-entry array, worked
28
+ # out from the record's own geometry rather than asked of a previewer, since none is exposed.
29
+ def drawn_extents(selector = "#overview-map")
30
+ page.evaluate_async_script(<<~JS, map_ready(selector))
31
+ const [map, done] = arguments
32
+
33
+ if (map.record) {
34
+ const geometry = map.record.getGeometry()
35
+ const points = geometry?.coordinates?.[0] ?? []
36
+ if (!points.length) return done([null])
37
+
38
+ const lngs = points.map(([lng]) => lng)
39
+ const lats = points.map(([, lat]) => lat)
40
+ return done([[[Math.min(...lngs), Math.min(...lats)], [Math.max(...lngs), Math.max(...lats)]]])
41
+ }
42
+
43
+ const drawn = map.previewers ?? [map.previewer]
44
+ Promise.all(Array.from(drawn, (previewer) => previewer?.getBounds() ?? null)).then(done)
45
+ JS
46
+ end
47
+
48
+ # Search the map the way a reader does: hold shift and drag a box over the middle of it. There is
49
+ # no control to click - the gesture is the whole of it - so this is a real mouse drag.
50
+ def search_map_area(selector = "#overview-map", size: 80)
51
+ map = map_ready(selector)
52
+
53
+ page.driver.browser.action
54
+ .move_to(map.native, -size / 2, -size / 2)
55
+ .key_down(:shift)
56
+ .click_and_hold
57
+ .move_by(size, size)
58
+ .release
59
+ .key_up(:shift)
60
+ .perform
61
+ end
62
+
63
+ # The area the query on screen asked after, as the bbox parameter states it
64
+ def searched_bbox
65
+ URI.decode_www_form(URI(page.current_url).query.to_s).to_h["bbox"]
66
+ end
67
+ end
68
+ end
@@ -2,21 +2,26 @@
2
2
 
3
3
  module Features
4
4
  module SessionHelpers
5
- def sign_up_with(email, password)
6
- visit new_user_registration_path
7
- fill_in "Email", with: email
8
- fill_in "Password", with: password
9
- click_button "Sign up"
5
+ def sign_in
6
+ skip_without_authentication
7
+ visit (AuthenticationHelpers.provider == :devise) ? new_user_session_path : new_session_path
8
+ fill_in_sign_in_form FactoryBot.create(:user)
9
+ # Wait for the persistent "Log Out" nav link rather than the one-shot
10
+ # "Signed in successfully." flash, which can render and then become
11
+ # non-visible before Capybara's default wait time elapses.
12
+ expect(page).to have_link "Log Out"
10
13
  end
11
14
 
12
- def sign_in
13
- user = FactoryBot.create(:user)
14
- user.save
15
- visit new_user_session_path
16
- fill_in "user_email", with: user.email
17
- fill_in "user_password", with: user.password
18
- click_button "Log in"
19
- expect(page).to have_content "Signed in successfully."
15
+ def fill_in_sign_in_form(user)
16
+ if AuthenticationHelpers.provider == :devise
17
+ fill_in "user_email", with: user.email
18
+ fill_in "user_password", with: user.password
19
+ click_button "Log in"
20
+ else
21
+ fill_in "email_address", with: user.email_address
22
+ fill_in "password", with: user.password
23
+ click_button "Sign in"
24
+ end
20
25
  end
21
26
  end
22
27
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Features
4
+ module ViewerHelpers
5
+ # What the item viewer offers to show, as the labels on its tabs. It works these out from the
6
+ # record's references once it has fetched the record, inside its own shadow root, so this waits
7
+ # for them rather than expecting them to be there when the page arrives.
8
+ def preview_tabs(wait: 20)
9
+ page.document.synchronize(wait) do
10
+ page.evaluate_script(<<~JS) || raise(Capybara::ElementNotFound)
11
+ (() => {
12
+ const previews = document.querySelector("ogm-viewer")?.shadowRoot?.querySelector("ogm-previews")
13
+ const tabs = previews?.shadowRoot?.querySelectorAll("wa-tab")
14
+ return tabs?.length ? Array.from(tabs).map((tab) => tab.textContent.trim()) : null
15
+ })()
16
+ JS
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,7 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require File.expand_path("../features/session_helpers.rb", __FILE__)
4
+ require File.expand_path("../features/map_helpers.rb", __FILE__)
5
+ require File.expand_path("../features/viewer_helpers.rb", __FILE__)
4
6
 
5
7
  RSpec.configure do |config|
6
8
  config.include Features::SessionHelpers, type: :feature
9
+ config.include Features::MapHelpers, type: :feature
10
+ config.include Features::ViewerHelpers, type: :feature
7
11
  end
@@ -16,7 +16,7 @@ class TestAppGenerator < Rails::Generators::Base
16
16
  # Install blacklight
17
17
  def run_blacklight_generator
18
18
  say_status("warning", "GENERATING BL", :yellow)
19
- generate "blacklight:install", "--devise", "--skip-solr"
19
+ generate "blacklight:install", ENV["BLACKLIGHT_INSTALL_OPTIONS"].presence || "--devise --skip-solr"
20
20
  end
21
21
 
22
22
  # Install geoblacklight
data/template.rb CHANGED
@@ -10,7 +10,7 @@ end
10
10
  run "bundle install"
11
11
 
12
12
  after_bundle do
13
- generate "blacklight:install", ENV.fetch("BLACKLIGHT_INSTALL_OPTIONS", "--devise")
13
+ generate "blacklight:install", ENV["BLACKLIGHT_INSTALL_OPTIONS"].presence || "--devise --skip-solr"
14
14
  generate "geoblacklight:install"
15
15
  rails_command "db:migrate"
16
16
  end