404 on click. The fix
// is to skip dynamic self-rendering blocks on BOTH sides, the way TagBlocks
// already skips loop subtrees in post content.
class TagTemplatePartsRoundTripTest extends WP_UnitTestCase
{
public function setUp(): void
{
parent::setUp();
$admin = self::factory()->user->create(['role' => 'administrator']);
wp_set_current_user($admin);
// Clear frames left by a prior test's render (statics persist across the
// process). reset() only touches $frames — not the $ignored list.
// Firing template_redirect here would trip redirect_canonical's headers.
TagTemplateParts::reset();
TagTemplateParts::init();
}
// The core pin: an editable block sitting AFTER a loop ($ignored) must get
// the same id from the render-time tagger and the finder. The loop renders
// its inner heading once per published post, so a one-sided skip would shift
// "after" and the load would resolve the wrong block.
public function test_block_after_ignored_loop_round_trips()
{
self::factory()->post->create(['post_status' => 'publish']);
self::factory()->post->create(['post_status' => 'publish']);
$slug = $this->createPart(
'
before
'
. ''
. ''
. '
inside-loop
'
. ''
. ''
. 'after
'
);
$rendered = $this->renderPart($slug);
$this->assertNull(
$this->idForText($rendered, 'h2', 'inside-loop'),
'a loop subtree inside a template part must not be tagged'
);
$afterId = $this->idForText($rendered, 'h2', 'after');
$this->assertNotNull($afterId, 'the block after the loop must be tagged');
$this->assertSame('after', $this->loadText($slug, $afterId, 'h2'));
}
// The mini-cart shape exactly: a dynamic block with NO parsed inner blocks
// that renders extra blocks at render time. The finder sees one leaf; the
// tagger must skip the injected subtree so the next block's id still lines
// up. Without the fix the injected headings inflate every later id.
public function test_block_after_dynamic_self_rendering_block_round_trips()
{
// WP-latest CI has WooCommerce active, so woocommerce/mini-cart is
// already registered; re-registering trips a doing_it_wrong notice that
// fails the test. Drop it first so the controlled stub render is used.
$registry = WP_Block_Type_Registry::get_instance();
if ($registry->is_registered('woocommerce/mini-cart')) {
unregister_block_type('woocommerce/mini-cart');
}
register_block_type('woocommerce/mini-cart', [
'render_callback' => static function () {
// Real mini-cart wraps its drawer in a div; the tagger stamps the
// block's first tag, so the wrapper (not an inner heading) carries
// the id.
return '' . do_blocks(
'
drawer-a
'
. 'drawer-b
'
) . '';
},
]);
$slug = $this->createPart(
'before
'
. ''
. ''
);
$rendered = $this->renderPart($slug);
$this->assertNull(
$this->idForText($rendered, 'h2', 'drawer-a'),
'a dynamic block render-injected subtree must not be tagged'
);
// core/button stamps its id on the block's first tag — the
//