Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
84 lines
3.4 KiB
PHP
84 lines
3.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Compose;
|
|
|
|
use LIT\MCP\Integrations\Divi\Compose\HtmlContext;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class HtmlContextTest extends TestCase
|
|
{
|
|
/**
|
|
* HTML with the probe marked as [[…]]: whether the browser reads it as text.
|
|
*
|
|
* @return array<string, array{string, bool}>
|
|
*/
|
|
public static function probes(): array
|
|
{
|
|
return [
|
|
'plain text' => ['a [[B]] c', true],
|
|
'between tags' => ['<p>[[B]]</p>', true],
|
|
'double-quoted ">" keeps the tag' => ['<a title="A > [[B]]" href="/x">T</a>', false],
|
|
'single-quoted ">" keeps the tag' => ["<img alt='A > [[B]]' src=x>", false],
|
|
'quoted value after spaces around =' => ['<a title = "A > [[B]]">', false],
|
|
'after a quoted ">"' => ['<a title="A > B">[[T]]</a>', true],
|
|
'quote in an attribute name is no value' => ['<a b"c>[[T]]', true],
|
|
'unquoted value' => ['<a href=[[x]]>', false],
|
|
'end tag' => ['</a [[x]]>', false],
|
|
'comment' => ['<!-- a > [[B]] -->', false],
|
|
'after a comment' => ['<!-- a -->[[B]]', true],
|
|
'comment closed by --!>' => ['<!-- a --!>[[B]]', true],
|
|
'abrupt comment' => ['<!-->[[B]]', true],
|
|
'unterminated comment' => ['<!-- a [[B]]', false],
|
|
'bogus comment' => ['<!DOCTYPE [[x]]>', false],
|
|
'processing instruction' => ['<?x [[B]] >', false],
|
|
'unterminated tag' => ['<a title="x [[B]]', false],
|
|
'unterminated quote' => ['<a title="x>[[B]]</a>', false],
|
|
'lone "<"' => ['a < [[B]]', true],
|
|
'"<" + digit' => ['a <3 [[B]]', true],
|
|
'script body' => ['<script>var x = "[[B]]";</script>', false],
|
|
'after the script' => ['<script>x</script>[[B]]', true],
|
|
'script end needs a delimiter' => ['<script>x</script-x>[[B]]</script>', false],
|
|
'script end in upper case' => ['<SCRIPT>x</Script >[[B]]', true],
|
|
'double-escaped script' => ['<script><!--<script></script>[[B]]</script>', false],
|
|
'style body' => ['<style>.a{} [[B]]</style>', false],
|
|
'iframe body' => ['<iframe>[[B]]</iframe>', false],
|
|
'plaintext' => ['<plaintext></plaintext>[[B]]', false],
|
|
'textarea is text' => ['<textarea>[[B]]</textarea>', true],
|
|
];
|
|
}
|
|
|
|
/** @dataProvider probes */
|
|
public function testKnowsWhatIsText(string $marked, bool $text): void
|
|
{
|
|
$start = (int) strpos($marked, '[[');
|
|
$html = str_replace(['[[', ']]'], '', $marked);
|
|
$end = (int) strpos($marked, ']]') - 2;
|
|
|
|
$spans = HtmlContext::markupSpans($html);
|
|
|
|
self::assertSame($text, HtmlContext::isText($spans, $start, $end), $marked);
|
|
}
|
|
|
|
public function testBoundaries(): void
|
|
{
|
|
$spans = HtmlContext::markupSpans('<p>ab</p>');
|
|
|
|
self::assertSame([[0, 3], [5, 9]], $spans);
|
|
self::assertTrue(HtmlContext::isBoundary($spans, 3));
|
|
self::assertTrue(HtmlContext::isBoundary($spans, 5));
|
|
self::assertFalse(HtmlContext::isBoundary($spans, 1));
|
|
}
|
|
|
|
public function testLongInputsStayLinear(): void
|
|
{
|
|
$start = microtime(true);
|
|
foreach (['<', '<a ', '<a title="', '<!--', '<script>', '</'] as $unit) {
|
|
HtmlContext::markupSpans(str_repeat($unit, 100000));
|
|
}
|
|
|
|
self::assertLessThan(2.0, microtime(true) - $start);
|
|
}
|
|
}
|