Skip to content

Commit 11e0411

Browse files
ivanovdrammockmunkmpre-commit-ci[bot]gabalafou
authored
use ellipsis to truncate breadcrumbs, and test with playwright (#1861)
This PR: - Fixes breadcrumb truncation to use CSS `overflow: ellipsis` - Provides an example for combining playwright and sphinx_build_factory to more thoroughly test our theme components. - Applies that approach to testing breadcrumb truncation via ellipsis when the breadcrumb is placed in various parts of our layout. joint work with @munkm and @drammock closes #1583 closes #1568 lays groundwork for addressing #229 --------- Co-authored-by: Daniel McCloy <[email protected]> Co-authored-by: Madicken Munk <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: gabalafou <[email protected]>
1 parent 1c338cb commit 11e0411

File tree

14 files changed

+259
-85
lines changed

14 files changed

+259
-85
lines changed

src/pydata_sphinx_theme/assets/styles/components/_breadcrumbs.scss

+12
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@ ul.bd-breadcrumbs {
1313
li.breadcrumb-item {
1414
display: flex;
1515
align-items: center;
16+
white-space: nowrap;
17+
overflow-x: hidden;
18+
19+
a,
20+
.ellipsis {
21+
overflow-x: hidden;
22+
text-overflow: ellipsis;
23+
24+
// Need to add margin, otherwise the overflow: hidden rule on the parent
25+
// will hide the focus ring
26+
margin: $focus-ring-width;
27+
}
1628

1729
// Style should look like heavier in-page links
1830
// keeping visited in the default link colour

src/pydata_sphinx_theme/assets/styles/sections/_header-article.scss

-9
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
11
.header-article__inner {
2-
display: flex;
32
padding: 0 0.5rem;
43

54
// The items define the height so that it disappears if there are no items
65
.header-article-item {
76
min-height: var(--pst-header-article-height);
8-
height: var(--pst-header-article-height);
9-
}
10-
11-
.header-article-items__start,
12-
.header-article-items__end {
13-
display: flex;
14-
align-items: start;
15-
gap: 0.5rem;
167
}
178

189
.header-article-items__end {

src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/breadcrumbs.html

+1-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
{# Displays (and links to) the parent section(s) of the currently viewed page. #}
22
{%- block breadcrumbs %}
3-
{#
4-
If we have more than 3 parents (excluding the home page) then we remove
5-
The ones in the middle and add an ellipsis.
6-
#}
7-
{% if parents|length>2 %}
8-
{% set parents=[parents[0], {"title": '<i class="fa-solid fa-ellipsis"></i>'}, parents[-1]] %}
9-
{% endif %}
103

114
{#- Hide breadcrumbs on the home page #}
125
{% if title and pagename != root_doc %}
@@ -25,7 +18,7 @@
2518
<li class="breadcrumb-item">{{ doc.title }}</li>
2619
{% endif %}
2720
{%- endfor %}
28-
<li class="breadcrumb-item active" aria-current="page">{{ title|truncate(15, False) }}</li>
21+
<li class="breadcrumb-item active" aria-current="page"><span class="ellipsis">{{ title }}</span></li>
2922
</ul>
3023
</nav>
3124
{% endif %}

tests/conftest.py

+48-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
"""Configuration of the pytest session."""
22

33
import re
4+
import time
5+
from http.client import HTTPConnection
46
from os import environ
57
from pathlib import Path
68
from shutil import copytree
9+
from subprocess import PIPE, Popen
710
from typing import Callable
811

912
import pytest
@@ -14,7 +17,9 @@
1417

1518
pytest_plugins = "sphinx.testing.fixtures"
1619

17-
path_tests = Path(__file__).parent
20+
tests_path = Path(__file__).parent
21+
repo_path = tests_path.parent
22+
docs_build_path = repo_path / "docs" / "_build" / "html"
1823

1924
# -- Utils method ------------------------------------------------------------
2025

@@ -81,8 +86,49 @@ def _func(src_folder: str, **kwargs) -> SphinxBuild:
8186

8287
srcdir = sphinx_path(srcdir)
8388

84-
copytree(path_tests / "sites" / src_folder, tmp_path / src_folder)
89+
copytree(tests_path / "sites" / src_folder, tmp_path / src_folder)
8590
app = make_app(srcdir=srcdir, **kwargs)
8691
return SphinxBuild(app, tmp_path / src_folder)
8792

8893
yield _func
94+
95+
96+
@pytest.fixture(scope="module")
97+
def url_base():
98+
"""Start local server on built docs and return the localhost URL as the base URL."""
99+
# Use a port that is not commonly used during development or else you will
100+
# force the developer to stop running their dev server in order to run the
101+
# tests.
102+
port = "8213"
103+
host = "localhost"
104+
url = f"http://{host}:{port}"
105+
106+
# Try starting the server
107+
process = Popen(
108+
["python", "-m", "http.server", port, "--directory", docs_build_path],
109+
stdout=PIPE,
110+
)
111+
112+
# Try connecting to the server
113+
retries = 5
114+
while retries > 0:
115+
conn = HTTPConnection(host, port)
116+
try:
117+
conn.request("HEAD", "/")
118+
response = conn.getresponse()
119+
if response is not None:
120+
yield url
121+
break
122+
except ConnectionRefusedError:
123+
time.sleep(1)
124+
retries -= 1
125+
126+
# If the code above never yields a URL, then we were never able to connect
127+
# to the server and retries == 0.
128+
if not retries:
129+
raise RuntimeError("Failed to start http server in 5 seconds")
130+
else:
131+
# Otherwise the server started and this fixture is done now and we clean
132+
# up by stopping the server.
133+
process.terminate()
134+
process.wait()

tests/sites/breadcrumbs/Makefile

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Minimal makefile for Sphinx documentation
2+
#
3+
4+
# You can set these variables from the command line, and also
5+
# from the environment for the first two.
6+
SPHINXOPTS ?=
7+
SPHINXBUILD ?= sphinx-build
8+
SOURCEDIR = .
9+
BUILDDIR = _build
10+
11+
# Put it first so that "make" without argument is like "make help".
12+
help:
13+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14+
15+
.PHONY: help Makefile
16+
17+
# Catch-all target: route all unknown targets to Sphinx using the new
18+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19+
%: Makefile
20+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

tests/sites/breadcrumbs/_static/emptydarklogo.png

Loading

tests/sites/breadcrumbs/_static/emptylogo.png

Loading

tests/sites/breadcrumbs/conf.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Test conf file."""
2+
3+
# -- Project information -----------------------------------------------------
4+
5+
project = "PyData Tests"
6+
copyright = "2020, Pydata community"
7+
author = "Pydata community"
8+
9+
root_doc = "index"
10+
11+
# -- General configuration ---------------------------------------------------
12+
13+
# Add any Sphinx extension module names here, as strings. They can be
14+
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
15+
# ones.
16+
extensions = []
17+
html_theme = "pydata_sphinx_theme"
18+
html_logo = "_static/emptylogo.png"
19+
html_copy_source = True
20+
html_sourcelink_suffix = ""
21+
22+
# Base options, we can add other key/vals later
23+
html_sidebars = {"section1/index": ["sidebar-nav-bs.html"]}
24+
25+
html_theme_options = {
26+
"footer_start": ["breadcrumbs"],
27+
"footer_center": ["breadcrumbs"],
28+
"footer_end": ["breadcrumbs"],
29+
"primary_sidebar_end": ["breadcrumbs"],
30+
"secondary_sidebar_items": ["breadcrumbs"],
31+
"article_header_start": ["breadcrumbs"],
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
In the oven with my sister, so hot right now. Soooo. Hotttt.
2+
============================================================

tests/sites/breadcrumbs/index.rst

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Index ``with code`` in title
2+
============================
3+
4+
.. toctree::
5+
:caption: My caption
6+
:numbered:
7+
8+
hansel/gretel/house
9+
10+
A header
11+
--------
12+
13+
A sub-header
14+
~~~~~~~~~~~~
15+
16+
A sub-sub-header
17+
````````````````
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"name": "v0.7.1 (stable)",
4+
"version": "0.7.1",
5+
"url": "https://pydata-sphinx-theme.readthedocs.io/en/v0.7.1/"
6+
},
7+
{
8+
"version": "0.7.0",
9+
"url": "https://pydata-sphinx-theme.readthedocs.io/en/v0.7.0/"
10+
},
11+
{
12+
"version": "0.6.3"
13+
}
14+
]

tests/sites/breadcrumbs/switcher.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[
2+
{
3+
"name": "v0.7.1 (stable)",
4+
"version": "0.7.1",
5+
"url": "https://pydata-sphinx-theme.readthedocs.io/en/v0.7.1/"
6+
},
7+
{
8+
"version": "0.7.0",
9+
"url": "https://pydata-sphinx-theme.readthedocs.io/en/v0.7.0/"
10+
},
11+
{
12+
"version": "0.6.3",
13+
"url": "https://pydata-sphinx-theme.readthedocs.io/en/v0.6.3/"
14+
}
15+
]

tests/test_a11y.py

+11-66
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
"""Using Axe-core, scan the Kitchen Sink pages for accessibility violations."""
22

3-
import time
4-
from http.client import HTTPConnection
5-
from pathlib import Path
6-
from subprocess import PIPE, Popen
73
from urllib.parse import urljoin
84

95
import pytest
@@ -25,50 +21,6 @@
2521
# people. It just means that page is free of common testable accessibility
2622
# pitfalls.
2723

28-
path_repo = Path(__file__).parent.parent
29-
path_docs_build = path_repo / "docs" / "_build" / "html"
30-
31-
32-
@pytest.fixture(scope="module")
33-
def url_base():
34-
"""Start local server on built docs and return the localhost URL as the base URL."""
35-
# Use a port that is not commonly used during development or else you will
36-
# force the developer to stop running their dev server in order to run the
37-
# tests.
38-
port = "8213"
39-
host = "localhost"
40-
url = f"http://{host}:{port}"
41-
42-
# Try starting the server
43-
process = Popen(
44-
["python", "-m", "http.server", port, "--directory", path_docs_build],
45-
stdout=PIPE,
46-
)
47-
48-
# Try connecting to the server
49-
retries = 5
50-
while retries > 0:
51-
conn = HTTPConnection(host, port)
52-
try:
53-
conn.request("HEAD", "/")
54-
response = conn.getresponse()
55-
if response is not None:
56-
yield url
57-
break
58-
except ConnectionRefusedError:
59-
time.sleep(1)
60-
retries -= 1
61-
62-
# If the code above never yields a URL, then we were never able to connect
63-
# to the server and retries == 0.
64-
if not retries:
65-
raise RuntimeError("Failed to start http server in 5 seconds")
66-
else:
67-
# Otherwise the server started and this fixture is done now and we clean
68-
# up by stopping the server.
69-
process.terminate()
70-
process.wait()
71-
7224

7325
def filter_ignored_violations(violations, url_pathname):
7426
"""Filter out ignored axe-core violations.
@@ -227,24 +179,6 @@ def test_axe_core(
227179
assert len(filtered_violations) == 0, format_violations(filtered_violations)
228180

229181

230-
def test_version_switcher_highlighting(page: Page, url_base: str) -> None:
231-
"""This isn't an a11y test, but needs a served site for Javascript to inject the version menu."""
232-
page.goto(url=url_base)
233-
# no need to include_hidden here ↓↓↓, we just need to get the active version name
234-
button = page.get_by_role("button").filter(has_text="dev")
235-
active_version_name = button.get_attribute("data-active-version-name")
236-
# here we do include_hidden, so sidebar & topbar menus should each have a matching entry:
237-
entries = page.get_by_role("option", include_hidden=True).filter(
238-
has_text=active_version_name
239-
)
240-
assert entries.count() == 2
241-
# make sure they're highlighted
242-
for entry in entries.all():
243-
light_mode = "rgb(10, 125, 145)" # pst-color-primary
244-
# dark_mode = "rgb(63, 177, 197)"
245-
expect(entry).to_have_css("color", light_mode)
246-
247-
248182
@pytest.mark.a11y
249183
def test_code_block_tab_stop(page: Page, url_base: str) -> None:
250184
"""Code blocks that have scrollable content should be tab stops."""
@@ -306,3 +240,14 @@ def test_notebook_ipywidget_output_tab_stop(page: Page, url_base: str) -> None:
306240
# ...and so our js code on the page should make it keyboard-focusable
307241
# (tabIndex = 0)
308242
assert ipywidget.evaluate("el => el.tabIndex") == 0
243+
244+
245+
def test_breadcrumb_expansion(page: Page, url_base: str) -> None:
246+
"""Foo."""
247+
# page.goto(urljoin(url_base, "community/practices/merge.html"))
248+
# expect(page.get_by_label("Breadcrumb").get_by_role("list")).to_contain_text("Merge and review policy")
249+
page.set_viewport_size({"width": 1440, "height": 720})
250+
page.goto(urljoin(url_base, "community/topics/config.html"))
251+
expect(page.get_by_label("Breadcrumb").get_by_role("list")).to_contain_text(
252+
"Update Sphinx configuration during the build"
253+
)

0 commit comments

Comments
 (0)