From f0ab76ba9a27e844e0460cb273b33341363d6fd4 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 15 May 2026 16:25:39 +0800 Subject: [PATCH 1/5] Abilites API: Block Post Helper A class to insert, update and delete a block from an existing WordPress Post --- .../class-convertkit-block-post-helper.php | 301 ++++++++++++++++++ wp-convertkit.php | 1 + 2 files changed, 302 insertions(+) create mode 100644 includes/blocks/helpers/class-convertkit-block-post-helper.php diff --git a/includes/blocks/helpers/class-convertkit-block-post-helper.php b/includes/blocks/helpers/class-convertkit-block-post-helper.php new file mode 100644 index 000000000..9f7426825 --- /dev/null +++ b/includes/blocks/helpers/class-convertkit-block-post-helper.php @@ -0,0 +1,301 @@ +post_content ); + $found = array(); + + $occurrence_index = 0; + + foreach ( $blocks as $index => $block ) { + if ( ! isset( $block['blockName'] ) || $block['blockName'] !== $block_name ) { + continue; + } + + $found[] = array( + 'index' => (int) $index, + 'occurrence_index' => (int) $occurrence_index, + 'attrs' => isset( $block['attrs'] ) ? (array) $block['attrs'] : array(), + ); + + ++$occurrence_index; + } + + // If no blocks found, return false. + if ( empty( $found ) ) { + return false; + } + + return $found; + + } + + /** + * Inserts a new block into the Post's content at the specified position. + * + * @since 3.4.0 + * + * @param int $post_id Post ID. + * @param string $block_name Programmatic Block Name. + * @param array $attrs Block Attributes. + * @param string $position One of 'prepend', 'append', 'index'. + * @param int $index Zero-based top-level block index; only used when $position is 'index'. + * @return int|WP_Error + */ + public static function insert( $post_id, $block_name, $attrs, $position = 'append', $index = 0 ) { + + // Get Post. + $post = get_post( $post_id ); + if ( ! $post ) { + return new WP_Error( + 'convertkit_block_post_helper_insert_block_post_not_found', + /* translators: %d: Post ID */ + sprintf( __( 'No Post exists with ID %d.', 'convertkit' ), $post_id ) + ); + } + + // Parse blocks. + $blocks = parse_blocks( $post->post_content ); + + // Build the new block to insert. + $new_block = array( + 'blockName' => $block_name, + 'attrs' => (array) $attrs, + 'innerBlocks' => array(), + 'innerHTML' => '', + 'innerContent' => array(), + ); + + // Resolve $position into a concrete zero-based splice point in the + // top-level block array. + switch ( $position ) { + case 'prepend': + $insert_at = 0; + break; + + case 'index': + $insert_at = max( 0, min( (int) $index, count( $blocks ) ) ); + break; + + case 'append': + default: + $insert_at = count( $blocks ); + break; + } + + // Splice in the new block. + array_splice( $blocks, $insert_at, 0, array( $new_block ) ); + + // Update Post. + $result = wp_update_post( + array( + 'ID' => $post_id, + 'post_content' => serialize_blocks( $blocks ), + ), + true + ); + + // Bail if the update failed. + if ( is_wp_error( $result ) ) { + return $result; + } + + // Return the index the block was inserted at. + return array( + 'post_id' => $post_id, + 'index' => $insert_at, + ); + + } + + /** + * Updates the attributes of an existing block in the Post's content. + * + * @since 3.4.0 + * + * @param int $post_id Post ID. + * @param string $block_name Programmatic Block Name. + * @param int $occurrence_index Position to update block. + * @param array $attrs Block Attributes. + * @return int|WP_Error + */ + public static function update( $post_id, $block_name, $occurrence_index, $attrs ) { + + // Get Post. + $post = get_post( $post_id ); + if ( ! $post ) { + return new WP_Error( + 'convertkit_block_post_helper_update_block_post_not_found', + /* translators: %d: post ID */ + sprintf( __( 'No Post exists with ID %d.', 'convertkit' ), $post_id ) + ); + } + + // Parse blocks. + $blocks = parse_blocks( $post->post_content ); + $update_at = 0; + $block_index = 0; + $matched = false; + + foreach ( $blocks as $key => $block ) { + ++$update_at; + + // Skip if the block name does not match. + if ( ! isset( $block['blockName'] ) || $block['blockName'] !== $block_name ) { + continue; + } + + // Update the block if the occurrence index matches. + if ( $block_index === (int) $occurrence_index ) { + $blocks[ $key ]['attrs'] = array_merge( (array) $block['attrs'], (array) $attrs ); + $matched = true; + break; + } + + ++$block_index; + } + + // Bail if the block was not found. + if ( ! $matched ) { + return new WP_Error( + 'convertkit_block_post_helper_occurrence_not_found', + /* translators: 1: block name, 2: occurrence index, 3: post ID */ + sprintf( __( 'No occurrence #%2$d of block %1$s found in post %3$d.', 'convertkit' ), $block_name, (int) $occurrence_index, $post_id ) + ); + } + + // Update Post. + $result = wp_update_post( + array( + 'ID' => $post_id, + 'post_content' => serialize_blocks( $blocks ), + ), + true + ); + + // Bail if the update failed. + if ( is_wp_error( $result ) ) { + return $result; + } + + // Return the index the block was updated at. + return array( + 'post_id' => $post_id, + 'index' => ( $update_at - 1 ), + ); + + } + + /** + * Deletes a specific block from the Post's content. + * + * @since 3.4.0 + * + * @param int $post_id Post ID. + * @param string $block_name Programmatic Block Name. + * @param int $occurrence_index Zero-based index among this block's occurrences in the post. + * @return int|WP_Error + */ + public static function delete( $post_id, $block_name, $occurrence_index ) { + + // Get Post. + $post = get_post( $post_id ); + if ( ! $post ) { + return new WP_Error( + 'convertkit_block_post_helper_update_block_post_not_found', + /* translators: %d: post ID */ + sprintf( __( 'No Post exists with ID %d.', 'convertkit' ), $post_id ) + ); + } + + // Parse blocks. + $blocks = parse_blocks( $post->post_content ); + $delete_at = 0; + $block_index = 0; + $matched = false; + + foreach ( $blocks as $key => $block ) { + ++$delete_at; + + // Skip if the block name does not match. + if ( ! isset( $block['blockName'] ) || $block['blockName'] !== $block_name ) { + continue; + } + + // Update the block if the occurrence index matches. + if ( $block_index === (int) $occurrence_index ) { + unset( $blocks[ $key ] ); + $blocks = array_values( $blocks ); + $matched = true; + break; + } + + ++$block_index; + } + + // Bail if the block was not found. + if ( ! $matched ) { + return new WP_Error( + 'convertkit_block_post_helper_occurrence_not_found', + /* translators: 1: block name, 2: occurrence index, 3: post ID */ + sprintf( __( 'No occurrence #%2$d of block %1$s found in post %3$d.', 'convertkit' ), $block_name, (int) $occurrence_index, $post_id ) + ); + } + + // Update Post. + $result = wp_update_post( + array( + 'ID' => $post_id, + 'post_content' => serialize_blocks( $blocks ), + ), + true + ); + + // Bail if the update failed. + if ( is_wp_error( $result ) ) { + return $result; + } + + // Return the index the block was deleted from. + return array( + 'post_id' => $post_id, + 'index' => ( $delete_at - 1 ), + ); + + } + +} diff --git a/wp-convertkit.php b/wp-convertkit.php index 0e5f7187f..55641a47a 100644 --- a/wp-convertkit.php +++ b/wp-convertkit.php @@ -107,6 +107,7 @@ require_once CONVERTKIT_PLUGIN_PATH . '/includes/blocks/class-convertkit-block-form-builder-field-name.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/blocks/class-convertkit-block-form-builder-field-custom.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/blocks/class-convertkit-block-product.php'; +require_once CONVERTKIT_PLUGIN_PATH . '/includes/blocks/helpers/class-convertkit-block-post-helper.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/block-formatters/class-convertkit-block-formatter.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/block-formatters/class-convertkit-block-formatter-form-link.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/block-formatters/class-convertkit-block-formatter-product-link.php'; From 246fe7cbdb3d2c12c9b1297ce7c246394ade0283 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 15 May 2026 16:25:43 +0800 Subject: [PATCH 2/5] Started tests --- tests/Integration/BlockPostHelperTest.php | 341 ++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 tests/Integration/BlockPostHelperTest.php diff --git a/tests/Integration/BlockPostHelperTest.php b/tests/Integration/BlockPostHelperTest.php new file mode 100644 index 000000000..46da650f6 --- /dev/null +++ b/tests/Integration/BlockPostHelperTest.php @@ -0,0 +1,341 @@ +postID = $this->createPost(); + } + + /** + * Performs actions after each test. + * + * @since 3.4.0 + */ + public function tearDown(): void + { + // Deactivate Plugin. + deactivate_plugins('convertkit/wp-convertkit.php'); + + parent::tearDown(); + } + + /** + * Test that the find() method returns the correct block indicies and attributes. + * + * @since 3.4.0 + */ + public function testFind() + { + // Find the block. + $blocks = \ConvertKit_Block_Post_Helper::find( $this->postID, 'convertkit/form' ); + $this->assertIsArray( $blocks ); + $this->assertCount( 2, $blocks ); + + // Assert first matching block indicies and attributes are correct. + $this->assertEquals( $this->formBlockIndices[0], $blocks[0]['index'] ); + $this->assertEquals( 0, $blocks[0]['occurrence_index'] ); + $this->assertEquals( $_ENV['CONVERTKIT_API_FORM_ID'], $blocks[0]['attrs']['form'] ); + + // Assert second matching block indicies and attributes are correct. + $this->assertEquals( $this->formBlockIndices[1], $blocks[1]['index'] ); + $this->assertEquals( 1, $blocks[1]['occurrence_index'] ); + $this->assertEquals( $_ENV['CONVERTKIT_API_FORM_ID'], $blocks[1]['attrs']['form'] ); + } + + public function testFindWhenNoBlocksMatch() + { + $this->assertFalse(\ConvertKit_Block_Post_Helper::find( $this->postID, 'fake/block' )); + } + + public function testFindWhenPostDoesNotExist() + { + $this->assertInstanceOf(\WP_Error::class, \ConvertKit_Block_Post_Helper::find( 999999, 'convertkit/form' )); + } + + public function testInsertPrepend() + { + $result = \ConvertKit_Block_Post_Helper::insert( + post_id: $this->postID, + block_name: 'convertkit/form', + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ], + position: 'prepend' + ); + + $this->assertIsArray( $result ); + $this->assertEquals( $this->postID, $result['post_id'] ); + $this->assertEquals( 0, $result['index'] ); + } + + public function testInsertAppend() + { + $result = \ConvertKit_Block_Post_Helper::insert( + post_id: $this->postID, + block_name: 'convertkit/form', + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ], + position: 'append' + ); + + $this->assertIsArray( $result ); + $this->assertEquals( $this->postID, $result['post_id'] ); + $this->assertEquals( $this->totalBlocks + 1, $result['index'] ); + } + + public function testInsertIndex() + { + $result = \ConvertKit_Block_Post_Helper::insert( + post_id: $this->postID, + block_name: 'convertkit/form', + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ], + position: 'index', + index: 1 + ); + + $this->assertIsArray( $result ); + $this->assertEquals( $this->postID, $result['post_id'] ); + $this->assertEquals( 1, $result['index'] ); + } + + public function testInsertIndexOutOfBounds() + { + $result = \ConvertKit_Block_Post_Helper::insert( + post_id: $this->postID, + block_name: 'convertkit/form', + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ], + position: 'index', + index: 100 + ); + + $this->assertIsArray( $result ); + $this->assertEquals( $this->postID, $result['post_id'] ); + $this->assertEquals( $this->totalBlocks + 1, $result['index'] ); + } + + public function testInsertIndexNegative() + { + $result = \ConvertKit_Block_Post_Helper::insert( + post_id: $this->postID, + block_name: 'convertkit/form', + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ], + position: 'index', + index: -1 + ); + + $this->assertIsArray( $result ); + $this->assertEquals( $this->postID, $result['post_id'] ); + $this->assertEquals( 0, $result['index'] ); + } + + public function testInsertWhenPostDoesNotExist() + { + $result = \ConvertKit_Block_Post_Helper::insert( + post_id: 999999, + block_name: 'convertkit/form', + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ], + position: 'index', + index: 0 + ); + $this->assertInstanceOf(\WP_Error::class, $result ); + } + + public function testUpdate() + { + $result = \ConvertKit_Block_Post_Helper::update( + post_id: $this->postID, + block_name: 'convertkit/form', + occurrence_index: 0, + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ] + ); + + $this->assertIsArray( $result ); + $this->assertEquals( $this->postID, $result['post_id'] ); + $this->assertEquals( $this->formBlockIndices[0], $result['index'] ); + + $result = \ConvertKit_Block_Post_Helper::update( + post_id: $this->postID, + block_name: 'convertkit/form', + occurrence_index: 1, + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ] + ); + + $this->assertIsArray( $result ); + $this->assertEquals( $this->postID, $result['post_id'] ); + $this->assertEquals( $this->formBlockIndices[1], $result['index'] ); + } + + public function testUpdateWhenOccurrenceIndexIsOutOfBounds() + { + $result = \ConvertKit_Block_Post_Helper::update( + post_id: $this->postID, + block_name: 'convertkit/form', + occurrence_index: 999, + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ] + ); + $this->assertInstanceOf(\WP_Error::class, $result ); + } + + public function testUpdateWhenPostDoesNotExist() + { + $result = \ConvertKit_Block_Post_Helper::update( + post_id: 999999, + block_name: 'convertkit/form', + occurrence_index: 0, + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ] + ); + $this->assertInstanceOf(\WP_Error::class, $result ); + } + + public function testDelete() + { + $result = \ConvertKit_Block_Post_Helper::delete( + post_id: $this->postID, + block_name: 'convertkit/form', + occurrence_index: 0 + ); + $this->assertIsArray( $result ); + $this->assertEquals( $this->postID, $result['post_id'] ); + $this->assertEquals( $this->formBlockIndices[0], $result['index'] ); + + $result = \ConvertKit_Block_Post_Helper::delete( + post_id: $this->postID, + block_name: 'convertkit/form', + occurrence_index: 1 + ); + $this->assertIsArray( $result ); + $this->assertEquals( $this->postID, $result['post_id'] ); + $this->assertEquals( $this->formBlockIndices[1], $result['index'] ); + + } + + /** + * Mocks a post for testing. + * + * @since 3.4.0 + * @return int + */ + private function createPost() + { + // Create a Post with the given block. + return $this->factory->post->create( + [ + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_title' => 'Block Post', + 'post_content' => ' +

Item #1

+ + + +

Item #1

+ + + +

Item #2: Adhaésionés altéram improbis mi pariendarum sit stulti triarium

+ + + +
Image #1
+ + + +

Item #2

+ + + + + +

Item #3

+ + + +
Image #2
+ + + + + +

Item #1

+ + + +

Item #4

+ + + +

Item #1

+ + + +

Item #5

+ + + +

Item #2

+ + + +

Item #2

+', + ] + ); + } +} From 4dab46658ebd5b8457832cd24807e0d95b4dc4a8 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 15 May 2026 16:44:47 +0800 Subject: [PATCH 3/5] Completed tests --- tests/Integration/BlockPostHelperTest.php | 561 +++++++++++++--------- 1 file changed, 327 insertions(+), 234 deletions(-) diff --git a/tests/Integration/BlockPostHelperTest.php b/tests/Integration/BlockPostHelperTest.php index 46da650f6..ac3e0ef9a 100644 --- a/tests/Integration/BlockPostHelperTest.php +++ b/tests/Integration/BlockPostHelperTest.php @@ -18,37 +18,37 @@ class BlockPostHelperTest extends WPTestCase */ protected $tester; - /** - * Holds the ConvertKit Block Post Helper class. - * - * @since 3.4.0 - * - * @var ConvertKit_Block_Post_Helper - */ - private $block_post_helper; - - /** - * Holds the Post ID. - * - * @since 3.4.0 - * - * @var int - */ - private $postID; - - /** - * Holds the indicides of the existing Form blocks in the Post. - * - * @since 3.4.0 - * - * @var array - */ - private $formBlockIndices = [ - 10, - 16, - ]; - - private $totalBlocks = 28; + /** + * Holds the ConvertKit Block Post Helper class. + * + * @since 3.4.0 + * + * @var ConvertKit_Block_Post_Helper + */ + private $block_post_helper; + + /** + * Holds the Post ID. + * + * @since 3.4.0 + * + * @var int + */ + private $postID; + + /** + * Holds the indicides of the existing Form blocks in the Post. + * + * @since 3.4.0 + * + * @var array + */ + private $formBlockIndices = [ + 10, + 16, + ]; + + private $totalBlocks = 28; /** * Performs actions before each test. @@ -62,8 +62,8 @@ public function setUp(): void // Activate Plugin. activate_plugins('convertkit/wp-convertkit.php'); - // Create Post. - $this->postID = $this->createPost(); + // Create Post. + $this->postID = $this->createPost(); } /** @@ -79,204 +79,297 @@ public function tearDown(): void parent::tearDown(); } - /** - * Test that the find() method returns the correct block indicies and attributes. - * - * @since 3.4.0 - */ - public function testFind() - { - // Find the block. - $blocks = \ConvertKit_Block_Post_Helper::find( $this->postID, 'convertkit/form' ); - $this->assertIsArray( $blocks ); - $this->assertCount( 2, $blocks ); - - // Assert first matching block indicies and attributes are correct. - $this->assertEquals( $this->formBlockIndices[0], $blocks[0]['index'] ); - $this->assertEquals( 0, $blocks[0]['occurrence_index'] ); - $this->assertEquals( $_ENV['CONVERTKIT_API_FORM_ID'], $blocks[0]['attrs']['form'] ); - - // Assert second matching block indicies and attributes are correct. - $this->assertEquals( $this->formBlockIndices[1], $blocks[1]['index'] ); - $this->assertEquals( 1, $blocks[1]['occurrence_index'] ); - $this->assertEquals( $_ENV['CONVERTKIT_API_FORM_ID'], $blocks[1]['attrs']['form'] ); - } - - public function testFindWhenNoBlocksMatch() - { - $this->assertFalse(\ConvertKit_Block_Post_Helper::find( $this->postID, 'fake/block' )); - } - - public function testFindWhenPostDoesNotExist() - { - $this->assertInstanceOf(\WP_Error::class, \ConvertKit_Block_Post_Helper::find( 999999, 'convertkit/form' )); - } - - public function testInsertPrepend() - { - $result = \ConvertKit_Block_Post_Helper::insert( - post_id: $this->postID, - block_name: 'convertkit/form', - attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ], - position: 'prepend' - ); - - $this->assertIsArray( $result ); - $this->assertEquals( $this->postID, $result['post_id'] ); - $this->assertEquals( 0, $result['index'] ); - } - - public function testInsertAppend() - { - $result = \ConvertKit_Block_Post_Helper::insert( - post_id: $this->postID, - block_name: 'convertkit/form', - attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ], - position: 'append' - ); - - $this->assertIsArray( $result ); - $this->assertEquals( $this->postID, $result['post_id'] ); - $this->assertEquals( $this->totalBlocks + 1, $result['index'] ); - } - - public function testInsertIndex() - { - $result = \ConvertKit_Block_Post_Helper::insert( - post_id: $this->postID, - block_name: 'convertkit/form', - attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ], - position: 'index', - index: 1 - ); - - $this->assertIsArray( $result ); - $this->assertEquals( $this->postID, $result['post_id'] ); - $this->assertEquals( 1, $result['index'] ); - } - - public function testInsertIndexOutOfBounds() - { - $result = \ConvertKit_Block_Post_Helper::insert( - post_id: $this->postID, - block_name: 'convertkit/form', - attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ], - position: 'index', - index: 100 - ); - - $this->assertIsArray( $result ); - $this->assertEquals( $this->postID, $result['post_id'] ); - $this->assertEquals( $this->totalBlocks + 1, $result['index'] ); - } - - public function testInsertIndexNegative() - { - $result = \ConvertKit_Block_Post_Helper::insert( - post_id: $this->postID, - block_name: 'convertkit/form', - attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ], - position: 'index', - index: -1 - ); - - $this->assertIsArray( $result ); - $this->assertEquals( $this->postID, $result['post_id'] ); - $this->assertEquals( 0, $result['index'] ); - } - - public function testInsertWhenPostDoesNotExist() - { - $result = \ConvertKit_Block_Post_Helper::insert( - post_id: 999999, - block_name: 'convertkit/form', - attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ], - position: 'index', - index: 0 - ); - $this->assertInstanceOf(\WP_Error::class, $result ); - } - - public function testUpdate() - { - $result = \ConvertKit_Block_Post_Helper::update( - post_id: $this->postID, - block_name: 'convertkit/form', - occurrence_index: 0, - attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ] - ); - - $this->assertIsArray( $result ); - $this->assertEquals( $this->postID, $result['post_id'] ); - $this->assertEquals( $this->formBlockIndices[0], $result['index'] ); - - $result = \ConvertKit_Block_Post_Helper::update( - post_id: $this->postID, - block_name: 'convertkit/form', - occurrence_index: 1, - attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ] - ); - - $this->assertIsArray( $result ); - $this->assertEquals( $this->postID, $result['post_id'] ); - $this->assertEquals( $this->formBlockIndices[1], $result['index'] ); - } - - public function testUpdateWhenOccurrenceIndexIsOutOfBounds() - { - $result = \ConvertKit_Block_Post_Helper::update( - post_id: $this->postID, - block_name: 'convertkit/form', - occurrence_index: 999, - attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ] - ); - $this->assertInstanceOf(\WP_Error::class, $result ); - } - - public function testUpdateWhenPostDoesNotExist() - { - $result = \ConvertKit_Block_Post_Helper::update( - post_id: 999999, - block_name: 'convertkit/form', - occurrence_index: 0, - attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ] - ); - $this->assertInstanceOf(\WP_Error::class, $result ); - } - - public function testDelete() - { - $result = \ConvertKit_Block_Post_Helper::delete( - post_id: $this->postID, - block_name: 'convertkit/form', - occurrence_index: 0 - ); - $this->assertIsArray( $result ); - $this->assertEquals( $this->postID, $result['post_id'] ); - $this->assertEquals( $this->formBlockIndices[0], $result['index'] ); - - $result = \ConvertKit_Block_Post_Helper::delete( - post_id: $this->postID, - block_name: 'convertkit/form', - occurrence_index: 1 - ); - $this->assertIsArray( $result ); - $this->assertEquals( $this->postID, $result['post_id'] ); - $this->assertEquals( $this->formBlockIndices[1], $result['index'] ); - - } - - /** - * Mocks a post for testing. - * - * @since 3.4.0 - * @return int - */ - private function createPost() - { - // Create a Post with the given block. - return $this->factory->post->create( - [ + /** + * Test that the find() method returns the correct block indicies and attributes. + * + * @since 3.4.0 + */ + public function testFind() + { + // Find the block. + $blocks = \ConvertKit_Block_Post_Helper::find( $this->postID, 'convertkit/form' ); + $this->assertIsArray( $blocks ); + $this->assertCount( 2, $blocks ); + + // Assert first matching block indicies and attributes are correct. + $this->assertEquals( $this->formBlockIndices[0], $blocks[0]['index'] ); + $this->assertEquals( 0, $blocks[0]['occurrence_index'] ); + $this->assertEquals( $_ENV['CONVERTKIT_API_FORM_ID'], $blocks[0]['attrs']['form'] ); + + // Assert second matching block indicies and attributes are correct. + $this->assertEquals( $this->formBlockIndices[1], $blocks[1]['index'] ); + $this->assertEquals( 1, $blocks[1]['occurrence_index'] ); + $this->assertEquals( $_ENV['CONVERTKIT_API_FORM_ID'], $blocks[1]['attrs']['form'] ); + } + + /** + * Test that the find() method returns false when no blocks match the given block name. + * + * @since 3.4.0 + */ + public function testFindWhenNoBlocksMatch() + { + $this->assertFalse(\ConvertKit_Block_Post_Helper::find( $this->postID, 'fake/block' )); + } + + /** + * Test that the find() method returns a WP_Error when the post does not exist. + * + * @since 3.4.0 + */ + public function testFindWhenPostDoesNotExist() + { + $this->assertInstanceOf(\WP_Error::class, \ConvertKit_Block_Post_Helper::find( 999999, 'convertkit/form' )); + } + + /** + * Test that the insert() method inserts a new block at the beginning of the content + * when the position is set to prepend. + * + * @since 3.4.0 + */ + public function testInsertPrepend() + { + $result = \ConvertKit_Block_Post_Helper::insert( + post_id: $this->postID, + block_name: 'convertkit/form', + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ], + position: 'prepend' + ); + + $this->assertIsArray( $result ); + $this->assertEquals( $this->postID, $result['post_id'] ); + $this->assertEquals( 0, $result['index'] ); + } + + /** + * Test that the insert() method inserts a new block at the end of the content + * when the position is set to append. + * + * @since 3.4.0 + */ + public function testInsertAppend() + { + $result = \ConvertKit_Block_Post_Helper::insert( + post_id: $this->postID, + block_name: 'convertkit/form', + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ], + position: 'append' + ); + + $this->assertIsArray( $result ); + $this->assertEquals( $this->postID, $result['post_id'] ); + $this->assertEquals( $this->totalBlocks + 1, $result['index'] ); + } + + /** + * Test that the insert() method inserts a new block at the specified index position. + * + * @since 3.4.0 + */ + public function testInsertIndex() + { + $result = \ConvertKit_Block_Post_Helper::insert( + post_id: $this->postID, + block_name: 'convertkit/form', + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ], + position: 'index', + index: 1 + ); + + $this->assertIsArray( $result ); + $this->assertEquals( $this->postID, $result['post_id'] ); + $this->assertEquals( 1, $result['index'] ); + } + + /** + * Test that the insert() method inserts a new block at end of the content when + * the index is out of bounds. + * + * @since 3.4.0 + */ + public function testInsertIndexOutOfBounds() + { + $result = \ConvertKit_Block_Post_Helper::insert( + post_id: $this->postID, + block_name: 'convertkit/form', + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ], + position: 'index', + index: 100 + ); + + $this->assertIsArray( $result ); + $this->assertEquals( $this->postID, $result['post_id'] ); + $this->assertEquals( $this->totalBlocks + 1, $result['index'] ); + } + + /** + * Test that the insert() method inserts a new block at the beginning of the content when + * the index is negative. + * + * @since 3.4.0 + */ + public function testInsertIndexNegative() + { + $result = \ConvertKit_Block_Post_Helper::insert( + post_id: $this->postID, + block_name: 'convertkit/form', + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ], + position: 'index', + index: -1 + ); + + $this->assertIsArray( $result ); + $this->assertEquals( $this->postID, $result['post_id'] ); + $this->assertEquals( 0, $result['index'] ); + } + + /** + * Test that the insert() method returns a WP_Error when the post does not exist. + * + * @since 3.4.0 + */ + public function testInsertWhenPostDoesNotExist() + { + $result = \ConvertKit_Block_Post_Helper::insert( + post_id: 999999, + block_name: 'convertkit/form', + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ], + position: 'index', + index: 0 + ); + $this->assertInstanceOf(\WP_Error::class, $result ); + } + + /** + * Test that the update() method updates the attributes of an existing block. + * + * @since 3.4.0 + */ + public function testUpdate() + { + $result = \ConvertKit_Block_Post_Helper::update( + post_id: $this->postID, + block_name: 'convertkit/form', + occurrence_index: 0, + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ] + ); + + $this->assertIsArray( $result ); + $this->assertEquals( $this->postID, $result['post_id'] ); + $this->assertEquals( $this->formBlockIndices[0], $result['index'] ); + + $result = \ConvertKit_Block_Post_Helper::update( + post_id: $this->postID, + block_name: 'convertkit/form', + occurrence_index: 1, + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ] + ); + + $this->assertIsArray( $result ); + $this->assertEquals( $this->postID, $result['post_id'] ); + $this->assertEquals( $this->formBlockIndices[1], $result['index'] ); + } + + /** + * Test that the update() method returns a WP_Error when the occurrence index is out of bounds. + * + * @since 3.4.0 + */ + public function testUpdateWhenOccurrenceIndexIsOutOfBounds() + { + $result = \ConvertKit_Block_Post_Helper::update( + post_id: $this->postID, + block_name: 'convertkit/form', + occurrence_index: 999, + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ] + ); + $this->assertInstanceOf(\WP_Error::class, $result ); + } + + /** + * Test that the update() method returns a WP_Error when the post does not exist. + * + * @since 3.4.0 + */ + public function testUpdateWhenPostDoesNotExist() + { + $result = \ConvertKit_Block_Post_Helper::update( + post_id: 999999, + block_name: 'convertkit/form', + occurrence_index: 0, + attrs: [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'] ] + ); + $this->assertInstanceOf(\WP_Error::class, $result ); + } + + /** + * Test that the delete() method deletes an existing block. + * + * @since 3.4.0 + */ + public function testDelete() + { + $result = \ConvertKit_Block_Post_Helper::delete( + post_id: $this->postID, + block_name: 'convertkit/form', + occurrence_index: 1 + ); + $this->assertIsArray( $result ); + $this->assertEquals( $this->postID, $result['post_id'] ); + $this->assertEquals( $this->formBlockIndices[1], $result['index'] ); + + $result = \ConvertKit_Block_Post_Helper::delete( + post_id: $this->postID, + block_name: 'convertkit/form', + occurrence_index: 0 + ); + $this->assertIsArray( $result ); + $this->assertEquals( $this->postID, $result['post_id'] ); + $this->assertEquals( $this->formBlockIndices[0], $result['index'] ); + } + + /** + * Test that the delete() method returns a WP_Error when the occurrence index is out of bounds. + * + * @since 3.4.0 + */ + public function testDeleteWhenOccurrenceIndexIsOutOfBounds() + { + $result = \ConvertKit_Block_Post_Helper::delete( + post_id: $this->postID, + block_name: 'convertkit/form', + occurrence_index: 999 + ); + $this->assertInstanceOf(\WP_Error::class, $result ); + } + + /** + * Test that the delete() method returns a WP_Error when the post does not exist. + * + * @since 3.4.0 + */ + public function testDeleteWhenPostDoesNotExist() + { + $result = \ConvertKit_Block_Post_Helper::delete( + post_id: 999999, + block_name: 'convertkit/form', + occurrence_index: 0 + ); + $this->assertInstanceOf(\WP_Error::class, $result ); + } + + /** + * Mocks a post for testing. + * + * @since 3.4.0 + * @return int + */ + private function createPost() + { + // Create a Post with the given block. + return $this->factory->post->create( + [ 'post_type' => 'page', 'post_status' => 'publish', 'post_title' => 'Block Post', @@ -335,7 +428,7 @@ private function createPost()

Item #2

', - ] - ); - } + ] + ); + } } From a8a442b88e326992b6f866a2e7ff0962c94992eb Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 15 May 2026 16:59:35 +0800 Subject: [PATCH 4/5] PHPStan compat. --- .../helpers/class-convertkit-block-post-helper.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/includes/blocks/helpers/class-convertkit-block-post-helper.php b/includes/blocks/helpers/class-convertkit-block-post-helper.php index 9f7426825..3fcaaab40 100644 --- a/includes/blocks/helpers/class-convertkit-block-post-helper.php +++ b/includes/blocks/helpers/class-convertkit-block-post-helper.php @@ -36,8 +36,8 @@ public static function find( $post_id, $block_name ) { } // Parse blocks. - $blocks = parse_blocks( $post->post_content ); - $found = array(); + $blocks = parse_blocks( $post->post_content ); + $found = array(); $occurrence_index = 0; @@ -49,7 +49,7 @@ public static function find( $post_id, $block_name ) { $found[] = array( 'index' => (int) $index, 'occurrence_index' => (int) $occurrence_index, - 'attrs' => isset( $block['attrs'] ) ? (array) $block['attrs'] : array(), + 'attrs' => $block['attrs'], ); ++$occurrence_index; @@ -74,7 +74,7 @@ public static function find( $post_id, $block_name ) { * @param array $attrs Block Attributes. * @param string $position One of 'prepend', 'append', 'index'. * @param int $index Zero-based top-level block index; only used when $position is 'index'. - * @return int|WP_Error + * @return WP_Error|array */ public static function insert( $post_id, $block_name, $attrs, $position = 'append', $index = 0 ) { @@ -151,7 +151,7 @@ public static function insert( $post_id, $block_name, $attrs, $position = 'appen * @param string $block_name Programmatic Block Name. * @param int $occurrence_index Position to update block. * @param array $attrs Block Attributes. - * @return int|WP_Error + * @return WP_Error|array */ public static function update( $post_id, $block_name, $occurrence_index, $attrs ) { @@ -228,7 +228,7 @@ public static function update( $post_id, $block_name, $occurrence_index, $attrs * @param int $post_id Post ID. * @param string $block_name Programmatic Block Name. * @param int $occurrence_index Zero-based index among this block's occurrences in the post. - * @return int|WP_Error + * @return WP_Error|array */ public static function delete( $post_id, $block_name, $occurrence_index ) { From e558396818b96797841181c7008c69dc2ebb2aa9 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 15 May 2026 18:00:51 +0800 Subject: [PATCH 5/5] Coding standards --- tests/Integration/BlockPostHelperTest.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/Integration/BlockPostHelperTest.php b/tests/Integration/BlockPostHelperTest.php index ac3e0ef9a..207a20d75 100644 --- a/tests/Integration/BlockPostHelperTest.php +++ b/tests/Integration/BlockPostHelperTest.php @@ -48,6 +48,13 @@ class BlockPostHelperTest extends WPTestCase 16, ]; + /** + * Holds the total number of blocks in the Post. + * + * @since 3.4.0 + * + * @var int + */ private $totalBlocks = 28; /**