feat: Divi manifests, adapter register and boundary test
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\Divi\Release;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One Divi function or static method an adapter calls (spec §4.1), with the number of arguments the plugin passes.
|
||||||
|
* $args null: only its existence matters (a function Divi itself needs, e.g. et_() inside store_template).
|
||||||
|
*/
|
||||||
|
final class Call
|
||||||
|
{
|
||||||
|
/** @param string $symbol "function_name" or "Fully\Qualified\Class::method". */
|
||||||
|
public function __construct(public readonly string $symbol, public readonly ?int $args = null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function className(): ?string
|
||||||
|
{
|
||||||
|
return str_contains($this->symbol, '::') ? ltrim(explode('::', $this->symbol, 2)[0], '\\') : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function member(): string
|
||||||
|
{
|
||||||
|
return str_contains($this->symbol, '::') ? explode('::', $this->symbol, 2)[1] : $this->symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
$class = $this->className();
|
||||||
|
|
||||||
|
return ($class === null ? '' : SourceFingerprint::shortName($class) . '::') . $this->member() . '()';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\Divi\Release;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/** A class that may touch Divi (spec §3); it lists every Divi dependency in its manifest. */
|
||||||
|
interface DiviAdapter
|
||||||
|
{
|
||||||
|
public static function manifest(): Manifest;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\Divi\Release;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/** The Divi boundary (spec §3): the only classes that touch Divi, in the order reports list them. */
|
||||||
|
final class DiviAdapters
|
||||||
|
{
|
||||||
|
/** @var list<class-string<DiviAdapter>> */
|
||||||
|
public const CLASSES = [];
|
||||||
|
|
||||||
|
/** @return array<class-string<DiviAdapter>, Manifest> */
|
||||||
|
public static function manifests(): array
|
||||||
|
{
|
||||||
|
$manifests = [];
|
||||||
|
foreach (self::CLASSES as $class) {
|
||||||
|
$manifests[$class] = $class::manifest();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $manifests;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return list<Mirror> */
|
||||||
|
public static function mirrors(): array
|
||||||
|
{
|
||||||
|
$mirrors = [];
|
||||||
|
foreach (self::manifests() as $manifest) {
|
||||||
|
array_push($mirrors, ...$manifest->mirrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $mirrors;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\Divi\Release;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds where PHP code touches Divi (DiviBoundaryTest, spec §3): calls of et_* functions, ET\… / ET_Core… /
|
||||||
|
* ET_Builder… classes (also as strings), ET_* constants (also as strings) and the strings 'et_…'/'_et_…' (post
|
||||||
|
* types, meta keys, options, function names for function_exists()). Comments are ignored; method calls named
|
||||||
|
* et_* ($x->et_a(), Foo::et_b()) are not Divi's.
|
||||||
|
*/
|
||||||
|
final class DiviTouches
|
||||||
|
{
|
||||||
|
private const IGNORED = [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT];
|
||||||
|
|
||||||
|
private const NOT_A_CALL = [
|
||||||
|
T_OBJECT_OPERATOR, T_NULLSAFE_OBJECT_OPERATOR, T_DOUBLE_COLON, T_FUNCTION, T_NEW, T_CONST,
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @return list<array{kind: string, name: string, line: int}> */
|
||||||
|
public static function inFile(string $path): array
|
||||||
|
{
|
||||||
|
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local PHP source, not a remote request.
|
||||||
|
$source = file_get_contents($path);
|
||||||
|
|
||||||
|
return is_string($source) ? self::inSource($source) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return list<array{kind: string, name: string, line: int}> */
|
||||||
|
public static function inSource(string $source): array
|
||||||
|
{
|
||||||
|
$tokens = array_values(array_filter(
|
||||||
|
token_get_all($source),
|
||||||
|
static fn (mixed $token): bool => ! is_array($token) || ! in_array($token[0], self::IGNORED, true)
|
||||||
|
));
|
||||||
|
|
||||||
|
$touches = [];
|
||||||
|
foreach ($tokens as $i => $token) {
|
||||||
|
if (! is_array($token)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$touch = match ($token[0]) {
|
||||||
|
T_STRING, T_NAME_FULLY_QUALIFIED => self::name($tokens, $i, ltrim($token[1], '\\')),
|
||||||
|
T_NAME_QUALIFIED => str_starts_with($token[1], 'ET\\') ? ['class', $token[1]] : null,
|
||||||
|
T_CONSTANT_ENCAPSED_STRING => self::literal($token[1]),
|
||||||
|
default => null,
|
||||||
|
};
|
||||||
|
if ($touch !== null) {
|
||||||
|
$touches[] = ['kind' => $touch[0], 'name' => $touch[1], 'line' => (int) $token[2]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $touches;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether inSource() reports $name when the code uses it (DiviBoundaryTest's unused-entry check). */
|
||||||
|
public static function detects(string $name): bool
|
||||||
|
{
|
||||||
|
return self::classify($name) !== null || preg_match('/^_?et_[a-z0-9_]*$/', $name) === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, mixed> $tokens
|
||||||
|
* @return array{0: string, 1: string}|null
|
||||||
|
*/
|
||||||
|
private static function name(array $tokens, int $i, string $name): ?array
|
||||||
|
{
|
||||||
|
$classified = self::classify($name);
|
||||||
|
if ($classified !== null) {
|
||||||
|
return $classified;
|
||||||
|
}
|
||||||
|
if (preg_match('/^et_[a-z0-9_]*$/', $name) !== 1 || ($tokens[$i + 1] ?? null) !== '(') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$previous = $tokens[$i - 1] ?? null;
|
||||||
|
|
||||||
|
return is_array($previous) && in_array($previous[0], self::NOT_A_CALL, true) ? null : ['function', $name];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array{0: string, 1: string}|null */
|
||||||
|
private static function literal(string $text): ?array
|
||||||
|
{
|
||||||
|
$value = substr($text, 1, -1);
|
||||||
|
if ($text[0] === "'") {
|
||||||
|
$value = str_replace(['\\\\', "\\'"], ['\\', "'"], $value);
|
||||||
|
}
|
||||||
|
$value = ltrim($value, '\\');
|
||||||
|
|
||||||
|
$classified = self::classify($value);
|
||||||
|
if ($classified !== null) {
|
||||||
|
return $classified;
|
||||||
|
}
|
||||||
|
|
||||||
|
return preg_match('/^_?et_[a-z0-9_]*$/', $value) === 1 ? ['string', $value] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array{0: string, 1: string}|null A Divi class or constant name. */
|
||||||
|
private static function classify(string $name): ?array
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
preg_match('/^ET\\\\[A-Za-z0-9_\\\\]+$/', $name) === 1
|
||||||
|
|| preg_match('/^ET_(?:Core|Builder)[A-Za-z0-9_]*$/', $name) === 1
|
||||||
|
) {
|
||||||
|
return ['class', $name];
|
||||||
|
}
|
||||||
|
|
||||||
|
return preg_match('/^ET_[A-Z0-9_]+$/', $name) === 1 ? ['constant', $name] : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\Divi\Release;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Divi dependencies of one adapter (spec §4.1) – the single source for DiviCompatibility, the release gate,
|
||||||
|
* the admin check and DiviBoundaryTest. Pure data, no WordPress calls.
|
||||||
|
*/
|
||||||
|
final class Manifest
|
||||||
|
{
|
||||||
|
public const PAGES = 'Seiten';
|
||||||
|
public const LIBRARY = 'Bibliothek';
|
||||||
|
public const THEME_BUILDER = 'Theme Builder';
|
||||||
|
public const DESIGN = 'Designsystem';
|
||||||
|
public const CATALOG = 'Katalog';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $area One of the constants above (the five areas of spec §3).
|
||||||
|
* @param list<Call> $calls Divi functions and static methods the adapter calls.
|
||||||
|
* @param list<string> $constants Divi constants it reads.
|
||||||
|
* @param list<string> $postTypes Divi post types it needs registered.
|
||||||
|
* @param list<string> $taxonomies Divi taxonomies it needs registered.
|
||||||
|
* @param list<string> $blockTypes Divi block types it needs (registered or in Divi's module metadata).
|
||||||
|
* @param list<string> $keys Other Divi names it uses as strings: meta keys, options, internal post
|
||||||
|
* types that need no registration check, query arguments.
|
||||||
|
* @param list<Mirror> $mirrors Divi functions whose storage logic it repeats.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public readonly string $area,
|
||||||
|
public readonly array $calls = [],
|
||||||
|
public readonly array $constants = [],
|
||||||
|
public readonly array $postTypes = [],
|
||||||
|
public readonly array $taxonomies = [],
|
||||||
|
public readonly array $blockTypes = [],
|
||||||
|
public readonly array $keys = [],
|
||||||
|
public readonly array $mirrors = []
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<string> Every Divi name the adapter's code may contain (DiviBoundaryTest): called functions and
|
||||||
|
* classes, constants, post types, taxonomies, keys and the names of mirrored functions.
|
||||||
|
*/
|
||||||
|
public function names(): array
|
||||||
|
{
|
||||||
|
$names = [];
|
||||||
|
foreach ($this->calls as $call) {
|
||||||
|
$names[] = $call->className() ?? $call->member();
|
||||||
|
}
|
||||||
|
$mirrored = array_map(static fn (Mirror $mirror): string => $mirror->function, $this->mirrors);
|
||||||
|
|
||||||
|
return array_values(array_unique(array_merge(
|
||||||
|
$names,
|
||||||
|
$this->constants,
|
||||||
|
$this->postTypes,
|
||||||
|
$this->taxonomies,
|
||||||
|
$this->keys,
|
||||||
|
$mirrored
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\Divi\Release;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Divi function whose storage logic an adapter repeats instead of calling it (spec §4.1 "mirrors"). Its
|
||||||
|
* fingerprint (SourceFingerprint) must match the one in fingerprints.json, else Divi changed what we copy.
|
||||||
|
*/
|
||||||
|
final class Mirror
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $id Key in fingerprints.json and name of tests/Fixtures/divi/reference/<id>.txt.
|
||||||
|
* @param string $file Divi file, relative to the Divi theme directory.
|
||||||
|
* @param string $function Function or method name.
|
||||||
|
* @param string|null $class Class of a method – the short name is enough (the file fixes where it
|
||||||
|
* lives, and a short name is no Divi name for DiviBoundaryTest); null for a function.
|
||||||
|
* @param string $what What the plugin repeats (German, for reports).
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public readonly string $id,
|
||||||
|
public readonly string $file,
|
||||||
|
public readonly string $function,
|
||||||
|
public readonly ?string $class,
|
||||||
|
public readonly string $what
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
$class = $this->class === null ? '' : SourceFingerprint::shortName($this->class) . '::';
|
||||||
|
|
||||||
|
return $class . $this->function . '()';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
||||||
|
|
||||||
|
use FilesystemIterator;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviAdapters;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviTouches;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use RecursiveDirectoryIterator;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spec §3: only the adapters in DiviAdapters::CLASSES touch Divi, and every Divi name an adapter uses stands in its
|
||||||
|
* manifest (and nothing more).
|
||||||
|
*/
|
||||||
|
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/Design/DesignSystemReader.php',
|
||||||
|
'Integrations/Divi/DiviCompatibility.php',
|
||||||
|
'Integrations/Divi/Library/DiviLibraryStore.php',
|
||||||
|
'Integrations/Divi/Library/DiviRoles.php',
|
||||||
|
'Integrations/Divi/Library/LibraryItemSpec.php',
|
||||||
|
'Integrations/Divi/Library/NewLibraryEntry.php',
|
||||||
|
'Integrations/Divi/Pages/DiviFlags.php',
|
||||||
|
'Integrations/Divi/Pages/DiviPage.php',
|
||||||
|
'Integrations/Divi/Pages/DiviSaver.php',
|
||||||
|
'Integrations/Divi/Pages/DynamicContentMap.php',
|
||||||
|
'Integrations/Divi/Pages/WpLinkTargets.php',
|
||||||
|
'Integrations/Divi/Schema/DiviModules.php',
|
||||||
|
'Integrations/Divi/ThemeBuilder/DiviThemeBuilder.php',
|
||||||
|
'Integrations/Media/MediaUsageFinder.php',
|
||||||
|
'Integrations/WordPress/Content/PostPresenter.php',
|
||||||
|
'Integrations/WordPress/Schema/ContentSchema.php',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function testOnlyAdaptersTouchDivi(): void
|
||||||
|
{
|
||||||
|
$offenders = [];
|
||||||
|
foreach (self::sourceFiles() as $relative => $path) {
|
||||||
|
$touches = DiviTouches::inFile($path);
|
||||||
|
if ($touches === [] || in_array($relative, self::LEGACY, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (! in_array(self::classOf($relative), DiviAdapters::CLASSES, true)) {
|
||||||
|
$offenders[] = $relative . ': ' . implode(', ', array_unique(array_column($touches, 'name')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self::assertSame([], $offenders, "Divi darf nur ein Adapter aus DiviAdapters::CLASSES berühren:\n"
|
||||||
|
. 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) {
|
||||||
|
self::assertTrue(is_subclass_of($class, DiviAdapter::class), $class . ' implementiert DiviAdapter nicht');
|
||||||
|
$declared = $class::manifest()->names();
|
||||||
|
$used = array_values(array_unique(array_column(
|
||||||
|
DiviTouches::inFile((string) (new ReflectionClass($class))->getFileName()),
|
||||||
|
'name'
|
||||||
|
)));
|
||||||
|
|
||||||
|
self::assertSame([], array_values(array_diff($used, $declared)), $class . ': nicht im Manifest');
|
||||||
|
self::assertSame(
|
||||||
|
[],
|
||||||
|
array_values(array_diff(array_filter($declared, [DiviTouches::class, 'detects']), $used)),
|
||||||
|
$class . ': im Manifest, aber nicht benutzt'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMirrorIdsAreUniqueAndFilesRelative(): void
|
||||||
|
{
|
||||||
|
$ids = [];
|
||||||
|
foreach (DiviAdapters::mirrors() as $mirror) {
|
||||||
|
self::assertNotContains($mirror->id, $ids, 'doppelte Mirror-ID ' . $mirror->id);
|
||||||
|
self::assertStringStartsNotWith('/', $mirror->file);
|
||||||
|
$ids[] = $mirror->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<string, string> Path below src/ => absolute path. */
|
||||||
|
private static function sourceFiles(): array
|
||||||
|
{
|
||||||
|
$root = dirname(__DIR__, 5) . '/src/';
|
||||||
|
$files = [];
|
||||||
|
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS));
|
||||||
|
foreach ($iterator as $file) {
|
||||||
|
if ($file->getExtension() === 'php') {
|
||||||
|
$files[substr((string) $file->getPathname(), strlen($root))] = (string) $file->getPathname();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ksort($files);
|
||||||
|
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function classOf(string $relative): string
|
||||||
|
{
|
||||||
|
return 'LIT\\MCP\\' . str_replace(['/', '.php'], ['\\', ''], $relative);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
||||||
|
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviTouches;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class DiviTouchesTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testFindsCallsClassesConstantsAndNames(): void
|
||||||
|
{
|
||||||
|
$source = <<<'PHP'
|
||||||
|
<?php
|
||||||
|
use ET\Builder\Packages\GlobalData\GlobalData;
|
||||||
|
// et_comment_only() and 'et_in_a_comment' are ignored.
|
||||||
|
/** @see et_theme_builder_get_template() */
|
||||||
|
final class X {
|
||||||
|
public const REGISTRY = 'ET\Builder\Packages\Module\DynamicContentOptions';
|
||||||
|
public function a(): void {
|
||||||
|
et_pb_is_allowed('theme_builder');
|
||||||
|
\et_theme_builder_clear_wp_cache('all');
|
||||||
|
\ET_Core_PageResource::remove_static_resources(1, 'all', true);
|
||||||
|
$v = defined('ET_BUILDER_PRODUCT_VERSION') ? ET_BUILDER_PRODUCT_VERSION : '';
|
||||||
|
get_post_meta(1, '_et_pb_use_builder', true);
|
||||||
|
function_exists('et_');
|
||||||
|
$this->et_method();
|
||||||
|
Foo::et_static();
|
||||||
|
get_option('lit_mcp_limits');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PHP;
|
||||||
|
|
||||||
|
$touches = array_map(
|
||||||
|
static fn (array $t): string => $t['kind'] . ':' . $t['name'],
|
||||||
|
DiviTouches::inSource($source)
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
[
|
||||||
|
'class:ET\Builder\Packages\GlobalData\GlobalData',
|
||||||
|
'class:ET\Builder\Packages\Module\DynamicContentOptions',
|
||||||
|
'function:et_pb_is_allowed',
|
||||||
|
'function:et_theme_builder_clear_wp_cache',
|
||||||
|
'class:ET_Core_PageResource',
|
||||||
|
'constant:ET_BUILDER_PRODUCT_VERSION',
|
||||||
|
'constant:ET_BUILDER_PRODUCT_VERSION',
|
||||||
|
'string:_et_pb_use_builder',
|
||||||
|
'string:et_',
|
||||||
|
],
|
||||||
|
$touches
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDetectsOnlyNamesTheScannerReports(): void
|
||||||
|
{
|
||||||
|
self::assertTrue(DiviTouches::detects('et_pb_layout'));
|
||||||
|
self::assertTrue(DiviTouches::detects('_et_template'));
|
||||||
|
self::assertTrue(DiviTouches::detects('ET\Builder\Packages\GlobalData\GlobalData'));
|
||||||
|
self::assertTrue(DiviTouches::detects('ET_Core_PageResource'));
|
||||||
|
self::assertTrue(DiviTouches::detects('ET_BUILDER_PRODUCT_VERSION'));
|
||||||
|
self::assertFalse(DiviTouches::detects('scope'));
|
||||||
|
self::assertFalse(DiviTouches::detects('divi/global-layout'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
||||||
|
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\Mirror;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class ManifestTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testCallsKnowTheirClassMemberAndLabel(): void
|
||||||
|
{
|
||||||
|
$function = new Call('et_pb_is_allowed', 1);
|
||||||
|
$method = new Call('ET\Builder\Packages\GlobalData\GlobalData::get_global_colors', 0);
|
||||||
|
|
||||||
|
self::assertNull($function->className());
|
||||||
|
self::assertSame('et_pb_is_allowed', $function->member());
|
||||||
|
self::assertSame('et_pb_is_allowed()', $function->label());
|
||||||
|
self::assertSame('ET\Builder\Packages\GlobalData\GlobalData', $method->className());
|
||||||
|
self::assertSame('get_global_colors', $method->member());
|
||||||
|
self::assertSame('GlobalData::get_global_colors()', $method->label());
|
||||||
|
self::assertSame('ET_Core_PageResource', (new Call('\ET_Core_PageResource::remove_static_resources', 3))->className());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMirrorLabel(): void
|
||||||
|
{
|
||||||
|
$function = new Mirror('tb-api-save', 'includes/api.php', 'et_theme_builder_api_save', null, 'Vorlagenliste');
|
||||||
|
$method = new Mirror('vb-flags', 'x.php', 'update', 'SyncToServerController', 'Kennzeichen');
|
||||||
|
|
||||||
|
self::assertSame('et_theme_builder_api_save()', $function->label());
|
||||||
|
self::assertSame('SyncToServerController::update()', $method->label());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNamesListEveryDeclaredDiviName(): void
|
||||||
|
{
|
||||||
|
$manifest = new Manifest(
|
||||||
|
Manifest::LIBRARY,
|
||||||
|
calls: [new Call('et_pb_is_allowed', 1), new Call('ET\Builder\GlobalLayout::is_global_layout_template', 1)],
|
||||||
|
constants: ['ET_BUILDER_PRODUCT_VERSION'],
|
||||||
|
postTypes: ['et_pb_layout'],
|
||||||
|
taxonomies: ['scope'],
|
||||||
|
blockTypes: ['divi/global-layout'],
|
||||||
|
keys: ['_et_pb_module_type']
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
['et_pb_is_allowed', 'ET\Builder\GlobalLayout', 'ET_BUILDER_PRODUCT_VERSION', 'et_pb_layout', 'scope',
|
||||||
|
'_et_pb_module_type'],
|
||||||
|
$manifest->names()
|
||||||
|
);
|
||||||
|
self::assertSame('Bibliothek', $manifest->area);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user