diff options
Diffstat (limited to 'tests/js')
-rw-r--r-- | tests/js/fixtures/cpp/searchindex.js | 1 | ||||
-rw-r--r-- | tests/js/fixtures/multiterm/searchindex.js | 1 | ||||
-rw-r--r-- | tests/js/fixtures/partial/searchindex.js | 1 | ||||
-rw-r--r-- | tests/js/fixtures/titles/searchindex.js | 1 | ||||
-rw-r--r-- | tests/js/language_data.js | 26 | ||||
-rw-r--r-- | tests/js/roots/cpp/conf.py | 0 | ||||
-rw-r--r-- | tests/js/roots/cpp/index.rst | 10 | ||||
-rw-r--r-- | tests/js/roots/multiterm/conf.py | 0 | ||||
-rw-r--r-- | tests/js/roots/multiterm/index.rst | 13 | ||||
-rw-r--r-- | tests/js/roots/partial/conf.py | 0 | ||||
-rw-r--r-- | tests/js/roots/partial/index.rst | 9 | ||||
-rw-r--r-- | tests/js/roots/titles/conf.py | 6 | ||||
-rw-r--r-- | tests/js/roots/titles/index.rst | 20 | ||||
-rw-r--r-- | tests/js/roots/titles/relevance.py | 7 | ||||
-rw-r--r-- | tests/js/roots/titles/relevance.rst | 13 | ||||
-rw-r--r-- | tests/js/searchtools.js | 166 |
16 files changed, 232 insertions, 42 deletions
diff --git a/tests/js/fixtures/cpp/searchindex.js b/tests/js/fixtures/cpp/searchindex.js new file mode 100644 index 0000000..46f4824 --- /dev/null +++ b/tests/js/fixtures/cpp/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {}, "docnames": ["index"], "envversion": {"sphinx": 62, "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/multiterm/searchindex.js b/tests/js/fixtures/multiterm/searchindex.js new file mode 100644 index 0000000..a868eb6 --- /dev/null +++ b/tests/js/fixtures/multiterm/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"Main Page": [[0, null]]}, "docnames": ["index"], "envversion": {"sphinx": 62, "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 new file mode 100644 index 0000000..356386a --- /dev/null +++ b/tests/js/fixtures/partial/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"sphinx_utils module": [[0, null]]}, "docnames": ["index"], "envversion": {"sphinx": 62, "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": {"also": 0, "ar": 0, "built": 0, "confirm": 0, "document": 0, "function": 0, "html": 0, "i": 0, "includ": 0, "input": 0, "javascript": 0, "known": 0, "match": 0, "partial": 0, "possibl": 0, "prefix": 0, "project": 0, "provid": 0, "restructuredtext": 0, "sampl": 0, "search": 0, "should": 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 new file mode 100644 index 0000000..9a229d0 --- /dev/null +++ b/tests/js/fixtures/titles/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"Main Page": [[0, null]], "Relevance": [[0, "relevance"], [1, null]]}, "docnames": ["index", "relevance"], "envversion": {"sphinx": 62, "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, "module-relevance", false]], "relevance (relevance.example attribute)": [[0, "relevance.Example.relevance", false]]}, "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, "For": 1, "In": [0, 1], "against": 0, "also": 1, "an": 0, "answer": 0, "appear": 1, "ar": 1, "area": 0, "ask": 0, "attribut": 0, "built": 1, "can": [0, 1], "class": 0, "code": [0, 1], "consid": 1, "contain": 0, "context": 0, "corpu": 1, "could": 1, "demonstr": 0, "describ": 1, "detail": 1, "determin": 1, "docstr": 0, "document": [0, 1], "domain": 1, "engin": 0, "exampl": [0, 1], "extract": 0, "find": 0, "found": 0, "from": 0, "function": 1, "ha": 1, "handl": 0, "happen": 1, "head": 0, "help": 0, "highli": 1, "how": 0, "i": [0, 1], "improv": 0, "inform": 0, "intend": 0, "issu": 1, "itself": 1, "knowledg": 0, "languag": 1, "less": 1, "like": [0, 1], "match": 0, "mention": 1, "name": [0, 1], "object": 0, "one": 1, "onli": 1, "other": 0, "page": 1, "part": 1, "particular": 0, "printf": 1, "program": 1, "project": 0, "queri": [0, 1], "question": 0, "re": 0, "rel": 0, "research": 0, "result": 1, "sai": 0, "same": 1, "score": 0, "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": 1, "thei": 0, "them": 0, "thi": 0, "titl": 0, "user": [0, 1], "we": [0, 1], "when": 0, "whether": 1, "within": 0, "would": 1}, "titles": ["Main Page", "Relevance"], "titleterms": {"main": 0, "page": 0, "relev": [0, 1]}})
\ No newline at end of file diff --git a/tests/js/language_data.js b/tests/js/language_data.js new file mode 100644 index 0000000..89083d9 --- /dev/null +++ b/tests/js/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, if 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 0000000..e69de29 --- /dev/null +++ b/tests/js/roots/cpp/conf.py diff --git a/tests/js/roots/cpp/index.rst b/tests/js/roots/cpp/index.rst new file mode 100644 index 0000000..d731343 --- /dev/null +++ b/tests/js/roots/cpp/index.rst @@ -0,0 +1,10 @@ +This is a sample C++ project used to generate a search engine index fixture. + +.. cpp:class:: public Sphinx + + The description of Sphinx class. + +Indexing and querying the term C++ can be challenging, because search-related +tokenization often drops punctuation and mathematical characters (they occur +frequently on the web and would inflate the cardinality and size of web search +indexes). diff --git a/tests/js/roots/multiterm/conf.py b/tests/js/roots/multiterm/conf.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/js/roots/multiterm/conf.py diff --git a/tests/js/roots/multiterm/index.rst b/tests/js/roots/multiterm/index.rst new file mode 100644 index 0000000..495e5ce --- /dev/null +++ b/tests/js/roots/multiterm/index.rst @@ -0,0 +1,13 @@ +Main Page +========= + +This is the main page of the ``multiterm`` test project. + +This document is used as a test fixture to check that the search functionality +included when projects are built into an HTML output format can successfully +match this document when a search query containing multiple terms is performed. + +At the time-of-writing this message, the application doesn't support "phrase +queries" -- queries that require all of the contained terms to appear adjacent +to each other and in the same order in the document as in the query; perhaps it +will do in future? diff --git a/tests/js/roots/partial/conf.py b/tests/js/roots/partial/conf.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/js/roots/partial/conf.py diff --git a/tests/js/roots/partial/index.rst b/tests/js/roots/partial/index.rst new file mode 100644 index 0000000..6a9561b --- /dev/null +++ b/tests/js/roots/partial/index.rst @@ -0,0 +1,9 @@ +sphinx_utils module +=================== + +Partial (also known as "prefix") matches on document titles should be possible +using the JavaScript search functionality included when HTML documentation +projects are built. + +This document provides a sample reStructuredText input to confirm that partial +title matching is possible. diff --git a/tests/js/roots/titles/conf.py b/tests/js/roots/titles/conf.py new file mode 100644 index 0000000..e5f6bb9 --- /dev/null +++ b/tests/js/roots/titles/conf.py @@ -0,0 +1,6 @@ +import os +import sys + +sys.path.insert(0, os.path.abspath('.')) + +extensions = ['sphinx.ext.autodoc'] diff --git a/tests/js/roots/titles/index.rst b/tests/js/roots/titles/index.rst new file mode 100644 index 0000000..464cd95 --- /dev/null +++ b/tests/js/roots/titles/index.rst @@ -0,0 +1,20 @@ +Main Page +========= + +This is the main page of the ``titles`` test project. + +In particular, this test project is intended to demonstrate how Sphinx +can handle scoring of query matches against document titles and subsection +heading titles relative to other document matches such as terms found within +document text and object names extracted from code. + +Relevance +--------- + +In the context of search engines, we can say that a document is **relevant** +to a user's query when it contains information that seems likely to help them +find an answer to a question they're asking, or to improve their knowledge of +the subject area they're researching. + +.. automodule:: relevance + :members: diff --git a/tests/js/roots/titles/relevance.py b/tests/js/roots/titles/relevance.py new file mode 100644 index 0000000..c4d0eec --- /dev/null +++ b/tests/js/roots/titles/relevance.py @@ -0,0 +1,7 @@ +class Example: + """Example class""" + num_attribute = 5 + text_attribute = "string" + + relevance = "testing" + """attribute docstring""" diff --git a/tests/js/roots/titles/relevance.rst b/tests/js/roots/titles/relevance.rst new file mode 100644 index 0000000..18f494f --- /dev/null +++ b/tests/js/roots/titles/relevance.rst @@ -0,0 +1,13 @@ +Relevance +========= + +In some domains, it can be straightforward to determine whether a search result +is relevant to the user's query. + +For example, if we are in a software programming language domain, and a user +has issued a query for the term ``printf``, then we could consider a document +in the corpus that describes a built-in language function with the same name +as (highly) relevant. A document that only happens to mention the ``printf`` +function name as part of some example code that appears on the page would +also be relevant, but likely less relevant than the one that describes the +function itself in detail. diff --git a/tests/js/searchtools.js b/tests/js/searchtools.js index 4f9984d..ebf37e5 100644 --- a/tests/js/searchtools.js +++ b/tests/js/searchtools.js @@ -1,20 +1,38 @@ 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; + } + + function checkRanking(expectedRanking, results) { + let [nextExpected, ...remainingItems] = expectedRanking; + + for (result of results.reverse()) { + if (!nextExpected) break; + + let [expectedPage, expectedTitle, expectedTarget] = nextExpected; + let [page, title, target] = result; + + if (page == expectedPage && title == expectedTitle && target == expectedTarget) { + [nextExpected, ...remainingItems] = remainingItems; + } + } + + expect(remainingItems.length).toEqual(0); + expect(nextExpected).toEqual(undefined); + } + 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")); + + [_searchQuery, searchterms, excluded, ..._remainingItems] = Search._parseQuery('C++'); + terms = Search._index.terms; + titleterms = Search._index.titleterms; hits = [[ "index", @@ -28,22 +46,11 @@ describe('Basic html theme search', function() { }); 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); + eval(loadFixture("multiterm/searchindex.js")); - searchterms = ['main', 'page']; - excluded = []; - terms = index.terms; - titleterms = index.titleterms; + [_searchQuery, searchterms, excluded, ..._remainingItems] = Search._parseQuery('main page'); + terms = Search._index.terms; + titleterms = Search._index.titleterms; hits = [[ 'index', 'Main Page', @@ -55,18 +62,11 @@ describe('Basic html theme search', function() { }); it('should partially-match "sphinx" when in title index', function() { - index = { - docnames:["index"], - filenames:["index.rst"], - terms:{'useful': 0, 'utilities': 0}, - titles:["sphinx_utils module"], - titleterms:{'sphinx_utils': 0} - } - Search.setIndex(index); - searchterms = ['sphinx']; - excluded = []; - terms = index.terms; - titleterms = index.titleterms; + eval(loadFixture("partial/searchindex.js")); + + [_searchQuery, searchterms, excluded, ..._remainingItems] = Search._parseQuery('sphinx'); + terms = Search._index.terms; + titleterms = Search._index.titleterms; hits = [[ "index", @@ -81,6 +81,88 @@ 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")); + + searchParameters = Search._parseQuery('main page'); + + hits = [ + [ + 'index', + 'Main Page', + '', + null, + 16, + 'index.rst' + ] + ]; + expect(Search._performSearch(...searchParameters)).toEqual(hits); + }); + + }); + + describe('search result ranking', function() { + + /* + * These tests should not proscribe precise expected ordering of search + * results; instead each test case should describe a single relevance rule + * that helps users to locate relevant information efficiently. + * + * If you think that one of the rules seems to be poorly-defined or is + * limiting the potential for search algorithm improvements, please check + * for existing discussion/bugreports related to it on GitHub[1] before + * creating one yourself. Suggestions for possible improvements are also + * welcome. + * + * [1] - https://github.com/sphinx-doc/sphinx.git/ + */ + + it('should score a code module match above a page-title match', function() { + eval(loadFixture("titles/searchindex.js")); + + expectedRanking = [ + ['index', 'relevance', '#module-relevance'], /* py:module documentation */ + ['relevance', 'Relevance', ''], /* main title */ + ]; + + searchParameters = Search._parseQuery('relevance'); + results = Search._performSearch(...searchParameters); + + checkRanking(expectedRanking, results); + }); + + it('should score a main-title match above an object member match', function() { + eval(loadFixture("titles/searchindex.js")); + + expectedRanking = [ + ['relevance', 'Relevance', ''], /* main title */ + ['index', 'relevance.Example.relevance', '#relevance.Example.relevance'], /* py:class attribute */ + ]; + + searchParameters = Search._parseQuery('relevance'); + results = Search._performSearch(...searchParameters); + + checkRanking(expectedRanking, results); + }); + + it('should score a main-title match above a subheading-title match', function() { + eval(loadFixture("titles/searchindex.js")); + + expectedRanking = [ + ['relevance', 'Relevance', ''], /* main title */ + ['index', 'Main Page > Relevance', '#relevance'], /* subsection heading title */ + ]; + + searchParameters = Search._parseQuery('relevance'); + results = Search._performSearch(...searchParameters); + + checkRanking(expectedRanking, results); + }); + + }); + }); describe("htmlToText", function() { @@ -100,15 +182,15 @@ describe("htmlToText", function() { </style> <!-- main content --> <section id="getting-started"> - <h1>Getting Started</h1> + <h1>Getting Started <a class="headerlink" href="#getting-started" title="Link to this heading">¶</a></h1> <p>Some text</p> </section> <section id="other-section"> - <h1>Other Section</h1> + <h1>Other Section <a class="headerlink" href="#other-section" title="Link to this heading">¶</a></h1> <p>Other text</p> </section> <section id="yet-another-section"> - <h1>Yet Another Section</h1> + <h1>Yet Another Section <a class="headerlink" href="#yet-another-section" title="Link to this heading">¶</a></h1> <p>More text</p> </section> </div> |