15A: Scenario suite — the load path, end to end - #28
Open
nikolaystrikhar wants to merge 1 commit into
Open
Conversation
Closed
nikolaystrikhar
force-pushed
the
15A-scenario-load
branch
2 times, most recently
from
August 13, 2026 11:17
61e8c72 to
a88768b
Compare
Six scenarios that bootstrap the library the way a host plugin does -- set the hook prefix, hand over a bare container, register, boot -- and then reach it only through the hooks boot() wired. A request is do_action( 'plugins_loaded' ) and an admin page load is do_action( 'all_admin_notices' ), so what runs is whatever boot() wired, in the order and at the priorities it wired it. Calling the steps directly is the one arrangement that cannot see an admin-only add_action() that never ran, or a step wired into a dispatch window that had already closed. Bootstrap_Test_Case carries the bootstrap, the two stubs and the helpers; the conflict and host scenarios extend the same parent and land beside this file. It is abstract and named _Test_Case so the runner never collects it. Neither request helper catches the redirect. Every plugins_loaded step wraps itself in catch ( Throwable ), so a stubbed redirect is swallowed and reported before it can leave the step -- which means a catch around the dispatch would pass whether or not the request redirected, and the helper asserting a request did *not* redirect would assert nothing at all. Both watch for the halt where the library announces it, and both halves are asserted: that something reported, and that what it reported was this halt rather than some other failure inside the step. The halted helper also empties plugins_loaded before it throws, since exit means the load pass one priority behind does not run either, and puts the hook back afterwards. WithBundledPlugins cleans up on its own @after hook rather than trusting a caller's tearDown: a failed assertion aborts the test where it stands, so a cleanup line at the end of a body is the one that does not run on the day it matters. Callers still clear it from tearDown, and the second call is a no-op. tests/README gains a section for the suite -- what a scenario may call, the four preconditions setUp establishes, and every case in prose with a mermaid diagram of the flow. README links to it; it never had a link at all.
nikolaystrikhar
force-pushed
the
15A-scenario-load
branch
from
August 13, 2026 11:35
a88768b to
c332206
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What: a
tests/unit/Scenario/suite that bootstraps the library the way a host does —Config::set_hook_prefix(),Config::set_container(),Absorber::register(),Absorber::boot()— and then reaches it only through the hooksboot()wired, dispatchingplugins_loadedfor the load pass andall_admin_noticesfor the notices, against a realactive_plugins, real site options and real files on disk; six load-path scenarios, the sharedBootstrap_Test_Case, an@afterfixture cleanup, and the tests/README section every case is written up in.Usage: the shape of a scenario — a bootstrap, a request, and assertions against what WordPress actually holds afterwards.
Why this way:
The hooks are dispatched, not the steps called. A request is
do_action( 'plugins_loaded' ), so what runs is whateverboot()wired, in the order and at the priorities it wired it. That is the half where the bugs are — an admin-onlyadd_action()that never ran, a step wired into a dispatch window that had already closed — and callingLoader::load_all()directly is exactly the arrangement that cannot see any of it. The container is handed over bare rather than pre-registered, for the same reason:boot()running the provider over it is one of the steps under test.Neither request helper catches the redirect, because it cannot. Every
plugins_loadedstep wraps itself incatch ( Throwable )so a hook this library owns can never white-screen a site — which means a stubbed redirect is swallowed and reported before it could leave the step, and acatcharound the dispatch would pass whether or not the request redirected. Both helpers watch for the halt where the library announces it and assert both halves: that something reported, and that what it reported was this halt rather than some other failure inside the step.The halted helper empties
plugins_loadedbefore it throws.exitmeans the load pass one priority behind does not run either, so a scenario asserting nothing loaded after a redirect would otherwise be asserting it about a request production never serves. The hook is put back afterwards, since emptying it is a statement about that request and not about the process.Each scenario writes its own fixture and its own guard constant.
require_oncededupes by resolved path for the lifetime of the process anddefine()lasts just as long, so a shared fixture lets a later scenario pass without loading anything — including if the load logic were deleted outright.WithBundledPluginsnow cleans up on its own@afterhook rather than trusting a caller's tearDown: a failed assertion aborts the test where it stands, so a cleanup line at the end of a body is the one that does not run on the day it matters.