feat: wp lit-mcp divi-check with diff and reference update
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,192 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Cli;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\CheckReport;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\CheckReportStore;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\CheckStep;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\CompatibilityCheck;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviAdapters;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviEnvironment;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviReference;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\LineDiff;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\ReleaseResolver;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\SourceFingerprint;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wp lit-mcp divi-check (spec §7): the admin check from the command line, for a developer checking a new Divi
|
||||||
|
* version. Never releases anything – a green version goes into DiviReleases::TESTED by hand.
|
||||||
|
*/
|
||||||
|
final class DiviCheckCommand
|
||||||
|
{
|
||||||
|
/** @var Closure(string): void */
|
||||||
|
private Closure $out;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Closure(): CompatibilityCheck $check
|
||||||
|
* @param (Closure(string): void)|null $out null = WP_CLI::line().
|
||||||
|
* @param (Closure(): ?string)|null $themeDir null = DiviEnvironment::themeDir().
|
||||||
|
* @param string|null $expectedFile null = DiviReference::FILE.
|
||||||
|
* @param (Closure(): ?string)|null $sourceDir null = DiviReference::sourceDir().
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private Closure $check,
|
||||||
|
private ?CheckReportStore $reports = null,
|
||||||
|
?Closure $out = null,
|
||||||
|
private ?Closure $themeDir = null,
|
||||||
|
private ?string $expectedFile = null,
|
||||||
|
private ?Closure $sourceDir = null
|
||||||
|
) {
|
||||||
|
$this->out = $out ?? static function (string $line): void {
|
||||||
|
\WP_CLI::line($line);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param Closure(): CompatibilityCheck $check */
|
||||||
|
public static function register(Closure $check): void
|
||||||
|
{
|
||||||
|
if (defined('WP_CLI') && WP_CLI) {
|
||||||
|
\WP_CLI::add_command('lit-mcp divi-check', new self($check));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft die installierte Divi-Version wie der Reiter „Divi“: Manifeste, Fingerabdrücke, Modulkatalog, Format
|
||||||
|
* und Schreibtest auf einer Wegwerfseite. Gibt nichts frei.
|
||||||
|
*
|
||||||
|
* ## OPTIONS
|
||||||
|
*
|
||||||
|
* [--diff]
|
||||||
|
* : Zeigt für jede geänderte nachgebaute Divi-Funktion den Unterschied zur Referenz.
|
||||||
|
*
|
||||||
|
* [--update-reference]
|
||||||
|
* : Schreibt Soll-Fingerabdrücke und Referenzquellen neu – nur wenn alle übrigen Schritte grün sind.
|
||||||
|
*
|
||||||
|
* ## EXAMPLES
|
||||||
|
*
|
||||||
|
* wp --user=admin lit-mcp divi-check --diff
|
||||||
|
*
|
||||||
|
* @param list<string> $args
|
||||||
|
* @param array<string, mixed> $assoc
|
||||||
|
*/
|
||||||
|
public function __invoke(array $args, array $assoc): void
|
||||||
|
{
|
||||||
|
$code = $this->run($assoc);
|
||||||
|
if ($code !== 0) {
|
||||||
|
\WP_CLI::halt($code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<string, mixed> $assoc */
|
||||||
|
public function run(array $assoc): int
|
||||||
|
{
|
||||||
|
if (! current_user_can('manage_options')) {
|
||||||
|
($this->out)('Bitte mit --user=<Administrator> ausführen: der Schreibtest legt eine Seite an.');
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$report = ($this->check)()->run(get_current_user_id());
|
||||||
|
($this->reports ?? new CheckReportStore())->save($report);
|
||||||
|
($this->out)(sprintf('Divi %s mit LIT MCP %s', $report->diviVersion ?: '?', $report->pluginVersion));
|
||||||
|
foreach ($report->steps as $step) {
|
||||||
|
($this->out)(($step->skipped ? '–' : ($step->green() ? '✓' : '✗')) . ' ' . $step->label());
|
||||||
|
foreach ($step->problems as $problem) {
|
||||||
|
($this->out)(' ' . $problem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($assoc['diff'])) {
|
||||||
|
$this->diff();
|
||||||
|
}
|
||||||
|
if (isset($assoc['update-reference'])) {
|
||||||
|
return $this->updateReference($report);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $report->green() ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function diff(): void
|
||||||
|
{
|
||||||
|
$themeDir = ($this->themeDir ?? DiviEnvironment::themeDir(...))();
|
||||||
|
$sourceDir = ($this->sourceDir ?? DiviReference::sourceDir(...))();
|
||||||
|
$expected = DiviReference::expected($this->expectedFile);
|
||||||
|
foreach (DiviAdapters::mirrors() as $mirror) {
|
||||||
|
try {
|
||||||
|
$print = SourceFingerprint::of(
|
||||||
|
(string) $themeDir . '/' . $mirror->file,
|
||||||
|
$mirror->function,
|
||||||
|
$mirror->class
|
||||||
|
);
|
||||||
|
} catch (RuntimeException $e) {
|
||||||
|
($this->out)(sprintf('%s: nicht lesbar (%s)', $mirror->id, $e->getMessage()));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (($expected[$mirror->id] ?? null) === $print->hash) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
($this->out)(sprintf(
|
||||||
|
'%s: %s, Zeilen %d–%d',
|
||||||
|
$mirror->id,
|
||||||
|
$mirror->file,
|
||||||
|
$print->startLine,
|
||||||
|
$print->endLine
|
||||||
|
));
|
||||||
|
$reference = $sourceDir === null ? null : DiviReference::readSource($mirror->id, $sourceDir);
|
||||||
|
if ($reference === null) {
|
||||||
|
($this->out)(' keine Referenzquelle (nur in einer Entwicklungskopie mit tests/)');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach (LineDiff::lines(explode("\n", rtrim($reference, "\n")), explode("\n", $print->source)) as $line) {
|
||||||
|
($this->out)(' ' . $line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateReference(CheckReport $report): int
|
||||||
|
{
|
||||||
|
foreach ($report->steps as $step) {
|
||||||
|
if ($step->key !== CheckStep::MIRRORS && ! $step->green()) {
|
||||||
|
($this->out)('Referenz nicht aktualisiert: nicht nur Fingerabdrücke sind rot (siehe oben).');
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$themeDir = ($this->themeDir ?? DiviEnvironment::themeDir(...))();
|
||||||
|
try {
|
||||||
|
$prints = DiviReference::current((string) $themeDir);
|
||||||
|
} catch (RuntimeException $e) {
|
||||||
|
($this->out)(
|
||||||
|
'Referenz nicht aktualisiert – eine nachgebaute Divi-Funktion ist nicht lesbar: ' . $e->getMessage()
|
||||||
|
);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiviReference::writeExpected(
|
||||||
|
array_map(static fn (SourceFingerprint $print): string => $print->hash, $prints),
|
||||||
|
$this->expectedFile
|
||||||
|
);
|
||||||
|
$sourceDir = ($this->sourceDir ?? DiviReference::sourceDir(...))();
|
||||||
|
foreach ($sourceDir === null ? [] : $prints as $id => $print) {
|
||||||
|
DiviReference::writeSource($id, $print->source, $sourceDir);
|
||||||
|
}
|
||||||
|
delete_transient(ReleaseResolver::CACHE);
|
||||||
|
ReleaseGate::forget();
|
||||||
|
($this->out)(sprintf(
|
||||||
|
'Referenz aktualisiert (%d Fingerabdrücke). Smoke-Skripte laufen lassen, dann committen.',
|
||||||
|
count($prints)
|
||||||
|
));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\Divi\Release;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/** A small line diff (longest common subsequence) for wp lit-mcp divi-check --diff. */
|
||||||
|
final class LineDiff
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param list<string> $old
|
||||||
|
* @param list<string> $new
|
||||||
|
* @return list<string> " kept", "- removed", "+ added"; "…" between changes more than 2 × $context lines apart.
|
||||||
|
*/
|
||||||
|
public static function lines(array $old, array $new, int $context = 2): array
|
||||||
|
{
|
||||||
|
$operations = self::operations($old, $new);
|
||||||
|
$keep = [];
|
||||||
|
foreach ($operations as $i => [$op]) {
|
||||||
|
if ($op === ' ') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for ($j = max(0, $i - $context); $j <= min(count($operations) - 1, $i + $context); ++$j) {
|
||||||
|
$keep[$j] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = [];
|
||||||
|
$last = null;
|
||||||
|
foreach ($operations as $i => [$op, $line]) {
|
||||||
|
if (! isset($keep[$i])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($last !== null && $i > $last + 1) {
|
||||||
|
$lines[] = '…';
|
||||||
|
}
|
||||||
|
$lines[] = $op . ' ' . $line;
|
||||||
|
$last = $i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<string> $a
|
||||||
|
* @param list<string> $b
|
||||||
|
* @return list<array{0: string, 1: string}>
|
||||||
|
*/
|
||||||
|
private static function operations(array $a, array $b): array
|
||||||
|
{
|
||||||
|
$n = count($a);
|
||||||
|
$m = count($b);
|
||||||
|
$lcs = array_fill(0, $n + 1, array_fill(0, $m + 1, 0));
|
||||||
|
for ($i = $n - 1; $i >= 0; --$i) {
|
||||||
|
for ($j = $m - 1; $j >= 0; --$j) {
|
||||||
|
$lcs[$i][$j] = $a[$i] === $b[$j] ? $lcs[$i + 1][$j + 1] + 1 : max($lcs[$i + 1][$j], $lcs[$i][$j + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$operations = [];
|
||||||
|
$i = 0;
|
||||||
|
$j = 0;
|
||||||
|
while ($i < $n && $j < $m) {
|
||||||
|
if ($a[$i] === $b[$j]) {
|
||||||
|
$operations[] = [' ', $a[$i]];
|
||||||
|
++$i;
|
||||||
|
++$j;
|
||||||
|
} elseif ($lcs[$i + 1][$j] >= $lcs[$i][$j + 1]) {
|
||||||
|
$operations[] = ['-', $a[$i++]];
|
||||||
|
} else {
|
||||||
|
$operations[] = ['+', $b[$j++]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while ($i < $n) {
|
||||||
|
$operations[] = ['-', $a[$i++]];
|
||||||
|
}
|
||||||
|
while ($j < $m) {
|
||||||
|
$operations[] = ['+', $b[$j++]];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $operations;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ use LIT\MCP\Admin\ToolsTab;
|
|||||||
use LIT\MCP\ChangeLog\ChangeLog;
|
use LIT\MCP\ChangeLog\ChangeLog;
|
||||||
use LIT\MCP\ChangeLog\ChangeLogCleanup;
|
use LIT\MCP\ChangeLog\ChangeLogCleanup;
|
||||||
use LIT\MCP\ChangeLog\ChangeLogInstaller;
|
use LIT\MCP\ChangeLog\ChangeLogInstaller;
|
||||||
|
use LIT\MCP\Cli\DiviCheckCommand;
|
||||||
use LIT\MCP\Guides\InstructionsBuilder;
|
use LIT\MCP\Guides\InstructionsBuilder;
|
||||||
use LIT\MCP\Integrations\Core\CoreIntegration;
|
use LIT\MCP\Integrations\Core\CoreIntegration;
|
||||||
use LIT\MCP\Integrations\Divi\DiviIntegration;
|
use LIT\MCP\Integrations\Divi\DiviIntegration;
|
||||||
@@ -140,6 +141,7 @@ final class Plugin
|
|||||||
// Spec §6: the admin check runs as a REST request (same context as MCP calls).
|
// Spec §6: the admin check runs as a REST request (same context as MCP calls).
|
||||||
$diviCheck = static fn (): CompatibilityCheck => CompatibilityCheck::create($divi->contentComposer());
|
$diviCheck = static fn (): CompatibilityCheck => CompatibilityCheck::create($divi->contentComposer());
|
||||||
(new DiviCheckRoute($diviCheck))->register();
|
(new DiviCheckRoute($diviCheck))->register();
|
||||||
|
DiviCheckCommand::register($diviCheck);
|
||||||
|
|
||||||
$policy = ExposurePolicy::fromOption()->withGroups($registry->groupMap(...));
|
$policy = ExposurePolicy::fromOption()->withGroups($registry->groupMap(...));
|
||||||
(new AbilityRegistrar($registry, $policy, new DiviToolGate($registry->groupMap(...), $changeLog)))->register();
|
(new AbilityRegistrar($registry, $policy, new DiviToolGate($registry->groupMap(...), $changeLog)))->register();
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Cli;
|
||||||
|
|
||||||
|
use Brain\Monkey\Functions;
|
||||||
|
use LIT\MCP\Cli\DiviCheckCommand;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\CheckReport;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\CheckReportStore;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\CheckStep;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\CompatibilityCheck;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviReference;
|
||||||
|
use LIT\MCP\Tests\Unit\TestCase;
|
||||||
|
use Mockery;
|
||||||
|
|
||||||
|
final class DiviCheckCommandTest extends TestCase
|
||||||
|
{
|
||||||
|
/** @var list<string> */
|
||||||
|
private array $lines = [];
|
||||||
|
|
||||||
|
private string $themeDir = '';
|
||||||
|
private string $sourceDir = '';
|
||||||
|
private string $expectedFile = '';
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->lines = [];
|
||||||
|
$base = sys_get_temp_dir() . '/lit-cli-' . uniqid();
|
||||||
|
$this->themeDir = $base . '/Divi';
|
||||||
|
$this->sourceDir = $base . '/reference';
|
||||||
|
mkdir($this->themeDir . '/includes/builder', 0777, true);
|
||||||
|
mkdir($this->sourceDir, 0777, true);
|
||||||
|
$this->expectedFile = $base . '/fingerprints.json';
|
||||||
|
Functions\when('current_user_can')->justReturn(true);
|
||||||
|
Functions\when('get_current_user_id')->justReturn(1);
|
||||||
|
Functions\when('delete_transient')->justReturn(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown(): void
|
||||||
|
{
|
||||||
|
exec('rm -rf ' . escapeshellarg(dirname($this->themeDir)));
|
||||||
|
parent::tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<string, list<string>> $problems Step key => problems. */
|
||||||
|
private function command(array $problems = []): DiviCheckCommand
|
||||||
|
{
|
||||||
|
$steps = array_map(
|
||||||
|
static fn (string $key): CheckStep => new CheckStep($key, $problems[$key] ?? []),
|
||||||
|
[CheckStep::MANIFEST, CheckStep::MIRRORS, CheckStep::CATALOG, CheckStep::FORMAT, CheckStep::WRITE]
|
||||||
|
);
|
||||||
|
$check = Mockery::mock(CompatibilityCheck::class);
|
||||||
|
$check->allows('run')->andReturn(new CheckReport('r1', 'd', 1, '5.3.0', '0.6.0', 'k', $steps));
|
||||||
|
$reports = Mockery::mock(CheckReportStore::class);
|
||||||
|
$reports->allows('save');
|
||||||
|
|
||||||
|
return new DiviCheckCommand(
|
||||||
|
static fn () => $check,
|
||||||
|
$reports,
|
||||||
|
function (string $line): void {
|
||||||
|
$this->lines[] = $line;
|
||||||
|
},
|
||||||
|
fn (): ?string => $this->themeDir,
|
||||||
|
$this->expectedFile,
|
||||||
|
fn (): ?string => $this->sourceDir
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNeedsAnAdministrator(): void
|
||||||
|
{
|
||||||
|
Functions\when('current_user_can')->justReturn(false);
|
||||||
|
|
||||||
|
self::assertSame(1, $this->command()->run([]));
|
||||||
|
self::assertStringContainsString('--user=', implode("\n", $this->lines));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPrintsTheStepsAndSucceedsWhenGreen(): void
|
||||||
|
{
|
||||||
|
self::assertSame(0, $this->command()->run([]));
|
||||||
|
self::assertContains('✓ Schreibtest auf einer Wegwerfseite', $this->lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailsWhenAStepIsRed(): void
|
||||||
|
{
|
||||||
|
self::assertSame(1, $this->command([CheckStep::CATALOG => ['divi/text: unbekanntes Modul']])->run([]));
|
||||||
|
self::assertContains('✗ Modulkatalog (Kurzform-Pfade)', $this->lines);
|
||||||
|
self::assertContains(' divi/text: unbekanntes Modul', $this->lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpdateReferenceRefusesWhenMoreThanFingerprintsIsRed(): void
|
||||||
|
{
|
||||||
|
$code = $this->command([CheckStep::FORMAT => ['kaputt']])->run(['update-reference' => true]);
|
||||||
|
|
||||||
|
self::assertSame(1, $code);
|
||||||
|
self::assertFileDoesNotExist($this->expectedFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDiffShowsWhatChangedInAMirroredFunction(): void
|
||||||
|
{
|
||||||
|
file_put_contents(
|
||||||
|
$this->themeDir . '/includes/builder/shortcode-core.php',
|
||||||
|
"<?php\nfunction et_pb_submit_layout( \$args ) {\n\t\$a = 1;\n\treturn 2;\n}\n"
|
||||||
|
);
|
||||||
|
DiviReference::writeExpected(['library-submit-layout' => str_repeat('0', 64)], $this->expectedFile);
|
||||||
|
DiviReference::writeSource(
|
||||||
|
'library-submit-layout',
|
||||||
|
"function et_pb_submit_layout( \$args ) {\n\t\$a = 1;\n\treturn 1;\n}",
|
||||||
|
$this->sourceDir
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->command([CheckStep::MIRRORS => ['Bibliothek: Nachbau von et_pb_submit_layout() – Terme']])
|
||||||
|
->run(['diff' => true]);
|
||||||
|
|
||||||
|
$output = implode("\n", $this->lines);
|
||||||
|
self::assertStringContainsString('library-submit-layout', $output);
|
||||||
|
self::assertStringContainsString("- \treturn 1;", $output);
|
||||||
|
self::assertStringContainsString("+ \treturn 2;", $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpdateReferenceWritesNothingWhenAMirrorIsUnreadable(): void
|
||||||
|
{
|
||||||
|
file_put_contents(
|
||||||
|
$this->themeDir . '/includes/builder/shortcode-core.php',
|
||||||
|
"<?php\nfunction et_pb_submit_layout( \$args ) {\n\treturn 2;\n}\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
$code = $this->command([CheckStep::MIRRORS => ['geändert']])->run(['update-reference' => true]);
|
||||||
|
|
||||||
|
// The other mirrored Divi files are missing in the temp theme.
|
||||||
|
self::assertSame(1, $code);
|
||||||
|
self::assertStringContainsString('nicht lesbar', implode("\n", $this->lines));
|
||||||
|
self::assertFileDoesNotExist($this->expectedFile, 'all or nothing');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpdateReferenceWritesFingerprintsAndSources(): void
|
||||||
|
{
|
||||||
|
$this->writeDivi();
|
||||||
|
|
||||||
|
$code = $this->command([CheckStep::MIRRORS => ['geändert']])->run(['update-reference' => true]);
|
||||||
|
|
||||||
|
self::assertSame(0, $code);
|
||||||
|
self::assertSame(
|
||||||
|
['library-submit-layout', 'tb-api-save', 'tb-load-setting-options', 'tb-store-template', 'vb-sync-flags'],
|
||||||
|
array_keys(DiviReference::expected($this->expectedFile))
|
||||||
|
);
|
||||||
|
self::assertStringStartsWith(
|
||||||
|
'function et_pb_submit_layout(',
|
||||||
|
(string) DiviReference::readSource('library-submit-layout', $this->sourceDir)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Minimal stand-ins for the five mirrored Divi functions at their real paths. */
|
||||||
|
private function writeDivi(): void
|
||||||
|
{
|
||||||
|
$files = [
|
||||||
|
'includes/builder/shortcode-core.php' => "function et_pb_submit_layout( \$args ) {\n\treturn 1;\n}",
|
||||||
|
'includes/builder/frontend-builder/theme-builder/api.php' => "function et_theme_builder_api_save() {\n}",
|
||||||
|
'includes/builder/frontend-builder/theme-builder/theme-builder.php' =>
|
||||||
|
"function et_theme_builder_store_template( \$a, \$b, \$c ) {\n}\n"
|
||||||
|
. "function et_theme_builder_load_template_setting_options( \$ids ) {\n}",
|
||||||
|
'includes/builder-5/server/VisualBuilder/REST/SyncToServer/SyncToServerController.php' =>
|
||||||
|
"class SyncToServerController {\n\tpublic static function update( \$request ) {\n\t}\n}",
|
||||||
|
];
|
||||||
|
foreach ($files as $path => $code) {
|
||||||
|
$file = $this->themeDir . '/' . $path;
|
||||||
|
if (! is_dir(dirname($file))) {
|
||||||
|
mkdir(dirname($file), 0777, true);
|
||||||
|
}
|
||||||
|
file_put_contents($file, "<?php\n" . $code . "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
||||||
|
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\LineDiff;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class LineDiffTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testShowsChangedLinesWithTwoLinesOfContext(): void
|
||||||
|
{
|
||||||
|
$old = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'];
|
||||||
|
$new = ['a', 'b', 'c', 'd', 'E', 'f', 'g', 'h', 'i', 'j'];
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
[' c', ' d', '- e', '+ E', ' f', ' g', ' h', ' i', '+ j'],
|
||||||
|
LineDiff::lines($old, $new)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMarksAGapBetweenDistantChanges(): void
|
||||||
|
{
|
||||||
|
$old = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
|
||||||
|
$new = ['A', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'J'];
|
||||||
|
|
||||||
|
self::assertSame(['- a', '+ A', ' b', ' c', '…', ' h', ' i', '- j', '+ J'], LineDiff::lines($old, $new));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEqualInputHasNoLines(): void
|
||||||
|
{
|
||||||
|
self::assertSame([], LineDiff::lines(['a', 'b'], ['a', 'b']));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user