Skip to content

Commit 8c4ef0f

Browse files
committed
Clean up sticky TOC implementation and add tests
- Remove debug console.log statements from scrollspy.js - Add CSS for contents_autoexpand=false mode (hides subsections) - Add documentation for sticky_contents and contents_autoexpand options - Add test_sticky_toc unit test for HTML output verification - Re-enable visual regression tests in CI workflow
1 parent 6e162a8 commit 8c4ef0f

File tree

5 files changed

+169
-54
lines changed

5 files changed

+169
-54
lines changed

.github/workflows/ci.yml

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -39,46 +39,46 @@ jobs:
3939
cd lecture-python-programming.myst
4040
jb build lectures --path-output ./
4141
42-
# Visual Regression Testing (temporarily disabled for sticky TOC development)
43-
# - name: Setup Node.js
44-
# uses: actions/setup-node@v6
45-
# with:
46-
# node-version: '18'
47-
# cache: 'npm'
48-
# - name: Install Playwright
49-
# run: |
50-
# npm ci
51-
# npx playwright install --with-deps chromium
52-
# - name: Run Visual Regression Tests
53-
# id: visual-tests
54-
# run: npm run test:visual
55-
# continue-on-error: true
56-
# env:
57-
# SITE_PATH: lecture-python-programming.myst/_build/html
58-
# - name: Upload Playwright Report
59-
# uses: actions/upload-artifact@v5
60-
# if: always()
61-
# with:
62-
# name: playwright-report
63-
# path: playwright-report/
64-
# retention-days: 30
65-
# - name: Upload Test Results (on failure)
66-
# uses: actions/upload-artifact@v5
67-
# if: steps.visual-tests.outcome == 'failure'
68-
# with:
69-
# name: visual-test-diff
70-
# path: test-results/
71-
# retention-days: 30
72-
# - name: Post Visual Test Results to PR
73-
# uses: daun/playwright-report-summary@v3
74-
# if: github.event_name == 'pull_request'
75-
# with:
76-
# github-token: ${{ secrets.GITHUB_TOKEN }}
77-
# report-file: playwright-report/results.json
78-
# comment-title: '🎭 Visual Regression Test Results'
79-
# - name: Fail if Visual Tests Failed
80-
# if: steps.visual-tests.outcome == 'failure'
81-
# run: exit 1
42+
# Visual Regression Testing
43+
- name: Setup Node.js
44+
uses: actions/setup-node@v6
45+
with:
46+
node-version: '18'
47+
cache: 'npm'
48+
- name: Install Playwright
49+
run: |
50+
npm ci
51+
npx playwright install --with-deps chromium
52+
- name: Run Visual Regression Tests
53+
id: visual-tests
54+
run: npm run test:visual
55+
continue-on-error: true
56+
env:
57+
SITE_PATH: lecture-python-programming.myst/_build/html
58+
- name: Upload Playwright Report
59+
uses: actions/upload-artifact@v5
60+
if: always()
61+
with:
62+
name: playwright-report
63+
path: playwright-report/
64+
retention-days: 30
65+
- name: Upload Test Results (on failure)
66+
uses: actions/upload-artifact@v5
67+
if: steps.visual-tests.outcome == 'failure'
68+
with:
69+
name: visual-test-diff
70+
path: test-results/
71+
retention-days: 30
72+
- name: Post Visual Test Results to PR
73+
uses: daun/playwright-report-summary@v3
74+
if: github.event_name == 'pull_request'
75+
with:
76+
github-token: ${{ secrets.GITHUB_TOKEN }}
77+
report-file: playwright-report/results.json
78+
comment-title: '🎭 Visual Regression Test Results'
79+
- name: Fail if Visual Tests Failed
80+
if: steps.visual-tests.outcome == 'failure'
81+
run: exit 1
8282

8383
- name: Preview Deploy to Netlify
8484
uses: nwtgck/[email protected]

docs/configure.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,63 @@ Available Pygments styles include: `default`, `friendly`, `monokai`, `github-dar
198198

199199
When `qetheme_code_style` is `True` (the default), the custom QuantEcon code highlighting is used and the `pygments_style` setting is ignored. When set to `False`, the theme will respect your `pygments_style` configuration.
200200

201+
## Sticky Table of Contents
202+
203+
The theme supports a sticky right-hand-side table of contents (TOC) that remains visible as you scroll through the page. This feature includes scroll spy functionality to highlight the currently visible section, and optional auto-expansion of subsections.
204+
205+
### Enabling Sticky TOC
206+
207+
To enable the sticky table of contents:
208+
209+
```python
210+
html_theme_options = {
211+
...
212+
"sticky_contents": True,
213+
...
214+
}
215+
```
216+
217+
For Jupyter Book projects, add to your `_config.yml`:
218+
219+
```yaml
220+
sphinx:
221+
config:
222+
html_theme_options:
223+
sticky_contents: true
224+
```
225+
226+
### Features
227+
228+
When enabled, the sticky TOC provides:
229+
230+
- **Fixed positioning**: The TOC stays visible as you scroll
231+
- **Active section highlighting**: The currently visible section is highlighted in the TOC
232+
- **Back to top button**: A discrete "Back to top" button appears after scrolling down 300px
233+
- **Auto-expand subsections**: Subsections automatically expand to show the current hierarchy
234+
235+
### Auto-Expand Subsections
236+
237+
By default, subsections in the TOC automatically expand as you scroll to show the current section hierarchy. This makes it easier to navigate deeply nested content.
238+
239+
To disable auto-expansion while keeping the sticky TOC:
240+
241+
```python
242+
html_theme_options = {
243+
...
244+
"sticky_contents": True,
245+
"contents_autoexpand": False,
246+
...
247+
}
248+
```
249+
250+
When `contents_autoexpand` is `True` (the default when sticky TOC is enabled), the TOC will:
251+
- Highlight the active section with bold text (scroll tracking)
252+
- Expand parent sections to show the active item
253+
- Expand the active item itself to show its subsections
254+
- Collapse unrelated sections to reduce visual clutter
255+
256+
When set to `False`, only top-level section names are displayed with bold highlighting to indicate which section the reader is currently in. This provides a cleaner, more compact TOC view while still tracking scroll position.
257+
201258
## Git-Based Metadata
202259

203260
The theme automatically displays git-based metadata for each page, including the last modified timestamp and an interactive changelog dropdown. This feature requires:

src/quantecon_book_theme/assets/scripts/scrollspy.js

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,16 @@ export function initScrollSpy() {
1313
// Only initialize if sticky TOC is enabled
1414
const stickyContainer = document.querySelector(".inner.sticky");
1515
if (!stickyContainer) {
16-
console.log("ScrollSpy: No sticky container found");
1716
return;
1817
}
1918

2019
const stickyToc = stickyContainer.querySelector("#bd-toc-nav");
2120
if (!stickyToc) {
22-
console.log("ScrollSpy: No TOC nav found");
2321
return;
2422
}
2523

2624
// Check if autoexpand is enabled via data attribute
2725
const autoExpandEnabled = stickyContainer.dataset.autoexpand === "true";
28-
console.log("ScrollSpy initialized, autoExpandEnabled:", autoExpandEnabled);
29-
console.log("data-autoexpand attribute:", stickyContainer.dataset.autoexpand);
3026

3127
const tocLinks = stickyToc.querySelectorAll("a");
3228
if (tocLinks.length === 0) {
@@ -152,34 +148,25 @@ export function initScrollSpy() {
152148
// Strategy: Expand all ancestors of the active item so it's visible,
153149
// AND expand the active item itself if it has children (to show its subsections)
154150

155-
// Debug logging
156-
console.log("Active section:", activeSection.id);
157-
console.log("Active listItem:", activeSection.listItem);
158-
159151
// 1. First, expand the active item itself if it has children
160152
// This ensures when you're on "4.2 Function Basics", you see 4.2.1, 4.2.2, etc.
161153
if (activeSection.listItem.querySelector(":scope > ul")) {
162154
activeSection.listItem.classList.add("expanded");
163-
console.log("Expanded active item (has children)");
164155
}
165156

166157
// 2. Expand all ancestors from the active item up to the root
167158
// Start from the parent of the active item's li
168159
let parentUl = activeSection.listItem.parentElement;
169-
console.log("Starting parent traversal from:", parentUl);
170160
while (parentUl) {
171161
// parentUl is a <ul>, find its parent <li>
172162
let parentLi = parentUl.parentElement;
173-
console.log("parentLi:", parentLi, "tagName:", parentLi?.tagName);
174163
if (parentLi && parentLi.tagName === "LI") {
175164
// This li contains a ul (which contains our active item), so expand it
176165
parentLi.classList.add("expanded");
177-
console.log("Expanded parent li:", parentLi);
178166
// Move up to the next level
179167
parentUl = parentLi.parentElement;
180168
} else {
181169
// We've reached the top (nav element or similar)
182-
console.log("Reached top, breaking");
183170
break;
184171
}
185172
}

src/quantecon_book_theme/assets/styles/_page.scss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,15 @@ Main page structure and components
128128
}
129129
}
130130

131+
// No auto-expand mode: only show top-level section names
132+
// Subsections are permanently hidden, active section is highlighted with bold
133+
.inner.sticky[data-autoexpand="false"] &-nav {
134+
// Hide all nested subsections permanently
135+
ul ul {
136+
display: none;
137+
}
138+
}
139+
131140
// Discrete "Back to top" button for sticky mode
132141
.back-to-top-btn {
133142
display: none;

tests/test_build.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,69 @@ def test_qetheme_code_style(sphinx_build):
424424
sphinx_build.clean()
425425

426426

427-
def test_git_functions_unit():
427+
def test_sticky_toc(sphinx_build):
428+
"""Test that sticky_contents and contents_autoexpand options work correctly."""
429+
sphinx_build.copy()
430+
431+
# Test default behavior - sticky TOC should be disabled
432+
sphinx_build.build()
433+
index_html = sphinx_build.get("index.html")
434+
toc_inner = index_html.find("div", class_="inner")
435+
# By default, sticky class should NOT be present
436+
assert toc_inner is None or "sticky" not in toc_inner.get("class", [])
437+
sphinx_build.clean()
438+
439+
# Test with sticky_contents enabled
440+
cmd = [
441+
"-D",
442+
"html_theme_options.sticky_contents=True",
443+
]
444+
sphinx_build.build(cmd)
445+
index_html = sphinx_build.get("index.html")
446+
toc_inner = index_html.find("div", class_="inner")
447+
# When enabled, sticky class should be present
448+
assert toc_inner is not None
449+
assert "sticky" in toc_inner.get("class", [])
450+
# Default contents_autoexpand should be True
451+
assert toc_inner.get("data-autoexpand") == "true"
452+
# Back to top button should be present
453+
back_to_top = index_html.find("a", class_="back-to-top-btn")
454+
assert back_to_top is not None
455+
sphinx_build.clean()
456+
457+
# Test with sticky_contents enabled and contents_autoexpand disabled
458+
cmd = [
459+
"-D",
460+
"html_theme_options.sticky_contents=True",
461+
"-D",
462+
"html_theme_options.contents_autoexpand=False",
463+
]
464+
sphinx_build.build(cmd)
465+
index_html = sphinx_build.get("index.html")
466+
toc_inner = index_html.find("div", class_="inner")
467+
# Sticky class should be present
468+
assert toc_inner is not None
469+
assert "sticky" in toc_inner.get("class", [])
470+
# contents_autoexpand should be false
471+
assert toc_inner.get("data-autoexpand") == "false"
472+
sphinx_build.clean()
473+
474+
# Test with string values (theme.conf passes strings)
475+
cmd = [
476+
"-D",
477+
"html_theme_options.sticky_contents=true",
478+
"-D",
479+
"html_theme_options.contents_autoexpand=false",
480+
]
481+
sphinx_build.build(cmd)
482+
index_html = sphinx_build.get("index.html")
483+
toc_inner = index_html.find("div", class_="inner")
484+
# String "true" should enable sticky
485+
assert toc_inner is not None
486+
assert "sticky" in toc_inner.get("class", [])
487+
# String "false" should disable autoexpand
488+
assert toc_inner.get("data-autoexpand") == "false"
489+
sphinx_build.clean()
428490
"""Unit tests for git helper functions."""
429491
from quantecon_book_theme import (
430492
get_git_last_modified,

0 commit comments

Comments
 (0)