feat: lit/tb-activate – the explicit switch, always a dry run first (answer F1)

- only _et_enabled; dry run in both directions lists conditions, exclusions,
  layouts, overlaps and possible overlaps (B9); confirm: true switches
- enabling needs conditions and published layouts (B10); a detached
  template needs reattach first; the default template is refused
- the off-switch reaches an attached, enabled template outside publish,
  e.g. in the trash (ruling Q16); result names the fast way back
- every switch in the change log; failed switch rolled back

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-27 16:33:29 +02:00
co-authored by Claude Opus 5.5
parent 0975b2f69b
commit 64fc439cb2
6 changed files with 598 additions and 4 deletions
@@ -202,4 +202,43 @@ final class ThemeBuilderAccessTest extends TestCase
self::assertToolError($hidden, 'not_found');
self::assertSame(self::clientError($missing), self::clientError($hidden));
}
/**
* Ruling Q16: Divi renders an attached, enabled template whatever its post status (B8) – the off-switch
* (offSwitch: true) reaches it in the trash; everything else about it reads like a missing template.
*/
public function testTheOffSwitchReachesALiveTemplateOutsidePublish(): void
{
$trashed = ThemeBuilderFixtures::template(1066, ['enabled' => true, 'status' => 'trash']);
$input = ['template_id' => 1066, 'template_rev' => $trashed->rev()];
LibraryFixtures::given([ThemeBuilderFixtures::templatePost(1066, ['post_status' => 'trash'])]);
$access = ThemeBuilderFixtures::access([], true, ThemeBuilderFixtures::store([$trashed]));
self::assertSame($trashed, $access->changeableTemplate($input, true));
$missing = $access->changeableTemplate($input);
self::assertToolError($missing, 'not_found');
$notLive = [
ThemeBuilderFixtures::access([], true, ThemeBuilderFixtures::store([], 0, [], [$trashed])),
ThemeBuilderFixtures::access([], true, ThemeBuilderFixtures::store([
ThemeBuilderFixtures::template(1066, ['status' => 'trash']),
])),
ThemeBuilderFixtures::access(['theme_builder'], true, ThemeBuilderFixtures::store([$trashed])),
];
foreach ($notLive as $index => $other) {
$refused = $other->changeableTemplate($input, true);
self::assertToolError($refused, 'not_found', (string) $index);
self::assertSame(self::clientError($missing), self::clientError($refused), (string) $index);
}
self::assertToolError(
ThemeBuilderFixtures::access([], false, ThemeBuilderFixtures::store([$trashed]))->changeableTemplate($input, true),
'forbidden'
);
self::assertToolError(
ThemeBuilderFixtures::access([], true, ThemeBuilderFixtures::store([$trashed]))
->changeableTemplate(['template_id' => 1066, 'template_rev' => 'alt'], true),
'rev_mismatch'
);
}
}