diff --git a/.gitignore b/.gitignore index a2f7d63b3ad..3c87572d26f 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ doc/_build/ doc/locale/ tests/.coverage tests/build/ +tests/js/roots/*/build tests/test-server.lock utils/regression_test.js diff --git a/.ruff.toml b/.ruff.toml index 68c4ecb425d..6e348346498 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -9,6 +9,7 @@ exclude = [ ".tox", ".venv", "tests/roots/*", + "tests/js/roots/*", "build/*", "doc/_build/*", "sphinx/search/*", diff --git a/CHANGES.rst b/CHANGES.rst index 8ffe9365d67..b2b55b02d21 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -127,6 +127,7 @@ Testing * pytest: report the result of ``test_run_epubcheck`` as ``skipped`` instead of ``success`` when Java and/or the ``epubcheck.jar`` code are not available. +* karma: refactor HTML search tests to use fixtures generated by Sphinx. Release 7.2.6 (released Sep 13, 2023) ===================================== diff --git a/karma.conf.js b/karma.conf.js index 8a18e80ba7a..b2bd6d2350e 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -15,11 +15,13 @@ module.exports = function(config) { // list of files / patterns to load in the browser files: [ - 'tests/js/documentation_options.js', + 'tests/js/project/documentation_options.js', + 'tests/js/project/language_data.js', 'sphinx/themes/basic/static/doctools.js', 'sphinx/themes/basic/static/searchtools.js', 'sphinx/themes/basic/static/sphinx_highlight.js', - 'tests/js/*.js' + 'tests/js/*.js', + { pattern: 'tests/js/fixtures/**/*.js', included: false, served: true } ], diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js index 8e4650f2236..7a976af1121 100644 --- a/sphinx/themes/basic/static/searchtools.js +++ b/sphinx/themes/basic/static/searchtools.js @@ -268,16 +268,7 @@ const Search = { else Search.deferQuery(query); }, - /** - * execute search (requires search index to be loaded) - */ - query: (query) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - const allTitles = Search._index.alltitles; - const indexEntries = Search._index.indexentries; - + _parseQuery: (query) => { // stem the search terms and add them to the correct list const stemmer = new Stemmer(); const searchTerms = new Set(); @@ -313,6 +304,19 @@ const Search = { // console.info("required: ", [...searchTerms]); // console.info("excluded: ", [...excludedTerms]); + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + // Collect multiple result groups to be sorted separately and then ordered. // Each is an array of [docname, title, anchor, descr, score, filename]. const normalResults = []; @@ -394,7 +398,13 @@ const Search = { return acc; }, []); - results = results.reverse(); + return results.reverse(); + }, + + query: (query) => { + + const searchTerms = Search._parseQuery(query); + const results = Search._performSearch(...searchTerms); // for debugging //Search.lastresults = results.slice(); // a copy diff --git a/tests/js/fixtures/cpp/searchindex.js b/tests/js/fixtures/cpp/searchindex.js new file mode 100644 index 00000000000..b829f7cc320 --- /dev/null +++ b/tests/js/fixtures/cpp/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {}, "docnames": ["index"], "envversion": {"sphinx": 61, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["index.rst"], "indexentries": {"sphinx (c++ class)": [[0, "_CPPv46Sphinx", false]]}, "objects": {"": [[0, 0, 1, "_CPPv46Sphinx", "Sphinx"]]}, "objnames": {"0": ["cpp", "class", "C++ class"]}, "objtypes": {"0": "cpp:class"}, "terms": {"The": 0, "c": 0, "class": 0, "descript": 0, "engin": 0, "fixtur": 0, "gener": 0, "i": 0, "index": 0, "project": 0, "sampl": 0, "search": 0, "sphinx": 0, "thi": 0, "us": 0}, "titles": ["<no title>"], "titleterms": {}}) \ No newline at end of file diff --git a/tests/js/fixtures/multiterm/searchindex.js b/tests/js/fixtures/multiterm/searchindex.js new file mode 100644 index 00000000000..282549de340 --- /dev/null +++ b/tests/js/fixtures/multiterm/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"Main Page": [[0, "main-page"]]}, "docnames": ["index"], "envversion": {"sphinx": 61, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["index.rst"], "indexentries": {}, "objects": {}, "objnames": {}, "objtypes": {}, "terms": {"welcom": 0}, "titles": ["Main Page"], "titleterms": {"main": 0, "page": 0}}) \ No newline at end of file diff --git a/tests/js/documentation_options.js b/tests/js/project/documentation_options.js similarity index 100% rename from tests/js/documentation_options.js rename to tests/js/project/documentation_options.js diff --git a/tests/js/project/language_data.js b/tests/js/project/language_data.js new file mode 100644 index 00000000000..db395c75dc0 --- /dev/null +++ b/tests/js/project/language_data.js @@ -0,0 +1,26 @@ +/* + * language_data.js + * ~~~~~~~~~~~~~~~~ + * + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +var stopwords = []; + + +/* Non-minified version is copied as a separate JS file, is available */ + +/** + * Dummy stemmer for languages without stemming rules. + */ +var Stemmer = function() { + this.stemWord = function(w) { + return w; + } +} + diff --git a/tests/js/roots/cpp/conf.py b/tests/js/roots/cpp/conf.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/js/roots/cpp/index.rst b/tests/js/roots/cpp/index.rst new file mode 100644 index 00000000000..c6cb1e12e5a --- /dev/null +++ b/tests/js/roots/cpp/index.rst @@ -0,0 +1,5 @@ +This is a sample C++ project used to generate a search engine index fixture. + +.. cpp:class:: public Sphinx + + The description of Sphinx class. diff --git a/tests/js/roots/multiterm/conf.py b/tests/js/roots/multiterm/conf.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/js/roots/multiterm/index.rst b/tests/js/roots/multiterm/index.rst new file mode 100644 index 00000000000..2e9f4967893 --- /dev/null +++ b/tests/js/roots/multiterm/index.rst @@ -0,0 +1,4 @@ +Main Page +========= + +Welcome to the... main page! diff --git a/tests/js/searchtools.js b/tests/js/searchtools.js index 8cbd796b860..1858f6bb72f 100644 --- a/tests/js/searchtools.js +++ b/tests/js/searchtools.js @@ -1,20 +1,18 @@ describe('Basic html theme search', function() { + function loadFixture(name) { + req = new XMLHttpRequest(); + req.open("GET", `base/tests/js/fixtures/${name}`, false); + req.send(null); + return req.responseText; + } + describe('terms search', function() { it('should find "C++" when in index', function() { - index = { - docnames:["index"], - filenames:["index.rst"], - terms:{'c++':0}, - titles:["<no title>"], - titleterms:{} - } - Search.setIndex(index); - searchterms = ['c++']; - excluded = []; - terms = index.terms; - titleterms = index.titleterms; + eval(loadFixture("cpp/searchindex.js")); + + searchTerms = Search._parseQuery('C++'); hits = [[ "index", @@ -24,34 +22,34 @@ describe('Basic html theme search', function() { 5, "index.rst" ]]; - expect(Search.performTermsSearch(searchterms, excluded, terms, titleterms)).toEqual(hits); + expect(Search._performSearch(...searchTerms)).toEqual(hits); }); it('should be able to search for multiple terms', function() { - index = { - alltitles: { - 'Main Page': [[0, 'main-page']], - }, - docnames:["index"], - filenames:["index.rst"], - terms:{main:0, page:0}, - titles:["Main Page"], - titleterms:{ main:0, page:0 } - } - Search.setIndex(index); - - searchterms = ['main', 'page']; - excluded = []; - terms = index.terms; - titleterms = index.titleterms; - hits = [[ - 'index', - 'Main Page', - '', - null, - 15, - 'index.rst']]; - expect(Search.performTermsSearch(searchterms, excluded, terms, titleterms)).toEqual(hits); + eval(loadFixture("multiterm/searchindex.js")); + + searchTerms = Search._parseQuery('main page'); + + // fixme: duplicate result due to https://github.com/sphinx-doc/sphinx/issues/11961 + hits = [ + [ + 'index', + 'Main Page', + '', + null, + 15, + 'index.rst' + ], + [ + 'index', + 'Main Page', + '#main-page', + null, + 100, + 'index.rst' + ] + ]; + expect(Search._performSearch(...searchTerms)).toEqual(hits); }); }); diff --git a/utils/generate_js_fixtures.py b/utils/generate_js_fixtures.py new file mode 100755 index 00000000000..beb5254393a --- /dev/null +++ b/utils/generate_js_fixtures.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +import subprocess +from pathlib import Path + +SPHINX_ROOT = Path(__file__).resolve().parents[1] +TEST_JS_FIXTURES = SPHINX_ROOT / "tests" / "js" / "fixtures" +TEST_JS_ROOTS = SPHINX_ROOT / "tests" / "js" / "roots" + + +def build(srcdir: Path) -> None: + cmd = ("sphinx-build", "-E", "-q", "-b", "html", f"{srcdir}", f"{srcdir}/build") + subprocess.run(cmd, check=True, capture_output=True) + + +def beautify(filename: Path) -> None: + cmd = ("js-beautify", "-r", filename) + subprocess.run(cmd, check=True, capture_output=True) + + +for directory in TEST_JS_ROOTS.iterdir(): + searchindex = directory / "build" / "searchindex.js" + destination = TEST_JS_FIXTURES / directory.name / "searchindex.js" + + print(f"Building {directory} ... ", end="") + build(directory) + print("done") + + print(f"Moving {searchindex} to {destination} ... ", end="") + searchindex.replace(destination) + print("done")