Core block-based template PHP API (adding blocks to templates) (#39470)

* Initial BlockTemplate implementation

* Initial BlockTemplate tests

* FIx undefined array key errors

* Fix linter errors (except for missing docs ones)

* Get as simple array

* Unit test updates for get_as_simple_array()

* Remove inner content (not used currently)

* Rename attrs to attributes

* Move name out of data array

* Move id out of data array

* Move order out of data array

* Move attributes out of data array; remove data array

* Add doc comments to Block

* Add doc comments to BlockTemplate

* Add doc comments to BlockContainerInterface and BlockContainerTrait

* Doc comment updates.

* Add doc comments to BlockTest

* Add doc comments to BlockTemplateTest

* Add BlockInterface

* Remove key consts from Block

* Move implementations to internal

* Clean up interfaces

* Do not have BlockInterface extend BlockContainerInterface

* FIx case in namespace declaration

* Add exceptions to add_block doc

* Rename BlockTemplate to BlockBasedTemplate

* Rename block-based template vars in tests

* Fix missing get_parent on block containers

* Changelog

* Add get_block_by_id to BlockBasedTemplateInterface

* Rename get_block_by_id to get_block

* Rename get_as_simple_array to get_as_formatted_template

* Rename child blocks to inner blocks

* Rename BlockBasedTemplate to BlockTemplate

* Move validation to separate method

* Move namespace to be non-product editor specific

* Rename get_as_formatted_ methods to get_formatted_

* Rename BlockBasedTemplateTest to BlockTemplateTest

* Add ability to use a custom block generator with add_block

* Add check that block belongs to root template in internal_add_block_to_template

* Fix up code docs related to $block_creator

* Fix code doc linting errors in tests

* Add test for a buggy block creator implementation

* Add test for an invalid block creator

* Rename internal_add_block_to_template to cache_block

* Add add_block_container() method

* Fix linting issue.

* Fix minor issues in ContainerInterface with get_root_template() and get_parent()

* Add block template with abstract blocks and templates (#39630)

* Make block template abstract and protected add block methods

* Create block abstraction and generic block

* Remove add_block from container interface

* Update tests for generic and custom blocks

* Add tests around custom block templates

* Fix up lint errors

* Fix errant comment for add_block

---------

Co-authored-by: Joshua T Flowers <joshuatf@gmail.com>
This commit is contained in:
Matt Sherman 2023-08-10 18:52:44 -04:00 committed by GitHub
parent 91fadfd8fe
commit e805b6b075
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1417 additions and 0 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
API for block-based templates.

View File

@ -0,0 +1,8 @@
<?php
namespace Automattic\WooCommerce\Admin\BlockTemplates;
/**
* Interface for block containers.
*/
interface BlockContainerInterface extends BlockInterface, ContainerInterface {}

View File

@ -0,0 +1,79 @@
<?php
namespace Automattic\WooCommerce\Admin\BlockTemplates;
/**
* Interface for block configuration used to specify blocks in BlockTemplate.
*/
interface BlockInterface {
/**
* Key for the block name in the block configuration.
*/
public const NAME_KEY = 'blockName';
/**
* Key for the block ID in the block configuration.
*/
public const ID_KEY = 'id';
/**
* Key for the internal order in the block configuration.
*/
public const ORDER_KEY = 'order';
/**
* Key for the block attributes in the block configuration.
*/
public const ATTRIBUTES_KEY = 'attributes';
/**
* Get the block name.
*/
public function get_name(): string;
/**
* Get the block ID.
*/
public function get_id(): string;
/**
* Get the block order.
*/
public function get_order(): int;
/**
* Set the block order.
*
* @param int $order The block order.
*/
public function set_order( int $order );
/**
* Get the block attributes.
*/
public function get_attributes(): array;
/**
* Set the block attributes.
*
* @param array $attributes The block attributes.
*/
public function set_attributes( array $attributes );
/**
* Get the parent container that the block belongs to.
*/
public function &get_parent(): ?ContainerInterface;
/**
* Get the root template that the block belongs to.
*/
public function &get_root_template(): BlockTemplateInterface;
/**
* Get the block configuration as a formatted template.
*
* @return array The block configuration as a formatted template.
*/
public function get_formatted_template(): array;
}

View File

@ -0,0 +1,23 @@
<?php
namespace Automattic\WooCommerce\Admin\BlockTemplates;
/**
* Interface for block-based template.
*/
interface BlockTemplateInterface extends ContainerInterface {
/**
* Get a block by ID.
*
* @param string $block_id The block ID.
*/
public function get_block( string $block_id ): ?BlockInterface;
/**
* Generate a block ID based on a base.
*
* @param string $id_base The base to use when generating an ID.
* @return string
*/
public function generate_block_id( string $id_base ): string;
}

View File

@ -0,0 +1,18 @@
<?php
namespace Automattic\WooCommerce\Admin\BlockTemplates;
/**
* Interface for block containers.
*/
interface ContainerInterface {
/**
* Get the root template that the block belongs to.
*/
public function &get_root_template(): BlockTemplateInterface;
/**
* Get the block configuration as a formatted template.
*/
public function get_formatted_template(): array;
}

View File

@ -0,0 +1,189 @@
<?php
namespace Automattic\WooCommerce\Internal\Admin\BlockTemplates;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\ContainerInterface;
/**
* Block configuration used to specify blocks in BlockTemplate.
*/
class AbstractBlock implements BlockInterface {
/**
* The block name.
*
* @var string
*/
private $name;
/**
* The block ID.
*
* @var string
*/
private $id;
/**
* The block order.
*
* @var int
*/
private $order = 10;
/**
* The block attributes.
*
* @var array
*/
private $attributes = [];
/**
* The block template that this block belongs to.
*
* @var BlockTemplate
*/
private $root_template;
/**
* The parent container.
*
* @var ContainerInterface
*/
private $parent;
/**
* Block constructor.
*
* @param array $config The block configuration.
* @param BlockTemplateInterface $root_template The block template that this block belongs to.
* @param BlockContainerInterface|null $parent The parent block container.
*
* @throws \ValueError If the block configuration is invalid.
* @throws \ValueError If the parent block container does not belong to the same template as the block.
*/
public function __construct( array $config, BlockTemplateInterface &$root_template, ContainerInterface &$parent = null ) {
$this->validate( $config, $root_template, $parent );
$this->root_template = $root_template;
$this->parent = is_null( $parent ) ? $root_template : $parent;
$this->name = $config[ self::NAME_KEY ];
if ( ! isset( $config[ self::ID_KEY ] ) ) {
$this->id = $this->root_template->generate_block_id( $this->get_name() );
} else {
$this->id = $config[ self::ID_KEY ];
}
if ( isset( $config[ self::ORDER_KEY ] ) ) {
$this->order = $config[ self::ORDER_KEY ];
}
if ( isset( $config[ self::ATTRIBUTES_KEY ] ) ) {
$this->attributes = $config[ self::ATTRIBUTES_KEY ];
}
}
/**
* Validate block configuration.
*
* @param array $config The block configuration.
* @param BlockTemplateInterface $root_template The block template that this block belongs to.
* @param ContainerInterface|null $parent The parent block container.
*
* @throws \ValueError If the block configuration is invalid.
* @throws \ValueError If the parent block container does not belong to the same template as the block.
*/
protected function validate( array $config, BlockTemplateInterface &$root_template, ContainerInterface &$parent = null ) {
if ( isset( $parent ) && ( $parent->get_root_template() !== $root_template ) ) {
throw new \ValueError( 'The parent block must belong to the same template as the block.' );
}
if ( ! isset( $config[ self::NAME_KEY ] ) || ! is_string( $config[ self::NAME_KEY ] ) ) {
throw new \ValueError( 'The block name must be specified.' );
}
if ( isset( $config[ self::ORDER_KEY ] ) && ! is_int( $config[ self::ORDER_KEY ] ) ) {
throw new \ValueError( 'The block order must be an integer.' );
}
if ( isset( $config[ self::ATTRIBUTES_KEY ] ) && ! is_array( $config[ self::ATTRIBUTES_KEY ] ) ) {
throw new \ValueError( 'The block attributes must be an array.' );
}
}
/**
* Get the block name.
*/
public function get_name(): string {
return $this->name;
}
/**
* Get the block ID.
*/
public function get_id(): string {
return $this->id;
}
/**
* Get the block order.
*/
public function get_order(): int {
return $this->order;
}
/**
* Set the block order.
*
* @param int $order The block order.
*/
public function set_order( int $order ) {
$this->order = $order;
}
/**
* Get the block attributes.
*/
public function get_attributes(): array {
return $this->attributes;
}
/**
* Set the block attributes.
*
* @param array $attributes The block attributes.
*/
public function set_attributes( array $attributes ) {
$this->attributes = $attributes;
}
/**
* Get the template that this block belongs to.
*/
public function &get_root_template(): BlockTemplateInterface {
return $this->root_template;
}
/**
* Get the parent block container.
*/
public function &get_parent(): ContainerInterface {
return $this->parent;
}
/**
* Get the block configuration as a formatted template.
*
* @return array The block configuration as a formatted template.
*/
public function get_formatted_template(): array {
$arr = [
$this->get_name(),
$this->get_attributes(),
];
return $arr;
}
}

View File

@ -0,0 +1,95 @@
<?php
namespace Automattic\WooCommerce\Internal\Admin\BlockTemplates;
use Automattic\WooCommerce\Admin\BlockTemplates\ContainerInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
/**
* Block template class.
*/
abstract class AbstractBlockTemplate implements BlockTemplateInterface {
use BlockContainerTrait;
/**
* The block cache.
*
* @var BlockInterface[]
*/
private $block_cache = [];
/**
* Get a block by ID.
*
* @param string $block_id The block ID.
*/
public function get_block( string $block_id ): ?BlockInterface {
return $this->block_cache[ $block_id ] ?? null;
}
/**
* 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.
*
* @param BlockInterface $block The block to cache.
*
* @throws \ValueError If a block with the specified ID already exists in the template.
* @throws \ValueError If the block template that the block belongs to is not this template.
*
* @ignore
*/
public function cache_block( BlockInterface &$block ) {
$id = $block->get_id();
if ( isset( $this->block_cache[ $id ] ) ) {
throw new \ValueError( 'A block with the specified ID already exists in the template.' );
}
if ( $block->get_root_template() !== $this ) {
throw new \ValueError( 'The block template that the block belongs to must be the same as this template.' );
}
$this->block_cache[ $id ] = $block;
}
/**
* Generate a block ID based on a base.
*
* @param string $id_base The base to use when generating an ID.
* @return string
*/
public function generate_block_id( string $id_base ): string {
$instance_count = 0;
do {
$instance_count++;
$block_id = $id_base . '-' . $instance_count;
} while ( isset( $this->block_cache[ $block_id ] ) );
return $block_id;
}
/**
* Get the root template.
*/
public function &get_root_template(): BlockTemplateInterface {
return $this;
}
/**
* Get the inner blocks as a formatted template.
*/
public function get_formatted_template(): array {
$inner_blocks = $this->get_inner_blocks_sorted_by_order();
$inner_blocks_formatted_template = array_map(
function( Block $block ) {
return $block->get_formatted_template();
},
$inner_blocks
);
return $inner_blocks_formatted_template;
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Automattic\WooCommerce\Internal\Admin\BlockTemplates;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockContainerInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
/**
* Generic block with container properties to be used in BlockTemplate.
*/
class Block extends AbstractBlock implements BlockContainerInterface {
use BlockContainerTrait;
/**
* Get the block configuration as a formatted template.
*
* @return array The block configuration as a formatted template.
*/
public function get_formatted_template(): array {
$arr = [
$this->get_name(),
$this->get_attributes(),
];
$inner_blocks = $this->get_inner_blocks_sorted_by_order();
if ( ! empty( $inner_blocks ) ) {
$arr[] = array_map(
function( BlockInterface $block ) {
return $block->get_formatted_template();
},
$inner_blocks
);
}
return $arr;
}
/**
* Add an inner block to this block.
*
* @param array $block_config The block data.
*/
public function &add_block( array $block_config ): BlockInterface {
$block = new Block( $block_config, $this->get_root_template(), $this );
return $this->add_inner_block( $block );
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace Automattic\WooCommerce\Internal\Admin\BlockTemplates;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
/**
* Trait for block containers.
*/
trait BlockContainerTrait {
/**
* The inner blocks.
*
* @var BlockInterface[]
*/
private $inner_blocks = [];
// phpcs doesn't take into account exceptions thrown by called methods.
// phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
/**
* Add a block to the block container.
*
* @param BlockInterface $block The block.
*
* @throws \ValueError If the block configuration is invalid.
* @throws \ValueError If a block with the specified ID already exists in the template.
* @throws \UnexpectedValueException If the block container is not the parent of the block.
*/
protected function &add_inner_block( BlockInterface $block ): BlockInterface {
if ( ! $block instanceof BlockInterface ) {
throw new \UnexpectedValueException( 'The block must return an instance of BlockInterface.' );
}
if ( $block->get_parent() !== $this ) {
throw new \UnexpectedValueException( 'The block container is not the parent of the block.' );
}
$root_template = $block->get_root_template();
$root_template->cache_block( $block );
$this->inner_blocks[] = &$block;
return $block;
}
// phpcs:enable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
/**
* Get the inner blocks sorted by order.
*/
private function get_inner_blocks_sorted_by_order(): array {
$sorted_inner_blocks = $this->inner_blocks;
usort(
$sorted_inner_blocks,
function( Block $a, Block $b ) {
return $a->get_order() <=> $b->get_order();
}
);
return $sorted_inner_blocks;
}
/**
* Get the inner blocks as a formatted template.
*/
public function get_formatted_template(): array {
$arr = [
$this->get_name(),
$this->get_attributes(),
];
$inner_blocks = $this->get_inner_blocks_sorted_by_order();
if ( ! empty( $inner_blocks ) ) {
$arr[] = array_map(
function( BlockInterface $block ) {
return $block->get_formatted_template();
},
$inner_blocks
);
}
return $arr;
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Automattic\WooCommerce\Internal\Admin\BlockTemplates;
use Automattic\WooCommerce\Admin\BlockTemplates\ContainerInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
/**
* Block template class.
*/
class BlockTemplate extends AbstractBlockTemplate {
/**
* Generate a block ID based on a base.
*
* @param array $block_config The block data.
*/
public function add_block( array $block_config ): BlockInterface {
$block = new Block( $block_config, $this->get_root_template(), $this );
return $this->add_inner_block( $block );
}
}

View File

@ -0,0 +1,367 @@
<?php
namespace Automattic\WooCommerce\Tests\Internal\Admin\BlockTemplates;
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\BlockTemplate;
use WC_Unit_Test_Case;
/**
* Tests for the BlockTemplate class.
*/
class BlockTemplateTest extends WC_Unit_Test_Case {
/**
* Test generating a block ID.
*/
public function test_generate_block_id() {
$template = new BlockTemplate();
$this->assertSame( 'test-block-id-1', $template->generate_block_id( 'test-block-id' ) );
}
/**
* Test adding a block.
*/
public function test_add_block() {
$template = new BlockTemplate();
$block = $template->add_block(
[
'id' => 'test-block-id',
'blockName' => 'test-block-name',
]
);
$this->assertSame( $block, $template->get_block( 'test-block-id' ) );
}
/**
* Test adding a block throws an exception if a block with the same ID already exists.
*/
public function test_add_block_throws_exception_if_block_with_same_id_already_exists() {
$template = new BlockTemplate();
$template->add_block(
[
'id' => 'test-block-id',
'blockName' => 'test-block-name',
]
);
$this->expectException( \ValueError::class );
$template->add_block(
[
'id' => 'test-block-id',
'blockName' => 'test-block-name',
]
);
}
/**
* Test adding a block generates an ID if one is not provided.
*/
public function test_add_block_generates_id_if_not_provided() {
$template = new BlockTemplate();
$block = $template->add_block(
[
'blockName' => 'test-block-name',
]
);
$this->assertSame( 'test-block-name-1', $block->get_id() );
}
/**
* Test getting a block by ID returns null if the block does not exist.
*/
public function test_get_block_returns_null_if_block_does_not_exist() {
$template = new BlockTemplate();
$this->assertNull( $template->get_block( 'test-block-id' ) );
}
/**
* Test getting a block by ID returns a reference to the block.
*/
public function test_get_block_returns_reference() {
$template = new BlockTemplate();
$template->add_block(
[
'id' => 'test-block-id',
'blockName' => 'test-block-name',
]
);
$block = $template->get_block( 'test-block-id' );
$block->set_order( 23 );
$this->assertSame( 23, $template->get_block( 'test-block-id' )->get_order() );
}
/**
* Test that the formatted template representation of a block template is correct.
*/
public function test_get_formatted_template() {
$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(
[
'blockName' => 'test-block-name-b',
'order' => 50,
'attributes' => [
'attr-1' => 'value-1',
'attr-2' => 'value-2',
],
]
);
$template->add_block(
[
'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,
]
);
$this->assertSame(
[
[
'test-block-name-a',
[
'attr-1' => 'value-1',
'attr-2' => 'value-2',
],
],
[
'test-block-name-b',
[
'attr-1' => 'value-1',
'attr-2' => 'value-2',
],
[
[
'test-block-name-1',
[
'attr-3' => 'value-3',
'attr-4' => 'value-4',
],
],
[
'test-block-name-2',
[
'attr-1' => 'value-1',
'attr-2' => 'value-2',
],
],
[
'test-block-name-3',
[],
],
],
],
[
'test-block-name-c',
[
'attr-c1' => 'value-c1',
'attr-c2' => 'value-c2',
],
],
],
$template->get_formatted_template(),
'Failed asserting that the block is converted to a simple array correctly.'
);
}
/**
* Test that inserting a block to a parent in the template works.
*/
public function test_inserting_block_by_parent_id() {
$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',
]
);
$another_block_to_insert_in = $template->get_block( 'b' );
$another_block_to_insert_in->add_block(
[
'blockName' => 'another-inserted-block',
'order' => 15,
]
);
$this->assertSame(
[
[
'test-block-name-a',
[
'attr-1' => 'value-1',
'attr-2' => 'value-2',
],
[
[
'inserted-block',
[],
],
],
],
[
'test-block-name-b',
[
'attr-1' => 'value-1',
'attr-2' => 'value-2',
],
[
[
'test-block-name-1',
[
'attr-3' => 'value-3',
'attr-4' => 'value-4',
],
],
[
'another-inserted-block',
[],
],
[
'test-block-name-2',
[
'attr-1' => 'value-1',
'attr-2' => 'value-2',
],
],
[
'test-block-name-3',
[],
],
],
],
[
'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.'
);
}
}

View File

@ -0,0 +1,306 @@
<?php
namespace Automattic\WooCommerce\Tests\Internal\Admin\BlockTemplates;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockContainerInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\Block;
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\BlockTemplate;
use WC_Unit_Test_Case;
/**
* Tests for the Block class.
*/
class BlockTest extends WC_Unit_Test_Case {
/**
* Test that the block name is required when creating a block.
*/
public function test_name_is_required() {
$template = new BlockTemplate();
$this->expectException( \ValueError::class );
new Block( [], $template );
}
/**
* Test that an ID is generated if not provided when creating a block.
*/
public function test_id_is_generated_if_not_provided() {
$template = new BlockTemplate();
$block = new Block(
[
'blockName' => 'test-block-name',
],
$template
);
$this->assertSame( 'test-block-name-1', $block->get_id() );
}
/**
* Test that setting a parent from a different template is prevented.
*/
public function test_parent_from_different_template_throws_exception() {
$template = new BlockTemplate();
$template_2 = new BlockTemplate();
$parent = new Block(
[
'blockName' => 'test-block-parent-name',
],
$template_2
);
$this->expectException( \ValueError::class );
new Block(
[
'blockName' => 'test-block-name',
],
$template,
$parent
);
}
/**
* Test that adding a block to a block sets the parent and root template correctly
* and that the block is added to the root template.
*/
public function test_add_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',
]
);
$this->assertSame(
$child_block->get_root_template(),
$block->get_root_template(),
'Failed asserting that the child block has the same root template as the parent block.'
);
$this->assertSame(
$block,
$child_block->get_parent(),
'Failed asserting that the child block\'s parent is the block it was added to.'
);
$this->assertSame(
$child_block,
$template->get_block( 'test-block-id-2' ),
'Failed asserting that the child block is in the root template.'
);
}
/**
* Test that adding nested blocks sets the parent and root template correctly.
*/
public function test_nested_add_block() {
$block_template = new BlockTemplate();
$block = $block_template->add_block(
[
'blockName' => 'test-block-name',
]
);
$child_block_1 = $block->add_block(
[
'blockName' => 'test-block-name',
]
);
$block->add_block(
[
'blockName' => 'test-block-name',
]
);
$grandchild_block = $child_block_1->add_block(
[
'blockName' => 'test-block-name',
]
);
$this->assertSame(
$block_template,
$grandchild_block->get_root_template(),
'Failed asserting that the grandchild block has the same root template.'
);
$grandchild_parent = $grandchild_block->get_parent();
$this->assertSame(
$child_block_1,
$grandchild_parent,
'Failed asserting that the grandchild block\'s parent is the block it was added to.'
);
$this->assertInstanceOf(
BlockContainerInterface::class,
$grandchild_parent,
'Failed asserting that the grandchild block\'s parent is a BlockContainerInterface instance.'
);
$this->assertSame(
$block,
$grandchild_parent->get_parent(),
'Failed asserting that the grandchild block\'s grandparent is correct.'
);
}
/**
* Test that getting the block as a formatted template is structured correctly.
*/
public function test_get_formatted_template() {
$template = new BlockTemplate();
$block = $template->add_block(
[
'id' => 'test-block-id',
'blockName' => 'test-block-name',
'attributes' => [
'attr-1' => 'value-1',
'attr-2' => 'value-2',
],
]
);
$block->add_block(
[
'id' => 'test-block-id-2',
'blockName' => 'test-block-name-2',
'attributes' => [
'attr-3' => 'value-3',
'attr-4' => 'value-4',
],
]
);
$block->add_block(
[
'id' => 'test-block-id-3',
'blockName' => 'test-block-name-3',
]
);
$formatted_template = $block->get_formatted_template();
$this->assertSame(
[
'test-block-name',
[
'attr-1' => 'value-1',
'attr-2' => 'value-2',
],
[
[
'test-block-name-2',
[
'attr-3' => 'value-3',
'attr-4' => 'value-4',
],
],
[
'test-block-name-3',
[],
],
],
],
$formatted_template,
'Failed asserting that the block is converted to a formatted template correctly.'
);
}
/**
* Test that getting the inner blocks as a sorted formatted template is ordered correctly.
*/
public function test_get_formatted_template_with_sorting() {
$template = new BlockTemplate();
$block = $template->add_block(
[
'blockName' => 'test-block-name',
]
);
$block->add_block(
[
'blockName' => 'five',
'order' => 5,
]
);
$block->add_block(
[
'blockName' => 'three',
'order' => 3,
]
);
$block->add_block(
[
'blockName' => 'one',
'order' => 1,
]
);
$block->add_block(
[
'blockName' => 'four',
'order' => 4,
]
);
$block->add_block(
[
'blockName' => 'two',
'order' => 2,
]
);
$this->assertSame(
[
'test-block-name',
[],
[
[
'one',
[],
],
[
'two',
[],
],
[
'three',
[],
],
[
'four',
[],
],
[
'five',
[],
],
],
],
$block->get_formatted_template(),
'Failed asserting that the inner blocks are sorted by order.'
);
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Automattic\WooCommerce\Tests\Internal\Admin\BlockTemplates;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockContainerInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\AbstractBlock;
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\Block;
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\BlockContainerTrait;
/**
* Custom block class for testing.
*/
class CustomBlock extends AbstractBlock implements CustomBlockInterface {
use BlockContainerTrait;
/**
* Custom method.
*/
public function add_custom_inner_block(): BlockInterface {
$block = new Block(
[
'blockName' => 'custom-inner-block',
],
$this->get_root_template(),
$this
);
$this->add_inner_block( $block );
return $block;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Automattic\WooCommerce\Tests\Internal\Admin\BlockTemplates;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockContainerInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
interface CustomBlockInterface extends BlockContainerInterface {
/**
* Adds a method to insert a specific custom inner block.
*/
public function add_custom_inner_block(): BlockInterface;
}

View File

@ -0,0 +1,21 @@
<?php
namespace Automattic\WooCommerce\Tests\Internal\Admin\BlockTemplates;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\AbstractBlockTemplate;
/**
* Custom block template class.
*/
class CustomBlockTemplate extends AbstractBlockTemplate {
/**
* Add a custom block type to this template.
*
* @param array $block_config The block data.
*/
public function add_custom_block( array $block_config ): BlockInterface {
$block = new CustomBlock( $block_config, $this->get_root_template(), $this );
return $this->add_inner_block( $block );
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Automattic\WooCommerce\Tests\Internal\Admin\BlockTemplates;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockContainerInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\Block;
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\BlockTemplate;
use WC_Unit_Test_Case;
/**
* Tests for the CustomBlockTemplate class.
*/
class CustomBlockTemplateTest extends WC_Unit_Test_Case {
/**
* Test that the add_block method does not exist by default on templates.
*/
public function test_add_block_does_not_exist() {
$template = new CustomBlockTemplate();
$this->assertFalse( method_exists( $template, 'add_block' ) );
}
/**
* Test that a custom block inserter method inserts as expected.
*/
public function test_add_custom_block() {
$template = new CustomBlockTemplate();
$template->add_custom_block(
[
'id' => 'test-block-name',
'blockName' => 'test-block-name',
]
);
$block = $template->get_block( 'test-block-name' );
$this->assertInstanceOf( CustomBlock::class, $block );
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace Automattic\WooCommerce\Tests\Internal\Admin\BlockTemplates;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockContainerInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\Block;
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\BlockTemplate;
use WC_Unit_Test_Case;
/**
* Tests for the CustomBlock class.
*/
class CustomBlockTest extends WC_Unit_Test_Case {
/**
* Test that the add_block method does not exist by default on blocks.
*/
public function test_add_block_does_not_exist() {
$template = new BlockTemplate();
$block = new CustomBlock(
[
'blockName' => 'test-block-name',
],
$template
);
$this->assertFalse( method_exists( $block, 'add_block' ) );
}
/**
* Test that a custom block inserter method inserts as expected.
*/
public function test_add_custom_inner_block() {
$template = new BlockTemplate();
$block = new CustomBlock(
[
'blockName' => 'test-block-name',
],
$template
);
$block->add_custom_inner_block();
$this->assertSame(
[
'test-block-name',
[],
[
[
'custom-inner-block',
[],
],
],
],
$block->get_formatted_template(),
'Failed asserting that the inner block was added'
);
}
}