Remove block from block template (#39900)
This commit is contained in:
commit
7da226ad63
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add ability to remove blocks from templates.
|
|
@ -62,14 +62,23 @@ interface BlockInterface {
|
|||
|
||||
/**
|
||||
* Get the parent container that the block belongs to.
|
||||
*
|
||||
* @throws \RuntimeException If the block does not have a parent.
|
||||
*/
|
||||
public function &get_parent(): ?ContainerInterface;
|
||||
|
||||
/**
|
||||
* Get the root template that the block belongs to.
|
||||
*
|
||||
* @throws \RuntimeException If the block does not have a root template.
|
||||
*/
|
||||
public function &get_root_template(): BlockTemplateInterface;
|
||||
|
||||
/**
|
||||
* Detach the block from its parent and root template.
|
||||
*/
|
||||
public function detach();
|
||||
|
||||
/**
|
||||
* Get the block configuration as a formatted template.
|
||||
*
|
||||
|
|
|
@ -15,4 +15,18 @@ interface ContainerInterface {
|
|||
* Get the block configuration as a formatted template.
|
||||
*/
|
||||
public function get_formatted_template(): array;
|
||||
|
||||
/**
|
||||
* Removes a block from the container.
|
||||
*
|
||||
* @param string $block_id The block ID.
|
||||
*
|
||||
* @throws \UnexpectedValueException If the block container is not an ancestor of the block.
|
||||
*/
|
||||
public function remove_block( string $block_id );
|
||||
|
||||
/**
|
||||
* Removes all blocks from the container.
|
||||
*/
|
||||
public function remove_blocks();
|
||||
}
|
||||
|
|
|
@ -161,18 +161,38 @@ class AbstractBlock implements BlockInterface {
|
|||
|
||||
/**
|
||||
* Get the template that this block belongs to.
|
||||
*
|
||||
* @throws \RuntimeException If the block does not have a root template.
|
||||
*/
|
||||
public function &get_root_template(): BlockTemplateInterface {
|
||||
if ( is_null( $this->root_template ) ) {
|
||||
throw new \RuntimeException( 'The block does not have a root template.' );
|
||||
}
|
||||
|
||||
return $this->root_template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent block container.
|
||||
*
|
||||
* @throws \RuntimeException If the block does not have a parent.
|
||||
*/
|
||||
public function &get_parent(): ContainerInterface {
|
||||
if ( is_null( $this->parent ) ) {
|
||||
throw new \RuntimeException( 'The block does not have a parent.' );
|
||||
}
|
||||
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach the block from its parent block container and the template it belongs to.
|
||||
*/
|
||||
public function detach() {
|
||||
$this->parent = null;
|
||||
$this->root_template = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block configuration as a formatted template.
|
||||
*
|
||||
|
|
|
@ -55,7 +55,7 @@ abstract class AbstractBlockTemplate implements BlockTemplateInterface {
|
|||
|
||||
/**
|
||||
* Caches a block in the template. This is an internal method and should not be called directly
|
||||
* except for classes that implement BlockContainerInterface, in their add_block() method.
|
||||
* except for from the BlockContainerTrait's add_inner_block() method.
|
||||
*
|
||||
* @param BlockInterface $block The block to cache.
|
||||
*
|
||||
|
@ -78,6 +78,20 @@ abstract class AbstractBlockTemplate implements BlockTemplateInterface {
|
|||
$this->block_cache[ $id ] = $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uncaches a block in the template. This is an internal method and should not be called directly
|
||||
* except for from the BlockContainerTrait's remove_block() method.
|
||||
*
|
||||
* @param string $block_id The block ID.
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
public function uncache_block( string $block_id ) {
|
||||
if ( isset( $this->block_cache[ $block_id ] ) ) {
|
||||
unset( $this->block_cache[ $block_id ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a block ID based on a base.
|
||||
*
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Automattic\WooCommerce\Internal\Admin\BlockTemplates;
|
||||
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Trait for block containers.
|
||||
|
@ -44,6 +45,91 @@ trait BlockContainerTrait {
|
|||
|
||||
// phpcs:enable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
|
||||
|
||||
/**
|
||||
* Checks if a block is a descendant of the block container.
|
||||
*
|
||||
* @param BlockInterface $block The block.
|
||||
*/
|
||||
private function is_block_descendant( BlockInterface $block ): bool {
|
||||
$parent = $block->get_parent();
|
||||
|
||||
if ( $parent === $this ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! $parent instanceof BlockInterface ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->is_block_descendant( $parent );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a block from the block container.
|
||||
*
|
||||
* @param string $block_id The block ID.
|
||||
*
|
||||
* @throws \UnexpectedValueException If the block container is not an ancestor of the block.
|
||||
*/
|
||||
public function remove_block( string $block_id ) {
|
||||
$root_template = $this->get_root_template();
|
||||
|
||||
$block = $root_template->get_block( $block_id );
|
||||
|
||||
if ( ! $block ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->is_block_descendant( $block ) ) {
|
||||
throw new \UnexpectedValueException( 'The block container is not an ancestor of the block.' );
|
||||
}
|
||||
|
||||
// If the block is a container, remove all of its blocks.
|
||||
if ( $block instanceof ContainerInterface ) {
|
||||
$block->remove_blocks();
|
||||
}
|
||||
|
||||
// Remove block from root template's cache.
|
||||
$root_template = $this->get_root_template();
|
||||
$root_template->uncache_block( $block->get_id() );
|
||||
|
||||
$parent = $block->get_parent();
|
||||
$parent->remove_inner_block( $block );
|
||||
|
||||
// Detach block from parent and root template.
|
||||
$block->detach();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all blocks from the block container.
|
||||
*/
|
||||
public function remove_blocks() {
|
||||
array_map(
|
||||
function ( BlockInterface $block ) {
|
||||
$this->remove_block( $block->get_id() );
|
||||
},
|
||||
$this->inner_blocks
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a block from the block container's inner blocks. This is an internal method and should not be called directly
|
||||
* except for from the BlockContainerTrait's remove_block() method.
|
||||
*
|
||||
* @param BlockInterface $block The block.
|
||||
*/
|
||||
public function remove_inner_block( BlockInterface $block ) {
|
||||
$this->inner_blocks = array_filter(
|
||||
$this->inner_blocks,
|
||||
function ( BlockInterface $inner_block ) use ( $block ) {
|
||||
return $inner_block !== $block;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the inner blocks sorted by order.
|
||||
*/
|
||||
|
|
|
@ -18,7 +18,7 @@ class BlockTemplate extends AbstractBlockTemplate {
|
|||
}
|
||||
|
||||
/**
|
||||
* Generate a block ID based on a base.
|
||||
* Add an inner block to this template.
|
||||
*
|
||||
* @param array $block_config The block data.
|
||||
*/
|
||||
|
|
|
@ -364,4 +364,112 @@ class BlockTemplateTest extends WC_Unit_Test_Case {
|
|||
'Failed asserting that the template is converted to a formatted template correctly.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that removing a block in the template works.
|
||||
*/
|
||||
public function test_removing_blocks() {
|
||||
$template = new BlockTemplate();
|
||||
|
||||
$template->add_block(
|
||||
[
|
||||
'blockName' => 'test-block-name-c',
|
||||
'order' => 100,
|
||||
'attributes' => [
|
||||
'attr-c1' => 'value-c1',
|
||||
'attr-c2' => 'value-c2',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$block_b = $template->add_block(
|
||||
[
|
||||
'id' => 'b',
|
||||
'blockName' => 'test-block-name-b',
|
||||
'order' => 50,
|
||||
'attributes' => [
|
||||
'attr-1' => 'value-1',
|
||||
'attr-2' => 'value-2',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$template->add_block(
|
||||
[
|
||||
'id' => 'a',
|
||||
'blockName' => 'test-block-name-a',
|
||||
'order' => 10,
|
||||
'attributes' => [
|
||||
'attr-1' => 'value-1',
|
||||
'attr-2' => 'value-2',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$block_b->add_block(
|
||||
[
|
||||
'blockName' => 'test-block-name-2',
|
||||
'order' => 20,
|
||||
'attributes' => [
|
||||
'attr-1' => 'value-1',
|
||||
'attr-2' => 'value-2',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$block_b->add_block(
|
||||
[
|
||||
'blockName' => 'test-block-name-1',
|
||||
'order' => 10,
|
||||
'attributes' => [
|
||||
'attr-3' => 'value-3',
|
||||
'attr-4' => 'value-4',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$block_b->add_block(
|
||||
[
|
||||
'blockName' => 'test-block-name-3',
|
||||
'order' => 30,
|
||||
]
|
||||
);
|
||||
|
||||
$block_to_insert_in = $template->get_block( 'a' );
|
||||
|
||||
$block_to_insert_in->add_block(
|
||||
[
|
||||
'blockName' => 'inserted-block',
|
||||
]
|
||||
);
|
||||
|
||||
$template->remove_block( 'b' );
|
||||
|
||||
$this->assertSame(
|
||||
[
|
||||
[
|
||||
'test-block-name-a',
|
||||
[
|
||||
'attr-1' => 'value-1',
|
||||
'attr-2' => 'value-2',
|
||||
],
|
||||
[
|
||||
[
|
||||
'inserted-block',
|
||||
[],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'test-block-name-c',
|
||||
[
|
||||
'attr-c1' => 'value-c1',
|
||||
'attr-c2' => 'value-c2',
|
||||
],
|
||||
],
|
||||
],
|
||||
$template->get_formatted_template(),
|
||||
'Failed asserting that the template is converted to a formatted template correctly.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,6 +105,110 @@ class BlockTest extends WC_Unit_Test_Case {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that removing a block from a block sets the parent and root template to null
|
||||
* and that the block is removed from the root template.
|
||||
*/
|
||||
public function test_remove_block() {
|
||||
$template = new BlockTemplate();
|
||||
|
||||
$block = $template->add_block(
|
||||
[
|
||||
'id' => 'test-block-id',
|
||||
'blockName' => 'test-block-name',
|
||||
]
|
||||
);
|
||||
|
||||
$child_block = $block->add_block(
|
||||
[
|
||||
'id' => 'test-block-id-2',
|
||||
'blockName' => 'test-block-name-2',
|
||||
]
|
||||
);
|
||||
|
||||
$block->remove_block( 'test-block-id-2' );
|
||||
|
||||
$this->assertNull(
|
||||
$template->get_block( 'test-block-id-2' ),
|
||||
'Failed asserting that the child block was removed from the root template.'
|
||||
);
|
||||
|
||||
$this->expectException( \RuntimeException::class );
|
||||
|
||||
$child_block->get_parent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that removing a block from a block sets the parent and root template to null
|
||||
* and that the block is removed from the root template, as well as any descendants.
|
||||
*/
|
||||
public function test_remove_nested_block() {
|
||||
$template = new BlockTemplate();
|
||||
|
||||
$block = $template->add_block(
|
||||
[
|
||||
'id' => 'test-block-id',
|
||||
'blockName' => 'test-block-name',
|
||||
]
|
||||
);
|
||||
|
||||
$child_block = $block->add_block(
|
||||
[
|
||||
'id' => 'test-block-id-2',
|
||||
'blockName' => 'test-block-name-2',
|
||||
]
|
||||
);
|
||||
|
||||
$template->remove_block( 'test-block-id-2' );
|
||||
|
||||
$this->assertNull(
|
||||
$template->get_block( 'test-block-id-2' ),
|
||||
'Failed asserting that the nested descendent block was removed from the root template.'
|
||||
);
|
||||
|
||||
$this->expectException( \RuntimeException::class );
|
||||
|
||||
$child_block->get_parent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that removing a block from a block sets the parent and root template to null
|
||||
* and that the block is removed from the root template, as well as any descendants.
|
||||
*/
|
||||
public function test_remove_block_and_descendants() {
|
||||
$template = new BlockTemplate();
|
||||
|
||||
$block = $template->add_block(
|
||||
[
|
||||
'id' => 'test-block-id',
|
||||
'blockName' => 'test-block-name',
|
||||
]
|
||||
);
|
||||
|
||||
$child_block = $block->add_block(
|
||||
[
|
||||
'id' => 'test-block-id-2',
|
||||
'blockName' => 'test-block-name-2',
|
||||
]
|
||||
);
|
||||
|
||||
$template->remove_block( 'test-block-id' );
|
||||
|
||||
$this->assertNull(
|
||||
$template->get_block( 'test-block-id' ),
|
||||
'Failed asserting that the child block was removed from the root template.'
|
||||
);
|
||||
|
||||
$this->assertNull(
|
||||
$template->get_block( 'test-block-id-2' ),
|
||||
'Failed asserting that the nested descendent block was removed from the root template.'
|
||||
);
|
||||
|
||||
$this->expectException( \RuntimeException::class );
|
||||
|
||||
$child_block->get_parent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that adding nested blocks sets the parent and root template correctly.
|
||||
*/
|
||||
|
|
|
@ -18,11 +18,16 @@ class CustomBlock extends AbstractBlock implements CustomBlockInterface {
|
|||
|
||||
/**
|
||||
* Custom method.
|
||||
*
|
||||
* @param string $title The title.
|
||||
*/
|
||||
public function add_custom_inner_block(): BlockInterface {
|
||||
public function add_custom_inner_block( string $title ): BlockInterface {
|
||||
$block = new Block(
|
||||
[
|
||||
'blockName' => 'custom-inner-block',
|
||||
'blockName' => 'custom-inner-block',
|
||||
'attributes' => [
|
||||
'title' => $title,
|
||||
],
|
||||
],
|
||||
$this->get_root_template(),
|
||||
$this
|
||||
|
|
|
@ -8,6 +8,8 @@ use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
|
|||
interface CustomBlockInterface extends BlockContainerInterface {
|
||||
/**
|
||||
* Adds a method to insert a specific custom inner block.
|
||||
*
|
||||
* @param string $title The title.
|
||||
*/
|
||||
public function add_custom_inner_block(): BlockInterface;
|
||||
public function add_custom_inner_block( string $title ): BlockInterface;
|
||||
}
|
||||
|
|
|
@ -62,4 +62,29 @@ class CustomBlockTemplateTest extends WC_Unit_Test_Case {
|
|||
$block = $template->get_block( 'test-block-name' );
|
||||
$this->assertInstanceOf( CustomBlock::class, $block );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a custom block can be removed as expected.
|
||||
*/
|
||||
public function test_remove_custom_block() {
|
||||
$template = new CustomBlockTemplate();
|
||||
|
||||
$template->add_custom_block(
|
||||
[
|
||||
'id' => 'test-block-name-1',
|
||||
'blockName' => 'test-block-name',
|
||||
]
|
||||
);
|
||||
|
||||
$template->add_custom_block(
|
||||
[
|
||||
'id' => 'test-block-name-2',
|
||||
'blockName' => 'test-block-name',
|
||||
]
|
||||
);
|
||||
|
||||
$template->remove_block( 'test-block-name-1' );
|
||||
|
||||
$this->assertNull( $template->get_block( 'test-block-name-1' ) );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,8 @@ class CustomBlockTest extends WC_Unit_Test_Case {
|
|||
$template
|
||||
);
|
||||
|
||||
$block->add_custom_inner_block();
|
||||
$block->add_custom_inner_block( 'a' );
|
||||
$block->add_custom_inner_block( 'b' );
|
||||
|
||||
$this->assertSame(
|
||||
[
|
||||
|
@ -50,7 +51,15 @@ class CustomBlockTest extends WC_Unit_Test_Case {
|
|||
[
|
||||
[
|
||||
'custom-inner-block',
|
||||
[],
|
||||
[
|
||||
'title' => 'a',
|
||||
],
|
||||
],
|
||||
[
|
||||
'custom-inner-block',
|
||||
[
|
||||
'title' => 'b',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
@ -58,4 +67,39 @@ class CustomBlockTest extends WC_Unit_Test_Case {
|
|||
'Failed asserting that the inner block was added'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a custom block is removed as expected.
|
||||
*/
|
||||
public function test_remove_custom_inner_block() {
|
||||
$template = new BlockTemplate();
|
||||
$block = new CustomBlock(
|
||||
[
|
||||
'blockName' => 'test-block-name',
|
||||
],
|
||||
$template
|
||||
);
|
||||
|
||||
$block->add_custom_inner_block( 'a' );
|
||||
$block->add_custom_inner_block( 'b' );
|
||||
|
||||
$template->remove_block( 'custom-inner-block-1' );
|
||||
|
||||
$this->assertSame(
|
||||
[
|
||||
'test-block-name',
|
||||
[],
|
||||
[
|
||||
[
|
||||
'custom-inner-block',
|
||||
[
|
||||
'title' => 'b',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
$block->get_formatted_template(),
|
||||
'Failed asserting that the inner block was removed'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue