From a8dab2099751f6273eac64a9b5828c2658bf86c4 Mon Sep 17 00:00:00 2001 From: Matt Sherman Date: Wed, 3 Jan 2024 20:02:38 -0500 Subject: [PATCH] Layout templates REST API (#43067) * Endpoint shell * Single layout template route * Add area query arg * Fix BlockTemplateRegistry::get_registered() return type * Check if templates are registered before registering * Use json format for layout templates response * Remove unused use * Use json format for layout templates response for get_items * Consolidate processing for get_items and get_item * LayoutTemplateRegistry * Fix matching by id * Get templates from LayoutTemplateRegistry * Remove unused method * Fix typo in code doc * Move template instantiation to registry * Return template instance, not json when instantiating * Use LayoutTemplateRegistry instead of BlockTemplateRegistry * Add code docs for rest controller * Fix code doc in BlockTemplateRegistry * Code docs for LayoutTemplateRegistry * Changelog * Add code doc for LayoutTemplatesServiceProvider * Unit test for registering a layout template * Unit tests for invalid params when registering layout template * Unit test for layout template instantiation * Unit test for instantiating layout templates with query params * Unit test for layout template instance caching * Cache layout template instances * Refactor layout template info querying * Add unit test for layout template instantiation actions * Add before and after layout template instantiation actions * Use layout template ID for array key * REST API unit test to get all items * Unit test for unregister_all * Method to unregister all layout templates * REST API unit test to get all items for a specific area * Fix to_json() in TestLayoutTemplate * REST API unit test to get single item * Fix get_item * REST API unit test for single item with invalid id * REST API unit test for get all items for invalid area * Fix test_cached_instances - array access * Test that old register hook is called * Call old register hook * Remove before hook (will put in separate PR) --- .../changelog/add-layout-templates-rest-api | 4 + ...ss-wc-rest-layout-templates-controller.php | 146 +++++++++++ .../woocommerce/includes/rest-api/Server.php | 1 + .../Features/ProductBlockEditor/Init.php | 37 ++- plugins/woocommerce/src/Container.php | 2 + .../LayoutTemplatesServiceProvider.php | 28 ++ .../LayoutTemplateRegistry.php | 190 ++++++++++++++ ...rest-layout-templates-controller-tests.php | 108 ++++++++ .../LayoutTemplateRegistryTest.php | 240 ++++++++++++++++++ .../LayoutTemplates/TestLayoutTemplate.php | 98 +++++++ 10 files changed, 844 insertions(+), 10 deletions(-) create mode 100644 plugins/woocommerce/changelog/add-layout-templates-rest-api create mode 100644 plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-layout-templates-controller.php create mode 100644 plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/LayoutTemplatesServiceProvider.php create mode 100644 plugins/woocommerce/src/LayoutTemplates/LayoutTemplateRegistry.php create mode 100644 plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-layout-templates-controller-tests.php create mode 100644 plugins/woocommerce/tests/php/src/LayoutTemplates/LayoutTemplateRegistryTest.php create mode 100644 plugins/woocommerce/tests/php/src/LayoutTemplates/TestLayoutTemplate.php diff --git a/plugins/woocommerce/changelog/add-layout-templates-rest-api b/plugins/woocommerce/changelog/add-layout-templates-rest-api new file mode 100644 index 00000000000..aa820d1202e --- /dev/null +++ b/plugins/woocommerce/changelog/add-layout-templates-rest-api @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Template layout REST API endpoints. diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-layout-templates-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-layout-templates-controller.php new file mode 100644 index 00000000000..d309f28cd2b --- /dev/null +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-layout-templates-controller.php @@ -0,0 +1,146 @@ +namespace, + '/' . $this->rest_base, + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'args' => array( + 'area' => array( + 'description' => __( 'Area to get templates for.', 'woocommerce' ), + 'type' => 'string', + 'default' => '', + ), + ), + ) + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P\w[\w\s\-]*)', + array( + 'args' => array( + 'id' => array( + 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), + 'type' => 'string', + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array(), + ), + ) + ); + } + + /** + * Check if a given request has access to read template layouts. + * + * @param WP_REST_Request $request The request. + */ + public function get_items_permissions_check( $request ): bool { + return true; + } + + /** + * Check if a given request has access to read a template layout. + * + * @param WP_REST_Request $request The request. + */ + public function get_item_permissions_check( $request ): bool { + return true; + } + + /** + * Handle request for template layouts. + * + * @param WP_REST_Request $request The request. + */ + public function get_items( $request ) { + $layout_templates = $this->get_layout_templates( + array( + 'area' => $request['area'], + ) + ); + + $response = rest_ensure_response( $layout_templates ); + + return $response; + } + + /** + * Handle request for a single template layout. + * + * @param WP_REST_Request $request The request. + */ + public function get_item( $request ) { + $layout_templates = $this->get_layout_templates( + array( + 'id' => $request['id'], + ) + ); + + if ( count( $layout_templates ) !== 1 ) { + return new WP_Error( 'woocommerce_rest_layout_template_invalid_id', __( 'Invalid layout template ID.', 'woocommerce' ), array( 'status' => 404 ) ); + } + + $response = rest_ensure_response( current( $layout_templates ) ); + + return $response; + } + + /** + * Get layout templates. + * + * @param array $query_params Query params. + */ + private function get_layout_templates( array $query_params ): array { + $layout_template_registry = wc_get_container()->get( LayoutTemplateRegistry::class ); + + return array_map( + function( $layout_template ) { + return $layout_template->to_json(); + }, + $layout_template_registry->instantiate_layout_templates( $query_params ) + ); + } +} diff --git a/plugins/woocommerce/includes/rest-api/Server.php b/plugins/woocommerce/includes/rest-api/Server.php index 6392adf07bd..e1ba98a00e0 100644 --- a/plugins/woocommerce/includes/rest-api/Server.php +++ b/plugins/woocommerce/includes/rest-api/Server.php @@ -143,6 +143,7 @@ class Server { 'coupons' => 'WC_REST_Coupons_Controller', 'customer-downloads' => 'WC_REST_Customer_Downloads_Controller', 'customers' => 'WC_REST_Customers_Controller', + 'layout-templates' => 'WC_REST_Layout_Templates_Controller', 'network-orders' => 'WC_REST_Network_Orders_Controller', 'order-notes' => 'WC_REST_Order_Notes_Controller', 'order-refunds' => 'WC_REST_Order_Refunds_Controller', diff --git a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php index dfeb9bc395a..9c632d0bd65 100644 --- a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php +++ b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php @@ -6,13 +6,14 @@ namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor; use Automattic\WooCommerce\Admin\Features\Features; -use Automattic\WooCommerce\Internal\Admin\Features\ProductBlockEditor\ProductTemplates\SimpleProductTemplate; -use Automattic\WooCommerce\Internal\Admin\Features\ProductBlockEditor\ProductTemplates\ProductVariationTemplate; use Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplate; use Automattic\WooCommerce\Admin\PageController; -use Automattic\WooCommerce\Internal\Admin\BlockTemplateRegistry\BlockTemplateRegistry; -use Automattic\WooCommerce\Internal\Admin\BlockTemplates\Block; +use Automattic\WooCommerce\LayoutTemplates\LayoutTemplateRegistry; + use Automattic\WooCommerce\Internal\Admin\BlockTemplates\BlockTemplateLogger; +use Automattic\WooCommerce\Internal\Admin\Features\ProductBlockEditor\ProductTemplates\SimpleProductTemplate; +use Automattic\WooCommerce\Internal\Admin\Features\ProductBlockEditor\ProductTemplates\ProductVariationTemplate; + use WP_Block_Editor_Context; /** @@ -68,6 +69,8 @@ class Init { add_action( 'current_screen', array( $this, 'set_current_screen_to_block_editor_if_wc_admin' ) ); + add_action( 'rest_api_init', array( $this, 'register_product_editor_templates' ) ); + // Make sure the block registry is initialized so that core blocks are registered. BlockRegistry::get_instance(); @@ -211,12 +214,12 @@ class Init { * Get the product editor settings. */ private function get_product_editor_settings() { - $layout_template_registry = wc_get_container()->get( BlockTemplateRegistry::class ); + $layout_template_registry = wc_get_container()->get( LayoutTemplateRegistry::class ); $layout_template_logger = BlockTemplateLogger::get_instance(); $editor_settings = array(); - foreach ( $layout_template_registry->get_all_registered() as $layout_template ) { + foreach ( $layout_template_registry->instantiate_layout_templates() as $layout_template ) { $editor_settings['layoutTemplates'][] = $layout_template->to_json(); $layout_template_logger->log_template_events_to_file( $layout_template->get_id() ); @@ -370,9 +373,23 @@ class Init { /** * Register product editor templates. */ - private function register_product_editor_templates() { - $template_registry = wc_get_container()->get( BlockTemplateRegistry::class ); - $template_registry->register( new SimpleProductTemplate() ); - $template_registry->register( new ProductVariationTemplate() ); + public function register_product_editor_templates() { + $layout_template_registry = wc_get_container()->get( LayoutTemplateRegistry::class ); + + if ( ! $layout_template_registry->is_registered( 'simple-product' ) ) { + $layout_template_registry->register( + 'simple-product', + 'product-form', + SimpleProductTemplate::class + ); + } + + if ( ! $layout_template_registry->is_registered( 'product-variation' ) ) { + $layout_template_registry->register( + 'product-variation', + 'product-form', + ProductVariationTemplate::class + ); + } } } diff --git a/plugins/woocommerce/src/Container.php b/plugins/woocommerce/src/Container.php index a263e2820d4..3b4e9af67a4 100644 --- a/plugins/woocommerce/src/Container.php +++ b/plugins/woocommerce/src/Container.php @@ -30,6 +30,7 @@ use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\Restoc use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\UtilsClassesServiceProvider; use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\BatchProcessingServiceProvider; use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\BlockTemplatesServiceProvider; +use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\LayoutTemplatesServiceProvider; /** * PSR11 compliant dependency injection container for WooCommerce. @@ -77,6 +78,7 @@ final class Container { MarketingServiceProvider::class, MarketplaceServiceProvider::class, BlockTemplatesServiceProvider::class, + LayoutTemplatesServiceProvider::class, LoggingServiceProvider::class, EnginesServiceProvider::class, ); diff --git a/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/LayoutTemplatesServiceProvider.php b/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/LayoutTemplatesServiceProvider.php new file mode 100644 index 00000000000..8eff7e2f43d --- /dev/null +++ b/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/LayoutTemplatesServiceProvider.php @@ -0,0 +1,28 @@ +share( LayoutTemplateRegistry::class ); + } +} diff --git a/plugins/woocommerce/src/LayoutTemplates/LayoutTemplateRegistry.php b/plugins/woocommerce/src/LayoutTemplates/LayoutTemplateRegistry.php new file mode 100644 index 00000000000..5f024924968 --- /dev/null +++ b/plugins/woocommerce/src/LayoutTemplates/LayoutTemplateRegistry.php @@ -0,0 +1,190 @@ +layout_templates_info = array(); + $this->layout_template_instances = array(); + } + + /** + * Check if a layout template is registered. + * + * @param string $layout_template_id Layout template ID. + */ + public function is_registered( $layout_template_id ): bool { + return isset( $this->layout_templates_info[ $layout_template_id ] ); + } + + /** + * Register a single layout template. + * + * @param string $layout_template_id Layout template ID. + * @param string $layout_template_area Layout template area. + * @param string $layout_template_class_name Layout template class to register. + * + * @throws \ValueError If a layout template with the same ID already exists. + * @throws \ValueError If the specified layout template area is empty. + * @throws \ValueError If the specified layout template class does not exist. + * @throws \ValueError If the specified layout template class does not implement the BlockTemplateInterface. + */ + public function register( $layout_template_id, $layout_template_area, $layout_template_class_name ) { + if ( $this->is_registered( $layout_template_id ) ) { + throw new \ValueError( 'A layout template with the specified ID already exists in the registry.' ); + } + + if ( empty( $layout_template_area ) ) { + throw new \ValueError( 'The specified layout template area is empty.' ); + } + + if ( ! class_exists( $layout_template_class_name ) ) { + throw new \ValueError( 'The specified layout template class does not exist.' ); + } + + if ( ! is_subclass_of( $layout_template_class_name, BlockTemplateInterface::class ) ) { + throw new \ValueError( 'The specified layout template class does not implement the BlockTemplateInterface.' ); + } + + $this->layout_templates_info[ $layout_template_id ] = array( + 'id' => $layout_template_id, + 'area' => $layout_template_area, + 'class_name' => $layout_template_class_name, + ); + } + + /** + * Instantiate the matching layout templates and return them. + * + * @param array $query_params Query params. + */ + public function instantiate_layout_templates( array $query_params = array() ): array { + $layout_templates = array(); + + $layout_templates_info = $this->get_matching_layout_templates_info( $query_params ); + foreach ( $layout_templates_info as $layout_template_info ) { + $layout_template = $this->get_layout_template_instance( $layout_template_info ); + + $layout_templates[ $layout_template->get_id() ] = $layout_template; + } + + return $layout_templates; + } + + /** + * Instantiate a single layout template and return it. + * + * @param array $layout_template_info Layout template info. + */ + private function get_layout_template_instance( $layout_template_info ): BlockTemplateInterface { + $class_name = $layout_template_info['class_name']; + + // Return the instance if it already exists. + + $layout_template_instance = isset( $this->layout_template_instances[ $class_name ] ) + ? $this->layout_template_instances[ $class_name ] + : null; + + if ( ! empty( $layout_template_instance ) ) { + return $layout_template_instance; + } + + // Instantiate the layout template. + + $layout_template_instance = new $class_name(); + $this->layout_template_instances[ $class_name ] = $layout_template_instance; + + // Call the after instantiation hooks. + + /** + * Fires after a layout template is instantiated. + * + * @param string $layout_template_id Layout template ID. + * @param string $layout_template_area Layout template area. + * @param BlockTemplateInterface $layout_template Layout template instance. + * + * @since 8.6.0 + */ + do_action( 'woocommerce_layout_template_after_instantiation', $layout_template_info['id'], $layout_template_info['area'], $layout_template_instance ); + + // Call the old, soon-to-be-deprecated, register hook. + + /** + * Fires when a template is registered. + * + * @param BlockTemplateInterface $template Template that was registered. + * + * @since 8.2.0 + */ + do_action( 'woocommerce_block_template_register', $layout_template_instance ); + + return $layout_template_instance; + } + + /** + * Get matching layout templates info. + * + * @param array $query_params Query params. + */ + private function get_matching_layout_templates_info( array $query_params = array() ): array { + $area_to_match = isset( $query_params['area'] ) ? $query_params['area'] : null; + $id_to_match = isset( $query_params['id'] ) ? $query_params['id'] : null; + + $matching_layout_templates_info = array(); + + foreach ( $this->layout_templates_info as $layout_template_info ) { + if ( ! empty( $area_to_match ) && $layout_template_info['area'] !== $area_to_match ) { + continue; + } + + if ( ! empty( $id_to_match ) && $layout_template_info['id'] !== $id_to_match ) { + continue; + } + + $matching_layout_templates_info[] = $layout_template_info; + } + + return $matching_layout_templates_info; + } +} diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-layout-templates-controller-tests.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-layout-templates-controller-tests.php new file mode 100644 index 00000000000..b269cb1e1a2 --- /dev/null +++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-layout-templates-controller-tests.php @@ -0,0 +1,108 @@ +get( LayoutTemplateRegistry::class ); + + $layout_template_registry->unregister_all(); + + $layout_template_registry->register( 'test-layout-template', 'test', TestLayoutTemplate::class ); + $layout_template_registry->register( 'simple-product', 'product-form', SimpleProductTemplate::class ); + $layout_template_registry->register( 'product-variation', 'product-form', ProductVariationTemplate::class ); + } + + /** + * Test getting all layout templates. + */ + public function test_get_all_items() { + $response = $this->do_rest_get_request( 'layout-templates' ); + + $this->assertEquals( 200, $response->get_status() ); + + $data = $response->get_data(); + + $this->assertNotEmpty( $data ); + + $this->assertCount( 3, $data ); + + $this->assertArrayHasKey( 'test-layout-template', $data ); + $this->assertArrayHasKey( 'simple-product', $data ); + $this->assertArrayHasKey( 'product-variation', $data ); + } + + /** + * Test getting all layout templates for a specific area. + */ + public function test_get_all_items_for_area() { + $response = $this->do_rest_get_request( 'layout-templates', array( 'area' => 'product-form' ) ); + + $this->assertEquals( 200, $response->get_status() ); + + $data = $response->get_data(); + + $this->assertNotEmpty( $data ); + + $this->assertCount( 2, $data ); + + $this->assertArrayHasKey( 'simple-product', $data ); + $this->assertArrayHasKey( 'product-variation', $data ); + } + + /** + * Test getting all layout templates for an invalid area. + */ + public function test_get_all_items_for_invalid_area() { + $response = $this->do_rest_get_request( 'layout-templates', array( 'area' => 'invalid-area' ) ); + + $this->assertEquals( 200, $response->get_status() ); + + $data = $response->get_data(); + + $this->assertEmpty( $data ); + } + + /** + * Test getting a single layout template. + */ + public function test_get_single_item() { + $response = $this->do_rest_get_request( 'layout-templates/test-layout-template' ); + + $this->assertEquals( 200, $response->get_status() ); + + $data = $response->get_data(); + + $this->assertNotEmpty( $data ); + + $this->assertEquals( 'test-layout-template', $data['id'] ); + $this->assertEquals( 'test', $data['area'] ); + + $this->assertArrayHasKey( 'title', $data ); + $this->assertArrayHasKey( 'description', $data ); + $this->assertArrayHasKey( 'blockTemplates', $data ); + } + + /** + * Test getting a single layout template with invalid id. + */ + public function test_get_single_item_with_invalid_id() { + $response = $this->do_rest_get_request( 'layout-templates/invalid-layout-template' ); + + $this->assertEquals( 404, $response->get_status() ); + } +} diff --git a/plugins/woocommerce/tests/php/src/LayoutTemplates/LayoutTemplateRegistryTest.php b/plugins/woocommerce/tests/php/src/LayoutTemplates/LayoutTemplateRegistryTest.php new file mode 100644 index 00000000000..fb1e90d4822 --- /dev/null +++ b/plugins/woocommerce/tests/php/src/LayoutTemplates/LayoutTemplateRegistryTest.php @@ -0,0 +1,240 @@ +layout_template_registry = new LayoutTemplateRegistry(); + + $this->layout_templates_to_register = array( + 'test-layout-template' => array( + 'area' => 'test', + 'class_name' => TestLayoutTemplate::class, + ), + 'simple-product' => array( + 'area' => 'product-form', + 'class_name' => SimpleProductTemplate::class, + ), + 'product-variation' => array( + 'area' => 'product-form', + 'class_name' => ProductVariationTemplate::class, + ), + ); + } + + /** + * Test registering a layout template. + */ + public function test_register() { + $this->assertFalse( $this->layout_template_registry->is_registered( 'test-layout-template' ) ); + + $this->layout_template_registry->register( 'test-layout-template', 'test', TestLayoutTemplate::class ); + + $this->assertTrue( $this->layout_template_registry->is_registered( 'test-layout-template' ) ); + } + + /** + * Test registering a layout template with an existing ID. + */ + public function test_register_duplicate_id() { + $this->expectException( \ValueError::class ); + $this->layout_template_registry->register( 'test-layout-template', 'test', TestLayoutTemplate::class ); + $this->layout_template_registry->register( 'test-layout-template', 'test', TestLayoutTemplate::class ); + } + + /** + * Test registering a layout template with an empty area. + */ + public function test_register_empty_area() { + $this->expectException( \ValueError::class ); + $this->layout_template_registry->register( 'test-layout-template', '', TestLayoutTemplate::class ); + } + + /** + * Test registering a layout template with a non-existing class. + */ + public function test_register_non_existing_class() { + $this->expectException( \ValueError::class ); + $this->layout_template_registry->register( 'test-layout-template', 'test', 'NonExistingClass' ); + } + + /** + * Test registering a layout template with a class that does not implement the BlockTemplateInterface. + */ + public function test_register_non_block_template_class() { + $this->expectException( \ValueError::class ); + $this->layout_template_registry->register( 'test-layout-template', 'test', \stdClass::class ); + } + + /** + * Test unregistering a layout template. + */ + public function test_unregister() { + $this->layout_template_registry->register( 'test-layout-template', 'test', TestLayoutTemplate::class ); + + $this->assertTrue( $this->layout_template_registry->is_registered( 'test-layout-template' ) ); + + $this->layout_template_registry->unregister_all( 'test-layout-template' ); + + $this->assertFalse( $this->layout_template_registry->is_registered( 'test-layout-template' ) ); + } + + /** + * Test instantiating layout templates. + */ + public function test_instantiate() { + foreach ( $this->layout_templates_to_register as $template_id => $template_info ) { + $this->layout_template_registry->register( $template_id, $template_info['area'], $template_info['class_name'] ); + } + + $layout_templates = $this->layout_template_registry->instantiate_layout_templates(); + + $this->assertCount( 3, $layout_templates ); + + foreach ( $layout_templates as $layout_template ) { + $template_info = $this->layout_templates_to_register[ $layout_template->get_id() ]; + $this->assertInstanceOf( $template_info['class_name'], $layout_template ); + } + } + + /** + * Test instantiating layout templates with area query param. + */ + public function test_instantiate_with_area_query_param() { + foreach ( $this->layout_templates_to_register as $template_id => $template_info ) { + $this->layout_template_registry->register( $template_id, $template_info['area'], $template_info['class_name'] ); + } + + $layout_templates = $this->layout_template_registry->instantiate_layout_templates( + array( 'area' => 'product-form' ) + ); + + $this->assertCount( 2, $layout_templates ); + + foreach ( $layout_templates as $layout_template ) { + $template_info = $this->layout_templates_to_register[ $layout_template->get_id() ]; + $this->assertInstanceOf( $template_info['class_name'], $layout_template ); + } + } + + /** + * Test instantiating layout templates with id query param. + */ + public function test_instantiate_with_id_query_param() { + foreach ( $this->layout_templates_to_register as $template_id => $template_info ) { + $this->layout_template_registry->register( $template_id, $template_info['area'], $template_info['class_name'] ); + } + + $layout_templates = $this->layout_template_registry->instantiate_layout_templates( + array( 'id' => 'simple-product' ) + ); + + $this->assertCount( 1, $layout_templates ); + + foreach ( $layout_templates as $layout_template ) { + $template_info = $this->layout_templates_to_register[ $layout_template->get_id() ]; + $this->assertInstanceOf( $template_info['class_name'], $layout_template ); + } + } + + /** + * Test layout templates are only instantiated once. + */ + public function test_cached_instances() { + $this->layout_template_registry->register( 'test-layout-template', 'test', TestLayoutTemplate::class ); + + $layout_templates = $this->layout_template_registry->instantiate_layout_templates( + array( 'id' => 'test-layout-template' ) + ); + + $layout_templates_again = $this->layout_template_registry->instantiate_layout_templates( + array( 'id' => 'test-layout-template' ) + ); + + $this->assertCount( 1, $layout_templates ); + $this->assertCount( 1, $layout_templates_again ); + + $this->assertSame( current( $layout_templates_again ), current( $layout_templates ) ); + } + + /** + * Test layout template instantiation actions are fired. + */ + public function test_instantiation_actions() { + $after_instantiation_hook_called = false; + + $after_instantiation_hook = function( string $layout_template_id, string $area, $layout_template ) use ( &$after_instantiation_hook_called ) { + $after_instantiation_hook_called = true; + + $this->assertEquals( 'test-layout-template', $layout_template_id ); + $this->assertEquals( 'test', $area ); + + $this->assertInstanceOf( TestLayoutTemplate::class, $layout_template ); + }; + + $deprecated_register_hook_called = false; + + $deprecated_register_hook = function( $layout_template ) use ( &$deprecated_register_hook_called ) { + $deprecated_register_hook_called = true; + + $this->assertInstanceOf( TestLayoutTemplate::class, $layout_template ); + + $this->assertEquals( 'test-layout-template', $layout_template->get_id() ); + $this->assertEquals( 'test', $layout_template->get_area() ); + + }; + + try { + add_action( 'woocommerce_layout_template_after_instantiation', $after_instantiation_hook, 10, 3 ); + + add_action( 'woocommerce_block_template_register', $deprecated_register_hook ); + + $this->layout_template_registry->register( 'test-layout-template', 'test', TestLayoutTemplate::class ); + + $this->layout_template_registry->instantiate_layout_templates( + array( 'id' => 'test-layout-template' ) + ); + + $this->assertTrue( + $after_instantiation_hook_called, + 'woocommerce_layout_template_after_instantiation hook was not called.' + ); + + $this->assertTrue( + $deprecated_register_hook_called, + 'woocommerce_block_template_register hook was not called.' + ); + } finally { + remove_action( 'woocommerce_layout_template_after_instantiation', $after_instantiation_hook ); + + remove_action( 'woocommerce_block_template_register', $deprecated_register_hook ); + } + } +} diff --git a/plugins/woocommerce/tests/php/src/LayoutTemplates/TestLayoutTemplate.php b/plugins/woocommerce/tests/php/src/LayoutTemplates/TestLayoutTemplate.php new file mode 100644 index 00000000000..77d3aaaf939 --- /dev/null +++ b/plugins/woocommerce/tests/php/src/LayoutTemplates/TestLayoutTemplate.php @@ -0,0 +1,98 @@ + $this->get_id(), + 'title' => $this->get_title(), + 'description' => $this->get_description(), + 'area' => $this->get_area(), + 'blockTemplates' => array(), + ); + } +}