Add PHPUnit test suite with 64 unit tests (v1.1.1)
Some checks failed
Create Release Package / PHP Lint (push) Successful in 1m6s
Create Release Package / PHPUnit Tests (push) Successful in 1m4s
Create Release Package / Build Release (push) Failing after 1m13s

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>
This commit is contained in:
2026-03-01 00:08:34 +01:00
parent 3165e60639
commit e607382e11
18 changed files with 3234 additions and 12 deletions

10
tests/Stubs/WpBlock.php Normal file
View File

@@ -0,0 +1,10 @@
<?php
/**
* Stub for WordPress WP_Block class.
*
* Used as a type hint in BlockRenderer handler methods.
* Only needs to exist — no functionality required.
*/
class WP_Block
{
}

View File

@@ -0,0 +1,135 @@
<?php
/**
* Functional stub for WordPress WP_HTML_Tag_Processor.
*
* Uses DOMDocument/DOMXPath to provide the subset of functionality
* used by BlockRenderer: next_tag(), add_class(), get_updated_html().
*
* This is NOT a complete implementation — only the methods and query
* modes actually used in the theme are supported.
*/
class WP_HTML_Tag_Processor
{
private string $html;
private \DOMDocument $doc;
private \DOMXPath $xpath;
private ?\DOMElement $current = null;
/** @var int[] Object IDs of already-visited elements. */
private array $visited = [];
public function __construct(string $html)
{
$this->html = $html;
$this->doc = new \DOMDocument();
$this->doc->encoding = 'UTF-8';
// Wrap in a full HTML document so <body> is always present.
// This ensures get_updated_html() can reliably extract content.
@$this->doc->loadHTML(
'<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body>'
. $html
. '</body></html>',
LIBXML_HTML_NODEFDTD
);
$this->xpath = new \DOMXPath($this->doc);
}
/**
* Advance to the next matching tag.
*
* @param string|array|null $query Tag name (string) or ['class_name' => '...'] (array).
*/
public function next_tag($query = null): bool
{
$tagName = null;
$className = null;
if (is_string($query)) {
$tagName = strtolower($query);
} elseif (is_array($query)) {
$tagName = isset($query['tag_name']) ? strtolower($query['tag_name']) : null;
$className = $query['class_name'] ?? null;
}
// Build XPath — search within <body> only.
$body = $this->doc->getElementsByTagName('body')->item(0);
if (!$body) {
return false;
}
$xpathExpr = './/*';
$conditions = [];
if ($tagName !== null) {
$conditions[] = sprintf(
"translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') = '%s'",
$tagName
);
}
if ($className !== null) {
$conditions[] = sprintf(
"contains(concat(' ', normalize-space(@class), ' '), ' %s ')",
$className
);
}
if ($conditions) {
$xpathExpr .= '[' . implode(' and ', $conditions) . ']';
}
$nodes = $this->xpath->query($xpathExpr, $body);
foreach ($nodes as $node) {
$oid = spl_object_id($node);
if (!in_array($oid, $this->visited, true)) {
$this->current = $node;
$this->visited[] = $oid;
return true;
}
}
$this->current = null;
return false;
}
/**
* Add a CSS class to the current tag (idempotent).
*/
public function add_class(string $className): bool
{
if ($this->current === null) {
return false;
}
$existing = $this->current->getAttribute('class');
$classes = $existing ? array_filter(explode(' ', $existing)) : [];
if (!in_array($className, $classes, true)) {
$classes[] = $className;
}
$this->current->setAttribute('class', implode(' ', $classes));
return true;
}
/**
* Return the modified HTML.
*/
public function get_updated_html(): string
{
$body = $this->doc->getElementsByTagName('body')->item(0);
if (!$body) {
return $this->html;
}
$html = '';
foreach ($body->childNodes as $child) {
$html .= $this->doc->saveHTML($child);
}
return $html;
}
}

10
tests/Stubs/WpWidget.php Normal file
View File

@@ -0,0 +1,10 @@
<?php
/**
* Stub for WordPress WP_Widget class.
*
* Used as a type hint in WidgetRenderer::processBlockWidgetContent().
* Only needs to exist — no functionality required.
*/
class WP_Widget
{
}