refactor(compat): Prüfbausteine nach src/Compatibility, Matrix und ManifestCheck generisch
Requirement, Call, ConstantRequirement, Mirror, Problem, FeatureStatus, CompatibilityMatrix, Manifest, ManifestCheck sowie Fingerprints und LineDiff liegen jetzt im divi-freien Kern. Neu: GlobalRequirement, CompatibilityMatrix::fromProblems(), ManifestCheck-Option signatures, DiviManifestCheck::create() und DiviAreas. Divi-Verhalten unverändert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
9beb55e228
commit
b511ef6348
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Compatibility;
|
||||
|
||||
use LIT\MCP\Compatibility\SourceFingerprint;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use RuntimeException;
|
||||
|
||||
final class SourceFingerprintTest extends TestCase
|
||||
{
|
||||
/** Shaped like Divi's files: a guarded function, two classes with a method of the same name. */
|
||||
private const SOURCE = <<<'PHP'
|
||||
<?php
|
||||
use function et_demo_save;
|
||||
if ( ! function_exists( 'et_demo_save' ) ) {
|
||||
/**
|
||||
* Saves.
|
||||
*/
|
||||
function et_demo_save( $id, $data = array() ) {
|
||||
$value = "{$data['x']}";
|
||||
if ( $id ) {
|
||||
update_post_meta( $id, '_et_demo', $value );
|
||||
}
|
||||
return Foo::class;
|
||||
}
|
||||
}
|
||||
|
||||
class Controller {
|
||||
public static function update( $request ) {
|
||||
return array( 'a' => 1 );
|
||||
}
|
||||
}
|
||||
|
||||
class Other {
|
||||
public static function update( $request ) {
|
||||
return array( 'b' => 2 );
|
||||
}
|
||||
}
|
||||
PHP;
|
||||
|
||||
public function testFindsAFunctionInsideAFunctionExistsGuard(): void
|
||||
{
|
||||
$print = SourceFingerprint::fromSource(self::SOURCE, 'et_demo_save');
|
||||
|
||||
self::assertNotNull($print);
|
||||
self::assertSame(7, $print->startLine);
|
||||
self::assertSame(13, $print->endLine);
|
||||
self::assertStringStartsWith('function et_demo_save(', $print->source);
|
||||
self::assertStringEndsWith('}', $print->source);
|
||||
self::assertMatchesRegularExpression('/^[0-9a-f]{64}$/', $print->hash);
|
||||
}
|
||||
|
||||
public function testClassConstantsAndInterpolationDoNotConfuseTheBraceCount(): void
|
||||
{
|
||||
$print = SourceFingerprint::fromSource(self::SOURCE, 'et_demo_save');
|
||||
|
||||
self::assertNotNull($print);
|
||||
self::assertStringContainsString('return Foo::class;', $print->source);
|
||||
self::assertStringNotContainsString('class Controller', $print->source);
|
||||
}
|
||||
|
||||
public function testMethodIsFoundInItsOwnClassOnly(): void
|
||||
{
|
||||
$controller = SourceFingerprint::fromSource(self::SOURCE, 'update', 'ET\Builder\VisualBuilder\Controller');
|
||||
$other = SourceFingerprint::fromSource(self::SOURCE, 'update', 'Other');
|
||||
|
||||
self::assertNotNull($controller);
|
||||
self::assertNotNull($other);
|
||||
self::assertStringContainsString("'a' => 1", $controller->source);
|
||||
self::assertStringContainsString("'b' => 2", $other->source);
|
||||
self::assertNotSame($controller->hash, $other->hash);
|
||||
self::assertNull(SourceFingerprint::fromSource(self::SOURCE, 'update'), 'a method is no top-level function');
|
||||
self::assertNull(SourceFingerprint::fromSource(self::SOURCE, 'et_demo_save', 'Controller'));
|
||||
self::assertNull(SourceFingerprint::fromSource(self::SOURCE, 'missing'));
|
||||
}
|
||||
|
||||
public function testCommentsAndWhitespaceDoNotChangeTheHash(): void
|
||||
{
|
||||
$reformatted = str_replace(
|
||||
["\t\t\$value = ", "if ( \$id ) {"],
|
||||
["\t\t// Why: demo.\n\t\t\$value = ", "if (\$id)\n\t\t{"],
|
||||
self::SOURCE
|
||||
);
|
||||
|
||||
self::assertSame(
|
||||
SourceFingerprint::fromSource(self::SOURCE, 'et_demo_save')?->hash,
|
||||
SourceFingerprint::fromSource($reformatted, 'et_demo_save')?->hash
|
||||
);
|
||||
}
|
||||
|
||||
public function testACodeChangeChangesTheHash(): void
|
||||
{
|
||||
$changed = str_replace("'_et_demo'", "'_et_demo_v2'", self::SOURCE);
|
||||
|
||||
self::assertNotSame(
|
||||
SourceFingerprint::fromSource(self::SOURCE, 'et_demo_save')?->hash,
|
||||
SourceFingerprint::fromSource($changed, 'et_demo_save')?->hash
|
||||
);
|
||||
}
|
||||
|
||||
public function testOfReadsTheFileAndThrowsForMissingFunctions(): void
|
||||
{
|
||||
$file = (string) tempnam(sys_get_temp_dir(), 'lit');
|
||||
file_put_contents($file, self::SOURCE);
|
||||
|
||||
try {
|
||||
self::assertSame(
|
||||
SourceFingerprint::fromSource(self::SOURCE, 'et_demo_save')?->hash,
|
||||
SourceFingerprint::of($file, 'et_demo_save')->hash
|
||||
);
|
||||
try {
|
||||
SourceFingerprint::of($file, 'update', 'Missing');
|
||||
self::fail('a missing method must throw');
|
||||
} catch (RuntimeException $e) {
|
||||
self::assertStringContainsString('Missing::update() not found', $e->getMessage());
|
||||
}
|
||||
} finally {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
public function testOfThrowsForAnUnreadableFile(): void
|
||||
{
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('is not readable');
|
||||
|
||||
SourceFingerprint::of('/nonexistent/lit-divi.php', 'et_demo_save');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user