diff --git a/CHANGES.rst b/CHANGES.rst
index 86e59279491..0f9e5c2c736 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -26,6 +26,9 @@ Bugs fixed
* LaTeX: fix a ``7.4.0`` typo in a default for ``\sphinxboxsetup``
(refs: PR #13152).
Patch by Jean-François B.
+* #13097: HTML Search: serialize search index in JSON format, to
+ handle a query edge-case.
+ Patch by James Addison
Testing
-------
diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py
index 9a1001fceaf..d2d57e2b645 100644
--- a/sphinx/builders/html/__init__.py
+++ b/sphinx/builders/html/__init__.py
@@ -124,7 +124,7 @@ class StandaloneHTMLBuilder(Builder):
supported_image_types = ['image/svg+xml', 'image/png', 'image/gif', 'image/jpeg']
supported_remote_images = True
supported_data_uri_images = True
- searchindex_filename = 'searchindex.js'
+ searchindex_filename = 'searchindex.json'
add_permalinks = True
allow_sharp_as_current_path = True
embedded = False # for things like HTML help or Qt help: suppresses sidebar
diff --git a/sphinx/search/__init__.py b/sphinx/search/__init__.py
index 3f19d3663a0..abcb485ad9d 100644
--- a/sphinx/search/__init__.py
+++ b/sphinx/search/__init__.py
@@ -164,19 +164,14 @@ class _JavaScriptIndex:
on the documentation search object to register the index.
"""
- PREFIX = 'Search.setIndex('
- SUFFIX = ')'
-
def dumps(self, data: Any) -> str:
- data_json = json.dumps(data, separators=(',', ':'), sort_keys=True)
- return self.PREFIX + data_json + self.SUFFIX
+ return json.dumps(data, separators=(',', ':'), sort_keys=True)
def loads(self, s: str) -> Any:
- data = s[len(self.PREFIX) : -len(self.SUFFIX)]
- if not data or not s.startswith(self.PREFIX) or not s.endswith(self.SUFFIX):
+ if not s:
msg = 'invalid data'
raise ValueError(msg)
- return json.loads(data)
+ return json.loads(s)
def dump(self, data: Any, f: IO[str]) -> None:
f.write(self.dumps(data))
diff --git a/sphinx/themes/basic/search.html b/sphinx/themes/basic/search.html
index 0ce54c43424..59730288ca6 100644
--- a/sphinx/themes/basic/search.html
+++ b/sphinx/themes/basic/search.html
@@ -7,7 +7,7 @@
{%- endblock %}
{% block extrahead %}
-
+
{{ super() }}
{% endblock %}
diff --git a/sphinx/themes/basic/static/searchindex.js b/sphinx/themes/basic/static/searchindex.js
new file mode 100644
index 00000000000..c7cd483aaf7
--- /dev/null
+++ b/sphinx/themes/basic/static/searchindex.js
@@ -0,0 +1 @@
+window.fetch("searchindex.json").then(response => response.json()).then(Search.setIndex);
diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js
index aaf078d2b91..f13f5027934 100644
--- a/sphinx/themes/basic/static/searchtools.js
+++ b/sphinx/themes/basic/static/searchtools.js
@@ -514,8 +514,8 @@ const Search = {
searchTerms.forEach((word) => {
const files = [];
const arr = [
- { files: terms[word], score: Scorer.term },
- { files: titleTerms[word], score: Scorer.title },
+ { files: terms.hasOwnProperty(word) ? terms[word] : [], score: Scorer.term },
+ { files: titleTerms.hasOwnProperty(word) ? titleTerms[word] : [], score: Scorer.title },
];
// add support for partial matches
if (word.length > 2) {
diff --git a/tests/js/fixtures/cpp/searchindex.js b/tests/js/fixtures/cpp/searchindex.js
deleted file mode 100644
index e5837e65d56..00000000000
--- a/tests/js/fixtures/cpp/searchindex.js
+++ /dev/null
@@ -1 +0,0 @@
-Search.setIndex({"alltitles":{},"docnames":["index"],"envversion":{"sphinx":64,"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,"becaus":0,"c":0,"can":0,"cardin":0,"challeng":0,"charact":0,"class":0,"descript":0,"drop":0,"engin":0,"fixtur":0,"frequent":0,"gener":0,"i":0,"index":0,"inflat":0,"mathemat":0,"occur":0,"often":0,"project":0,"punctuat":0,"queri":0,"relat":0,"sampl":0,"search":0,"size":0,"sphinx":0,"term":0,"thei":0,"thi":0,"token":0,"us":0,"web":0,"would":0},"titles":["<no title>"],"titleterms":{}})
\ No newline at end of file
diff --git a/tests/js/fixtures/cpp/searchindex.json b/tests/js/fixtures/cpp/searchindex.json
new file mode 100644
index 00000000000..188056c9fcf
--- /dev/null
+++ b/tests/js/fixtures/cpp/searchindex.json
@@ -0,0 +1 @@
+{"alltitles":{},"docnames":["index"],"envversion":{"sphinx":64,"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,"becaus":0,"c":0,"can":0,"cardin":0,"challeng":0,"charact":0,"class":0,"descript":0,"drop":0,"engin":0,"fixtur":0,"frequent":0,"gener":0,"i":0,"index":0,"inflat":0,"mathemat":0,"occur":0,"often":0,"project":0,"punctuat":0,"queri":0,"relat":0,"sampl":0,"search":0,"size":0,"sphinx":0,"term":0,"thei":0,"thi":0,"token":0,"us":0,"web":0,"would":0},"titles":["<no title>"],"titleterms":{}}
\ No newline at end of file
diff --git a/tests/js/fixtures/ecmascript/searchindex.json b/tests/js/fixtures/ecmascript/searchindex.json
new file mode 100644
index 00000000000..aef1ae4e0fa
--- /dev/null
+++ b/tests/js/fixtures/ecmascript/searchindex.json
@@ -0,0 +1 @@
+{"alltitles":{"ECMAScript":[[0,null]]},"docnames":["index"],"envversion":{"sphinx":64,"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":{"__proto__":0,"access":0,"aka":0,"an":0,"ani":0,"engin":0,"fixtur":0,"gener":0,"i":0,"index":0,"instanc":0,"javascript":0,"object":0,"project":0,"properti":0,"prototyp":0,"sampl":0,"search":0,"thi":0,"us":0},"titles":["ECMAScript"],"titleterms":{"ecmascript":0}}
\ No newline at end of file
diff --git a/tests/js/fixtures/multiterm/searchindex.js b/tests/js/fixtures/multiterm/searchindex.js
deleted file mode 100644
index b3e2977792c..00000000000
--- a/tests/js/fixtures/multiterm/searchindex.js
+++ /dev/null
@@ -1 +0,0 @@
-Search.setIndex({"alltitles":{"Main Page":[[0,null]]},"docnames":["index"],"envversion":{"sphinx":64,"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":{"At":0,"adjac":0,"all":0,"an":0,"appear":0,"applic":0,"ar":0,"built":0,"can":0,"check":0,"contain":0,"do":0,"document":0,"doesn":0,"each":0,"fixtur":0,"format":0,"function":0,"futur":0,"html":0,"i":0,"includ":0,"match":0,"messag":0,"multipl":0,"multiterm":0,"order":0,"other":0,"output":0,"perform":0,"perhap":0,"phrase":0,"project":0,"queri":0,"requir":0,"same":0,"search":0,"successfulli":0,"support":0,"t":0,"term":0,"test":0,"thi":0,"time":0,"us":0,"when":0,"write":0},"titles":["Main Page"],"titleterms":{"main":0,"page":0}})
\ No newline at end of file
diff --git a/tests/js/fixtures/multiterm/searchindex.json b/tests/js/fixtures/multiterm/searchindex.json
new file mode 100644
index 00000000000..e14c6d48e60
--- /dev/null
+++ b/tests/js/fixtures/multiterm/searchindex.json
@@ -0,0 +1 @@
+{"alltitles":{"Main Page":[[0,null]]},"docnames":["index"],"envversion":{"sphinx":64,"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":{"At":0,"adjac":0,"all":0,"an":0,"appear":0,"applic":0,"ar":0,"built":0,"can":0,"check":0,"contain":0,"do":0,"document":0,"doesn":0,"each":0,"fixtur":0,"format":0,"function":0,"futur":0,"html":0,"i":0,"includ":0,"match":0,"messag":0,"multipl":0,"multiterm":0,"order":0,"other":0,"output":0,"perform":0,"perhap":0,"phrase":0,"project":0,"queri":0,"requir":0,"same":0,"search":0,"successfulli":0,"support":0,"t":0,"term":0,"test":0,"thi":0,"time":0,"us":0,"when":0,"write":0},"titles":["Main Page"],"titleterms":{"main":0,"page":0}}
\ No newline at end of file
diff --git a/tests/js/fixtures/partial/searchindex.js b/tests/js/fixtures/partial/searchindex.js
deleted file mode 100644
index ac024bf0c6e..00000000000
--- a/tests/js/fixtures/partial/searchindex.js
+++ /dev/null
@@ -1 +0,0 @@
-Search.setIndex({"alltitles":{"sphinx_utils module":[[0,null]]},"docnames":["index"],"envversion":{"sphinx":64,"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":{"ar":0,"both":0,"built":0,"confirm":0,"document":0,"function":0,"html":0,"i":0,"includ":0,"input":0,"javascript":0,"match":0,"partial":0,"possibl":0,"project":0,"provid":0,"restructuredtext":0,"sampl":0,"search":0,"should":0,"term":0,"thi":0,"titl":0,"us":0,"when":0},"titles":["sphinx_utils module"],"titleterms":{"modul":0,"sphinx_util":0}})
\ No newline at end of file
diff --git a/tests/js/fixtures/partial/searchindex.json b/tests/js/fixtures/partial/searchindex.json
new file mode 100644
index 00000000000..a0a50be326e
--- /dev/null
+++ b/tests/js/fixtures/partial/searchindex.json
@@ -0,0 +1 @@
+{"alltitles":{"sphinx_utils module":[[0,null]]},"docnames":["index"],"envversion":{"sphinx":64,"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":{"ar":0,"both":0,"built":0,"confirm":0,"document":0,"function":0,"html":0,"i":0,"includ":0,"input":0,"javascript":0,"match":0,"partial":0,"possibl":0,"project":0,"provid":0,"restructuredtext":0,"sampl":0,"search":0,"should":0,"term":0,"thi":0,"titl":0,"us":0,"when":0},"titles":["sphinx_utils module"],"titleterms":{"modul":0,"sphinx_util":0}}
\ No newline at end of file
diff --git a/tests/js/fixtures/titles/searchindex.js b/tests/js/fixtures/titles/searchindex.js
deleted file mode 100644
index 987be77992a..00000000000
--- a/tests/js/fixtures/titles/searchindex.js
+++ /dev/null
@@ -1 +0,0 @@
-Search.setIndex({"alltitles":{"Main Page":[[0,null]],"Relevance":[[0,"relevance"],[1,null]],"Result Scoring":[[0,"result-scoring"]]},"docnames":["index","relevance"],"envversion":{"sphinx":64,"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","relevance.rst"],"indexentries":{"example (class in relevance)":[[0,"relevance.Example",false]],"module":[[0,"module-relevance",false]],"relevance":[[0,"index-1",false],[0,"module-relevance",false]],"relevance (relevance.example attribute)":[[0,"relevance.Example.relevance",false]],"scoring":[[0,"index-0",true]]},"objects":{"":[[0,0,0,"-","relevance"]],"relevance":[[0,1,1,"","Example"]],"relevance.Example":[[0,2,1,"","relevance"]]},"objnames":{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","attribute","Python attribute"]},"objtypes":{"0":"py:module","1":"py:class","2":"py:attribute"},"terms":{"":[0,1],"A":1,"By":0,"For":[0,1],"In":[0,1],"against":0,"align":0,"also":1,"an":0,"answer":0,"appear":1,"ar":1,"area":0,"ask":0,"assign":0,"attempt":0,"attribut":0,"both":0,"built":1,"can":[0,1],"class":0,"code":[0,1],"collect":0,"consid":1,"contain":0,"context":0,"corpu":1,"could":1,"demonstr":0,"describ":1,"detail":1,"determin":[0,1],"docstr":0,"document":[0,1],"domain":1,"dure":0,"engin":0,"evalu":0,"exampl":[0,1],"extract":0,"feedback":0,"find":0,"found":0,"from":0,"function":1,"ha":1,"handl":0,"happen":1,"head":0,"help":0,"highli":[0,1],"how":0,"i":[0,1],"improv":0,"inform":0,"intend":0,"issu":[0,1],"itself":1,"knowledg":0,"languag":1,"less":1,"like":[0,1],"mani":0,"match":0,"mention":1,"more":0,"name":[0,1],"numer":0,"object":0,"often":0,"one":[0,1],"onli":[0,1],"order":0,"other":0,"over":0,"page":1,"part":1,"particular":0,"present":0,"printf":1,"program":1,"project":0,"queri":[0,1],"question":0,"re":0,"rel":0,"research":0,"result":1,"retriev":0,"sai":0,"same":1,"search":[0,1],"seem":0,"softwar":1,"some":1,"sphinx":0,"straightforward":1,"subject":0,"subsect":0,"term":[0,1],"test":0,"text":0,"than":[0,1],"thei":0,"them":0,"thi":0,"time":0,"titl":0,"two":0,"typic":0,"us":0,"user":[0,1],"we":[0,1],"when":0,"whether":1,"which":0,"within":0,"word":0,"would":[0,1]},"titles":["Main Page","Relevance"],"titleterms":{"main":0,"page":0,"relev":[0,1],"result":0,"score":0}})
\ No newline at end of file
diff --git a/tests/js/fixtures/titles/searchindex.json b/tests/js/fixtures/titles/searchindex.json
new file mode 100644
index 00000000000..e94d9c6f83c
--- /dev/null
+++ b/tests/js/fixtures/titles/searchindex.json
@@ -0,0 +1 @@
+{"alltitles":{"Main Page":[[0,null]],"Relevance":[[0,"relevance"],[1,null]],"Result Scoring":[[0,"result-scoring"]]},"docnames":["index","relevance"],"envversion":{"sphinx":64,"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","relevance.rst"],"indexentries":{"example (class in relevance)":[[0,"relevance.Example",false]],"module":[[0,"module-relevance",false]],"relevance":[[0,"index-1",false],[0,"module-relevance",false]],"relevance (relevance.example attribute)":[[0,"relevance.Example.relevance",false]],"scoring":[[0,"index-0",true]]},"objects":{"":[[0,0,0,"-","relevance"]],"relevance":[[0,1,1,"","Example"]],"relevance.Example":[[0,2,1,"","relevance"]]},"objnames":{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","attribute","Python attribute"]},"objtypes":{"0":"py:module","1":"py:class","2":"py:attribute"},"terms":{"":[0,1],"A":1,"By":0,"For":[0,1],"In":[0,1],"against":0,"align":0,"also":1,"an":0,"answer":0,"appear":1,"ar":1,"area":0,"ask":0,"assign":0,"attempt":0,"attribut":0,"both":0,"built":1,"can":[0,1],"class":0,"code":[0,1],"collect":0,"consid":1,"contain":0,"context":0,"corpu":1,"could":1,"demonstr":0,"describ":1,"detail":1,"determin":[0,1],"docstr":0,"document":[0,1],"domain":1,"dure":0,"engin":0,"evalu":0,"exampl":[0,1],"extract":0,"feedback":0,"find":0,"found":0,"from":0,"function":1,"ha":1,"handl":0,"happen":1,"head":0,"help":0,"highli":[0,1],"how":0,"i":[0,1],"improv":0,"inform":0,"intend":0,"issu":[0,1],"itself":1,"knowledg":0,"languag":1,"less":1,"like":[0,1],"mani":0,"match":0,"mention":1,"more":0,"name":[0,1],"numer":0,"object":0,"often":0,"one":[0,1],"onli":[0,1],"order":0,"other":0,"over":0,"page":1,"part":1,"particular":0,"present":0,"printf":1,"program":1,"project":0,"queri":[0,1],"question":0,"re":0,"rel":0,"research":0,"result":1,"retriev":0,"sai":0,"same":1,"search":[0,1],"seem":0,"softwar":1,"some":1,"sphinx":0,"straightforward":1,"subject":0,"subsect":0,"term":[0,1],"test":0,"text":0,"than":[0,1],"thei":0,"them":0,"thi":0,"time":0,"titl":0,"two":0,"typic":0,"us":0,"user":[0,1],"we":[0,1],"when":0,"whether":1,"which":0,"within":0,"word":0,"would":[0,1]},"titles":["Main Page","Relevance"],"titleterms":{"main":0,"page":0,"relev":[0,1],"result":0,"score":0}}
\ No newline at end of file
diff --git a/tests/js/jasmine-browser.mjs b/tests/js/jasmine-browser.mjs
index b84217fd8c5..17b0c04ad34 100644
--- a/tests/js/jasmine-browser.mjs
+++ b/tests/js/jasmine-browser.mjs
@@ -4,7 +4,6 @@ export default {
'sphinx/themes/basic/static/doctools.js',
'sphinx/themes/basic/static/searchtools.js',
'sphinx/themes/basic/static/sphinx_highlight.js',
- 'tests/js/fixtures/**/*.js',
'tests/js/documentation_options.js',
'tests/js/language_data.js',
],
diff --git a/tests/js/roots/ecmascript/conf.py b/tests/js/roots/ecmascript/conf.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/js/roots/ecmascript/index.rst b/tests/js/roots/ecmascript/index.rst
new file mode 100644
index 00000000000..aa0f23efb44
--- /dev/null
+++ b/tests/js/roots/ecmascript/index.rst
@@ -0,0 +1,6 @@
+ECMAScript
+----------
+
+This is a sample JavaScript (aka ``ECMAScript``) project used to generate a search engine index fixture.
+
+Use the `__proto__` property to access the `prototype `_ (if any) of an object instance.
diff --git a/tests/js/searchtools.spec.js b/tests/js/searchtools.spec.js
index cfe5fdcf7ed..1a3b73cbe8d 100644
--- a/tests/js/searchtools.spec.js
+++ b/tests/js/searchtools.spec.js
@@ -1,10 +1,10 @@
describe('Basic html theme search', function() {
- function loadFixture(name) {
+ function loadIndex(name) {
req = new XMLHttpRequest();
req.open("GET", `__src__/tests/js/fixtures/${name}`, false);
req.send(null);
- return req.responseText;
+ Search.setIndex(JSON.parse(req.responseText));
}
function checkRanking(expectedRanking, results) {
@@ -28,7 +28,7 @@ describe('Basic html theme search', function() {
describe('terms search', function() {
it('should find "C++" when in index', function() {
- eval(loadFixture("cpp/searchindex.js"));
+ loadIndex("cpp/searchindex.json");
[_searchQuery, searchterms, excluded, ..._remainingItems] = Search._parseQuery('C++');
@@ -45,7 +45,7 @@ describe('Basic html theme search', function() {
});
it('should be able to search for multiple terms', function() {
- eval(loadFixture("multiterm/searchindex.js"));
+ loadIndex("multiterm/searchindex.json");
[_searchQuery, searchterms, excluded, ..._remainingItems] = Search._parseQuery('main page');
hits = [[
@@ -61,7 +61,7 @@ describe('Basic html theme search', function() {
});
it('should partially-match "sphinx" when in title index', function() {
- eval(loadFixture("partial/searchindex.js"));
+ loadIndex("partial/searchindex.json");
[_searchQuery, searchterms, excluded, ..._remainingItems] = Search._parseQuery('sphinx');
@@ -78,7 +78,7 @@ describe('Basic html theme search', function() {
});
it('should partially-match within "possible" when in term index', function() {
- eval(loadFixture("partial/searchindex.js"));
+ loadIndex("partial/searchindex.json");
[_searchQuery, searchterms, excluded, ..._remainingItems] = Search._parseQuery('ossibl');
terms = Search._index.terms;
@@ -101,7 +101,7 @@ describe('Basic html theme search', function() {
describe('aggregation of search results', function() {
it('should combine document title and document term matches', function() {
- eval(loadFixture("multiterm/searchindex.js"));
+ loadIndex("multiterm/searchindex.json");
searchParameters = Search._parseQuery('main page');
@@ -138,7 +138,7 @@ describe('Basic html theme search', function() {
*/
it('should score a code module match above a page-title match', function() {
- eval(loadFixture("titles/searchindex.js"));
+ loadIndex("titles/searchindex.json");
expectedRanking = [
['index', 'relevance', '#module-relevance'], /* py:module documentation */
@@ -152,7 +152,7 @@ describe('Basic html theme search', function() {
});
it('should score a main-title match above an object member match', function() {
- eval(loadFixture("titles/searchindex.js"));
+ loadIndex("titles/searchindex.json");
expectedRanking = [
['relevance', 'Relevance', ''], /* main title */
@@ -166,7 +166,7 @@ describe('Basic html theme search', function() {
});
it('should score a title match above a standard index entry match', function() {
- eval(loadFixture("titles/searchindex.js"));
+ loadIndex("titles/searchindex.json");
expectedRanking = [
['relevance', 'Relevance', ''], /* title */
@@ -180,7 +180,7 @@ describe('Basic html theme search', function() {
});
it('should score a priority index entry match above a title match', function() {
- eval(loadFixture("titles/searchindex.js"));
+ loadIndex("titles/searchindex.json");
expectedRanking = [
['index', 'Main Page', '#index-0'], /* index entry */
@@ -194,7 +194,7 @@ describe('Basic html theme search', function() {
});
it('should score a main-title match above a subheading-title match', function() {
- eval(loadFixture("titles/searchindex.js"));
+ loadIndex("titles/searchindex.json");
expectedRanking = [
['relevance', 'Relevance', ''], /* main title */
@@ -209,6 +209,38 @@ describe('Basic html theme search', function() {
});
+ describe('can handle edge-case search queries', function() {
+
+ it('can search for the javascript prototype property', function() {
+ loadIndex("ecmascript/searchindex.json");
+
+ searchParameters = Search._parseQuery('__proto__');
+
+ hits = [
+ [
+ 'index',
+ 'ECMAScript',
+ '',
+ null,
+ 5,
+ 'index.rst',
+ 'text'
+ ]
+ ];
+ expect(Search._performSearch(...searchParameters)).toEqual(hits);
+ });
+
+ it('does not find the javascript prototype property in unrelated documents', function() {
+ loadIndex("partial/searchindex.json");
+
+ searchParameters = Search._parseQuery('__proto__');
+
+ hits = [];
+ expect(Search._performSearch(...searchParameters)).toEqual(hits);
+ });
+
+ });
+
});
describe("htmlToText", function() {
diff --git a/tests/test_search.py b/tests/test_search.py
index 600f66cb9f6..7b81c891c48 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -64,10 +64,7 @@ def get_objects(self) -> list[tuple[str, str, str, str, str, int]]:
def load_searchindex(path: Path) -> Any:
searchindex = path.read_text(encoding='utf8')
- assert searchindex.startswith('Search.setIndex(')
- assert searchindex.endswith(')')
-
- return json.loads(searchindex[16:-1])
+ return json.loads(searchindex)
def is_registered_term(index: Any, keyword: str) -> bool:
@@ -90,7 +87,7 @@ def is_registered_term(index: Any, keyword: str) -> bool:
@pytest.mark.sphinx('html', testroot='ext-viewcode')
def test_objects_are_escaped(app):
app.build(force_all=True)
- index = load_searchindex(app.outdir / 'searchindex.js')
+ index = load_searchindex(app.outdir / 'searchindex.json')
for item in index.get('objects').get(''):
if item[-1] == 'n::Array<T, d>': # n::Array is escaped
break
@@ -101,7 +98,7 @@ def test_objects_are_escaped(app):
@pytest.mark.sphinx('html', testroot='search')
def test_meta_keys_are_handled_for_language_en(app):
app.build(force_all=True)
- searchindex = load_searchindex(app.outdir / 'searchindex.js')
+ searchindex = load_searchindex(app.outdir / 'searchindex.json')
assert not is_registered_term(searchindex, 'thisnoteith')
assert is_registered_term(searchindex, 'thisonetoo')
assert is_registered_term(searchindex, 'findthiskei')
@@ -119,7 +116,7 @@ def test_meta_keys_are_handled_for_language_en(app):
)
def test_meta_keys_are_handled_for_language_de(app):
app.build(force_all=True)
- searchindex = load_searchindex(app.outdir / 'searchindex.js')
+ searchindex = load_searchindex(app.outdir / 'searchindex.json')
assert not is_registered_term(searchindex, 'thisnoteith')
assert is_registered_term(searchindex, 'thisonetoo')
assert not is_registered_term(searchindex, 'findthiskei')
@@ -132,14 +129,14 @@ def test_meta_keys_are_handled_for_language_de(app):
@pytest.mark.sphinx('html', testroot='search')
def test_stemmer_does_not_remove_short_words(app):
app.build(force_all=True)
- searchindex = (app.outdir / 'searchindex.js').read_text(encoding='utf8')
+ searchindex = (app.outdir / 'searchindex.json').read_text(encoding='utf8')
assert 'bat' in searchindex
@pytest.mark.sphinx('html', testroot='search')
def test_stemmer(app):
app.build(force_all=True)
- searchindex = load_searchindex(app.outdir / 'searchindex.js')
+ searchindex = load_searchindex(app.outdir / 'searchindex.json')
print(searchindex)
assert is_registered_term(searchindex, 'findthisstemmedkei')
assert is_registered_term(searchindex, 'intern')
@@ -148,7 +145,7 @@ def test_stemmer(app):
@pytest.mark.sphinx('html', testroot='search')
def test_term_in_heading_and_section(app):
app.build(force_all=True)
- searchindex = (app.outdir / 'searchindex.js').read_text(encoding='utf8')
+ searchindex = (app.outdir / 'searchindex.json').read_text(encoding='utf8')
# if search term is in the title of one doc and in the text of another
# both documents should be a hit in the search index as a title,
# respectively text hit
@@ -159,7 +156,7 @@ def test_term_in_heading_and_section(app):
@pytest.mark.sphinx('html', testroot='search')
def test_term_in_raw_directive(app):
app.build(force_all=True)
- searchindex = load_searchindex(app.outdir / 'searchindex.js')
+ searchindex = load_searchindex(app.outdir / 'searchindex.json')
assert not is_registered_term(searchindex, 'raw')
assert is_registered_term(searchindex, 'rawword')
assert not is_registered_term(searchindex, 'latex_keyword')
@@ -380,7 +377,7 @@ def test_IndexBuilder_lookup():
)
def test_search_index_gen_zh(app):
app.build(force_all=True)
- index = load_searchindex(app.outdir / 'searchindex.js')
+ index = load_searchindex(app.outdir / 'searchindex.json')
assert 'chinesetest ' not in index['terms']
assert 'chinesetest' in index['terms']
assert 'chinesetesttwo' in index['terms']
@@ -394,7 +391,7 @@ def test_search_index_gen_zh(app):
)
def test_nosearch(app):
app.build()
- index = load_searchindex(app.outdir / 'searchindex.js')
+ index = load_searchindex(app.outdir / 'searchindex.json')
assert index['docnames'] == ['index', 'nosearch', 'tocitem']
assert 'latex' not in index['terms']
assert 'bat' in index['terms']
@@ -411,14 +408,14 @@ def test_nosearch(app):
)
def test_parallel(app):
app.build()
- index = load_searchindex(app.outdir / 'searchindex.js')
+ index = load_searchindex(app.outdir / 'searchindex.json')
assert index['docnames'] == ['index', 'nosearch', 'tocitem']
@pytest.mark.sphinx('html', testroot='search')
def test_search_index_is_deterministic(app):
app.build(force_all=True)
- index = load_searchindex(app.outdir / 'searchindex.js')
+ index = load_searchindex(app.outdir / 'searchindex.json')
# Pretty print the index. Only shown by pytest on failure.
print(f'searchindex.js contents:\n\n{json.dumps(index, indent=2)}')
assert_is_sorted(index, '')
@@ -470,9 +467,9 @@ def test_check_js_search_indexes(make_app, sphinx_test_tempdir, directory):
)
app.build()
- fresh_searchindex = app.outdir / 'searchindex.js'
+ fresh_searchindex = app.outdir / 'searchindex.json'
existing_searchindex = (
- TESTS_ROOT / 'js' / 'fixtures' / directory.name / 'searchindex.js'
+ TESTS_ROOT / 'js' / 'fixtures' / directory.name / 'searchindex.json'
)
msg = (
diff --git a/utils/generate_js_fixtures.py b/utils/generate_js_fixtures.py
index ecf13a94741..df15c048ff6 100755
--- a/utils/generate_js_fixtures.py
+++ b/utils/generate_js_fixtures.py
@@ -27,8 +27,8 @@ def build(srcdir: Path) -> None:
for directory in TEST_JS_ROOTS:
- searchindex = directory / '_build' / 'searchindex.js'
- destination = TEST_JS_FIXTURES / directory.name / 'searchindex.js'
+ searchindex = directory / '_build' / 'searchindex.json'
+ destination = TEST_JS_FIXTURES / directory.name / 'searchindex.json'
print(f'Building {directory} ... ', end='')
build(directory)