feat: manifest checks, Divi compatibility from manifests, reference fingerprints
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,10 @@ declare(strict_types=1);
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi;
|
||||
|
||||
use LIT\MCP\Integrations\Divi\DiviCompatibility;
|
||||
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||
use LIT\MCP\Integrations\Divi\Release\ManifestCheck;
|
||||
use LIT\MCP\Integrations\Divi\Release\Mirror;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use RuntimeException;
|
||||
|
||||
@@ -23,34 +27,35 @@ final class DiviCompatibilityTest extends TestCase
|
||||
self::assertSame(['B', 'C'], $compatibility->problems());
|
||||
}
|
||||
|
||||
public function testDefaultsCoverTheDiviInternalsInUse(): void
|
||||
public function testDefaultsAreCodeRegistrationsAndFingerprints(): void
|
||||
{
|
||||
$check = new ManifestCheck(
|
||||
['X' => new Manifest(
|
||||
Manifest::LIBRARY,
|
||||
calls: [new Call('strlen', 1)],
|
||||
postTypes: ['et_pb_layout'],
|
||||
mirrors: [new Mirror('m', 'a.php', 'a', null, 'Terme')]
|
||||
)],
|
||||
[],
|
||||
static fn (): ?string => null,
|
||||
static fn (): bool => true
|
||||
);
|
||||
|
||||
self::assertSame(
|
||||
[
|
||||
'ModuleRegistration (Modul-Katalog)',
|
||||
'DynamicContentOptions (Dynamic Content)',
|
||||
'et_pb_is_allowed (Divi-Rollen)',
|
||||
'Post-Typ et_pb_layout (Bibliothek)',
|
||||
'Taxonomien scope, layout_type, module_width (Bibliothek)',
|
||||
'GlobalLayout::is_global_layout_template (globale Module)',
|
||||
'ET_Core_PageResource (CSS-Cache)',
|
||||
'Block-Typ divi/global-layout (Instanzen globaler Module)',
|
||||
'Taxonomien layout_category, layout_tag (Bibliothek)',
|
||||
'Theme Builder: Funktionen et_theme_builder_* und et_()',
|
||||
'Theme Builder: Post-Typen et_template und Layouts',
|
||||
],
|
||||
array_keys(DiviCompatibility::defaults())
|
||||
['Bibliothek: strlen()', 'Bibliothek: Post-Typ et_pb_layout', 'Bibliothek: Nachbau von a() – Terme'],
|
||||
array_keys(DiviCompatibility::defaults($check))
|
||||
);
|
||||
}
|
||||
|
||||
/** Review M-2: et_theme_builder_clear_wp_cache is one of DiviThemeBuilder::FUNCTIONS – reported once. */
|
||||
public function testChecksTheThemeBuilderInternals(): void
|
||||
public function testRealDefaultsCoverEveryAdapterArea(): void
|
||||
{
|
||||
$labels = array_keys(DiviCompatibility::defaults());
|
||||
$labels = implode("\n", array_keys(DiviCompatibility::defaults()));
|
||||
|
||||
self::assertNotContains('et_theme_builder_clear_wp_cache (Theme-Builder-Cache)', $labels);
|
||||
|
||||
self::assertContains('Theme Builder: Funktionen et_theme_builder_* und et_()', $labels);
|
||||
self::assertContains('Theme Builder: Post-Typen et_template und Layouts', $labels);
|
||||
$areas = [Manifest::PAGES, Manifest::LIBRARY, Manifest::THEME_BUILDER, Manifest::DESIGN, Manifest::CATALOG];
|
||||
foreach ($areas as $area) {
|
||||
self::assertStringContainsString($area . ': ', $labels);
|
||||
}
|
||||
self::assertStringContainsString('Theme Builder: Nachbau von et_theme_builder_api_save()', $labels);
|
||||
self::assertStringContainsString('Katalog: Block-Typ divi/global-layout', $labels);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,21 +19,12 @@ use ReflectionClass;
|
||||
*/
|
||||
final class DiviBoundaryTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Files that still touch Divi outside an adapter (paths below src/). Tasks 3–7
|
||||
* shrink this list to DiviCompatibility.php; Task 8 removes it together with
|
||||
* testLegacyFilesStillTouchDivi.
|
||||
*/
|
||||
private const LEGACY = [
|
||||
'Integrations/Divi/DiviCompatibility.php',
|
||||
];
|
||||
|
||||
public function testOnlyAdaptersTouchDivi(): void
|
||||
{
|
||||
$offenders = [];
|
||||
foreach (self::sourceFiles() as $relative => $path) {
|
||||
$touches = DiviTouches::inFile($path);
|
||||
if ($touches === [] || in_array($relative, self::LEGACY, true)) {
|
||||
if ($touches === []) {
|
||||
continue;
|
||||
}
|
||||
if (! in_array(self::classOf($relative), DiviAdapters::CLASSES, true)) {
|
||||
@@ -45,16 +36,6 @@ final class DiviBoundaryTest extends TestCase
|
||||
. implode("\n", $offenders));
|
||||
}
|
||||
|
||||
public function testLegacyFilesStillTouchDivi(): void
|
||||
{
|
||||
$files = self::sourceFiles();
|
||||
foreach (self::LEGACY as $relative) {
|
||||
self::assertArrayHasKey($relative, $files, $relative . ' gibt es nicht mehr – aus LEGACY streichen');
|
||||
self::assertNotSame([], DiviTouches::inFile($files[$relative]), $relative . ' ist sauber – aus LEGACY streichen');
|
||||
self::assertNotContains(self::classOf($relative), DiviAdapters::CLASSES, $relative . ' ist ein Adapter');
|
||||
}
|
||||
}
|
||||
|
||||
public function testAdaptersDeclareExactlyTheDiviNamesTheyUse(): void
|
||||
{
|
||||
foreach (DiviAdapters::CLASSES as $class) {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
||||
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviAdapters;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviReference;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class DiviReferenceTest extends TestCase
|
||||
{
|
||||
public function testEveryMirrorHasAnExpectedFingerprintAndAReferenceSource(): void
|
||||
{
|
||||
$ids = array_map(static fn ($mirror): string => $mirror->id, DiviAdapters::mirrors());
|
||||
sort($ids);
|
||||
$expected = DiviReference::expected();
|
||||
|
||||
self::assertSame($ids, array_keys($expected));
|
||||
foreach ($expected as $id => $hash) {
|
||||
self::assertMatchesRegularExpression('/^[0-9a-f]{64}$/', $hash, $id);
|
||||
self::assertNotNull(DiviReference::readSource($id, (string) DiviReference::sourceDir()), $id);
|
||||
}
|
||||
}
|
||||
|
||||
public function testWritesAndReadsExpectedFingerprintsSorted(): void
|
||||
{
|
||||
$file = (string) tempnam(sys_get_temp_dir(), 'lit');
|
||||
|
||||
try {
|
||||
DiviReference::writeExpected(['b' => str_repeat('b', 64), 'a' => str_repeat('a', 64)], $file);
|
||||
|
||||
self::assertSame(['a' => str_repeat('a', 64), 'b' => str_repeat('b', 64)], DiviReference::expected($file));
|
||||
self::assertStringEndsWith("\n", (string) file_get_contents($file));
|
||||
} finally {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
public function testMissingOrBrokenFileMeansNoExpectations(): void
|
||||
{
|
||||
self::assertSame([], DiviReference::expected('/nonexistent/fingerprints.json'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
||||
|
||||
use ArrayObject;
|
||||
use Brain\Monkey\Functions;
|
||||
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||
use LIT\MCP\Integrations\Divi\Release\ManifestCheck;
|
||||
use LIT\MCP\Integrations\Divi\Release\Mirror;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use RuntimeException;
|
||||
|
||||
final class ManifestCheckTest extends TestCase
|
||||
{
|
||||
private function check(?\Closure $current = null): ManifestCheck
|
||||
{
|
||||
$manifests = [
|
||||
'A' => new Manifest(
|
||||
Manifest::PAGES,
|
||||
calls: [
|
||||
new Call('strlen', 1),
|
||||
new Call('lit_mcp_missing_function', 0),
|
||||
new Call(ArrayObject::class . '::count', 0),
|
||||
new Call('Missing\Klass::run', 0),
|
||||
new Call('str_replace'),
|
||||
],
|
||||
constants: ['PHP_VERSION', 'LIT_MCP_MISSING_CONSTANT'],
|
||||
postTypes: ['et_pb_layout'],
|
||||
taxonomies: ['scope'],
|
||||
blockTypes: ['divi/global-layout'],
|
||||
mirrors: [
|
||||
new Mirror('same', 'a.php', 'a', null, 'gleich'),
|
||||
new Mirror('changed', 'b.php', 'b', null, 'geändert'),
|
||||
new Mirror('unreadable', 'c.php', 'c', null, 'fehlt'),
|
||||
new Mirror('no-expectation', 'd.php', 'd', null, 'ohne Soll'),
|
||||
]
|
||||
),
|
||||
];
|
||||
|
||||
$currentHashes = ['same' => 'h1', 'changed' => 'hX', 'no-expectation' => 'h4'];
|
||||
|
||||
return new ManifestCheck(
|
||||
$manifests,
|
||||
['same' => 'h1', 'changed' => 'h2', 'unreadable' => 'h3'],
|
||||
$current ?? static fn (Mirror $mirror): ?string => $currentHashes[$mirror->id] ?? null,
|
||||
static fn (string $name): bool => false
|
||||
);
|
||||
}
|
||||
|
||||
public function testCodeChecksCoverExistenceAndArgumentCount(): void
|
||||
{
|
||||
self::assertSame(
|
||||
[
|
||||
'Seiten: strlen()',
|
||||
'Seiten: lit_mcp_missing_function()',
|
||||
'Seiten: ArrayObject::count()',
|
||||
'Seiten: Klass::run()',
|
||||
'Seiten: str_replace()',
|
||||
'Seiten: Konstante PHP_VERSION',
|
||||
'Seiten: Konstante LIT_MCP_MISSING_CONSTANT',
|
||||
],
|
||||
array_keys($this->check()->codeChecks())
|
||||
);
|
||||
self::assertSame(
|
||||
[
|
||||
'Seiten: lit_mcp_missing_function()',
|
||||
'Seiten: Klass::run()',
|
||||
'Seiten: Konstante LIT_MCP_MISSING_CONSTANT',
|
||||
],
|
||||
ManifestCheck::failed($this->check()->codeChecks())
|
||||
);
|
||||
}
|
||||
|
||||
public function testAcceptsChecksTheArgumentCountTheAdapterPasses(): void
|
||||
{
|
||||
self::assertTrue(ManifestCheck::accepts(new Call('strlen', 1)));
|
||||
self::assertFalse(ManifestCheck::accepts(new Call('strlen', 2)), 'strlen takes one argument');
|
||||
self::assertFalse(ManifestCheck::accepts(new Call('str_replace', 2)), 'str_replace needs three');
|
||||
self::assertTrue(ManifestCheck::accepts(new Call('sprintf', 5)), 'variadic');
|
||||
self::assertTrue(ManifestCheck::accepts(new Call('str_replace')), 'existence only');
|
||||
}
|
||||
|
||||
public function testMirrorChecksCompareWithTheExpectedFingerprints(): void
|
||||
{
|
||||
$checks = $this->check()->mirrorChecks();
|
||||
|
||||
self::assertSame(
|
||||
[
|
||||
'Seiten: Nachbau von a() – gleich',
|
||||
'Seiten: Nachbau von b() – geändert',
|
||||
'Seiten: Nachbau von c() – fehlt',
|
||||
'Seiten: Nachbau von d() – ohne Soll',
|
||||
],
|
||||
array_keys($checks)
|
||||
);
|
||||
self::assertSame(
|
||||
[
|
||||
'Seiten: Nachbau von b() – geändert',
|
||||
'Seiten: Nachbau von c() – fehlt',
|
||||
'Seiten: Nachbau von d() – ohne Soll',
|
||||
],
|
||||
ManifestCheck::failed($checks)
|
||||
);
|
||||
}
|
||||
|
||||
public function testUnreadableMirrorIsAFailedCheck(): void
|
||||
{
|
||||
$check = $this->check(static function (): ?string {
|
||||
throw new RuntimeException('Divi file gone');
|
||||
});
|
||||
|
||||
self::assertCount(4, ManifestCheck::failed($check->mirrorChecks()));
|
||||
}
|
||||
|
||||
public function testRegistrationChecksAskWordPressAndDivi(): void
|
||||
{
|
||||
Functions\when('post_type_exists')->justReturn(true);
|
||||
Functions\when('taxonomy_exists')->justReturn(false);
|
||||
|
||||
$checks = $this->check()->registrationChecks();
|
||||
|
||||
self::assertSame(
|
||||
['Seiten: Post-Typ et_pb_layout', 'Seiten: Taxonomie scope', 'Seiten: Block-Typ divi/global-layout'],
|
||||
array_keys($checks)
|
||||
);
|
||||
self::assertSame(
|
||||
['Seiten: Taxonomie scope', 'Seiten: Block-Typ divi/global-layout'],
|
||||
ManifestCheck::failed($checks)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user