You've already forked wp-bootstrap
Add PHPUnit test suite with 64 unit tests (v1.1.1)
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:
10
tests/Stubs/WpBlock.php
Normal file
10
tests/Stubs/WpBlock.php
Normal 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
|
||||
{
|
||||
}
|
||||
135
tests/Stubs/WpHtmlTagProcessor.php
Normal file
135
tests/Stubs/WpHtmlTagProcessor.php
Normal 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
10
tests/Stubs/WpWidget.php
Normal 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
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user