diff --git a/.gitignore b/.gitignore index 24e8192..943ee58 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .env -/vendor \ No newline at end of file +/vendor +.phpunit.result.cache diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7e00744 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,94 @@ +# CLAUDE.md + +Guidance for working in this repository. + +## What this is + +A **starter WordPress plugin** built on the [Roots Acorn](https://roots.io/acorn/) +framework (Laravel components inside WordPress) with **Livewire 4** for reactive +front-end components. It is a scaffold/template designed to **coexist with other +Acorn-based plugins** in the same WordPress install without booting the framework +more than once. + +- `roots/acorn: ^6.0`, `livewire/livewire: ^4.1` +- Requires PHP 8.4, WordPress 5.2+ +- PSR-4: `AcornPlugin\` → `app/`, `AcornPlugin\Tests\` → `tests/` +- Acorn is bundled (in `vendor/`); the plugin `wp_die`s if `Roots\Acorn\Application` + is somehow absent. + +## The single-Acorn contract (read this before touching `bootstrap.php`) + +PHP classes and the Acorn container are **global to a request**. If two Acorn +plugins each call `Application::configure()->boot()`, the second clobbers the +first. This plugin avoids that with a cooperative **boot election** that every +Acorn plugin in the install must follow: + +1. **One booter per request, elected via a shared action.** On `after_setup_theme` + priority 0, a plugin boots Acorn only if `did_action('acorn/booted')` is false, + then fires `do_action('acorn/booted')`; otherwise it stands down. This lives in + `app/Acorn/BootCoordinator.php` (`BootCoordinator::BOOTED_ACTION = 'acorn/booted'`). + `did_action()` is used deliberately — it is a **read-only** probe, unlike + `Application::getInstance()`, which force-creates and installs an empty container. +2. **Providers are auto-discovered, never hand-registered.** Each plugin declares + its provider in `composer.json` under `extra.acorn.providers`. Whichever plugin + wins the election scans **all** active plugins' manifests and registers every + provider — so a plugin's provider boots even when a *sibling* booted Acorn. +3. **Livewire assets are emitted once, by the boot owner.** Only the elected booter + calls `LivewireAssets::registerOnce()` (`@livewireStyles` on `wp_head`, + `@livewireScripts` on `wp_footer`). Livewire 4 self-initialises — never add + `Livewire.start()` or a hand-rolled ESM ` - -@livewireStyles diff --git a/tests/Acorn/BootCoordinatorTest.php b/tests/Acorn/BootCoordinatorTest.php new file mode 100644 index 0000000..a261243 --- /dev/null +++ b/tests/Acorn/BootCoordinatorTest.php @@ -0,0 +1,50 @@ +justReturn(0); + + $result = (new BootCoordinator())->shouldBoot(); + + $this->assertTrue($result); + } + + public function test_should_boot_returns_false_when_action_already_fired(): void + { + Functions\expect('did_action') + ->once() + ->with(BootCoordinator::BOOTED_ACTION) + ->andReturn(1); + + $result = (new BootCoordinator())->shouldBoot(); + + $this->assertFalse($result); + } + + // ----------------------------------------------------------------------- + // markBooted() + // ----------------------------------------------------------------------- + + public function test_mark_booted_fires_the_booted_action_once(): void + { + Functions\expect('do_action') + ->once() + ->with(BootCoordinator::BOOTED_ACTION); + + (new BootCoordinator())->markBooted(); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..b004a3f --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,28 @@ +