Changeset 58327
- Timestamp:
- 06/04/2024 10:59:01 AM (4 months ago)
- Location:
- trunk
- Files:
-
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/interactivity-api/class-wp-interactivity-api.php
r58321 r58327 75 75 76 76 /** 77 * Stack of namespaces defined by `data-wp-interactive` directives, in 78 * the order they are processed. 79 * 80 * This is only available during directive processing, otherwise it is `null`. 81 * 82 * @since 6.6.0 83 * @var array<string>|null 84 */ 85 private $namespace_stack = null; 86 87 /** 88 * Stack of contexts defined by `data-wp-context` directives, in 89 * the order they are processed. 90 * 91 * This is only available during directive processing, otherwise it is `null`. 92 * 93 * @since 6.6.0 94 * @var array<array<mixed>>|null 95 */ 96 private $context_stack = null; 97 98 /** 77 99 * Gets and/or sets the initial state of an Interactivity API store for a 78 100 * given namespace. … … 81 103 * provided state with the existing one. 82 104 * 83 * @since 6.5.0 84 * 85 * @param string $store_namespace The unique store namespace identifier. 105 * When no namespace is specified, it returns the state defined for the 106 * current value in the internal namespace stack during a `process_directives` call. 107 * 108 * @since 6.5.0 109 * @since 6.6.0 The `$store_namespace` param is optional. 110 * 111 * @param string $store_namespace Optional. The unique store namespace identifier. 86 112 * @param array $state Optional. The array that will be merged with the existing state for the specified 87 113 * store namespace. … … 89 115 * argument was provided. 90 116 */ 91 public function state( string $store_namespace, array $state = array() ): array { 117 public function state( ?string $store_namespace = null, ?array $state = null ): array { 118 if ( ! $store_namespace ) { 119 if ( $state ) { 120 _doing_it_wrong( 121 __METHOD__, 122 __( 'The namespace is required when state data is passed.' ), 123 '6.6.0' 124 ); 125 return array(); 126 } 127 if ( null !== $store_namespace ) { 128 _doing_it_wrong( 129 __METHOD__, 130 __( 'The namespace should be a non-empty string.' ), 131 '6.6.0' 132 ); 133 return array(); 134 } 135 if ( null === $this->namespace_stack ) { 136 _doing_it_wrong( 137 __METHOD__, 138 __( 'The namespace can only be omitted during directive processing.' ), 139 '6.6.0' 140 ); 141 return array(); 142 } 143 144 $store_namespace = end( $this->namespace_stack ); 145 } 92 146 if ( ! isset( $this->state_data[ $store_namespace ] ) ) { 93 147 $this->state_data[ $store_namespace ] = array(); … … 213 267 214 268 /** 269 * Returns the latest value on the context stack with the passed namespace. 270 * 271 * When the namespace is omitted, it uses the current namespace on the 272 * namespace stack during a `process_directives` call. 273 * 274 * @since 6.6.0 275 * 276 * @param string $store_namespace Optional. The unique store namespace identifier. 277 */ 278 public function get_context( ?string $store_namespace = null ): array { 279 if ( null === $this->context_stack ) { 280 _doing_it_wrong( 281 __METHOD__, 282 __( 'The context can only be read during directive processing.' ), 283 '6.6.0' 284 ); 285 return array(); 286 } 287 288 if ( ! $store_namespace ) { 289 if ( null !== $store_namespace ) { 290 _doing_it_wrong( 291 __METHOD__, 292 __( 'The namespace should be a non-empty string.' ), 293 '6.6.0' 294 ); 295 return array(); 296 } 297 298 $store_namespace = end( $this->namespace_stack ); 299 } 300 301 $context = end( $this->context_stack ); 302 303 return ( $store_namespace && $context && isset( $context[ $store_namespace ] ) ) 304 ? $context[ $store_namespace ] 305 : array(); 306 } 307 308 /** 215 309 * Registers the `@wordpress/interactivity` script modules. 216 310 * … … 259 353 } 260 354 261 $context_stack = array(); 262 $namespace_stack = array(); 263 $result = $this->process_directives_args( $html, $context_stack, $namespace_stack ); 355 $this->namespace_stack = array(); 356 $this->context_stack = array(); 357 358 $result = $this->_process_directives( $html ); 359 360 $this->namespace_stack = null; 361 $this->context_stack = null; 362 264 363 return null === $result ? $html : $result; 265 364 } … … 269 368 * and updates the markup accordingly. 270 369 * 271 * It needs the context and namespace stacks to be passed by reference, and 272 * it returns null if the HTML contains unbalanced tags. 273 * 274 * @since 6.5.0 275 * @since 6.6.0 The function displays a warning message when the HTML contains unbalanced tags or a directive appears in a MATH or SVG tag. 276 * 277 * @param string $html The HTML content to process. 278 * @param array $context_stack The reference to the array used to keep track of contexts during processing. 279 * @param array $namespace_stack The reference to the array used to manage namespaces during processing. 370 * It uses the WP_Interactivity_API instance's context and namespace stacks, 371 * which are shared between all calls. 372 * 373 * This method returns null if the HTML contains unbalanced tags. 374 * 375 * @since 6.6.0 376 * 377 * @param string $html The HTML content to process. 280 378 * @return string|null The processed HTML content. It returns null when the HTML contains unbalanced tags. 281 379 */ 282 private function process_directives_args( string $html, array &$context_stack, array &$namespace_stack) {380 private function _process_directives( string $html ) { 283 381 $p = new WP_Interactivity_API_Directives_Processor( $html ); 284 382 $tag_stack = array(); … … 287 385 $directive_processor_prefixes = array_keys( self::$directive_processors ); 288 386 $directive_processor_prefixes_reversed = array_reverse( $directive_processor_prefixes ); 387 388 /* 389 * Save the current size for each stack to restore them in case 390 * the processing finds unbalanced tags. 391 */ 392 $namespace_stack_size = count( $this->namespace_stack ); 393 $context_stack_size = count( $this->context_stack ); 289 394 290 395 while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) { … … 299 404 if ( $p->get_attribute_names_with_prefix( 'data-wp-' ) ) { 300 405 /* translators: 1: SVG or MATH HTML tag, 2: Namespace of the interactive block. */ 301 $message = sprintf( __( 'Interactivity directives were detected on an incompatible %1$s tag when processing "%2$s". These directives will be ignored in the server side render.' ), $tag_name, end( $ namespace_stack ) );406 $message = sprintf( __( 'Interactivity directives were detected on an incompatible %1$s tag when processing "%2$s". These directives will be ignored in the server side render.' ), $tag_name, end( $this->namespace_stack ) ); 302 407 _doing_it_wrong( __METHOD__, $message, '6.6.0' ); 303 408 } … … 382 487 : array( $this, self::$directive_processors[ $directive_prefix ] ); 383 488 384 call_user_func_array( 385 $func, 386 array( $p, $mode, &$context_stack, &$namespace_stack, &$tag_stack ) 387 ); 388 } 389 } 390 } 489 call_user_func_array( $func, array( $p, $mode, &$tag_stack ) ); 490 } 491 } 492 } 493 494 if ( $unbalanced ) { 495 // Reset the namespace and context stacks to their previous values. 496 array_splice( $this->namespace_stack, $namespace_stack_size ); 497 array_splice( $this->context_stack, $context_stack_size ); 498 } 499 391 500 /* 392 501 * It returns null if the HTML is unbalanced because unbalanced HTML is … … 398 507 $tag_errored = 0 < count( $tag_stack ) ? end( $tag_stack )[0] : $tag_name; 399 508 /* translators: %1s: Namespace processed, %2s: The tag that caused the error; could be any HTML tag. */ 400 $message = sprintf( __( 'Interactivity directives failed to process in "%1$s" due to a missing "%2$s" end tag.' ), end( $ namespace_stack ), $tag_errored );509 $message = sprintf( __( 'Interactivity directives failed to process in "%1$s" due to a missing "%2$s" end tag.' ), end( $this->namespace_stack ), $tag_errored ); 401 510 _doing_it_wrong( __METHOD__, $message, '6.6.0' ); 402 511 return null; … … 412 521 * @since 6.5.0 413 522 * @since 6.6.0 The function now adds a warning when the namespace is null, falsy, or the directive value is empty. 414 * 415 * @param string|true $directive_value The directive attribute value string or `true` when it's a boolean attribute. 416 * @param string $default_namespace The default namespace to use if none is explicitly defined in the directive 417 * value. 418 * @param array|false $context The current context for evaluating the directive or false if there is no 419 * context. 523 * @since 6.6.0 Removed `default_namespace` and `context` arguments. 524 * 525 * @param string|true $directive_value The directive attribute value string or `true` when it's a boolean attribute. 420 526 * @return mixed|null The result of the evaluation. Null if the reference path doesn't exist or the namespace is falsy. 421 527 */ 422 private function evaluate( $directive_value, string $default_namespace, $context = false ) { 528 private function evaluate( $directive_value ) { 529 $default_namespace = end( $this->namespace_stack ); 530 $context = end( $this->context_stack ); 531 423 532 list( $ns, $path ) = $this->extract_directive_value( $directive_value, $default_namespace ); 424 533 if ( ! $ns || ! $path ) { … … 451 560 } 452 561 562 if ( $current instanceof Closure ) { 563 /* 564 * This state getter's namespace is added to the stack so that 565 * `state()` or `get_config()` read that namespace when called 566 * without specifying one. 567 */ 568 array_push( $this->namespace_stack, $ns ); 569 try { 570 $current = $current(); 571 } catch ( Throwable $e ) { 572 _doing_it_wrong( 573 __METHOD__, 574 sprintf( 575 /* translators: 1: Path pointing to an Interactivity API state property, 2: Namespace for an Interactivity API store. */ 576 __( 'Uncaught error executing a derived state callback with path "%1$s" and namespace "%2$s".' ), 577 $path, 578 $ns 579 ), 580 '6.6.0' 581 ); 582 return null; 583 } finally { 584 // Remove the property's namespace from the stack. 585 array_pop( $this->namespace_stack ); 586 } 587 } 588 453 589 // Returns the opposite if it contains a negation operator (!). 454 590 return $should_negate_value ? ! $current : $current; … … 552 688 * @since 6.5.0 553 689 * 554 * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. 555 * @param string $mode Whether the processing is entering or exiting the tag. 556 * @param array $context_stack The reference to the context stack. 557 * @param array $namespace_stack The reference to the store namespace stack. 558 */ 559 private function data_wp_interactive_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) { 690 * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. 691 * @param string $mode Whether the processing is entering or exiting the tag. 692 */ 693 private function data_wp_interactive_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) { 560 694 // When exiting tags, it removes the last namespace from the stack. 561 695 if ( 'exit' === $mode ) { 562 array_pop( $ namespace_stack );696 array_pop( $this->namespace_stack ); 563 697 return; 564 698 } … … 584 718 } 585 719 } 586 $ namespace_stack[] = ( $new_namespace && 1 === preg_match( '/^([\w\-_\/]+)/', $new_namespace ) )720 $this->namespace_stack[] = ( $new_namespace && 1 === preg_match( '/^([\w\-_\/]+)/', $new_namespace ) ) 587 721 ? $new_namespace 588 : end( $ namespace_stack );722 : end( $this->namespace_stack ); 589 723 } 590 724 … … 599 733 * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. 600 734 * @param string $mode Whether the processing is entering or exiting the tag. 601 * @param array $context_stack The reference to the context stack. 602 * @param array $namespace_stack The reference to the store namespace stack. 603 */ 604 private function data_wp_context_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) { 735 */ 736 private function data_wp_context_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) { 605 737 // When exiting tags, it removes the last context from the stack. 606 738 if ( 'exit' === $mode ) { 607 array_pop( $ context_stack );739 array_pop( $this->context_stack ); 608 740 return; 609 741 } 610 742 611 743 $attribute_value = $p->get_attribute( 'data-wp-context' ); 612 $namespace_value = end( $ namespace_stack );744 $namespace_value = end( $this->namespace_stack ); 613 745 614 746 // Separates the namespace from the context JSON object. … … 622 754 */ 623 755 if ( is_string( $namespace_value ) ) { 624 $ context_stack[] = array_replace_recursive(625 end( $ context_stack ) !== false ? end( $context_stack ) : array(),756 $this->context_stack[] = array_replace_recursive( 757 end( $this->context_stack ) !== false ? end( $this->context_stack ) : array(), 626 758 array( $namespace_value => is_array( $decoded_json ) ? $decoded_json : array() ) 627 759 ); … … 632 764 * from the stack whenever it finds a `data-wp-context`'s closing tag. 633 765 */ 634 $ context_stack[] = end( $context_stack );766 $this->context_stack[] = end( $this->context_stack ); 635 767 } 636 768 } … … 646 778 * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. 647 779 * @param string $mode Whether the processing is entering or exiting the tag. 648 * @param array $context_stack The reference to the context stack. 649 * @param array $namespace_stack The reference to the store namespace stack. 650 */ 651 private function data_wp_bind_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) { 780 */ 781 private function data_wp_bind_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) { 652 782 if ( 'enter' === $mode ) { 653 783 $all_bind_directives = $p->get_attribute_names_with_prefix( 'data-wp-bind--' ); … … 660 790 661 791 $attribute_value = $p->get_attribute( $attribute_name ); 662 $result = $this->evaluate( $attribute_value , end( $namespace_stack ), end( $context_stack ));792 $result = $this->evaluate( $attribute_value ); 663 793 664 794 if ( … … 700 830 * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. 701 831 * @param string $mode Whether the processing is entering or exiting the tag. 702 * @param array $context_stack The reference to the context stack. 703 * @param array $namespace_stack The reference to the store namespace stack. 704 */ 705 private function data_wp_class_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) { 832 */ 833 private function data_wp_class_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) { 706 834 if ( 'enter' === $mode ) { 707 835 $all_class_directives = $p->get_attribute_names_with_prefix( 'data-wp-class--' ); … … 714 842 715 843 $attribute_value = $p->get_attribute( $attribute_name ); 716 $result = $this->evaluate( $attribute_value , end( $namespace_stack ), end( $context_stack ));844 $result = $this->evaluate( $attribute_value ); 717 845 718 846 if ( $result ) { … … 735 863 * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. 736 864 * @param string $mode Whether the processing is entering or exiting the tag. 737 * @param array $context_stack The reference to the context stack. 738 * @param array $namespace_stack The reference to the store namespace stack. 739 */ 740 private function data_wp_style_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) { 865 */ 866 private function data_wp_style_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) { 741 867 if ( 'enter' === $mode ) { 742 868 $all_style_attributes = $p->get_attribute_names_with_prefix( 'data-wp-style--' ); … … 749 875 750 876 $directive_attribute_value = $p->get_attribute( $attribute_name ); 751 $style_property_value = $this->evaluate( $directive_attribute_value , end( $namespace_stack ), end( $context_stack ));877 $style_property_value = $this->evaluate( $directive_attribute_value ); 752 878 $style_attribute_value = $p->get_attribute( 'style' ); 753 879 $style_attribute_value = ( $style_attribute_value && ! is_bool( $style_attribute_value ) ) ? $style_attribute_value : ''; … … 828 954 * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. 829 955 * @param string $mode Whether the processing is entering or exiting the tag. 830 * @param array $context_stack The reference to the context stack. 831 * @param array $namespace_stack The reference to the store namespace stack. 832 */ 833 private function data_wp_text_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) { 956 */ 957 private function data_wp_text_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) { 834 958 if ( 'enter' === $mode ) { 835 959 $attribute_value = $p->get_attribute( 'data-wp-text' ); 836 $result = $this->evaluate( $attribute_value , end( $namespace_stack ), end( $context_stack ));960 $result = $this->evaluate( $attribute_value ); 837 961 838 962 /* … … 966 1090 * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. 967 1091 * @param string $mode Whether the processing is entering or exiting the tag. 968 * @param array $context_stack The reference to the context stack.969 * @param array $namespace_stack The reference to the store namespace stack.970 1092 * @param array $tag_stack The reference to the tag stack. 971 1093 */ 972 private function data_wp_each_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$ context_stack, array &$namespace_stack, array &$tag_stack ) {1094 private function data_wp_each_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$tag_stack ) { 973 1095 if ( 'enter' === $mode && 'TEMPLATE' === $p->get_tag() ) { 974 1096 $attribute_name = $p->get_attribute_names_with_prefix( 'data-wp-each' )[0]; … … 976 1098 $item_name = isset( $extracted_suffix[1] ) ? $this->kebab_to_camel_case( $extracted_suffix[1] ) : 'item'; 977 1099 $attribute_value = $p->get_attribute( $attribute_name ); 978 $result = $this->evaluate( $attribute_value , end( $namespace_stack ), end( $context_stack ));1100 $result = $this->evaluate( $attribute_value ); 979 1101 980 1102 // Gets the content between the template tags and leaves the cursor in the closer tag. … … 1010 1132 1011 1133 // Extracts the namespace from the directive attribute value. 1012 $namespace_value = end( $ namespace_stack );1134 $namespace_value = end( $this->namespace_stack ); 1013 1135 list( $namespace_value ) = is_string( $attribute_value ) && ! empty( $attribute_value ) 1014 1136 ? $this->extract_directive_value( $attribute_value, $namespace_value ) … … 1019 1141 foreach ( $result as $item ) { 1020 1142 // Creates a new context that includes the current item of the array. 1021 $ context_stack[] = array_replace_recursive(1022 end( $ context_stack ) !== false ? end( $context_stack ) : array(),1143 $this->context_stack[] = array_replace_recursive( 1144 end( $this->context_stack ) !== false ? end( $this->context_stack ) : array(), 1023 1145 array( $namespace_value => array( $item_name => $item ) ) 1024 1146 ); 1025 1147 1026 1148 // Processes the inner content with the new context. 1027 $processed_item = $this-> process_directives_args( $inner_content, $context_stack, $namespace_stack);1149 $processed_item = $this->_process_directives( $inner_content ); 1028 1150 1029 1151 if ( null === $processed_item ) { 1030 1152 // If the HTML is unbalanced, stop processing it. 1031 array_pop( $ context_stack );1153 array_pop( $this->context_stack ); 1032 1154 return; 1033 1155 } … … 1042 1164 1043 1165 // Removes the current context from the stack. 1044 array_pop( $ context_stack );1166 array_pop( $this->context_stack ); 1045 1167 } 1046 1168 -
trunk/src/wp-includes/interactivity-api/interactivity-api.php
r58234 r58327 48 48 * provided state with the existing one. 49 49 * 50 * The namespace can be omitted inside derived state getters, using the 51 * namespace where the getter is defined. 52 * 50 53 * @since 6.5.0 54 * @since 6.6.0 The namespace can be omitted when called inside derived state getters. 51 55 * 52 56 * @param string $store_namespace The unique store namespace identifier. … … 56 60 * provided. 57 61 */ 58 function wp_interactivity_state( string $store_namespace, array $state = array() ): array {62 function wp_interactivity_state( ?string $store_namespace = null, array $state = array() ): array { 59 63 return wp_interactivity()->state( $store_namespace, $state ); 60 64 } … … 104 108 '\''; 105 109 } 110 111 /** 112 * Gets the current Interactivity API context for a given namespace. 113 * 114 * The function should be used only during directive processing. If the 115 * `$store_namespace` parameter is omitted, it uses the current namespace value 116 * on the internal namespace stack. 117 * 118 * It returns an empty array when the specified namespace is not defined. 119 * 120 * @since 6.6.0 121 * 122 * @param string $store_namespace Optional. The unique store namespace identifier. 123 * @return array The context for the specified store namespace. 124 */ 125 function wp_interactivity_get_context( ?string $store_namespace = null ): array { 126 return wp_interactivity()->get_context( $store_namespace ); 127 } -
trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-each.php
r58321 r58327 582 582 * @covers ::process_directives 583 583 * 584 * @expectedIncorrectUsage WP_Interactivity_API:: process_directives_args584 * @expectedIncorrectUsage WP_Interactivity_API::_process_directives 585 585 */ 586 586 public function test_wp_each_unbalanced_tags() { … … 602 602 * @covers ::process_directives 603 603 * 604 * @expectedIncorrectUsage WP_Interactivity_API:: process_directives_args604 * @expectedIncorrectUsage WP_Interactivity_API::_process_directives 605 605 */ 606 606 public function test_wp_each_unbalanced_tags_in_nested_template_tags() { -
trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-text.php
r58321 r58327 133 133 * @covers ::process_directives 134 134 * 135 * @expectedIncorrectUsage WP_Interactivity_API:: process_directives_args135 * @expectedIncorrectUsage WP_Interactivity_API::_process_directives 136 136 */ 137 137 public function test_wp_text_fails_with_unbalanced_and_same_tags_inside_content() { -
trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php
r58321 r58327 33 33 34 34 /** 35 * Modifies the internal namespace stack as if the WP_Interactivity_API 36 * instance had found `data-wp-interactive` directives during 37 * `process_directives` execution. 38 * 39 * @param array<string> $stack Values for the internal namespace stack. 40 */ 41 private function set_internal_namespace_stack( ...$stack ) { 42 $interactivity = new ReflectionClass( $this->interactivity ); 43 $namespace_stack = $interactivity->getProperty( 'namespace_stack' ); 44 $namespace_stack->setAccessible( true ); 45 $namespace_stack->setValue( $this->interactivity, $stack ); 46 } 47 48 /** 49 * Modifies the internal context stack as if the WP_Interactivity_API 50 * instance had found `data-wp-context` directives during 51 * `process_directives` execution. 52 * 53 * @param array<array<mixed>> $stack Values for the internal context stack. 54 */ 55 private function set_internal_context_stack( ...$stack ) { 56 $interactivity = new ReflectionClass( $this->interactivity ); 57 $context_stack = $interactivity->getProperty( 'context_stack' ); 58 $context_stack->setAccessible( true ); 59 $context_stack->setValue( $this->interactivity, $stack ); 60 } 61 62 /** 35 63 * Tests that the state and config methods return an empty array at the 36 64 * beginning. … … 424 452 JSON; 425 453 $this->assertEquals( $expected, $interactivity_data_string[1] ); 454 } 455 456 /** 457 * Test that calling state without a namespace arg returns the state data 458 * for the current namespace in the internal namespace stack. 459 * 460 * @ticket 61037 461 * 462 * @covers ::state 463 */ 464 public function test_state_without_namespace() { 465 $this->set_internal_namespace_stack( 'myPlugin' ); 466 467 $this->interactivity->state( 'myPlugin', array( 'a' => 1 ) ); 468 $this->interactivity->state( 'otherPlugin', array( 'b' => 2 ) ); 469 470 $this->assertEquals( 471 array( 'a' => 1 ), 472 $this->interactivity->state() 473 ); 474 } 475 476 /** 477 * Test that passing state data without a valid namespace does nothing and 478 * just returns an empty array. 479 * 480 * @ticket 61037 481 * 482 * @covers ::state 483 * @expectedIncorrectUsage WP_Interactivity_API::state 484 */ 485 public function test_state_with_data_and_invalid_namespace() { 486 $this->set_internal_namespace_stack( 'myPlugin' ); 487 488 $this->interactivity->state( 'myPlugin', array( 'a' => 1 ) ); 489 $this->interactivity->state( 'otherPlugin', array( 'b' => 2 ) ); 490 491 $this->assertEquals( 492 array(), 493 $this->interactivity->state( null, array( 'newProp' => 'value' ) ) 494 ); 495 } 496 497 /** 498 * Test that calling state with an empty string as namespace is not allowed. 499 * 500 * @ticket 61037 501 * 502 * @covers ::state 503 * @expectedIncorrectUsage WP_Interactivity_API::state 504 */ 505 public function test_state_with_empty_string_as_namespace() { 506 $this->set_internal_namespace_stack( 'myPlugin' ); 507 508 $this->interactivity->state( 'myPlugin', array( 'a' => 1 ) ); 509 $this->interactivity->state( 'otherPlugin', array( 'b' => 2 ) ); 510 511 $this->assertEquals( 512 array(), 513 $this->interactivity->state( '' ) 514 ); 515 } 516 517 /** 518 * Tests that calling state without namespace outside of 519 * `process_directives` execution is not allowed. 520 * 521 * @ticket 61037 522 * 523 * @covers ::state 524 * @expectedIncorrectUsage WP_Interactivity_API::state 525 */ 526 public function test_state_without_namespace_outside_directive_processing() { 527 $this->assertEquals( 528 array(), 529 $this->interactivity->state() 530 ); 531 } 532 533 /** 534 * Test that `get_context` returns the latest context value for the given 535 * namespace. 536 * 537 * @ticket 61037 538 * 539 * @covers ::get_context 540 */ 541 public function test_get_context_with_namespace() { 542 $this->set_internal_namespace_stack( 'myPlugin' ); 543 $this->set_internal_context_stack( 544 array( 545 'myPlugin' => array( 'a' => 0 ), 546 ), 547 array( 548 'myPlugin' => array( 'a' => 1 ), 549 'otherPlugin' => array( 'b' => 2 ), 550 ) 551 ); 552 553 $this->assertEquals( 554 array( 'a' => 1 ), 555 $this->interactivity->get_context( 'myPlugin' ) 556 ); 557 $this->assertEquals( 558 array( 'b' => 2 ), 559 $this->interactivity->get_context( 'otherPlugin' ) 560 ); 561 } 562 563 /** 564 * Test that `get_context` uses the current namespace in the internal 565 * namespace stack when the parameter is omitted. 566 * 567 * @ticket 61037 568 * 569 * @covers ::get_context 570 */ 571 public function test_get_context_without_namespace() { 572 $this->set_internal_namespace_stack( 'myPlugin' ); 573 $this->set_internal_context_stack( 574 array( 575 'myPlugin' => array( 'a' => 0 ), 576 ), 577 array( 578 'myPlugin' => array( 'a' => 1 ), 579 'otherPlugin' => array( 'b' => 2 ), 580 ) 581 ); 582 583 $this->assertEquals( 584 array( 'a' => 1 ), 585 $this->interactivity->get_context() 586 ); 587 } 588 589 /** 590 * Test that `get_context` returns an empty array when the context stack is 591 * empty. 592 * 593 * @ticket 61037 594 * 595 * @covers ::get_context 596 */ 597 public function test_get_context_with_empty_context_stack() { 598 $this->set_internal_namespace_stack( 'myPlugin' ); 599 $this->set_internal_context_stack(); 600 601 $this->assertEquals( 602 array(), 603 $this->interactivity->get_context( 'myPlugin' ) 604 ); 605 } 606 607 /** 608 * Test that `get_context` returns an empty array if the given namespace is 609 * not defined. 610 * 611 * @ticket 61037 612 * 613 * @covers ::get_context 614 */ 615 public function test_get_context_with_undefined_namespace() { 616 $this->set_internal_namespace_stack( 'myPlugin' ); 617 $this->set_internal_context_stack( 618 array( 619 'myPlugin' => array( 'a' => 0 ), 620 ), 621 array( 622 'myPlugin' => array( 'a' => 1 ), 623 ) 624 ); 625 626 $this->assertEquals( 627 array(), 628 $this->interactivity->get_context( 'otherPlugin' ) 629 ); 630 } 631 632 /** 633 * Test that `get_context` should not be called with an empty string. 634 * 635 * @ticket 61037 636 * 637 * @covers ::get_context 638 * @expectedIncorrectUsage WP_Interactivity_API::get_context 639 */ 640 public function test_get_context_with_empty_namespace() { 641 $this->set_internal_namespace_stack( 'myPlugin' ); 642 $this->set_internal_context_stack( 643 array( 644 'myPlugin' => array( 'a' => 0 ), 645 ), 646 array( 647 'myPlugin' => array( 'a' => 1 ), 648 ) 649 ); 650 651 $this->assertEquals( 652 array(), 653 $this->interactivity->get_context( '' ) 654 ); 655 } 656 657 658 /** 659 * Tests that `get_context` should not be called outside of 660 * `process_directives` execution. 661 * 662 * @ticket 61037 663 * 664 * @covers ::get_context 665 * @expectedIncorrectUsage WP_Interactivity_API::get_context 666 */ 667 public function test_get_context_outside_of_directive_processing() { 668 $context = $this->interactivity->get_context(); 669 $this->assertEquals( array(), $context ); 426 670 } 427 671 … … 650 894 * @dataProvider data_html_with_unbalanced_tags 651 895 * 652 * @expectedIncorrectUsage WP_Interactivity_API:: process_directives_args896 * @expectedIncorrectUsage WP_Interactivity_API::_process_directives 653 897 * 654 898 * @param string $html HTML containing unbalanced tags and also a directive. … … 749 993 * 750 994 * @covers ::process_directives 751 * @expectedIncorrectUsage WP_Interactivity_API:: process_directives_args995 * @expectedIncorrectUsage WP_Interactivity_API::_process_directives 752 996 */ 753 997 public function test_process_directives_change_html_if_contains_math() { … … 784 1028 * 785 1029 * @covers ::process_directives 786 * @expectedIncorrectUsage WP_Interactivity_API:: process_directives_args1030 * @expectedIncorrectUsage WP_Interactivity_API::_process_directives 787 1031 * @expectedIncorrectUsage WP_Interactivity_API_Directives_Processor::skip_to_tag_closer 788 1032 */ … … 814 1058 * 815 1059 * @param string $directive_value The directive attribute value to evaluate. 816 * @param string $default_namespace The default namespace used with directives.817 1060 * @return mixed The result of the evaluate method. 818 1061 */ 819 private function evaluate( $directive_value, $default_namespace = 'myPlugin' ) { 820 $generate_state = function ( $name ) { 821 $obj = new stdClass(); 822 $obj->prop = $name; 823 return array( 824 'key' => $name, 825 'nested' => array( 'key' => $name . '-nested' ), 1062 private function evaluate( $directive_value ) { 1063 /* 1064 * The global WP_Interactivity_API instance is momentarily replaced to 1065 * make global functions like `wp_interactivity_state` and 1066 * `wp_interactivity_get_config` work as expected. 1067 */ 1068 global $wp_interactivity; 1069 $wp_interactivity_prev = $wp_interactivity; 1070 $wp_interactivity = $this->interactivity; 1071 1072 $evaluate = new ReflectionMethod( $this->interactivity, 'evaluate' ); 1073 $evaluate->setAccessible( true ); 1074 1075 $result = $evaluate->invokeArgs( $this->interactivity, array( $directive_value ) ); 1076 1077 // Restore the original WP_Interactivity_API instance. 1078 $wp_interactivity = $wp_interactivity_prev; 1079 1080 return $result; 1081 } 1082 1083 /** 1084 * Tests that the `evaluate` method operates correctly for valid expressions. 1085 * 1086 * @ticket 60356 1087 * 1088 * @covers ::evaluate 1089 */ 1090 public function test_evaluate_value() { 1091 $obj = new stdClass(); 1092 $obj->prop = 'object property'; 1093 $this->interactivity->state( 1094 'myPlugin', 1095 array( 1096 'key' => 'myPlugin-state', 826 1097 'obj' => $obj, 827 1098 'arrAccess' => new class() implements ArrayAccess { 828 #[\ReturnTypeWillChange] 829 public function offsetExists( $offset ) { 1099 public function offsetExists( $offset ): bool { 830 1100 return true; 831 1101 } 832 1102 833 public function offsetGet( $offset ): string { 1103 #[\ReturnTypeWillChange] 1104 public function offsetGet( $offset ) { 834 1105 return $offset; 835 1106 } … … 839 1110 public function offsetUnset( $offset ): void {} 840 1111 }, 841 ); 842 }; 843 $this->interactivity->state( 'myPlugin', $generate_state( 'myPlugin-state' ) ); 844 $this->interactivity->state( 'otherPlugin', $generate_state( 'otherPlugin-state' ) ); 845 $context = array( 846 'myPlugin' => $generate_state( 'myPlugin-context' ), 847 'otherPlugin' => $generate_state( 'otherPlugin-context' ), 848 'obj' => new stdClass(), 849 ); 850 $evaluate = new ReflectionMethod( $this->interactivity, 'evaluate' ); 851 $evaluate->setAccessible( true ); 852 return $evaluate->invokeArgs( $this->interactivity, array( $directive_value, $default_namespace, $context ) ); 853 } 854 855 /** 856 * Tests that the `evaluate` method operates correctly for valid expressions. 857 * 858 * @ticket 60356 859 * 860 * @covers ::evaluate 861 */ 862 public function test_evaluate_value() { 1112 ) 1113 ); 1114 $this->interactivity->state( 'otherPlugin', array( 'key' => 'otherPlugin-state' ) ); 1115 $this->set_internal_context_stack( 1116 array( 1117 'myPlugin' => array( 'key' => 'myPlugin-context' ), 1118 'otherPlugin' => array( 'key' => 'otherPlugin-context' ), 1119 ) 1120 ); 1121 $this->set_internal_namespace_stack( 'myPlugin' ); 1122 863 1123 $result = $this->evaluate( 'state.key' ); 864 1124 $this->assertEquals( 'myPlugin-state', $result ); … … 874 1134 875 1135 $result = $this->evaluate( 'state.obj.prop' ); 876 $this->assertSame( ' myPlugin-state', $result );1136 $this->assertSame( 'object property', $result ); 877 1137 878 1138 $result = $this->evaluate( 'state.arrAccess.1' ); … … 889 1149 */ 890 1150 public function test_evaluate_value_negation() { 1151 $this->interactivity->state( 'myPlugin', array( 'key' => 'myPlugin-state' ) ); 1152 $this->interactivity->state( 'otherPlugin', array( 'key' => 'otherPlugin-state' ) ); 1153 $this->set_internal_context_stack( 1154 array( 1155 'myPlugin' => array( 'key' => 'myPlugin-context' ), 1156 'otherPlugin' => array( 'key' => 'otherPlugin-context' ), 1157 ) 1158 ); 1159 $this->set_internal_namespace_stack( 'myPlugin' ); 1160 891 1161 $result = $this->evaluate( '!state.key' ); 892 1162 $this->assertFalse( $result ); … … 910 1180 */ 911 1181 public function test_evaluate_non_existent_path() { 1182 $this->interactivity->state( 'myPlugin', array( 'key' => 'myPlugin-state' ) ); 1183 $this->interactivity->state( 'otherPlugin', array( 'key' => 'otherPlugin-state' ) ); 1184 $this->set_internal_context_stack( 1185 array( 1186 'myPlugin' => array( 'key' => 'myPlugin-context' ), 1187 'otherPlugin' => array( 'key' => 'otherPlugin-context' ), 1188 ) 1189 ); 1190 $this->set_internal_namespace_stack( 'myPlugin' ); 1191 912 1192 $result = $this->evaluate( 'state.nonExistentKey' ); 913 1193 $this->assertNull( $result ); … … 937 1217 */ 938 1218 public function test_evaluate_nested_value() { 1219 $this->interactivity->state( 1220 'myPlugin', 1221 array( 1222 'nested' => array( 'key' => 'myPlugin-state-nested' ), 1223 ) 1224 ); 1225 $this->interactivity->state( 1226 'otherPlugin', 1227 array( 1228 'nested' => array( 'key' => 'otherPlugin-state-nested' ), 1229 ) 1230 ); 1231 $this->set_internal_context_stack( 1232 array( 1233 'myPlugin' => array( 1234 'nested' => array( 'key' => 'myPlugin-context-nested' ), 1235 ), 1236 'otherPlugin' => array( 1237 'nested' => array( 'key' => 'otherPlugin-context-nested' ), 1238 ), 1239 ) 1240 ); 1241 $this->set_internal_namespace_stack( 'myPlugin' ); 1242 939 1243 $result = $this->evaluate( 'state.nested.key' ); 940 1244 $this->assertEquals( 'myPlugin-state-nested', $result ); … … 959 1263 */ 960 1264 public function test_evaluate_unvalid_namespaces() { 1265 $this->set_internal_context_stack( array() ); 1266 $this->set_internal_namespace_stack(); 1267 961 1268 $result = $this->evaluate( 'path', 'null' ); 962 1269 $this->assertNull( $result ); … … 966 1273 967 1274 $result = $this->evaluate( 'path', '{}' ); 1275 $this->assertNull( $result ); 1276 } 1277 1278 /** 1279 * Tests the `evaluate` method for derived state functions. 1280 * 1281 * @ticket 61037 1282 * 1283 * @covers ::evaluate 1284 * @covers wp_interactivity_state 1285 * @covers wp_interactivity_get_context 1286 */ 1287 public function test_evaluate_derived_state() { 1288 $this->interactivity->state( 1289 'myPlugin', 1290 array( 1291 'key' => 'myPlugin-state', 1292 'derived' => function () { 1293 $state = wp_interactivity_state(); 1294 $context = wp_interactivity_get_context(); 1295 return 'Derived state: ' . 1296 $state['key'] . 1297 "\n" . 1298 'Derived context: ' . 1299 $context['key']; 1300 }, 1301 ) 1302 ); 1303 $this->set_internal_context_stack( 1304 array( 1305 'myPlugin' => array( 1306 'key' => 'myPlugin-context', 1307 ), 1308 ) 1309 ); 1310 $this->set_internal_namespace_stack( 'myPlugin' ); 1311 1312 $result = $this->evaluate( 'state.derived' ); 1313 $this->assertSame( "Derived state: myPlugin-state\nDerived context: myPlugin-context", $result ); 1314 } 1315 1316 /** 1317 * Tests the `evaluate` method for derived state functions accessing a 1318 * different namespace. 1319 * 1320 * @ticket 61037 1321 * 1322 * @covers ::evaluate 1323 * @covers wp_interactivity_state 1324 * @covers wp_interactivity_get_context 1325 */ 1326 public function test_evaluate_derived_state_accessing_different_namespace() { 1327 $this->interactivity->state( 1328 'myPlugin', 1329 array( 1330 'key' => 'myPlugin-state', 1331 'derived' => function () { 1332 $state = wp_interactivity_state( 'otherPlugin' ); 1333 $context = wp_interactivity_get_context( 'otherPlugin' ); 1334 return 'Derived state: ' . 1335 $state['key'] . 1336 "\n" . 1337 'Derived context: ' . 1338 $context['key']; 1339 }, 1340 ) 1341 ); 1342 $this->interactivity->state( 'otherPlugin', array( 'key' => 'otherPlugin-state' ) ); 1343 $this->set_internal_context_stack( 1344 array( 1345 'myPlugin' => array( 1346 'key' => 'myPlugin-context', 1347 ), 1348 'otherPlugin' => array( 1349 'key' => 'otherPlugin-context', 1350 ), 1351 ) 1352 ); 1353 $this->set_internal_namespace_stack( 'myPlugin' ); 1354 1355 $result = $this->evaluate( 'state.derived' ); 1356 $this->assertSame( "Derived state: otherPlugin-state\nDerived context: otherPlugin-context", $result ); 1357 } 1358 1359 /** 1360 * Tests the `evaluate` method for derived state functions defined in a 1361 * different namespace. 1362 * 1363 * @ticket 61037 1364 * 1365 * @covers ::evaluate 1366 * @covers wp_interactivity_state 1367 * @covers wp_interactivity_get_context 1368 */ 1369 public function test_evaluate_derived_state_defined_in_different_namespace() { 1370 $this->interactivity->state( 'myPlugin', array( 'key' => 'myPlugin-state' ) ); 1371 $this->interactivity->state( 1372 'otherPlugin', 1373 array( 1374 'key' => 'otherPlugin-state', 1375 'derived' => function () { 1376 $state = wp_interactivity_state(); 1377 $context = wp_interactivity_get_context(); 1378 return 'Derived state: ' . 1379 $state['key'] . 1380 "\n" . 1381 'Derived context: ' . 1382 $context['key']; 1383 }, 1384 ) 1385 ); 1386 $this->set_internal_context_stack( 1387 array( 1388 'myPlugin' => array( 1389 'key' => 'myPlugin-context', 1390 ), 1391 'otherPlugin' => array( 1392 'key' => 'otherPlugin-context', 1393 ), 1394 ) 1395 ); 1396 $this->set_internal_namespace_stack( 'myPlugin' ); 1397 1398 $result = $this->evaluate( 'otherPlugin::state.derived' ); 1399 $this->assertSame( "Derived state: otherPlugin-state\nDerived context: otherPlugin-context", $result ); 1400 } 1401 1402 1403 /** 1404 * Tests the `evaluate` method for derived state functions that throw. 1405 * 1406 * @ticket 61037 1407 * 1408 * @covers ::evaluate 1409 * @expectedIncorrectUsage WP_Interactivity_API::evaluate 1410 */ 1411 public function test_evaluate_derived_state_that_throws() { 1412 $this->interactivity->state( 1413 'myPlugin', 1414 array( 1415 'derivedThatThrows' => function () { 1416 throw new Error( 'Something bad happened.' ); 1417 }, 1418 ) 1419 ); 1420 $this->set_internal_context_stack(); 1421 $this->set_internal_namespace_stack( 'myPlugin' ); 1422 1423 $result = $this->evaluate( 'state.derivedThatThrows' ); 968 1424 $this->assertNull( $result ); 969 1425 }
Note: See TracChangeset
for help on using the changeset viewer.