Commit aae4b81
Fix ValueError crash when tab-set contains non-tab-item children (#258)
## Fix tab-set crash with invalid children
Fixes #243
### Summary
This PR fixes a `ValueError: not enough values to unpack (expected 2,
got 1)` crash that occurred when a `.. tab-set::` directive contained
invalid content (non-`tab-item` children).
### Changes Made
**1. Modified `TabSetDirective.run_with_defaults()` in
`sphinx_design/tabs.py` (lines 38-53):**
- Changed from `break` to `continue` to warn about ALL invalid children,
not just the first one
- Added filtering to remove invalid children from `tab_set.children`
- Valid `tab-item` directives are preserved in a new list and reassigned
**2. Added defensive validation in `TabSetHtmlTransform.run()` in
`sphinx_design/tabs.py` (lines 247-256):**
- Added check to skip non-`tab-item` children that may have slipped
through
- Added validation to ensure `tab-item` has exactly 2 children before
unpacking
- Logs appropriate warnings for malformed directives
**3. Added comprehensive test in `tests/test_misc.py`:**
- Tests that tab-set with invalid children does not crash
- Verifies warnings are properly logged
- Confirms valid tab items are still processed and rendered correctly
### Behavior After Fix
✅ **Before:** Sphinx-design crashed with `ValueError` when encountering
invalid content in a tab-set
✅ **After:** Sphinx-design logs warnings for each invalid child and
continues to render valid tab-items
**Example input with invalid content:**
```rst
.. tab-set::
.. tab-item:: A
A content
foo <-- Invalid content
.. tab-item:: B
B content
```
**Result:** Both valid tabs (A and B) render correctly, and a warning is
logged for the invalid "foo" content.
### Visual Verification
The fix was manually tested with a document containing invalid content
between tab items:


All three valid tab items render and function correctly despite invalid
content in the source.
### Tests
All 110 tests pass, including the new test specifically for this issue:
- ✅ `test_tab_set_with_invalid_children` - New test reproducing and
validating the fix
- ✅ All existing tests continue to pass
- ✅ All pre-commit hooks pass
### Checklist
- [x] Understand the issue and explore the codebase
- [x] Create tests to reproduce the ValueError bug
- [x] Fix `TabSetDirective.run_with_defaults()` to filter invalid
children
- [x] Add defensive validation in `TabSetHtmlTransform.run()`
- [x] Run tests to verify the fix (110/110 passed)
- [x] Verify build and lint pass
- [x] Manual verification with visual test
- [x] Fix pre-commit formatting issues
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
> ## Issue
> Fixes #243
>
> Sphinx-design crashes with a `ValueError: not enough values to unpack
(expected 2, got 1)` when a `.. tab-set::` directive contains something
other than `.. tab-item::` directives.
>
> ### Reproduction
> ```rst
> Tab Test document
> =================
>
> .. tab-set::
>
> .. tab-item:: A
>
> A content
>
> foo <-- This line causes the crash
>
> .. tab-item:: B
>
> B content
> ```
>
> ### Root Cause
> The problem is in `sphinx_design/tabs.py`:
>
> 1. **In `TabSetDirective.run_with_defaults()` (lines 38-47):** When a
non-`tab-item` child is found, the code logs a warning but only `break`s
after the first invalid child. It does NOT remove the invalid children
from `tab_set.children`.
>
> 2. **In `TabSetHtmlTransform.run()` (line 244):** The code assumes all
children are valid `tab-item` components with exactly 2 children:
> ```python
> tab_label, tab_content = tab_item.children
> ```
> When an invalid child (like a text node) is encountered, it doesn't
have 2 children, causing the unpacking error.
>
> ### Required Fix
>
> **1. Modify `TabSetDirective.run_with_defaults()` in
`sphinx_design/tabs.py`:**
>
> Change the loop that validates children to:
> - Use `continue` instead of `break` to warn about ALL invalid
children, not just the first
> - Filter out invalid children so only valid `tab-item` components
remain in `tab_set.children`
>
> The current code:
> ```python
> for item in tab_set.children:
> if not is_component(item, "tab-item"):
> LOGGER.warning(
> f"All children of a 'tab-set' "
> f"should be 'tab-item' [{WARNING_TYPE}.tab]",
> location=item,
> type=WARNING_TYPE,
> subtype="tab",
> )
> break
> if "sync_id" in item.children[0]:
> item.children[0]["sync_group"] = self.options.get("sync-group", "tab")
> return [tab_set]
> ```
>
> Should become:
> ```python
> valid_children = []
> for item in tab_set.children:
> if not is_component(item, "tab-item"):
> LOGGER.warning(
> f"All children of a 'tab-set' "
> f"should be 'tab-item' [{WARNING_TYPE}.tab]",
> location=item,
> type=WARNING_TYPE,
> subtype="tab",
> )
> continue # Skip invalid children instead of breaking
> if "sync_id" in item.children[0]:
> item.children[0]["sync_group"] = self.options.get("sync-group", "tab")
> valid_children.append(item)
>
> tab_set.children = valid_children
> return [tab_set]
> ```
>
> **2. Add defensive validation in `TabSetHtmlTransform.run()` in
`sphinx_design/tabs.py`:**
>
> In the loop at line 242, add a check before unpacking to handle any
edge cases:
> ```python
> for idx, tab_item in enumerate(tab_set.children):
> if not is_component(tab_item, "tab-item"):
> continue # Skip non tab-item children
> if len(tab_item.children) != 2:
> LOGGER.warning(
> f"Malformed 'tab-item' directive [{WARNING_TYPE}.tab]",
> location=tab_item,
> type=WARNING_TYPE,
> subtype="tab",
> )
> continue
> tab_label, tab_content = tab_item.children
> # ... rest of processing unchanged
> ```
>
> Note: You'll need to import `is_component` at the top of the
`TabSetHtmlTransform.run()` method if it's not already available in that
scope (it should be available since it's imported from `.shared`).
>
> ### Expected Behavior After Fix
> - Sphinx-design should NOT crash when invalid content is inside a
`tab-set`
> - A warning should be logged for each invalid child
> - Valid `tab-item` directives should still be processed and rendered
correctly
> - The warning should include accurate location information
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
*This pull request was created from Copilot chat.*
>
<!-- START COPILOT CODING AGENT TIPS -->
---
💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.
---------
Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: chrisjsewell <[email protected]>
Co-authored-by: Chris Sewell <[email protected]>1 parent a500594 commit aae4b81
File tree
3 files changed
+75
-5
lines changed- sphinx_design
- tests
- test_misc
3 files changed
+75
-5
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| 38 | + | |
38 | 39 | | |
39 | 40 | | |
40 | 41 | | |
| |||
44 | 45 | | |
45 | 46 | | |
46 | 47 | | |
47 | | - | |
| 48 | + | |
48 | 49 | | |
49 | 50 | | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
50 | 54 | | |
51 | 55 | | |
52 | 56 | | |
| |||
240 | 244 | | |
241 | 245 | | |
242 | 246 | | |
243 | | - | |
244 | | - | |
245 | | - | |
246 | | - | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
247 | 258 | | |
248 | 259 | | |
249 | 260 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
0 commit comments