| 1 |
<?php |
| 2 |
|
| 3 |
namespace Extendify\Tests\Integration\Agent\Controllers; |
| 4 |
|
| 5 |
use Extendify\Agent\Controllers\WPController; |
| 6 |
use Extendify\QuickEdit\Controllers\SaveController; |
| 7 |
use Extendify\Shared\Services\Import\Post; |
| 8 |
use WP_UnitTestCase; |
| 9 |
|
| 10 |
class WPControllerTest extends WP_UnitTestCase |
| 11 |
{ |
| 12 |
public function test_lock_post_prevents_importer_update() |
| 13 |
{ |
| 14 |
$postId = self::factory()->post->create(['post_content' => '<p>original</p>']); |
| 15 |
$post = get_post($postId); |
| 16 |
|
| 17 |
$user = self::factory()->user->create(); |
| 18 |
wp_set_current_user($user); |
| 19 |
|
| 20 |
$request = new \WP_REST_Request(); |
| 21 |
$request->set_param('postId', $postId); |
| 22 |
WPController::lockPost($request); |
| 23 |
|
| 24 |
// Attempt to update as the cron user (no user). |
| 25 |
wp_set_current_user(0); |
| 26 |
$result = Post::update($post, '<p>imported content</p>'); |
| 27 |
|
| 28 |
$this->assertWPError($result); |
| 29 |
$this->assertSame(1005, $result->get_error_code()); |
| 30 |
$this->assertSame('<p>original</p>', get_post($postId)->post_content); |
| 31 |
} |
| 32 |
|
| 33 |
// A template-part block must load by (partSlug, blockId) using the same |
| 34 |
// preorder numbering TagTemplateParts assigns on the front end. Before the |
| 35 |
// template-part branch, getBlockCode read only postId, so a header CTA |
| 36 |
// could be saved but never opened — the click silently no-op'd. |
| 37 |
public function test_get_block_code_loads_template_part_block_by_slug() |
| 38 |
{ |
| 39 |
$this->loginAsAdmin(); |
| 40 |
$slug = $this->createHeaderPart( |
| 41 |
'<!-- wp:paragraph --><p>AAA</p><!-- /wp:paragraph -->' |
| 42 |
. '<!-- wp:group --><div class="wp-block-group">' |
| 43 |
. '<!-- wp:heading --><h2>BBB</h2><!-- /wp:heading -->' |
| 44 |
. '<!-- wp:paragraph --><p>CCC</p><!-- /wp:paragraph -->' |
| 45 |
. '</div><!-- /wp:group -->' |
| 46 |
); |
| 47 |
|
| 48 |
// Preorder: 1=paragraph(AAA) 2=group 3=heading(BBB) 4=paragraph(CCC). |
| 49 |
$req = new \WP_REST_Request(); |
| 50 |
$req->set_param('partSlug', $slug); |
| 51 |
$req->set_param('blockId', 3); |
| 52 |
$res = WPController::getBlockCode($req); |
| 53 |
|
| 54 |
$this->assertSame(200, $res->get_status()); |
| 55 |
$data = $res->get_data(); |
| 56 |
$this->assertSame('core/heading', $data['name']); |
| 57 |
$this->assertStringContainsString('<h2>BBB</h2>', $data['block']); |
| 58 |
} |
| 59 |
|
| 60 |
// Parity: the block load resolves for (slug, N) must be the exact block save |
| 61 |
// mutates for (slug, N). Both share TemplatePartBlockFinder, so an edit aimed |
| 62 |
// at the loaded block lands there and nowhere else — no silent off-by-one |
| 63 |
// between two walks, the central correctness risk of this feature. |
| 64 |
public function test_template_part_load_and_save_resolve_the_same_block() |
| 65 |
{ |
| 66 |
$this->loginAsAdmin(); |
| 67 |
$slug = $this->createHeaderPart( |
| 68 |
'<!-- wp:paragraph --><p>AAA</p><!-- /wp:paragraph -->' |
| 69 |
. '<!-- wp:group --><div class="wp-block-group">' |
| 70 |
. '<!-- wp:heading --><h2>BBB</h2><!-- /wp:heading -->' |
| 71 |
. '<!-- wp:paragraph --><p>CCC</p><!-- /wp:paragraph -->' |
| 72 |
. '</div><!-- /wp:group -->' |
| 73 |
); |
| 74 |
|
| 75 |
$loadReq = new \WP_REST_Request(); |
| 76 |
$loadReq->set_param('partSlug', $slug); |
| 77 |
$loadReq->set_param('blockId', 3); |
| 78 |
$loaded = WPController::getBlockCode($loadReq)->get_data(); |
| 79 |
$this->assertStringContainsString('<h2>BBB</h2>', $loaded['block']); |
| 80 |
|
| 81 |
$saveReq = new \WP_REST_Request('POST', '/extendify/v1/quick-edit/save'); |
| 82 |
$saveReq->set_header('Content-Type', 'application/json'); |
| 83 |
$saveReq->set_body(wp_json_encode([ |
| 84 |
'source' => ['kind' => 'template-part', 'partSlug' => $slug], |
| 85 |
'blockId' => 3, |
| 86 |
'blockType' => 'core/heading', |
| 87 |
'rawBlock' => '<!-- wp:heading --><h2>ZZZ</h2><!-- /wp:heading -->', |
| 88 |
])); |
| 89 |
$saveRes = SaveController::handleSave($saveReq); |
| 90 |
$this->assertSame(200, $saveRes->get_status()); |
| 91 |
|
| 92 |
$template = get_block_template(get_stylesheet() . '//' . $slug, 'wp_template_part'); |
| 93 |
$stored = get_post($template->wp_id)->post_content; |
| 94 |
$this->assertStringContainsString('<h2>ZZZ</h2>', $stored); |
| 95 |
$this->assertStringNotContainsString('<h2>BBB</h2>', $stored); |
| 96 |
$this->assertStringContainsString('<p>AAA</p>', $stored); |
| 97 |
$this->assertStringContainsString('<p>CCC</p>', $stored); |
| 98 |
} |
| 99 |
|
| 100 |
// The post path (postId + TagBlocks top-level numbering) stays unchanged by |
| 101 |
// the new template-part branch. |
| 102 |
public function test_get_block_code_post_path_still_resolves_block() |
| 103 |
{ |
| 104 |
$this->loginAsAdmin(); |
| 105 |
$postId = self::factory()->post->create([ |
| 106 |
'post_content' => '<!-- wp:paragraph --><p>one</p><!-- /wp:paragraph -->' |
| 107 |
. '<!-- wp:heading --><h2>two</h2><!-- /wp:heading -->', |
| 108 |
]); |
| 109 |
|
| 110 |
$req = new \WP_REST_Request(); |
| 111 |
$req->set_param('postId', $postId); |
| 112 |
$req->set_param('blockId', 2); |
| 113 |
$res = WPController::getBlockCode($req); |
| 114 |
|
| 115 |
$this->assertSame(200, $res->get_status()); |
| 116 |
$data = $res->get_data(); |
| 117 |
$this->assertSame('core/heading', $data['name']); |
| 118 |
$this->assertStringContainsString('<h2>two</h2>', $data['block']); |
| 119 |
} |
| 120 |
|
| 121 |
private function loginAsAdmin(): void |
| 122 |
{ |
| 123 |
$admin = self::factory()->user->create(['role' => 'administrator']); |
| 124 |
wp_set_current_user($admin); |
| 125 |
} |
| 126 |
|
| 127 |
private function createHeaderPart(string $content): string |
| 128 |
{ |
| 129 |
$slug = 'qe-header-' . wp_generate_password(6, false); |
| 130 |
$partId = self::factory()->post->create([ |
| 131 |
'post_type' => 'wp_template_part', |
| 132 |
'post_name' => $slug, |
| 133 |
'post_status' => 'publish', |
| 134 |
'post_content' => $content, |
| 135 |
]); |
| 136 |
wp_set_object_terms($partId, get_stylesheet(), 'wp_theme'); |
| 137 |
return $slug; |
| 138 |
} |
| 139 |
} |
| 140 |
|