Skip to content

Commit

Permalink
Trim empty levels from top of menu tree (fixes #1)
Browse files Browse the repository at this point in the history
  • Loading branch information
caseyamcl committed Oct 1, 2019
1 parent b9f05e5 commit df0cab3
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -9,6 +9,7 @@ All notable changes to this project are documented in this file.
### Fixed
- Several issues in the README (typos, etc)
- Version number in COPYRIGHT notice
- Empty levels are now automatically trimmed from the generated output (fixes #1)

### Changed
- Updated PHP requirements to modern versions (7.1+)
Expand Down
33 changes: 30 additions & 3 deletions src/TocGenerator.php
Expand Up @@ -36,6 +36,8 @@ class TocGenerator
{
use HtmlHelper;

private const DEFAULT_NAME = 'TOC';

/**
* @var HTML5
*/
Expand Down Expand Up @@ -71,7 +73,7 @@ public function __construct(MenuFactory $menuFactory = null, HTML5 $htmlParser =
public function getMenu(string $markup, int $topLevel = 1, int $depth = 6): ItemInterface
{
// Setup an empty menu object
$menu = $this->menuFactory->createItem('TOC');
$menu = $this->menuFactory->createItem(static::DEFAULT_NAME);

// Empty? Return empty menu item
if (trim($markup) == '') {
Expand All @@ -86,7 +88,7 @@ public function getMenu(string $markup, int $topLevel = 1, int $depth = 6): Item

// Do it...
$domDocument = $this->domParser->loadHTML($markup);
foreach ($this->traverseHeaderTags($domDocument, $topLevel, $depth) as $node) {
foreach ($this->traverseHeaderTags($domDocument, $topLevel, $depth) as $i => $node) {
// Skip items without IDs
if (! $node->hasAttribute('id')) {
continue;
Expand Down Expand Up @@ -123,7 +125,32 @@ public function getMenu(string $markup, int $topLevel = 1, int $depth = 6): Item
);
}

return $menu;
return $this->trimMenu($menu);
}

/**
* Trim empty items from the menu
*
* @param ItemInterface $menuItem
* @return ItemInterface
*/
protected function trimMenu(ItemInterface $menuItem): ItemInterface
{
// if any of these circumstances are true, then just bail and return the menu item
if (
count($menuItem->getChildren()) === 0
or count($menuItem->getChildren()) > 1
or ! empty($menuItem->getFirstChild()->getLabel())
) {
return $menuItem;
}

// otherwise, find the first level where there is actual content and use that.
while (count($menuItem->getChildren()) == 1 && empty($menuItem->getFirstChild()->getLabel())) {
$menuItem = $menuItem->getFirstChild();
}

return $menuItem;
}

/**
Expand Down
47 changes: 45 additions & 2 deletions tests/TocGeneratorTest.php
Expand Up @@ -19,6 +19,7 @@

namespace TOC;

use Knp\Menu\ItemInterface;
use PHPUnit\Framework\TestCase;
use TOC\Util\TOCTestUtils;

Expand Down Expand Up @@ -64,7 +65,7 @@ public function testGetMenuTraversesLevelsCorrectly(): void
$this->assertEquals($fixture, $actual);
}

public function testGetMenuGeneratesIdsForElementsWithoutIDs(): void
public function testGetMenuDoesNotGenerateIDsForElementsWithoutIDs(): void
{
$html = "
<h1 id='a'>A-Header</h1><p>Foobar</p>
Expand Down Expand Up @@ -121,11 +122,53 @@ public function testGetMenuReturnsEmptyMenuItemWhenNoContentOrMatches(): void
$this->assertEquals(0, count($obj->getMenu("")));
}

public function testGetMenuRespectsOlOption(): void
public function testGetOrderedMenu(): void
{
$obj = new TocGenerator();
$html = "<h1 id='x'>A-Header</h1><h1 id='y'>A-Header</h1>";
$menuHtml = $obj->getOrderedHtmlMenu($html, 1, 6, null);
$this->assertStringStartsWith('<ol>', $menuHtml);
}

/**
* @dataProvider unusedHeadingLevelsAreTrimmedDataProvider
* @param ItemInterface $menuItem
* @param int $expectedTopLevelItems
* @param int $expectedSubItems
*/
public function testUnusedHeadingLevelsAreTrimmedFromGeneratedMenu(
ItemInterface $menuItem,
int $expectedTopLevelItems,
int $expectedSubItems = 0
): void {
$this->assertEquals($expectedTopLevelItems, count($menuItem->getChildren()));

if ($expectedSubItems > 0) {
$this->assertEquals($expectedSubItems, count($menuItem->getFirstChild()->getChildren()));
}
}

/**
* @return iterable|ItemInterface[]
*/
public function unusedHeadingLevelsAreTrimmedDataProvider(): iterable
{
$obj = new TocGenerator();

yield [
$obj->getMenu("<h3 id='x'>X-Header</h3><h4 id='y'>Y-Header</h4><h4 id='z'>Z-Header</h4>", 1, 6),
1,
2
];

yield [$obj->getMenu("<h1 id='x'>X-Header</h1>", 1, 6), 1];

yield [
$obj->getMenu('<h5 id="x">X-Header</h5><h5 id="y">Y-Header</h5>', 1, 6), 2
];

yield [
$obj->getMenu('<h6 id="y">Y-Header</h6>', 1, 5), 0
];
}
}

0 comments on commit df0cab3

Please sign in to comment.