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>
132 lines
4.3 KiB
PHP
132 lines
4.3 KiB
PHP
<?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');
|
|
}
|
|
}
|