diff options
author | Sarah German <sgerman@gitlab.com> | 2022-09-22 18:10:42 +0300 |
---|---|---|
committer | Kati Paizee <kpaizee@gitlab.com> | 2022-09-22 18:10:42 +0300 |
commit | 6ef828e917f82a7bd8a621c8a652e3127666ac37 (patch) | |
tree | 62b2f8c2f54d46bda3a58e8c45b1cad2e9777514 | |
parent | a5ac9d88bd1251c252d53680e8af0df0eef1d743 (diff) |
Add Lunr search UI components
-rw-r--r-- | .gitlab/ci/build-and-deploy.gitlab-ci.yml | 1 | ||||
-rw-r--r-- | Rules | 3 | ||||
-rw-r--r-- | commands/frontend.rb | 9 | ||||
-rw-r--r-- | content/404.html | 2 | ||||
-rw-r--r-- | content/assets/stylesheets/_landing.scss | 14 | ||||
-rw-r--r-- | content/assets/stylesheets/stylesheet.scss | 13 | ||||
-rw-r--r-- | content/frontend/search/components/lunr_page.vue | 97 | ||||
-rw-r--r-- | content/frontend/search/components/lunr_search_form.vue | 23 | ||||
-rw-r--r-- | content/frontend/search/lunrsearch.js | 26 | ||||
-rw-r--r-- | content/frontend/search/search.js | 5 | ||||
-rw-r--r-- | content/index.erb | 6 | ||||
-rw-r--r-- | content/search/index.md | 2 | ||||
-rw-r--r-- | doc/docsearch.md | 46 | ||||
-rw-r--r-- | layouts/default.html | 4 | ||||
-rw-r--r-- | layouts/head.html | 9 | ||||
-rw-r--r-- | layouts/header.html | 6 | ||||
-rw-r--r-- | layouts/home.html | 6 | ||||
-rw-r--r-- | layouts/search.html (renamed from layouts/instantsearch.html) | 11 | ||||
-rw-r--r-- | lib/helpers/generic.rb | 6 | ||||
-rw-r--r-- | scripts/lunr/preindex.js | 6 | ||||
-rw-r--r-- | spec/frontend/search/lunr_search_spec.js | 26 |
21 files changed, 291 insertions, 30 deletions
diff --git a/.gitlab/ci/build-and-deploy.gitlab-ci.yml b/.gitlab/ci/build-and-deploy.gitlab-ci.yml index 8cc8aa13..7cbf976f 100644 --- a/.gitlab/ci/build-and-deploy.gitlab-ci.yml +++ b/.gitlab/ci/build-and-deploy.gitlab-ci.yml @@ -55,7 +55,6 @@ compile_dev: variables: ALGOLIA_SEARCH: 'false' - ############################################### # Review Apps # ############################################### @@ -39,6 +39,9 @@ preprocess do item[:title_badge] = badges_filter.run_from_markdown(raw_title.match(BadgesFilter::BADGES_MARKDOWN_PATTERN).to_s) end end + + config[:algolia] = ENV['ALGOLIA_SEARCH'] || 'true' + end compile '/404.*' do diff --git a/commands/frontend.rb b/commands/frontend.rb index 7f93d113..c88c475f 100644 --- a/commands/frontend.rb +++ b/commands/frontend.rb @@ -34,6 +34,13 @@ run do |opts, args, cmd| gl_ui_dest = 'public/frontend/shared' Dir.children(gl_ui_src).each do |filename| - puts "Copied #{gl_ui_src}/#{filename}" if filename.include?("map") && File.write("#{gl_ui_dest}/#{filename}", File.read("#{root}/#{gl_ui_src}/#{filename}")) + puts "- Copied #{gl_ui_src}/#{filename}" if filename.include?("map") && File.write("#{gl_ui_dest}/#{filename}", File.read("#{root}/#{gl_ui_src}/#{filename}")) end + + if ENV['ALGOLIA_SEARCH'] == "false" + lunr_src = "node_modules/lunr/lunr.min.js" + puts 'Copying Lunr.js...' + puts "- Copied #{lunr_src}" if File.write('public/assets/javascripts/lunr.min.js', File.read("#{root}/#{lunr_src}")) + end + end diff --git a/content/404.html b/content/404.html index f937955b..7bc135f2 100644 --- a/content/404.html +++ b/content/404.html @@ -8,12 +8,14 @@ searchbar: false <img src="<%= @items['/assets/images/error-404.svg'].path %>" alt="404"/> <div id="js-error-message"></div> <br> + <% if @config[:algolia] == "true" %> <div class="card search w-100 text-center"> <div class="card-body"> <h3 class="card-title mb-4">Search the docs</h3> <div id="docsearch" class="mb-3 d-flex justify-content-center"></div> </div> </div> + <% end %> <div class="d-flex justify-content-center my-4"> <a href="javascript:history.back()" class="btn btn-outline-primary"> <%= icon('arrow-left') %> diff --git a/content/assets/stylesheets/_landing.scss b/content/assets/stylesheets/_landing.scss index 5356297e..ff5012fb 100644 --- a/content/assets/stylesheets/_landing.scss +++ b/content/assets/stylesheets/_landing.scss @@ -1,5 +1,5 @@ --- -version: 12 +version: 13 --- @import 'variables'; @@ -163,7 +163,7 @@ version: 12 color: $gds-purple-900; } - .btn { + .btn:not(.gl-search-box-by-click-clear-button, .gl-search-box-by-click-search-button) { color: $gds-white; background: $landing-gl-purple-500; border: 1px solid $landing-gl-purple-600; @@ -176,6 +176,16 @@ version: 12 background: $gds-white; } } + + .lunr-search { + margin-bottom: 2em; + .btn:not(.gl-search-box-by-click-clear-button) { + background: $landing-gl-purple-500; + } + svg { + fill: $gds-white; + } + } } diff --git a/content/assets/stylesheets/stylesheet.scss b/content/assets/stylesheets/stylesheet.scss index ad211677..59e1afa3 100644 --- a/content/assets/stylesheets/stylesheet.scss +++ b/content/assets/stylesheets/stylesheet.scss @@ -295,6 +295,12 @@ ol { -webkit-mask: url('/assets/images/ellipsis_h.svg') no-repeat center; } } + + .lunr-search { + .input-group-append { + background-color: inherit; + } + } } //badges @@ -594,3 +600,10 @@ a.gl-tab-nav-item:hover { } } } + +.lunr-search { + .input-group>.form-control:focus, + .gl-search-box-by-click-clear-button { + z-index: auto; + } +} diff --git a/content/frontend/search/components/lunr_page.vue b/content/frontend/search/components/lunr_page.vue new file mode 100644 index 00000000..5edcb04c --- /dev/null +++ b/content/frontend/search/components/lunr_page.vue @@ -0,0 +1,97 @@ +<script> +/* global lunr */ +import { GlSearchBoxByClick, GlLink } from '@gitlab/ui'; + +export default { + components: { + GlSearchBoxByClick, + GlLink, + }, + data() { + return { + query: '', + submitted: false, + error: false, + results: [], + contentMap: [], + relevancy_threshold: 15, + }; + }, + computed: { + noResults() { + return this.submitted && !this.results.length && !this.error; + }, + }, + async created() { + try { + // Load the search index and content map. + const [indexResp, mapResp] = await Promise.all([ + fetch('/assets/javascripts/lunr-index.json'), + fetch('/assets/javascripts/lunr-map.json'), + ]); + const lindex = await indexResp.json(); + this.contentMap = await mapResp.json(); + + // Initialize Lunr. + const idx = lunr.Index.load(lindex); + window.idx = idx; + + // If we have a query string in the URL, run the search. + const searchParams = new URLSearchParams(window.location.search); + if (searchParams.has('query')) { + this.search(searchParams.get('query')); + } + } catch (e) { + this.handleError(e); + } + }, + methods: { + onSubmit() { + if (this.query) { + this.search(this.query); + } + }, + search(query) { + this.query = query; + this.submitted = true; + + // Run the search. + this.results = window.idx.search(this.query); + + // Limit the results by relevancy score. + this.results = this.results.filter((key) => key.score > this.relevancy_threshold); + + // Add page titles to the result set. + Object.keys(this.results).forEach((key) => { + const contentItem = this.contentMap.find(({ id }) => id === this.results[key].ref); + this.results[key].title = contentItem.h1; + }); + + // Add the search term to the URL to allow linking to result pages. + const url = new URL(window.location); + url.searchParams.set('query', this.query); + window.history.pushState(null, '', url.toString()); + }, + handleError() { + this.error = true; + }, + }, +}; +</script> + +<template> + <div class="lunr-search"> + <h1>Search</h1> + <gl-search-box-by-click v-model="query" :value="query" @submit="onSubmit" /> + <div v-if="results.length" class="gl-font-sm gl-mb-6">{{ results.length }} results found</div> + + <ul v-if="results.length"> + <li v-for="result in results" :key="result.ref"> + <gl-link :href="`/${result.ref}`">{{ result.title }}</gl-link> + </li> + </ul> + + <p v-if="noResults" class="gl-py-5">No results found.</p> + <p v-if="error" class="gl-py-5" data-testid="lunr-error">Error fetching search index.</p> + </div> +</template> diff --git a/content/frontend/search/components/lunr_search_form.vue b/content/frontend/search/components/lunr_search_form.vue new file mode 100644 index 00000000..958e5b07 --- /dev/null +++ b/content/frontend/search/components/lunr_search_form.vue @@ -0,0 +1,23 @@ +<script> +import { GlSearchBoxByClick } from '@gitlab/ui'; + +export default { + components: { + GlSearchBoxByClick, + }, + data() { + return { + query: '', + }; + }, + methods: { + onSubmit() { + window.location.href = `/search/?query=${encodeURI(this.query)}`; + }, + }, +}; +</script> + +<template> + <gl-search-box-by-click v-model="query" class="lunr-search" @submit="onSubmit" /> +</template> diff --git a/content/frontend/search/lunrsearch.js b/content/frontend/search/lunrsearch.js new file mode 100644 index 00000000..ae0a06d9 --- /dev/null +++ b/content/frontend/search/lunrsearch.js @@ -0,0 +1,26 @@ +import Vue from 'vue'; +import LunrPage from './components/lunr_page.vue'; +import LunrSearchForm from './components/lunr_search_form.vue'; + +// Search results page (/search) +document.addEventListener('DOMContentLoaded', () => { + return new Vue({ + el: '.js-lunrsearch', + render(createElement) { + return createElement(LunrPage); + }, + }); +}); + +// Homepage and interior navbar search forms +document.addEventListener('DOMContentLoaded', () => { + return new Vue({ + el: '.js-lunr-form', + components: { + LunrSearchForm, + }, + render(createElement) { + return createElement(LunrSearchForm); + }, + }); +}); diff --git a/content/frontend/search/search.js b/content/frontend/search/search.js index ab202445..7c53542f 100644 --- a/content/frontend/search/search.js +++ b/content/frontend/search/search.js @@ -27,7 +27,10 @@ export const getAlgoliaCredentials = (crawler = 'production') => { export const getDocsVersion = () => { let docsVersion = 'main'; - if (document.querySelector('meta[name="docsearch:version"]').content.length > 0) { + if ( + document.querySelector('meta[name="docsearch:version"]') && + document.querySelector('meta[name="docsearch:version"]').content.length + ) { docsVersion = document.querySelector('meta[name="docsearch:version"]').content; } return docsVersion; diff --git a/content/index.erb b/content/index.erb index 05b35f29..8f4ecd93 100644 --- a/content/index.erb +++ b/content/index.erb @@ -17,7 +17,11 @@ title: GitLab Documentation <h5 class="card-title pb-3 mb-2">Search the docs</h5> <% if @item[:searchbar].nil? %> <% unless @item.identifier.to_s.split('/')[1] == 'search' %> - <div id="docsearch" class="mb-3 d-flex justify-content-center"></div> + <% if @config[:algolia] == "true" %> + <div id="docsearch" class="gl-mb-4 gl-display-flex gl-justify-content-center"></div> + <% else %> + <div class="js-lunr-form"></div> + <% end %> <% end %> <% end %> <a href="/ee/" class="card-link"> diff --git a/content/search/index.md b/content/search/index.md index a4d484db..a9eaccf3 100644 --- a/content/search/index.md +++ b/content/search/index.md @@ -1,4 +1,4 @@ --- title: Search GitLab Docs -layout: instantsearch +layout: search --- diff --git a/doc/docsearch.md b/doc/docsearch.md index 38c1d032..8c922d86 100644 --- a/doc/docsearch.md +++ b/doc/docsearch.md @@ -1,4 +1,8 @@ -# Algolia DocSearch +# Search + +GitLab Docs uses either Algolia DocSearch or Lunr.js as a search backend, depending on where the instance is hosted. The primary production site, docs.gitlab.com, runs Algolia. + +## Algolia DocSearch GitLab is a member of the [Algolia's DocSearch program](https://docsearch.algolia.com/), which is a free tier of [Algolia](https://www.algolia.com/). We use @@ -8,7 +12,7 @@ Algolia [crawls](#configure-the-algolia-crawler) our documentation, pushes the c [index](https://www.algolia.com/doc/guides/sending-and-managing-data/manage-your-indices/), and DocSearch provides a dropdown search experience on our website. -## DocSearch implementation details +### DocSearch implementation details DocSearch layouts are defined in various places: @@ -22,14 +26,14 @@ and an index name that are needed for Algolia to show the results: - Dedicated search page under `/search`: [`content/frontend/search/index.js`](../content/frontend/search/index.js) - Every other page: [`content/frontend/search/docsearch.js`](../content/frontend/search/docsearch.js) -## Override DocSearch CSS +### Override DocSearch CSS DocSearch defines its various classes starting with `DocSearch-`. To override those, there's one file to edit: - [`content/assets/stylesheets/_docsearch.scss`](../content/assets/stylesheets/_docsearch.scss) -## Navigate Algolia as a GitLab member +### Navigate Algolia as a GitLab member GitLab members can access Algolia's dashboard with the credentials that are stored in 1Password (search for Algolia). After you log in, you can visit: @@ -37,7 +41,7 @@ stored in 1Password (search for Algolia). After you log in, you can visit: - The index dashboard - The Algolia crawler -### Browse the index dashboard +#### Browse the index dashboard The [index dashboard](https://www.algolia.com/apps/3PNCFOU757/analytics/overview/gitlab) provides information about the data that Algolia has extracted from the docs site. @@ -47,7 +51,7 @@ Useful information: - [Sorting](https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/) - [Custom ranking](https://www.algolia.com/doc/guides/managing-results/must-do/custom-ranking/) -### Configure the Algolia crawler +#### Configure the Algolia crawler An Algolia crawler does three things: @@ -74,7 +78,7 @@ Read more about the crawler: - Watch this [short video](https://www.youtube.com/watch?v=w84K1cbUbmY) that explains what a crawler is and how it works. -### Analytics and weekly reports of the search usage +#### Analytics and weekly reports of the search usage You can view the search usage in the [analytics dashboard](https://www.algolia.com/apps/3PNCFOU757/analytics/overview/gitlab). @@ -83,7 +87,7 @@ If you want to receive weekly reports of the search usage, open a new [access request](https://about.gitlab.com/handbook/engineering/#access-requests) issue and ask that your email is added to the DocSearch alias (the same email as found in 1Password). -## Testing Algolia configuration changes +### Testing Algolia configuration changes In order to test configuration changes without impacting docs.gitlab.com, you can create an additional crawler via the [Algolia dashboard](https://crawler.algolia.com/admin/crawlers?sort=status&order=ASC&limit=20). @@ -99,3 +103,29 @@ To use a test index from a review app or locally, update the `apiKey` and `index - [InstantSearch config](../content/frontend/search/index.js) (dedicated search page). You can find the API key under **Crawler > Settings** on the Algolia dashboard. This is a public, read-only key, so you can use it in merge requests. + +## Lunr.js Search + +Lunr.js is available as an alternative search backend for self-hosted GitLab Docs installations. Lunr search can also be used in offline or air-gapped environments. Lunr search requires an additional build step to create a search index. + +Documentation review apps use Lunr search by default. + +### Manually generate the Lunr search index + +1. `yarn install` +1. `nanoc compile` +1. `make build-lunr-index` + +Note that compiling the site will remove the index files, so if you recompile the site, you'll need to run the `make` command after to regenerate the required files. + +## Toggle the search backend + +Production always runs Algolia, but you can build the site with Lunr either locally or in a review app. + +### Local environment + +You can compile your local Nanoc site to use a specific search backend by setting the `ALGOLIA_SEARCH` environment variable. + +- Use Algolia search: `export ALGOLIA_SEARCH="true"` (or leave this undefined) +- Use Lunr search: `export ALGOLIA_SEARCH="false"` +- If you do not set this variable before compiling, the build will default to Algolia. diff --git a/layouts/default.html b/layouts/default.html index b932c75b..f0674675 100644 --- a/layouts/default.html +++ b/layouts/default.html @@ -94,7 +94,11 @@ </div> </section> <script src="<%= @items['/frontend/shared/global_imports.*'].path %>"></script> + <% if @config[:algolia] == "true" %> <script src="<%= @items['/frontend/search/docsearch.*'].path %>"></script> + <% else %> + <script src="<%= @items['/frontend/search/lunrsearch.*'].path %>"></script> + <% end %> <script src="<%= @items['/assets/javascripts/toggle_popover.*'].path %>"></script> <script src="<%= @items['/frontend/shared/clipboardjs.*'].path %>"></script> <script src="<%= @items['/assets/javascripts/badges.*'].path %>"></script> diff --git a/layouts/head.html b/layouts/head.html index df60be49..d1b264d4 100644 --- a/layouts/head.html +++ b/layouts/head.html @@ -15,6 +15,8 @@ <% if @item[:noindex] or !production_and_default_branch? %> <meta name="robots" content="noindex, nofollow"> <% end %> + +<% if @config[:algolia] == "true" %> <!--https://community.algolia.com/docsearch/required-configuration.html#introduces-global-information-as-meta-tags--> <meta name="docsearch:language" content="en" /> <% if !ENV['CI_COMMIT_REF_NAME'].nil? and stable_version?(ENV['CI_COMMIT_REF_NAME']) %> @@ -23,6 +25,10 @@ <meta name="docsearch:version" content="<%= ENV['CI_DEFAULT_BRANCH'] %>" /> <% end %> <link crossorigin href="https://3PNCFOU757-dsn.algolia.net" rel="preconnect" /> +<!-- Algolia Searching from the URL bar https://www.algolia.com/doc/tutorials/search-ui/ux-patterns/search-from-the-url-bar/ --> +<link href='/opensearch.xml' rel='search' title='Search through GitLab Docs' type='application/opensearchdescription+xml'> +<link rel="stylesheet" href="/frontend/search/docsearch.css"> +<% end %> <!-- Enable CSP headers --> <% unless ENV['DISABLE_CSP'] %> @@ -31,7 +37,6 @@ <!-- End of CSP headers --> <link rel="stylesheet" href="/frontend/shared/global_imports.css"> -<link rel="stylesheet" href="/frontend/search/docsearch.css"> <link rel="stylesheet" href="<%= @items['/assets/stylesheets/stylesheet.*'].path %>"> <link rel="stylesheet" href="<%= @items['/assets/stylesheets/highlight.*'].path %>"> <link rel="stylesheet" href="<%= @items['/assets/stylesheets/footer.*'].path %>"> @@ -76,8 +81,6 @@ <!-- you don't need to keep this, but it's cool for stats! --> <meta name="generator" content="Nanoc <%= Nanoc::VERSION %>"> -<!-- Algolia Searching from the URL bar https://www.algolia.com/doc/tutorials/search-ui/ux-patterns/search-from-the-url-bar/ --> -<link href='/opensearch.xml' rel='search' title='Search through GitLab Docs' type='application/opensearchdescription+xml'> <!-- Apple Touch Icons and Microsoft Tiles --> <link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png"> <link rel="manifest" href="/assets/manifests/site.webmanifest"> diff --git a/layouts/header.html b/layouts/header.html index e521a008..3a8be124 100644 --- a/layouts/header.html +++ b/layouts/header.html @@ -12,7 +12,11 @@ <% if @item[:searchbar].nil? %> <% location = @item.identifier.to_s.split('/')[1] %> <% unless %w(search index.erb).any?(location) %> - <div id="docsearch" class="my-2 my-lg-0"></div> + <% if @config[:algolia] == "true" %> + <div id="docsearch" class="gl-my-3 my-lg-0"></div> + <% else %> + <div class="js-lunr-form"></div> + <% end %> <% end %> <% end %> </li> diff --git a/layouts/home.html b/layouts/home.html index 3b79e055..c573033d 100644 --- a/layouts/home.html +++ b/layouts/home.html @@ -16,7 +16,11 @@ <script src="<%= @items['/frontend/header/index.*'].path %>"></script> <script src="<%= @items['/frontend/shared/global_imports.*'].path %>"></script> <script src="<%= @items['/frontend/default/default.*'].path %>"></script> - <script src="<%= @items['/frontend/search/docsearch.*'].path %>"></script> <script src="<%= @items['/assets/javascripts/badges.*'].path %>"></script> + <% if @config[:algolia] == "true" %> + <script src="<%= @items['/frontend/search/docsearch.*'].path %>"></script> + <% else %> + <script src="<%= @items['/frontend/search/lunrsearch.*'].path %>"></script> + <% end %> </body> </html> diff --git a/layouts/instantsearch.html b/layouts/search.html index 514e7273..9d33e0b8 100644 --- a/layouts/instantsearch.html +++ b/layouts/search.html @@ -3,7 +3,13 @@ <head> <%= render '/head.*' %> <link rel="canonical" href="<%= @config[:base_url] %>/search/"> + <% if @config[:algolia] == "true" %> <link rel="stylesheet" href="/frontend/search/instantsearch.css"> + <script src="<%= @items['/frontend/search/instantsearch.*'].path %>"></script> + <% else %> + <script src="/assets/javascripts/lunr.min.js"></script> + <script src="<%= @items['/frontend/search/lunrsearch.*'].path %>"></script> + <% end %> </head> <body> <%= render '/gtm.*' %> @@ -12,7 +18,11 @@ <div class="row"> <div class="col-12 mt-5"> <div class="main class pl-lg-4"> + <% if @config[:algolia] == "true" %> <div class="js-instantsearch"></div> + <% else %> + <div class="js-lunrsearch"></div> + <% end %> <%= render '/footer.*' %> </div> </div> @@ -20,7 +30,6 @@ </section> <script src="<%= @items['/frontend/shared/global_imports.*'].path %>"></script> <script src="<%= @items['/frontend/header/index.*'].path %>"></script> - <script src="<%= @items['/frontend/search/instantsearch.*'].path %>"></script> <% if production? %> <%# Add analytics only in production %> diff --git a/lib/helpers/generic.rb b/lib/helpers/generic.rb index 5db5f02f..cb6e8c12 100644 --- a/lib/helpers/generic.rb +++ b/lib/helpers/generic.rb @@ -54,11 +54,5 @@ module Nanoc::Helpers @items['/_data/banner.yaml'][:show_banner] end - # - # Check if this environment is set to run Algolia search. - # - def algolia? - ENV['ALGOLIA_SEARCH'] == "true" - end end end diff --git a/scripts/lunr/preindex.js b/scripts/lunr/preindex.js index ffae7d8c..b883d9a0 100644 --- a/scripts/lunr/preindex.js +++ b/scripts/lunr/preindex.js @@ -5,8 +5,8 @@ * Creates data files required for Lunr search. * * This script creates two JSON files: - * - lunr-index.js: A serialized search index. - * - lunr-map.js: Maps index item IDs to their human-readable titles. + * - lunr-index.json: A serialized search index. + * - lunr-map.json: Maps index item IDs to their human-readable titles. * * @see https://lunrjs.com/guides/index_prebuilding.html */ @@ -68,7 +68,7 @@ buildIndex(htmlSrc, (err, filenames) => { if (title.length) { pages.push({ id: filename.slice(htmlSrc.length), - h1: title, + h1: title.trim(), h2: getText($, 'h2'), h3: getText($, 'h3'), }); diff --git a/spec/frontend/search/lunr_search_spec.js b/spec/frontend/search/lunr_search_spec.js new file mode 100644 index 00000000..234fa794 --- /dev/null +++ b/spec/frontend/search/lunr_search_spec.js @@ -0,0 +1,26 @@ +/** + * @jest-environment jsdom + */ + +import { shallowMount } from '@vue/test-utils'; +import SearchPage from '../../../content/frontend/search/components/lunr_page.vue'; + +describe('content/frontend/search/components/lunr_page.vue', () => { + it('Search form renders', () => { + const wrapper = shallowMount(SearchPage); + expect(wrapper.findComponent(SearchPage).isVisible()).toBe(true); + }); + + it('Index fetch failure shows an error', async () => { + const wrapper = shallowMount(SearchPage); + const fetch = jest.fn(() => { + throw new Error('error'); + }); + + try { + await fetch('/assets/javascripts/lunr-index.json'); + } catch (e) { + expect(wrapper.find('[data-testid="lunr-error"]').isVisible()).toBe(true); + } + }); +}); |