wordpress-develop/tests/phpunit/tests/block-template-utils.php
Bernie Reiter dcd6e5c036 Patterns, Templates: Inject theme attr into Template Part blocks.
It was found that Template Part blocks were broken in the Site Editor, showing the `Template part has been deleted or is unavailable` message, due to a missing `theme` attribute.

This bug seems to have been introduced by [56896], whose goal was to only inject that attribute into the markup returned by the templates and patterns REST API endpoints but not on the frontend, in order to improve performance. It has been demonstrated locally that reverting that changeset fixes the bug.

Reverts [56896].
Props mmcalister, swisspidy, thelovelist, hellofromTonya, pbiron, Pauthake015, richtabor, nicolefurlan, huzaifaalmesbah, annezazu, kafleg, aegkr, sunitarai, shresthaaman, andraganescu, onemaggie, gziolo.
Fixes #59629.

git-svn-id: https://develop.svn.wordpress.org/trunk@56960 602fd350-edb4-49c9-b593-d223f7449a82
2023-10-17 15:46:44 +00:00

527 lines
19 KiB
PHP

<?php
/**
* Tests for the Block Templates abstraction layer.
*
* @package WordPress
*
* @group block-templates
*/
class Tests_Block_Template_Utils extends WP_UnitTestCase {
const TEST_THEME = 'block-theme';
private static $template_post;
private static $template_part_post;
public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
/*
* Set up a template post corresponding to a different theme.
* We do this to ensure resolution and slug creation works as expected,
* even with another post of that same name present for another theme.
*/
self::$template_post = $factory->post->create_and_get(
array(
'post_type' => 'wp_template',
'post_name' => 'my_template',
'post_title' => 'My Template',
'post_content' => 'Content',
'post_excerpt' => 'Description of my template',
'tax_input' => array(
'wp_theme' => array(
'this-theme-should-not-resolve',
),
),
)
);
wp_set_post_terms( self::$template_post->ID, 'this-theme-should-not-resolve', 'wp_theme' );
// Set up template post.
self::$template_post = $factory->post->create_and_get(
array(
'post_type' => 'wp_template',
'post_name' => 'my_template',
'post_title' => 'My Template',
'post_content' => 'Content',
'post_excerpt' => 'Description of my template',
'tax_input' => array(
'wp_theme' => array(
self::TEST_THEME,
),
),
)
);
wp_set_post_terms( self::$template_post->ID, self::TEST_THEME, 'wp_theme' );
// Set up template part post.
self::$template_part_post = $factory->post->create_and_get(
array(
'post_type' => 'wp_template_part',
'post_name' => 'my_template_part',
'post_title' => 'My Template Part',
'post_content' => 'Content',
'post_excerpt' => 'Description of my template part',
'tax_input' => array(
'wp_theme' => array(
self::TEST_THEME,
),
'wp_template_part_area' => array(
WP_TEMPLATE_PART_AREA_HEADER,
),
),
)
);
wp_set_post_terms( self::$template_part_post->ID, WP_TEMPLATE_PART_AREA_HEADER, 'wp_template_part_area' );
wp_set_post_terms( self::$template_part_post->ID, self::TEST_THEME, 'wp_theme' );
}
public static function wpTearDownAfterClass() {
wp_delete_post( self::$template_post->ID );
}
public function set_up() {
parent::set_up();
switch_theme( self::TEST_THEME );
}
public function test_build_block_template_result_from_post() {
$template = _build_block_template_result_from_post(
self::$template_post,
'wp_template'
);
$this->assertNotWPError( $template );
$this->assertSame( get_stylesheet() . '//my_template', $template->id );
$this->assertSame( get_stylesheet(), $template->theme );
$this->assertSame( 'my_template', $template->slug );
$this->assertSame( 'publish', $template->status );
$this->assertSame( 'custom', $template->source );
$this->assertSame( 'My Template', $template->title );
$this->assertSame( 'Description of my template', $template->description );
$this->assertSame( 'wp_template', $template->type );
$this->assertSame( self::$template_post->post_modified, $template->modified, 'Template result properties match' );
// Test template parts.
$template_part = _build_block_template_result_from_post(
self::$template_part_post,
'wp_template_part'
);
$this->assertNotWPError( $template_part );
$this->assertSame( get_stylesheet() . '//my_template_part', $template_part->id );
$this->assertSame( get_stylesheet(), $template_part->theme );
$this->assertSame( 'my_template_part', $template_part->slug );
$this->assertSame( 'publish', $template_part->status );
$this->assertSame( 'custom', $template_part->source );
$this->assertSame( 'My Template Part', $template_part->title );
$this->assertSame( 'Description of my template part', $template_part->description );
$this->assertSame( 'wp_template_part', $template_part->type );
$this->assertSame( WP_TEMPLATE_PART_AREA_HEADER, $template_part->area );
$this->assertSame( self::$template_part_post->post_modified, $template_part->modified, 'Template part result properties match' );
}
public function test_build_block_template_result_from_file() {
$template = _build_block_template_result_from_file(
array(
'slug' => 'single',
'path' => __DIR__ . '/../data/templates/template.html',
),
'wp_template'
);
$this->assertSame( get_stylesheet() . '//single', $template->id );
$this->assertSame( get_stylesheet(), $template->theme );
$this->assertSame( 'single', $template->slug );
$this->assertSame( 'publish', $template->status );
$this->assertSame( 'theme', $template->source );
$this->assertSame( 'Single Posts', $template->title );
$this->assertSame( 'Displays single posts on your website unless a custom template has been applied to that post or a dedicated template exists.', $template->description );
$this->assertSame( 'wp_template', $template->type );
$this->assertEmpty( $template->modified );
// Test template parts.
$template_part = _build_block_template_result_from_file(
array(
'slug' => 'header',
'path' => __DIR__ . '/../data/templates/template.html',
'area' => WP_TEMPLATE_PART_AREA_HEADER,
),
'wp_template_part'
);
$this->assertSame( get_stylesheet() . '//header', $template_part->id );
$this->assertSame( get_stylesheet(), $template_part->theme );
$this->assertSame( 'header', $template_part->slug );
$this->assertSame( 'publish', $template_part->status );
$this->assertSame( 'theme', $template_part->source );
$this->assertSame( 'header', $template_part->title );
$this->assertSame( '', $template_part->description );
$this->assertSame( 'wp_template_part', $template_part->type );
$this->assertSame( WP_TEMPLATE_PART_AREA_HEADER, $template_part->area );
$this->assertEmpty( $template_part->modified );
}
/**
* @ticket 59325
*
* @covers ::_build_block_template_result_from_file
*
* @dataProvider data_build_block_template_result_from_file_injects_theme_attribute
*
* @param string $filename The template's filename.
* @param string $expected The expected block markup.
*/
public function test_build_block_template_result_from_file_injects_theme_attribute( $filename, $expected ) {
$template = _build_block_template_result_from_file(
array(
'slug' => 'single',
'path' => DIR_TESTDATA . "/templates/$filename",
),
'wp_template'
);
$this->assertSame( $expected, $template->content );
}
/**
* Data provider.
*
* @return array[]
*/
public function data_build_block_template_result_from_file_injects_theme_attribute() {
$theme = 'block-theme';
return array(
'a template with a template part block' => array(
'filename' => 'template-with-template-part.html',
'expected' => sprintf(
'<!-- wp:template-part {"slug":"header","align":"full","tagName":"header","className":"site-header","theme":"%s"} /-->',
$theme
),
),
'a template with a template part block nested inside another block' => array(
'filename' => 'template-with-nested-template-part.html',
'expected' => sprintf(
'<!-- wp:group -->
<!-- wp:template-part {"slug":"header","align":"full","tagName":"header","className":"site-header","theme":"%s"} /-->
<!-- /wp:group -->',
$theme
),
),
'a template with a template part block with an existing theme attribute' => array(
'filename' => 'template-with-template-part-with-existing-theme-attribute.html',
'expected' => '<!-- wp:template-part {"slug":"header","theme":"fake-theme","align":"full","tagName":"header","className":"site-header"} /-->',
),
'a template with no template part block' => array(
'filename' => 'template.html',
'expected' => '<!-- wp:paragraph -->
<p>Just a paragraph</p>
<!-- /wp:paragraph -->',
),
);
}
/**
* @ticket 59338
*
* @covers ::_inject_theme_attribute_in_template_part_block
*/
public function test_inject_theme_attribute_in_template_part_block() {
$template_part_block = array(
'blockName' => 'core/template-part',
'attrs' => array(
'slug' => 'header',
'align' => 'full',
'tagName' => 'header',
'className' => 'site-header',
),
'innerHTML' => '',
'innerContent' => array(),
'innerBlocks' => array(),
);
_inject_theme_attribute_in_template_part_block( $template_part_block );
$expected = array(
'blockName' => 'core/template-part',
'attrs' => array(
'slug' => 'header',
'align' => 'full',
'tagName' => 'header',
'className' => 'site-header',
'theme' => get_stylesheet(),
),
'innerHTML' => '',
'innerContent' => array(),
'innerBlocks' => array(),
);
$this->assertSame(
$expected,
$template_part_block,
'`theme` attribute was not correctly injected in template part block.'
);
}
/**
* @ticket 59338
*
* @covers ::_inject_theme_attribute_in_template_part_block
*/
public function test_not_inject_theme_attribute_in_template_part_block_theme_attribute_exists() {
$template_part_block = array(
'blockName' => 'core/template-part',
'attrs' => array(
'slug' => 'header',
'align' => 'full',
'tagName' => 'header',
'className' => 'site-header',
'theme' => 'fake-theme',
),
'innerHTML' => '',
'innerContent' => array(),
'innerBlocks' => array(),
);
$expected = $template_part_block;
_inject_theme_attribute_in_template_part_block( $template_part_block );
$this->assertSame(
$expected,
$template_part_block,
'Existing `theme` attribute in template part block was not respected by attribute injection.'
);
}
/**
* @ticket 59338
*
* @covers ::_inject_theme_attribute_in_template_part_block
*/
public function test_not_inject_theme_attribute_non_template_part_block() {
$non_template_part_block = array(
'blockName' => 'core/post-content',
'attrs' => array(),
'innerHTML' => '',
'innerContent' => array(),
'innerBlocks' => array(),
);
$expected = $non_template_part_block;
_inject_theme_attribute_in_template_part_block( $non_template_part_block );
$this->assertSame(
$expected,
$non_template_part_block,
'`theme` attribute injection modified non-template-part block.'
);
}
/**
* @ticket 59452
*
* @covers ::_inject_theme_attribute_in_block_template_content
*
* @expectedDeprecated _inject_theme_attribute_in_block_template_content
*/
public function test_inject_theme_attribute_in_block_template_content() {
$theme = get_stylesheet();
$content_without_theme_attribute = '<!-- wp:template-part {"slug":"header","align":"full", "tagName":"header","className":"site-header"} /-->';
$template_content = _inject_theme_attribute_in_block_template_content(
$content_without_theme_attribute,
$theme
);
$expected = sprintf(
'<!-- wp:template-part {"slug":"header","align":"full","tagName":"header","className":"site-header","theme":"%s"} /-->',
get_stylesheet()
);
$this->assertSame( $expected, $template_content );
$content_without_theme_attribute_nested = '<!-- wp:group --><!-- wp:template-part {"slug":"header","align":"full", "tagName":"header","className":"site-header"} /--><!-- /wp:group -->';
$template_content = _inject_theme_attribute_in_block_template_content(
$content_without_theme_attribute_nested,
$theme
);
$expected = sprintf(
'<!-- wp:group --><!-- wp:template-part {"slug":"header","align":"full","tagName":"header","className":"site-header","theme":"%s"} /--><!-- /wp:group -->',
get_stylesheet()
);
$this->assertSame( $expected, $template_content );
// Does not inject theme when there is an existing theme attribute.
$content_with_existing_theme_attribute = '<!-- wp:template-part {"slug":"header","theme":"fake-theme","align":"full", "tagName":"header","className":"site-header"} /-->';
$template_content = _inject_theme_attribute_in_block_template_content(
$content_with_existing_theme_attribute,
$theme
);
$this->assertSame( $content_with_existing_theme_attribute, $template_content );
// Does not inject theme when there is no template part.
$content_with_no_template_part = '<!-- wp:post-content /-->';
$template_content = _inject_theme_attribute_in_block_template_content(
$content_with_no_template_part,
$theme
);
$this->assertSame( $content_with_no_template_part, $template_content );
}
/**
* @ticket 54448
* @ticket 59460
*
* @dataProvider data_remove_theme_attribute_in_block_template_content
*
* @expectedDeprecated _remove_theme_attribute_in_block_template_content
*/
public function test_remove_theme_attribute_in_block_template_content( $template_content, $expected ) {
$this->assertSame( $expected, _remove_theme_attribute_in_block_template_content( $template_content ) );
}
/**
* @ticket 59460
*
* @covers ::_remove_theme_attribute_from_template_part_block
* @covers ::traverse_and_serialize_blocks
*
* @dataProvider data_remove_theme_attribute_in_block_template_content
*
* @param string $template_content The template markup.
* @param string $expected The expected markup after removing the theme attribute from Template Part blocks.
*/
public function test_remove_theme_attribute_from_template_part_block( $template_content, $expected ) {
$template_content_parsed_blocks = parse_blocks( $template_content );
$this->assertSame(
$expected,
traverse_and_serialize_blocks(
$template_content_parsed_blocks,
'_remove_theme_attribute_from_template_part_block'
)
);
}
public function data_remove_theme_attribute_in_block_template_content() {
return array(
array(
'<!-- wp:template-part {"slug":"header","theme":"tt1-blocks","align":"full","tagName":"header","className":"site-header"} /-->',
'<!-- wp:template-part {"slug":"header","align":"full","tagName":"header","className":"site-header"} /-->',
),
array(
'<!-- wp:group --><!-- wp:template-part {"slug":"header","theme":"tt1-blocks","align":"full","tagName":"header","className":"site-header"} /--><!-- /wp:group -->',
'<!-- wp:group --><!-- wp:template-part {"slug":"header","align":"full","tagName":"header","className":"site-header"} /--><!-- /wp:group -->',
),
// Does not modify content when there is no existing theme attribute.
array(
'<!-- wp:template-part {"slug":"header","align":"full","tagName":"header","className":"site-header"} /-->',
'<!-- wp:template-part {"slug":"header","align":"full","tagName":"header","className":"site-header"} /-->',
),
// Does not remove theme when there is no template part.
array(
'<!-- wp:post-content /-->',
'<!-- wp:post-content /-->',
),
);
}
/**
* Should retrieve the template from the theme files.
*/
public function test_get_block_template_from_file() {
$id = get_stylesheet() . '//' . 'index';
$template = get_block_template( $id, 'wp_template' );
$this->assertSame( $id, $template->id );
$this->assertSame( get_stylesheet(), $template->theme );
$this->assertSame( 'index', $template->slug );
$this->assertSame( 'publish', $template->status );
$this->assertSame( 'theme', $template->source );
$this->assertSame( 'wp_template', $template->type );
// Test template parts.
$id = get_stylesheet() . '//' . 'small-header';
$template = get_block_template( $id, 'wp_template_part' );
$this->assertSame( $id, $template->id );
$this->assertSame( get_stylesheet(), $template->theme );
$this->assertSame( 'small-header', $template->slug );
$this->assertSame( 'publish', $template->status );
$this->assertSame( 'theme', $template->source );
$this->assertSame( 'wp_template_part', $template->type );
$this->assertSame( WP_TEMPLATE_PART_AREA_HEADER, $template->area );
}
/**
* Should retrieve the template from the CPT.
*/
public function test_get_block_template_from_post() {
$id = get_stylesheet() . '//' . 'my_template';
$template = get_block_template( $id, 'wp_template' );
$this->assertSame( $id, $template->id );
$this->assertSame( get_stylesheet(), $template->theme );
$this->assertSame( 'my_template', $template->slug );
$this->assertSame( 'publish', $template->status );
$this->assertSame( 'custom', $template->source );
$this->assertSame( 'wp_template', $template->type );
// Test template parts.
$id = get_stylesheet() . '//' . 'my_template_part';
$template = get_block_template( $id, 'wp_template_part' );
$this->assertSame( $id, $template->id );
$this->assertSame( get_stylesheet(), $template->theme );
$this->assertSame( 'my_template_part', $template->slug );
$this->assertSame( 'publish', $template->status );
$this->assertSame( 'custom', $template->source );
$this->assertSame( 'wp_template_part', $template->type );
$this->assertSame( WP_TEMPLATE_PART_AREA_HEADER, $template->area );
}
/**
* Should flatten nested blocks
*/
public function test_flatten_blocks() {
$content_template_part_inside_group = '<!-- wp:group --><!-- wp:template-part {"slug":"header"} /--><!-- /wp:group -->';
$blocks = parse_blocks( $content_template_part_inside_group );
$actual = _flatten_blocks( $blocks );
$expected = array( $blocks[0], $blocks[0]['innerBlocks'][0] );
$this->assertSame( $expected, $actual );
$content_template_part_inside_group_inside_group = '<!-- wp:group --><!-- wp:group --><!-- wp:template-part {"slug":"header"} /--><!-- /wp:group --><!-- /wp:group -->';
$blocks = parse_blocks( $content_template_part_inside_group_inside_group );
$actual = _flatten_blocks( $blocks );
$expected = array( $blocks[0], $blocks[0]['innerBlocks'][0], $blocks[0]['innerBlocks'][0]['innerBlocks'][0] );
$this->assertSame( $expected, $actual );
$content_without_inner_blocks = '<!-- wp:group /-->';
$blocks = parse_blocks( $content_without_inner_blocks );
$actual = _flatten_blocks( $blocks );
$expected = array( $blocks[0] );
$this->assertSame( $expected, $actual );
}
/**
* Should generate block templates export file.
*
* @ticket 54448
* @requires extension zip
*/
public function test_wp_generate_block_templates_export_file() {
$filename = wp_generate_block_templates_export_file();
$this->assertFileExists( $filename, 'zip file is created at the specified path' );
$this->assertTrue( filesize( $filename ) > 0, 'zip file is larger than 0 bytes' );
// Open ZIP file and make sure the directories exist.
$zip = new ZipArchive();
$zip->open( $filename );
$has_theme_json = $zip->locateName( 'theme.json' ) !== false;
$has_block_templates_dir = $zip->locateName( 'templates/' ) !== false;
$has_block_template_parts_dir = $zip->locateName( 'parts/' ) !== false;
$this->assertTrue( $has_theme_json, 'theme.json exists' );
$this->assertTrue( $has_block_templates_dir, 'theme/templates directory exists' );
$this->assertTrue( $has_block_template_parts_dir, 'theme/parts directory exists' );
// ZIP file contains at least one HTML file.
$has_html_files = false;
$num_files = $zip->numFiles;
for ( $i = 0; $i < $num_files; $i++ ) {
$filename = $zip->getNameIndex( $i );
if ( '.html' === substr( $filename, -5 ) ) {
$has_html_files = true;
break;
}
}
$this->assertTrue( $has_html_files, 'contains at least one html file' );
}
}