Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-docs.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSarah German <sgerman@gitlab.com>2022-09-22 18:10:42 +0300
committerKati Paizee <kpaizee@gitlab.com>2022-09-22 18:10:42 +0300
commit6ef828e917f82a7bd8a621c8a652e3127666ac37 (patch)
tree62b2f8c2f54d46bda3a58e8c45b1cad2e9777514
parenta5ac9d88bd1251c252d53680e8af0df0eef1d743 (diff)
Add Lunr search UI components
-rw-r--r--.gitlab/ci/build-and-deploy.gitlab-ci.yml1
-rw-r--r--Rules3
-rw-r--r--commands/frontend.rb9
-rw-r--r--content/404.html2
-rw-r--r--content/assets/stylesheets/_landing.scss14
-rw-r--r--content/assets/stylesheets/stylesheet.scss13
-rw-r--r--content/frontend/search/components/lunr_page.vue97
-rw-r--r--content/frontend/search/components/lunr_search_form.vue23
-rw-r--r--content/frontend/search/lunrsearch.js26
-rw-r--r--content/frontend/search/search.js5
-rw-r--r--content/index.erb6
-rw-r--r--content/search/index.md2
-rw-r--r--doc/docsearch.md46
-rw-r--r--layouts/default.html4
-rw-r--r--layouts/head.html9
-rw-r--r--layouts/header.html6
-rw-r--r--layouts/home.html6
-rw-r--r--layouts/search.html (renamed from layouts/instantsearch.html)11
-rw-r--r--lib/helpers/generic.rb6
-rw-r--r--scripts/lunr/preindex.js6
-rw-r--r--spec/frontend/search/lunr_search_spec.js26
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 #
###############################################
diff --git a/Rules b/Rules
index 17d58c3e..4d137bf9 100644
--- a/Rules
+++ b/Rules
@@ -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);
+ }
+ });
+});