You've already forked wp-bootstrap
PHPUnit 11 + Brain\Monkey for WordPress function mocking. Tests cover BlockRenderer (28), WidgetRenderer (9), NavWalker (14), and TemplateController (12). Includes functional WP_HTML_Tag_Processor stub, CI test job between lint and build-release, prebuild hook gating npm build on passing tests, and release package exclusions for test files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
324 lines
13 KiB
PHP
324 lines
13 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace WPBootstrap\Tests\Unit\Block;
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use Brain\Monkey;
|
|
use Brain\Monkey\Functions;
|
|
use WPBootstrap\Block\BlockRenderer;
|
|
|
|
class BlockRendererTest extends TestCase
|
|
{
|
|
private BlockRenderer $renderer;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
Monkey\setUp();
|
|
|
|
// Return true so the constructor skips add_filter() registration.
|
|
Functions\when('is_admin')->justReturn(true);
|
|
Functions\when('wp_doing_ajax')->justReturn(false);
|
|
|
|
$this->renderer = new BlockRenderer();
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
Monkey\tearDown();
|
|
parent::tearDown();
|
|
}
|
|
|
|
// ── core/table ──────────────────────────────────────────────
|
|
|
|
public function testRenderTableAddsTableClass(): void
|
|
{
|
|
$html = '<figure class="wp-block-table"><table><thead><tr><th>A</th></tr></thead><tbody><tr><td>1</td></tr></tbody></table></figure>';
|
|
$block = ['attrs' => []];
|
|
$result = $this->renderer->renderTable($html, $block);
|
|
|
|
$this->assertStringContainsString('table', $this->classesOf('table', $result));
|
|
}
|
|
|
|
public function testRenderTableWithStripesAddsStripedClass(): void
|
|
{
|
|
$html = '<figure class="wp-block-table is-style-stripes"><table><tr><td>1</td></tr></table></figure>';
|
|
$block = ['attrs' => ['className' => 'is-style-stripes']];
|
|
$result = $this->renderer->renderTable($html, $block);
|
|
|
|
$classes = $this->classesOf('table', $result);
|
|
$this->assertStringContainsString('table', $classes);
|
|
$this->assertStringContainsString('table-striped', $classes);
|
|
}
|
|
|
|
public function testRenderTableEmptyContentReturnsEmpty(): void
|
|
{
|
|
$this->assertSame('', $this->renderer->renderTable('', []));
|
|
}
|
|
|
|
public function testRenderTableWithoutTableTagReturnsOriginal(): void
|
|
{
|
|
$html = '<p>No table here</p>';
|
|
$this->assertSame($html, $this->renderer->renderTable($html, []));
|
|
}
|
|
|
|
// ── core/button ─────────────────────────────────────────────
|
|
|
|
public function testRenderButtonAddsBtnPrimaryByDefault(): void
|
|
{
|
|
$html = '<div class="wp-block-button"><a class="wp-block-button__link" href="#">Click</a></div>';
|
|
$block = ['attrs' => []];
|
|
$result = $this->renderer->renderButton($html, $block);
|
|
|
|
$classes = $this->classesOf('a', $result);
|
|
$this->assertStringContainsString('btn', $classes);
|
|
$this->assertStringContainsString('btn-primary', $classes);
|
|
}
|
|
|
|
public function testRenderButtonWithBackgroundColor(): void
|
|
{
|
|
$html = '<div class="wp-block-button"><a class="wp-block-button__link" href="#">Click</a></div>';
|
|
$block = ['attrs' => ['backgroundColor' => 'danger']];
|
|
$result = $this->renderer->renderButton($html, $block);
|
|
|
|
$classes = $this->classesOf('a', $result);
|
|
$this->assertStringContainsString('btn', $classes);
|
|
$this->assertStringContainsString('btn-danger', $classes);
|
|
}
|
|
|
|
public function testRenderButtonOutlineStyle(): void
|
|
{
|
|
$html = '<div class="wp-block-button is-style-outline"><a class="wp-block-button__link" href="#">Click</a></div>';
|
|
$block = ['attrs' => ['className' => 'is-style-outline', 'textColor' => 'success']];
|
|
$result = $this->renderer->renderButton($html, $block);
|
|
|
|
$classes = $this->classesOf('a', $result);
|
|
$this->assertStringContainsString('btn', $classes);
|
|
$this->assertStringContainsString('btn-outline-success', $classes);
|
|
}
|
|
|
|
public function testRenderButtonOutlineDefaultsToPrimary(): void
|
|
{
|
|
$html = '<div class="wp-block-button is-style-outline"><a class="wp-block-button__link" href="#">Click</a></div>';
|
|
$block = ['attrs' => ['className' => 'is-style-outline']];
|
|
$result = $this->renderer->renderButton($html, $block);
|
|
|
|
$this->assertStringContainsString('btn-outline-primary', $this->classesOf('a', $result));
|
|
}
|
|
|
|
public function testRenderButtonWithGradientOnlyAddsBtnBase(): void
|
|
{
|
|
$html = '<div class="wp-block-button"><a class="wp-block-button__link" href="#">Click</a></div>';
|
|
$block = ['attrs' => ['gradient' => 'vivid-cyan-blue']];
|
|
$result = $this->renderer->renderButton($html, $block);
|
|
|
|
$classes = $this->classesOf('a', $result);
|
|
$this->assertStringContainsString('btn', $classes);
|
|
$this->assertStringNotContainsString('btn-primary', $classes);
|
|
}
|
|
|
|
public function testRenderButtonWithUnknownColorDefaultsToPrimary(): void
|
|
{
|
|
$html = '<div class="wp-block-button"><a class="wp-block-button__link" href="#">Click</a></div>';
|
|
$block = ['attrs' => ['backgroundColor' => 'custom-teal']];
|
|
$result = $this->renderer->renderButton($html, $block);
|
|
|
|
$this->assertStringContainsString('btn-primary', $this->classesOf('a', $result));
|
|
}
|
|
|
|
public function testRenderButtonEmptyContentReturnsEmpty(): void
|
|
{
|
|
$this->assertSame('', $this->renderer->renderButton('', []));
|
|
}
|
|
|
|
// ── core/buttons ────────────────────────────────────────────
|
|
|
|
public function testRenderButtonsAddsFlexClasses(): void
|
|
{
|
|
$html = '<div class="wp-block-buttons"><div class="wp-block-button"><a class="wp-block-button__link" href="#">A</a></div></div>';
|
|
$block = ['attrs' => []];
|
|
$result = $this->renderer->renderButtons($html, $block);
|
|
|
|
$classes = $this->classesOfFirst('.wp-block-buttons', $result);
|
|
$this->assertStringContainsString('d-flex', $classes);
|
|
$this->assertStringContainsString('flex-wrap', $classes);
|
|
$this->assertStringContainsString('gap-2', $classes);
|
|
}
|
|
|
|
public function testRenderButtonsEmptyContentReturnsEmpty(): void
|
|
{
|
|
$this->assertSame('', $this->renderer->renderButtons('', []));
|
|
}
|
|
|
|
// ── core/image ──────────────────────────────────────────────
|
|
|
|
public function testRenderImageAddsImgFluid(): void
|
|
{
|
|
$html = '<figure class="wp-block-image"><img src="photo.jpg" alt="Photo"></figure>';
|
|
$block = ['attrs' => []];
|
|
$result = $this->renderer->renderImage($html, $block);
|
|
|
|
$this->assertStringContainsString('img-fluid', $this->classesOf('img', $result));
|
|
}
|
|
|
|
public function testRenderImageEmptyContentReturnsEmpty(): void
|
|
{
|
|
$this->assertSame('', $this->renderer->renderImage('', []));
|
|
}
|
|
|
|
// ── core/search ─────────────────────────────────────────────
|
|
|
|
public function testRenderSearchAddsBootstrapClasses(): void
|
|
{
|
|
$html = '<form class="wp-block-search" role="search">'
|
|
. '<div class="wp-block-search__inside-wrapper">'
|
|
. '<input class="wp-block-search__input" type="search" placeholder="Search">'
|
|
. '<button class="wp-block-search__button" type="submit">Search</button>'
|
|
. '</div></form>';
|
|
|
|
$block = ['attrs' => []];
|
|
$result = $this->renderer->renderSearch($html, $block);
|
|
|
|
$this->assertStringContainsString('input-group', $result);
|
|
$this->assertStringContainsString('form-control', $result);
|
|
$this->assertStringContainsString('btn-primary', $result);
|
|
}
|
|
|
|
public function testRenderSearchEmptyContentReturnsEmpty(): void
|
|
{
|
|
$this->assertSame('', $this->renderer->renderSearch('', []));
|
|
}
|
|
|
|
// ── core/quote ──────────────────────────────────────────────
|
|
|
|
public function testRenderQuoteAddsBlockquoteClass(): void
|
|
{
|
|
$html = '<blockquote class="wp-block-quote"><p>Quote text</p></blockquote>';
|
|
$block = ['attrs' => []];
|
|
$result = $this->renderer->renderQuote($html, $block);
|
|
|
|
$this->assertStringContainsString('blockquote', $this->classesOf('blockquote', $result));
|
|
}
|
|
|
|
public function testRenderQuoteWithCiteAddsFooterClass(): void
|
|
{
|
|
$html = '<blockquote class="wp-block-quote"><p>Quote</p><cite>Author</cite></blockquote>';
|
|
$block = ['attrs' => []];
|
|
$result = $this->renderer->renderQuote($html, $block);
|
|
|
|
$this->assertStringContainsString('blockquote-footer', $result);
|
|
}
|
|
|
|
public function testRenderQuoteWithoutCite(): void
|
|
{
|
|
$html = '<blockquote class="wp-block-quote"><p>Quote only</p></blockquote>';
|
|
$block = ['attrs' => []];
|
|
$result = $this->renderer->renderQuote($html, $block);
|
|
|
|
$this->assertStringContainsString('blockquote', $this->classesOf('blockquote', $result));
|
|
$this->assertStringNotContainsString('blockquote-footer', $result);
|
|
}
|
|
|
|
public function testRenderQuoteEmptyContentReturnsEmpty(): void
|
|
{
|
|
$this->assertSame('', $this->renderer->renderQuote('', []));
|
|
}
|
|
|
|
// ── core/pullquote ──────────────────────────────────────────
|
|
|
|
public function testRenderPullquoteAddsBlockquoteClass(): void
|
|
{
|
|
$html = '<figure class="wp-block-pullquote"><blockquote><p>Pull</p></blockquote></figure>';
|
|
$block = ['attrs' => []];
|
|
$result = $this->renderer->renderPullquote($html, $block);
|
|
|
|
$this->assertStringContainsString('blockquote', $this->classesOf('blockquote', $result));
|
|
}
|
|
|
|
public function testRenderPullquoteWithCiteAddsFooterClass(): void
|
|
{
|
|
$html = '<figure class="wp-block-pullquote"><blockquote><p>Pull</p><cite>Source</cite></blockquote></figure>';
|
|
$block = ['attrs' => []];
|
|
$result = $this->renderer->renderPullquote($html, $block);
|
|
|
|
$this->assertStringContainsString('blockquote-footer', $result);
|
|
}
|
|
|
|
public function testRenderPullquoteEmptyContentReturnsEmpty(): void
|
|
{
|
|
$this->assertSame('', $this->renderer->renderPullquote('', []));
|
|
}
|
|
|
|
// ── core/list ───────────────────────────────────────────────
|
|
|
|
public function testRenderListGroupAddsClasses(): void
|
|
{
|
|
$html = '<ul class="is-style-list-group"><li>A</li><li>B</li><li>C</li></ul>';
|
|
$block = ['attrs' => ['className' => 'is-style-list-group']];
|
|
$result = $this->renderer->renderList($html, $block);
|
|
|
|
$this->assertStringContainsString('list-group', $this->classesOf('ul', $result));
|
|
$this->assertStringContainsString('list-group-item', $result);
|
|
}
|
|
|
|
public function testRenderListGroupOrderedList(): void
|
|
{
|
|
$html = '<ol class="is-style-list-group"><li>1</li><li>2</li></ol>';
|
|
$block = ['attrs' => ['className' => 'is-style-list-group', 'ordered' => true]];
|
|
$result = $this->renderer->renderList($html, $block);
|
|
|
|
$this->assertStringContainsString('list-group', $this->classesOf('ol', $result));
|
|
}
|
|
|
|
public function testRenderListWithoutGroupStyleReturnsUnchanged(): void
|
|
{
|
|
$html = '<ul><li>A</li></ul>';
|
|
$block = ['attrs' => []];
|
|
$result = $this->renderer->renderList($html, $block);
|
|
|
|
$this->assertStringNotContainsString('list-group', $result);
|
|
}
|
|
|
|
public function testRenderListEmptyContentReturnsEmpty(): void
|
|
{
|
|
$this->assertSame('', $this->renderer->renderList('', []));
|
|
}
|
|
|
|
// ── helpers ─────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Extract the class attribute value of the first matching tag.
|
|
*/
|
|
private function classesOf(string $tagName, string $html): string
|
|
{
|
|
$doc = new \DOMDocument();
|
|
@$doc->loadHTML('<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body>' . $html . '</body></html>', LIBXML_HTML_NODEFDTD);
|
|
$elements = $doc->getElementsByTagName($tagName);
|
|
|
|
return $elements->length > 0 ? ($elements->item(0)->getAttribute('class') ?? '') : '';
|
|
}
|
|
|
|
/**
|
|
* Extract classes from the first element matching a CSS selector-style class.
|
|
*/
|
|
private function classesOfFirst(string $selector, string $html): string
|
|
{
|
|
$className = ltrim($selector, '.');
|
|
$doc = new \DOMDocument();
|
|
@$doc->loadHTML('<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body>' . $html . '</body></html>', LIBXML_HTML_NODEFDTD);
|
|
$xpath = new \DOMXPath($doc);
|
|
$body = $doc->getElementsByTagName('body')->item(0);
|
|
if (!$body) {
|
|
return '';
|
|
}
|
|
$nodes = $xpath->query(sprintf(
|
|
".//*[contains(concat(' ', normalize-space(@class), ' '), ' %s ')]",
|
|
$className
|
|
), $body);
|
|
|
|
return $nodes->length > 0 ? ($nodes->item(0)->getAttribute('class') ?? '') : '';
|
|
}
|
|
}
|