render.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. <?php
  2. /**
  3. * Block rendering tests.
  4. *
  5. * @package WordPress
  6. * @subpackage Blocks
  7. * @since 5.0.0
  8. */
  9. /**
  10. * Tests for block rendering functions
  11. *
  12. * @since 5.0.0
  13. *
  14. * @group blocks
  15. */
  16. class WP_Test_Block_Render extends WP_UnitTestCase {
  17. /**
  18. * The location of the fixtures to test with.
  19. *
  20. * @since 5.0.0
  21. * @var string
  22. */
  23. protected static $fixtures_dir;
  24. /**
  25. * Test block instance number.
  26. *
  27. * @since 5.0.0
  28. *
  29. * @var int
  30. */
  31. protected $test_block_instance_number = 0;
  32. /**
  33. * Tear down after each test.
  34. *
  35. * @since 5.0.0
  36. */
  37. public function tearDown() {
  38. $this->test_block_instance_number = 0;
  39. $registry = WP_Block_Type_Registry::get_instance();
  40. if ( $registry->is_registered( 'core/test' ) ) {
  41. $registry->unregister( 'core/test' );
  42. }
  43. if ( $registry->is_registered( 'core/dynamic' ) ) {
  44. $registry->unregister( 'core/dynamic' );
  45. }
  46. parent::tearDown();
  47. }
  48. /**
  49. * @ticket 45109
  50. */
  51. public function test_do_blocks_removes_comments() {
  52. $original_html = file_get_contents( DIR_TESTDATA . '/blocks/do-blocks-original.html' );
  53. $expected_html = file_get_contents( DIR_TESTDATA . '/blocks/do-blocks-expected.html' );
  54. $actual_html = do_blocks( $original_html );
  55. $this->assertSameIgnoreEOL( $expected_html, $actual_html );
  56. }
  57. /**
  58. * @ticket 45109
  59. */
  60. public function test_the_content() {
  61. add_shortcode( 'someshortcode', array( $this, 'handle_shortcode' ) );
  62. $classic_content = "Foo\n\n[someshortcode]\n\nBar\n\n[/someshortcode]\n\nBaz";
  63. $block_content = "<!-- wp:core/paragraph -->\n<p>Foo</p>\n<!-- /wp:core/paragraph -->\n\n<!-- wp:core/shortcode -->[someshortcode]\n\nBar\n\n[/someshortcode]<!-- /wp:core/shortcode -->\n\n<!-- wp:core/paragraph -->\n<p>Baz</p>\n<!-- /wp:core/paragraph -->";
  64. $classic_filtered_content = apply_filters( 'the_content', $classic_content );
  65. $block_filtered_content = apply_filters( 'the_content', $block_content );
  66. // Block rendering add some extra blank lines, but we're not worried about them.
  67. $block_filtered_content = preg_replace( "/\n{2,}/", "\n", $block_filtered_content );
  68. remove_shortcode( 'someshortcode' );
  69. $this->assertSame( trim( $classic_filtered_content ), trim( $block_filtered_content ) );
  70. }
  71. function handle_shortcode( $atts, $content ) {
  72. return $content;
  73. }
  74. /**
  75. * @ticket 45495
  76. */
  77. function test_nested_calls_to_the_content() {
  78. register_block_type(
  79. 'core/test',
  80. array(
  81. 'render_callback' => array(
  82. $this,
  83. 'dynamic_the_content_call',
  84. ),
  85. )
  86. );
  87. $content = "foo\n\nbar";
  88. $the_content = apply_filters( 'the_content', '<!-- wp:core/test -->' . $content . '<!-- /wp:core/test -->' );
  89. $this->assertSame( $content, $the_content );
  90. }
  91. function dynamic_the_content_call( $attrs, $content ) {
  92. apply_filters( 'the_content', '' );
  93. return $content;
  94. }
  95. public function test_can_nest_at_least_so_deep() {
  96. $minimum_depth = 99;
  97. $content = 'deep inside';
  98. for ( $i = 0; $i < $minimum_depth; $i++ ) {
  99. $content = '<!-- wp:core/test -->' . $content . '<!-- /wp:core/test -->';
  100. }
  101. $this->assertSame( 'deep inside', do_blocks( $content ) );
  102. }
  103. public function test_can_nest_at_least_so_deep_with_dynamic_blocks() {
  104. $minimum_depth = 99;
  105. $content = '0';
  106. for ( $i = 0; $i < $minimum_depth; $i++ ) {
  107. $content = '<!-- wp:core/test -->' . $content . '<!-- /wp:core/test -->';
  108. }
  109. register_block_type(
  110. 'core/test',
  111. array(
  112. 'render_callback' => array(
  113. $this,
  114. 'render_dynamic_incrementer',
  115. ),
  116. )
  117. );
  118. $this->assertSame( $minimum_depth, (int) do_blocks( $content ) );
  119. }
  120. public function render_dynamic_incrementer( $attrs, $content ) {
  121. return (string) ( 1 + (int) $content );
  122. }
  123. /**
  124. * @ticket 45290
  125. */
  126. public function test_blocks_arent_autopeed() {
  127. $expected_content = 'test';
  128. $test_content = "<!-- wp:fake/block -->\n$expected_content\n<!-- /wp:fake/block -->";
  129. $current_priority = has_action( 'the_content', 'wpautop' );
  130. $filtered_content = trim( apply_filters( 'the_content', $test_content ) );
  131. $this->assertSame( $expected_content, $filtered_content );
  132. // Check that wpautop() is still defined in the same place.
  133. $this->assertSame( $current_priority, has_action( 'the_content', 'wpautop' ) );
  134. // ... and that the restore function has removed itself.
  135. $this->assertFalse( has_action( 'the_content', '_restore_wpautop_hook' ) );
  136. $test_content = 'test';
  137. $expected_content = "<p>$test_content</p>";
  138. $current_priority = has_action( 'the_content', 'wpautop' );
  139. $filtered_content = trim( apply_filters( 'the_content', $test_content ) );
  140. $this->assertSame( $expected_content, $filtered_content );
  141. $this->assertSame( $current_priority, has_action( 'the_content', 'wpautop' ) );
  142. $this->assertFalse( has_action( 'the_content', '_restore_wpautop_hook' ) );
  143. }
  144. /**
  145. * @ticket 45109
  146. */
  147. public function data_do_block_test_filenames() {
  148. self::$fixtures_dir = DIR_TESTDATA . '/blocks/fixtures';
  149. $fixture_filenames = array_merge(
  150. glob( self::$fixtures_dir . '/*.json' ),
  151. glob( self::$fixtures_dir . '/*.html' )
  152. );
  153. $fixture_filenames = array_values(
  154. array_unique(
  155. array_map(
  156. array( $this, 'clean_fixture_filename' ),
  157. $fixture_filenames
  158. )
  159. )
  160. );
  161. return array_map(
  162. array( $this, 'pass_parser_fixture_filenames' ),
  163. $fixture_filenames
  164. ); }
  165. /**
  166. * @dataProvider data_do_block_test_filenames
  167. * @ticket 45109
  168. */
  169. public function test_do_block_output( $html_filename, $server_html_filename ) {
  170. $html_path = self::$fixtures_dir . '/' . $html_filename;
  171. $server_html_path = self::$fixtures_dir . '/' . $server_html_filename;
  172. foreach ( array( $html_path, $server_html_path ) as $filename ) {
  173. if ( ! file_exists( $filename ) ) {
  174. throw new Exception( "Missing fixture file: '$filename'" );
  175. }
  176. }
  177. $html = do_blocks( self::strip_r( file_get_contents( $html_path ) ) );
  178. $expected_html = self::strip_r( file_get_contents( $server_html_path ) );
  179. $this->assertSame(
  180. $expected_html,
  181. $html,
  182. "File '$html_path' does not match expected value"
  183. );
  184. }
  185. /**
  186. * @ticket 45109
  187. */
  188. public function test_dynamic_block_rendering() {
  189. $settings = array(
  190. 'render_callback' => array(
  191. $this,
  192. 'render_test_block',
  193. ),
  194. );
  195. register_block_type( 'core/test', $settings );
  196. // The duplicated dynamic blocks below are there to ensure that do_blocks() replaces each one-by-one.
  197. $post_content =
  198. 'before' .
  199. '<!-- wp:core/test {"value":"b1"} --><!-- /wp:core/test -->' .
  200. '<!-- wp:core/test {"value":"b1"} --><!-- /wp:core/test -->' .
  201. 'between' .
  202. '<!-- wp:core/test {"value":"b2"} /-->' .
  203. '<!-- wp:core/test {"value":"b2"} /-->' .
  204. 'after';
  205. $updated_post_content = do_blocks( $post_content );
  206. $this->assertSame(
  207. $updated_post_content,
  208. 'before' .
  209. '1:b1' .
  210. '2:b1' .
  211. 'between' .
  212. '3:b2' .
  213. '4:b2' .
  214. 'after'
  215. );
  216. }
  217. /**
  218. * @ticket 45109
  219. */
  220. public function test_global_post_persistence() {
  221. global $post;
  222. register_block_type(
  223. 'core/test',
  224. array(
  225. 'render_callback' => array(
  226. $this,
  227. 'render_test_block_wp_query',
  228. ),
  229. )
  230. );
  231. $posts = self::factory()->post->create_many( 5 );
  232. $post = get_post( end( $posts ) );
  233. $global_post = $post;
  234. do_blocks( '<!-- wp:core/test /-->' );
  235. $this->assertSame( $global_post, $post );
  236. }
  237. public function test_render_latest_comments_on_password_protected_post() {
  238. $post_id = self::factory()->post->create(
  239. array(
  240. 'post_password' => 'password',
  241. )
  242. );
  243. $comment_text = wp_generate_password( 10, false );
  244. self::factory()->comment->create(
  245. array(
  246. 'comment_post_ID' => $post_id,
  247. 'comment_content' => $comment_text,
  248. )
  249. );
  250. $comments = do_blocks( '<!-- wp:latest-comments {"commentsToShow":1,"displayExcerpt":true} /-->' );
  251. $this->assertNotContains( $comment_text, $comments );
  252. }
  253. /**
  254. * @ticket 45109
  255. */
  256. public function test_dynamic_block_renders_string() {
  257. $settings = array(
  258. 'render_callback' => array(
  259. $this,
  260. 'render_test_block_numeric',
  261. ),
  262. );
  263. register_block_type( 'core/test', $settings );
  264. $block_type = new WP_Block_Type( 'core/test', $settings );
  265. $rendered = $block_type->render();
  266. $this->assertSame( '10', $rendered );
  267. $this->assertInternalType( 'string', $rendered );
  268. }
  269. public function test_dynamic_block_gets_inner_html() {
  270. register_block_type(
  271. 'core/dynamic',
  272. array(
  273. 'render_callback' => array(
  274. $this,
  275. 'render_serialize_dynamic_block',
  276. ),
  277. )
  278. );
  279. $output = do_blocks( '<!-- wp:dynamic -->inner<!-- /wp:dynamic -->' );
  280. $data = unserialize( base64_decode( $output ) );
  281. $this->assertSame( 'inner', $data[1] );
  282. }
  283. public function test_dynamic_block_gets_rendered_inner_blocks() {
  284. register_block_type(
  285. 'core/test',
  286. array(
  287. 'render_callback' => array(
  288. $this,
  289. 'render_test_block_numeric',
  290. ),
  291. )
  292. );
  293. register_block_type(
  294. 'core/dynamic',
  295. array(
  296. 'render_callback' => array(
  297. $this,
  298. 'render_serialize_dynamic_block',
  299. ),
  300. )
  301. );
  302. $output = do_blocks( '<!-- wp:dynamic -->before<!-- wp:test /-->after<!-- /wp:dynamic -->' );
  303. $data = unserialize( base64_decode( $output ) );
  304. $this->assertSame( 'before10after', $data[1] );
  305. }
  306. public function test_dynamic_block_gets_rendered_inner_dynamic_blocks() {
  307. register_block_type(
  308. 'core/dynamic',
  309. array(
  310. 'render_callback' => array(
  311. $this,
  312. 'render_serialize_dynamic_block',
  313. ),
  314. )
  315. );
  316. $output = do_blocks( '<!-- wp:dynamic -->before<!-- wp:dynamic -->deep inner<!-- /wp:dynamic -->after<!-- /wp:dynamic -->' );
  317. $data = unserialize( base64_decode( $output ) );
  318. $inner = $this->render_serialize_dynamic_block( array(), 'deep inner' );
  319. $this->assertSame( $data[1], 'before' . $inner . 'after' );
  320. }
  321. /**
  322. * Helper function to remove relative paths and extension from a filename, leaving just the fixture name.
  323. *
  324. * @since 5.0.0
  325. *
  326. * @param string $filename The filename to clean.
  327. * @return string The cleaned fixture name.
  328. */
  329. protected function clean_fixture_filename( $filename ) {
  330. $filename = wp_basename( $filename );
  331. $filename = preg_replace( '/\..+$/', '', $filename );
  332. return $filename;
  333. }
  334. /**
  335. * Helper function to return the filenames needed to test the parser output.
  336. *
  337. * @since 5.0.0
  338. *
  339. * @param string $filename The cleaned fixture name.
  340. * @return array The input and expected output filenames for that fixture.
  341. */
  342. protected function pass_parser_fixture_filenames( $filename ) {
  343. return array(
  344. "$filename.html",
  345. "$filename.server.html",
  346. );
  347. }
  348. /**
  349. * Helper function to remove '\r' characters from a string.
  350. *
  351. * @since 5.0.0
  352. *
  353. * @param string $input The string to remove '\r' from.
  354. * @return string The input string, with '\r' characters removed.
  355. */
  356. protected function strip_r( $input ) {
  357. return str_replace( "\r", '', $input );
  358. }
  359. /**
  360. * Test block rendering function.
  361. *
  362. * @since 5.0.0
  363. *
  364. * @param array $attributes Block attributes.
  365. * @return string Block output.
  366. */
  367. public function render_test_block( $attributes ) {
  368. $this->test_block_instance_number += 1;
  369. return $this->test_block_instance_number . ':' . $attributes['value'];
  370. }
  371. /**
  372. * Test block rendering function, returning numeric value.
  373. *
  374. * @since 5.0.0
  375. *
  376. * @return int Block output.
  377. */
  378. public function render_test_block_numeric() {
  379. return 10;
  380. }
  381. /**
  382. * Test block rendering function, returning base64 encoded serialised value.
  383. *
  384. * @since 5.0.0
  385. *
  386. * @return string Block output.
  387. */
  388. public function render_serialize_dynamic_block( $attributes, $content ) {
  389. return base64_encode( serialize( array( $attributes, $content ) ) );
  390. }
  391. /**
  392. * Test block rendering function, creating a new WP_Query instance.
  393. *
  394. * @since 5.0.0
  395. *
  396. * @return string Block output.
  397. */
  398. public function render_test_block_wp_query() {
  399. $content = '';
  400. $recent = new WP_Query(
  401. array(
  402. 'numberposts' => 10,
  403. 'orderby' => 'ID',
  404. 'order' => 'DESC',
  405. 'post_type' => 'post',
  406. 'post_status' => 'draft, publish, future, pending, private',
  407. 'suppress_filters' => true,
  408. )
  409. );
  410. while ( $recent->have_posts() ) {
  411. $recent->the_post();
  412. $content .= get_the_title();
  413. }
  414. wp_reset_postdata();
  415. return $content;
  416. }
  417. }