diff --git a/src/wp-includes/interactivity-api/class-wp-interactivity-api.php b/src/wp-includes/interactivity-api/class-wp-interactivity-api.php index 171d0bcffe29f..dc584c014849f 100644 --- a/src/wp-includes/interactivity-api/class-wp-interactivity-api.php +++ b/src/wp-includes/interactivity-api/class-wp-interactivity-api.php @@ -478,27 +478,11 @@ private function _process_directives( string $html ) { // Checks if there is a server directive processor registered for each directive. foreach ( $p->get_attribute_names_with_prefix( 'data-wp-' ) as $attribute_name ) { - if ( ! preg_match( - /* - * This must align with the client-side regex used by the interactivity API. - * @see https://github.com/WordPress/gutenberg/blob/ca616014255efbb61f34c10917d52a2d86c1c660/packages/interactivity/src/vdom.ts#L20-L32 - */ - '/' . - '^data-wp-' . - // Match alphanumeric characters including hyphen-separated - // segments. It excludes underscore intentionally to prevent confusion. - // E.g., "custom-directive". - '([a-z0-9]+(?:-[a-z0-9]+)*)' . - // (Optional) Match '--' followed by any alphanumeric charachters. It - // excludes underscore intentionally to prevent confusion, but it can - // contain multiple hyphens. E.g., "--custom-prefix--with-more-info". - '(?:--([a-z0-9_-]+))?$' . - '/i', - $attribute_name - ) ) { + $parsed_directive = $this->parse_directive_name( $attribute_name ); + if ( empty( $parsed_directive ) ) { continue; } - list( $directive_prefix ) = $this->extract_prefix_and_suffix( $attribute_name ); + $directive_prefix = 'data-wp-' . $parsed_directive['prefix']; if ( array_key_exists( $directive_prefix, self::$directive_processors ) ) { $directives_prefixes[] = $directive_prefix; } @@ -575,14 +559,10 @@ private function _process_directives( string $html ) { /* * It returns null if the HTML is unbalanced because unbalanced HTML is * not safe to process. In that case, the Interactivity API runtime will - * update the HTML on the client side during the hydration. It will also - * display a notice to the developer to inform them about the issue. + * update the HTML on the client side during the hydration. It will display + * a notice to the developer in the console to inform them about the issue. */ if ( $unbalanced || 0 < count( $tag_stack ) ) { - $tag_errored = 0 < count( $tag_stack ) ? end( $tag_stack )[0] : $tag_name; - /* translators: %1s: Namespace processed, %2s: The tag that caused the error; could be any HTML tag. */ - $message = sprintf( __( 'Interactivity directives failed to process in "%1$s" due to a missing "%2$s" end tag.' ), end( $this->namespace_stack ), $tag_errored ); - _doing_it_wrong( __METHOD__, $message, '6.6.0' ); return null; } @@ -597,18 +577,18 @@ private function _process_directives( string $html ) { * @since 6.6.0 The function now adds a warning when the namespace is null, falsy, or the directive value is empty. * @since 6.6.0 Removed `default_namespace` and `context` arguments. * @since 6.6.0 Add support for derived state. + * @since 6.9.0 Recieve $entry as an argument instead of the directive value string. * - * @param string|true $directive_value The directive attribute value string or `true` when it's a boolean attribute. + * @param array $entry An array containing a whole directive entry with its namespace, value, suffix, or unique ID. * @return mixed|null The result of the evaluation. Null if the reference path doesn't exist or the namespace is falsy. */ - private function evaluate( $directive_value ) { - $default_namespace = end( $this->namespace_stack ); - $context = end( $this->context_stack ); + private function evaluate( $entry ) { + $context = end( $this->context_stack ); + ['namespace' => $ns, 'value' => $path] = $entry; - list( $ns, $path ) = $this->extract_directive_value( $directive_value, $default_namespace ); if ( ! $ns || ! $path ) { /* translators: %s: The directive value referenced. */ - $message = sprintf( __( 'Namespace or reference path cannot be empty. Directive value referenced: %s' ), $directive_value ); + $message = sprintf( __( 'Namespace or reference path cannot be empty. Directive value referenced: %s' ), json_encode( $entry ) ); _doing_it_wrong( __METHOD__, $message, '6.6.0' ); return null; } @@ -712,25 +692,74 @@ private function evaluate( $directive_value ) { } /** - * Extracts the directive attribute name to separate and return the directive - * prefix and an optional suffix. - * - * The suffix is the string after the first double hyphen and the prefix is - * everything that comes before the suffix. + * Parse the directive name to extract the following parts: + * - Prefix: The main directive name without "data-wp-". + * - Suffix: An optional suffix used during directive processing, extracted after the first double hyphen "--". + * - Unique ID: An optional unique identifier, extracted after the first triple hyphen "---". * - * Example: + * This function has an equivalent version for the client side. + * See `parseDirectiveName` in https://github.com/WordPress/gutenberg/blob/trunk/packages/interactivity/src/vdom.ts.: * - * extract_prefix_and_suffix( 'data-wp-interactive' ) => array( 'data-wp-interactive', null ) - * extract_prefix_and_suffix( 'data-wp-bind--src' ) => array( 'data-wp-bind', 'src' ) - * extract_prefix_and_suffix( 'data-wp-foo--and--bar' ) => array( 'data-wp-foo', 'and--bar' ) + * See examples in the function unit tests `test_parse_directive_name`. * - * @since 6.5.0 + * @since 6.9.0 * * @param string $directive_name The directive attribute name. - * @return array An array containing the directive prefix and optional suffix. + * @return array An array containing the directive prefix, optional suffix, and optional unique ID. */ - private function extract_prefix_and_suffix( string $directive_name ): array { - return explode( '--', $directive_name, 2 ); + private function parse_directive_name( string $directive_name ): ?array { + // Remove the first 8 characters (assumes "data-wp-" prefix) + $name = substr( $directive_name, 8 ); + + // Check for invalid characters (anything not a-z, 0-9, -, or _) + if ( preg_match( '/[^a-z0-9\-_]/i', $name ) ) { + return null; + } + + // Find the first occurrence of '--' to separate the prefix + $suffix_index = strpos( $name, '--' ); + + if ( false === $suffix_index ) { + return array( + 'prefix' => $name, + 'suffix' => null, + 'unique_id' => null, + ); + } + + $prefix = substr( $name, 0, $suffix_index ); + $remaining = substr( $name, $suffix_index ); + + // If remaining starts with '---' but not '----', it's a unique_id + if ( '---' === substr( $remaining, 0, 3 ) && '-' !== ( $remaining[3] ?? '' ) ) { + return array( + 'prefix' => $prefix, + 'suffix' => null, + 'unique_id' => '---' !== $remaining ? substr( $remaining, 3 ) : null, + ); + } + + // Otherwise, remove the first two dashes for a potential suffix + $suffix = substr( $remaining, 2 ); + + // Look for '---' in the suffix for a unique_id + $unique_id_index = strpos( $suffix, '---' ); + + if ( false !== $unique_id_index && '-' !== ( $suffix[ $unique_id_index + 3 ] ?? '' ) ) { + $unique_id = substr( $suffix, $unique_id_index + 3 ); + $suffix = substr( $suffix, 0, $unique_id_index ); + return array( + 'prefix' => $prefix, + 'suffix' => empty( $suffix ) ? null : $suffix, + 'unique_id' => empty( $unique_id ) ? null : $unique_id, + ); + } + + return array( + 'prefix' => $prefix, + 'suffix' => empty( $suffix ) ? null : $suffix, + 'unique_id' => null, + ); } /** @@ -782,6 +811,53 @@ private function extract_directive_value( $directive_value, $default_namespace = return array( $default_namespace, $directive_value ); } + /** + * Parse the HTML element and get all the valid directives with the given prefix. + * + * @since 6.9.0 + * + * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. + * @param string $prefix The directive prefix to filter by. + * @return array An array of entries containing the directive namespace, value, suffix, and unique ID. + */ + private function get_directive_entries( WP_Interactivity_API_Directives_Processor $p, string $prefix ) { + $directive_attributes = $p->get_attribute_names_with_prefix( 'data-wp-' . $prefix ); + $entries = array(); + foreach ( $directive_attributes as $attribute_name ) { + [ 'prefix' => $attr_prefix, 'suffix' => $suffix, 'unique_id' => $unique_id] = $this->parse_directive_name( $attribute_name ); + // Ensure it is the desired directive. + if ( $prefix !== $attr_prefix ) { + continue; + } + list( $namespace, $value ) = $this->extract_directive_value( $p->get_attribute( $attribute_name ), end( $this->namespace_stack ) ); + $entries[] = array( + 'namespace' => $namespace, + 'value' => $value, + 'suffix' => $suffix, + 'unique_id' => $unique_id, + ); + } + // Sort directive entries to ensure stable ordering with the client. + // Put nulls first, then sort by suffix and finally by uniqueIds. + usort( + $entries, + function ( $a, $b ) { + $a_suffix = $a['suffix'] ?? ''; + $b_suffix = $b['suffix'] ?? ''; + if ( $a_suffix !== $b_suffix ) { + return $a_suffix < $b_suffix ? -1 : 1; + } + $a_id = $a['unique_id'] ?? ''; + $b_id = $b['unique_id'] ?? ''; + if ( $a_id === $b_id ) { + return 0; + } + return $a_id > $b_id ? 1 : -1; + } + ); + return $entries; + } + /** * Transforms a kebab-case string to camelCase. * @@ -863,31 +939,19 @@ private function data_wp_context_processor( WP_Interactivity_API_Directives_Proc return; } - $attribute_value = $p->get_attribute( 'data-wp-context' ); - $namespace_value = end( $this->namespace_stack ); - - // Separates the namespace from the context JSON object. - list( $namespace_value, $decoded_json ) = is_string( $attribute_value ) && ! empty( $attribute_value ) - ? $this->extract_directive_value( $attribute_value, $namespace_value ) - : array( $namespace_value, null ); + $entries = $this->get_directive_entries( $p, 'context' ); + $context = end( $this->context_stack ) !== false ? end( $this->context_stack ) : array(); + foreach ( $entries as $entry ) { + if ( null !== $entry['suffix'] ) { + continue; + } - /* - * If there is a namespace, it adds a new context to the stack merging the - * previous context with the new one. - */ - if ( is_string( $namespace_value ) ) { - $this->context_stack[] = array_replace_recursive( - end( $this->context_stack ) !== false ? end( $this->context_stack ) : array(), - array( $namespace_value => is_array( $decoded_json ) ? $decoded_json : array() ) + $context = array_replace_recursive( + $context, + array( $entry['namespace'] => is_array( $entry['value'] ) ? $entry['value'] : array() ) ); - } else { - /* - * If there is no namespace, it pushes the current context to the stack. - * It needs to do so because the function pops out the current context - * from the stack whenever it finds a `data-wp-context`'s closing tag. - */ - $this->context_stack[] = end( $this->context_stack ); } + $this->context_stack[] = $context; } /** @@ -903,22 +967,19 @@ private function data_wp_context_processor( WP_Interactivity_API_Directives_Proc */ private function data_wp_bind_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) { if ( 'enter' === $mode ) { - $all_bind_directives = $p->get_attribute_names_with_prefix( 'data-wp-bind--' ); - - foreach ( $all_bind_directives as $attribute_name ) { - list( , $bound_attribute ) = $this->extract_prefix_and_suffix( $attribute_name ); - if ( empty( $bound_attribute ) ) { - return; + $entries = $this->get_directive_entries( $p, 'bind' ); + foreach ( $entries as $entry ) { + if ( empty( $entry['suffix'] ) || null !== $entry['unique_id'] ) { + return; } - $attribute_value = $p->get_attribute( $attribute_name ); - $result = $this->evaluate( $attribute_value ); + $result = $this->evaluate( $entry ); if ( null !== $result && ( false !== $result || - ( strlen( $bound_attribute ) > 5 && '-' === $bound_attribute[4] ) + ( strlen( $entry['suffix'] ) > 5 && '-' === $entry['suffix'][4] ) ) ) { /* @@ -930,13 +991,13 @@ private function data_wp_bind_processor( WP_Interactivity_API_Directives_Process */ if ( is_bool( $result ) && - ( strlen( $bound_attribute ) > 5 && '-' === $bound_attribute[4] ) + ( strlen( $entry['suffix'] ) > 5 && '-' === $entry['suffix'][4] ) ) { $result = $result ? 'true' : 'false'; } - $p->set_attribute( $bound_attribute, $result ); + $p->set_attribute( $entry['suffix'], $result ); } else { - $p->remove_attribute( $bound_attribute ); + $p->remove_attribute( $entry['suffix'] ); } } } @@ -956,15 +1017,20 @@ private function data_wp_bind_processor( WP_Interactivity_API_Directives_Process private function data_wp_class_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) { if ( 'enter' === $mode ) { $all_class_directives = $p->get_attribute_names_with_prefix( 'data-wp-class--' ); + $entries = $this->get_directive_entries( $p, 'class' ); + foreach ( $entries as $entry ) { + if ( empty( $entry['suffix'] ) ) { + continue; + } + $class_name = isset( $entry['unique_id'] ) && $entry['unique_id'] + ? "{$entry['suffix']}---{$entry['unique_id']}" + : $entry['suffix']; - foreach ( $all_class_directives as $attribute_name ) { - list( , $class_name ) = $this->extract_prefix_and_suffix( $attribute_name ); if ( empty( $class_name ) ) { return; } - $attribute_value = $p->get_attribute( $attribute_name ); - $result = $this->evaluate( $attribute_value ); + $result = $this->evaluate( $entry ); if ( $result ) { $p->add_class( $class_name ); @@ -988,18 +1054,16 @@ private function data_wp_class_processor( WP_Interactivity_API_Directives_Proces */ private function data_wp_style_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) { if ( 'enter' === $mode ) { - $all_style_attributes = $p->get_attribute_names_with_prefix( 'data-wp-style--' ); - - foreach ( $all_style_attributes as $attribute_name ) { - list( , $style_property ) = $this->extract_prefix_and_suffix( $attribute_name ); - if ( empty( $style_property ) ) { + $entries = $this->get_directive_entries( $p, 'style' ); + foreach ( $entries as $entry ) { + $style_property = $entry['suffix']; + if ( empty( $style_property ) || null !== $entry['unique_id'] ) { continue; } - $directive_attribute_value = $p->get_attribute( $attribute_name ); - $style_property_value = $this->evaluate( $directive_attribute_value ); - $style_attribute_value = $p->get_attribute( 'style' ); - $style_attribute_value = ( $style_attribute_value && ! is_bool( $style_attribute_value ) ) ? $style_attribute_value : ''; + $style_property_value = $this->evaluate( $entry ); + $style_attribute_value = $p->get_attribute( 'style' ); + $style_attribute_value = ( $style_attribute_value && ! is_bool( $style_attribute_value ) ) ? $style_attribute_value : ''; /* * Checks first if the style property is not falsy and the style @@ -1079,8 +1143,19 @@ private function merge_style_property( string $style_attribute_value, string $st */ private function data_wp_text_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) { if ( 'enter' === $mode ) { - $attribute_value = $p->get_attribute( 'data-wp-text' ); - $result = $this->evaluate( $attribute_value ); + $entries = $this->get_directive_entries( $p, 'text' ); + $valid_entry = null; + // Get the first valid `data-wp-text` entry without suffix or unique ID. + foreach ( $entries as $entry ) { + if ( null === $entry['suffix'] && null === $entry['unique_id'] && ! empty( $entry['value'] ) ) { + $valid_entry = $entry; + break; + } + } + if ( null === $valid_entry ) { + return; + } + $result = $this->evaluate( $valid_entry ); /* * Follows the same logic as Preact in the client and only changes the @@ -1209,11 +1284,17 @@ private function data_wp_router_region_processor( WP_Interactivity_API_Directive */ private function data_wp_each_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$tag_stack ) { if ( 'enter' === $mode && 'TEMPLATE' === $p->get_tag() ) { - $attribute_name = $p->get_attribute_names_with_prefix( 'data-wp-each' )[0]; - $extracted_suffix = $this->extract_prefix_and_suffix( $attribute_name ); - $item_name = isset( $extracted_suffix[1] ) ? $this->kebab_to_camel_case( $extracted_suffix[1] ) : 'item'; - $attribute_value = $p->get_attribute( $attribute_name ); - $result = $this->evaluate( $attribute_value ); + $entries = $this->get_directive_entries( $p, 'each' ); + if ( count( $entries ) > 1 || empty( $entries ) ) { + // There should be only one `data-wp-each` directive per template tag. + return; + } + $entry = $entries[0]; + if ( null !== $entry['unique_id'] ) { + return; + } + $item_name = isset( $entry['suffix'] ) ? $this->kebab_to_camel_case( $entry['suffix'] ) : 'item'; + $result = $this->evaluate( $entry ); // Gets the content between the template tags and leaves the cursor in the closer tag. $inner_content = $p->get_content_between_balanced_template_tags(); @@ -1246,19 +1327,13 @@ private function data_wp_each_processor( WP_Interactivity_API_Directives_Process return; } - // Extracts the namespace from the directive attribute value. - $namespace_value = end( $this->namespace_stack ); - list( $namespace_value, $path ) = is_string( $attribute_value ) && ! empty( $attribute_value ) - ? $this->extract_directive_value( $attribute_value, $namespace_value ) - : array( $namespace_value, null ); - // Processes the inner content for each item of the array. $processed_content = ''; foreach ( $result as $item ) { // Creates a new context that includes the current item of the array. $this->context_stack[] = array_replace_recursive( end( $this->context_stack ) !== false ? end( $this->context_stack ) : array(), - array( $namespace_value => array( $item_name => $item ) ) + array( $entry['namespace'] => array( $item_name => $item ) ) ); // Processes the inner content with the new context. @@ -1283,7 +1358,7 @@ private function data_wp_each_processor( WP_Interactivity_API_Directives_Process */ $i = new WP_Interactivity_API_Directives_Processor( $processed_item ); while ( $i->next_tag() ) { - $i->set_attribute( 'data-wp-each-child', $namespace_value . '::' . $path ); + $i->set_attribute( 'data-wp-each-child', $entry['namespace'] . '::' . $entry['value'] ); $i->next_balanced_tag_closer_tag(); } $processed_content .= $i->get_updated_html(); diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-bind.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-bind.php index 0a6ffcb2a5da0..cd3f17c8ae254 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-bind.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-bind.php @@ -398,4 +398,22 @@ public function test_wp_bind_handles_true_value() { list($p) = $this->process_directives( $html ); $this->assertSame( true, $p->get_attribute( 'id' ) ); } + + /** + * Tests ignores unique IDs in bind directive. + * + * @ticket 64106 + * + * @covers ::process_directives + */ + public function test_wp_bind_ignores_unique_ids() { + $html = '
'; + list($p) = $this->process_directives( $html ); + $this->assertSame( true, $p->get_attribute( 'id' ) ); + + $html = '
'; + list($p) = $this->process_directives( $html ); + $this->assertNull( $p->get_attribute( 'id' ) ); + $this->assertNull( $p->get_attribute( 'id---unique-id' ) ); + } } diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-class.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-class.php index d7bf77bb66470..72162a3c684ef 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-class.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-class.php @@ -76,7 +76,7 @@ public function test_wp_class_sets_multiple_class_names() { data-wp-class--other-class="myPlugin::state.true" >Text'; list($p) = $this->process_directives( $html ); - $this->assertSame( 'some-class other-class', $p->get_attribute( 'class' ) ); + $this->assertSame( 'other-class some-class', $p->get_attribute( 'class' ) ); } /** @@ -328,4 +328,25 @@ public function test_wp_class_sets_class_name_on_falsy_values() { list($p) = $this->process_directives( $html ); $this->assertNull( $p->get_attribute( 'class' ) ); } + + /** + * Tests that classes with several dashes can be used. + * + * @ticket 64106 + * + * @covers ::process_directives + */ + public function test_wp_class_can_use_several_dashes() { + $html = '
Text
'; + list($p) = $this->process_directives( $html ); + $this->assertSame( 'main-bg--color', $p->get_attribute( 'class' ) ); + + $html = '
Text
'; + list($p) = $this->process_directives( $html ); + $this->assertSame( 'main-bg---color', $p->get_attribute( 'class' ) ); + + $html = '
Text
'; + list($p) = $this->process_directives( $html ); + $this->assertSame( 'main-bg----color', $p->get_attribute( 'class' ) ); + } } diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-context.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-context.php index 442c62c84e52f..9685a3f979cd9 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-context.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-context.php @@ -522,4 +522,45 @@ public function test_wp_context_directive_replaces_old_context_after_closing_tag $p->next_tag( array( 'class_name' => 'test' ) ); $this->assertSame( 'some-id-1', $p->get_attribute( 'id' ) ); } + + /** + * Tests supports multiple context directives in the same element. + * + * @ticket 64106 + * + * @covers ::process_directives + */ + public function test_wp_context_supports_multiple_directives_in_the_same_element() { + $html = ' +
+
+ +
+
+ '; + list($p) = $this->process_directives( $html ); + $this->assertSame( 'id1', $p->get_attribute( 'data-test-prop' ) ); + foreach ( array( 'parent', 'default', 'id1', 'other' ) as $attribute ) { + $attr_name = "data-test-$attribute"; + $this->assertSame( + 'true', + $p->get_attribute( $attr_name ), + "Failed asserting that $attr_name equals 'true'" + ); + } + } } diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-each.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-each.php index f0952dfabea89..1b2290f96560d 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-each.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-each.php @@ -580,8 +580,6 @@ public function test_wp_each_nested_template_tags_using_previous_item_as_list() * @ticket 60356 * * @covers ::process_directives - * - * @expectedIncorrectUsage WP_Interactivity_API::_process_directives */ public function test_wp_each_unbalanced_tags() { $original = '' . @@ -600,8 +598,6 @@ public function test_wp_each_unbalanced_tags() { * @ticket 60356 * * @covers ::process_directives - * - * @expectedIncorrectUsage WP_Interactivity_API::_process_directives */ public function test_wp_each_unbalanced_tags_in_nested_template_tags() { $this->interactivity->state( 'myPlugin', array( 'list2' => array( 3, 4 ) ) ); @@ -684,4 +680,36 @@ public function test_wp_each_doesnt_process_with_manual_server_directive_process $new = $this->interactivity->process_directives( $original ); $this->assertSame( $expected, $new ); } + + /** + * Tests it doesn't support multiple directives. + * + * @ticket 64106 + * + * @covers ::process_directives + */ + public function test_wp_each_doesnt_support_multiple_directives() { + $original = '' . + '
' . + '' . + '' . + '
Text
' . + '
'; + $expected = '' . + '
' . + '' . + '' . + '
Text
' . + '
'; + $new = $this->interactivity->process_directives( $original ); + $this->assertSame( $expected, $new ); + } } diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-style.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-style.php index 75577979604a4..1a5884f34b8e6 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-style.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-style.php @@ -190,7 +190,7 @@ public function test_wp_style_sets_multiple_style_properties() { data-wp-style--background="myPlugin::state.green" >Text'; list($p) = $this->process_directives( $html ); - $this->assertSame( 'color:green;background:green;', $p->get_attribute( 'style' ) ); + $this->assertSame( 'background:green;color:green;', $p->get_attribute( 'style' ) ); } /** @@ -448,4 +448,30 @@ public function test_wp_style_doesnt_add_style_property_on_falsy_values() { list($p) = $this->process_directives( $html ); $this->assertNull( $p->get_attribute( 'style' ) ); } + + /** + * Tests it can use CSS variables. + * + * @ticket 64106 + * + * @covers ::process_directives + */ + public function test_wp_style_can_use_CSS_variables() { + $html = '
Text
'; + list($p) = $this->process_directives( $html ); + $this->assertSame( '--text-color:green;', $p->get_attribute( 'style' ) ); + } + + /** + * Tests it ignores unique IDs. + * + * @ticket 64106 + * + * @covers ::process_directives + */ + public function test_wp_style_ignores_unique_ids() { + $html = '
Text
'; + list($p) = $this->process_directives( $html ); + $this->assertNull( $p->get_attribute( 'style' ) ); + } } diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-text.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-text.php index dc19b1117de39..988dce96b77c2 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-text.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-text.php @@ -12,7 +12,7 @@ * * @group interactivity-api */ -class Tests_Interactivity_API_WpInteractivityAPIWPText extends WP_UnitTestCase { +class Tests_WP_Interactivity_API_WP_Text extends WP_UnitTestCase { /** * Instance of WP_Interactivity_API. * @@ -131,8 +131,6 @@ public function test_wp_text_sets_inner_content_even_with_unbalanced_but_differe * @ticket 60356 * * @covers ::process_directives - * - * @expectedIncorrectUsage WP_Interactivity_API::_process_directives */ public function test_wp_text_fails_with_unbalanced_and_same_tags_inside_content() { $html = '
Text
'; @@ -154,4 +152,35 @@ public function test_wp_text_cant_set_inner_html_in_the_content() { $new_html = $this->interactivity->process_directives( $html ); $this->assertSame( '
<span>Updated</span>
', $new_html ); } + + /** + * Tests it ignores suffixes and unique-ids. + * + * @ticket 64106 + * + * @covers ::process_directives + */ + public function test_wp_text_ignores_suffixes_and_unique_ids() { + $html = 'Text'; + $new_html = $this->interactivity->process_directives( $html ); + $this->assertSame( $html, $new_html ); + + $html = 'Text'; + $new_html = $this->interactivity->process_directives( $html ); + $this->assertSame( $html, $new_html ); + } + + /** + * Tests first `data-wp-text` works even when suffixes and unique-ids are included. + * + * @ticket 64106 + * + * @covers ::process_directives + */ + public function test_wp_text_works_even_when_suffixes_and_unique_ids_are_included() { + $original = 'Text'; + $expected = 'Updated'; + $new_html = $this->interactivity->process_directives( $original ); + $this->assertSame( $expected, $new_html ); + } } diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php index a64f0f0c956bd..8aa70e188ff15 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php @@ -369,14 +369,39 @@ function () use ( $returns_whatever, $returns_array ) { // Multiple evaluations should be serialized only once. $this->set_internal_namespace_stack( 'pluginWithInvokedDerivedState' ); - $this->evaluate( 'state.derivedProp' ); - $this->evaluate( 'state.derivedProp' ); - $this->evaluate( 'state.nested.derivedProp' ); - $this->evaluate( 'state.nested.derivedProp' ); + $this->evaluate( + array( + 'namespace' => 'pluginWithInvokedDerivedState', + 'value' => 'state.derivedProp', + ) + ); + $this->evaluate( + array( + 'namespace' => 'pluginWithInvokedDerivedState', + 'value' => 'state.derivedProp', + ) + ); + $this->evaluate( + array( + 'namespace' => 'pluginWithInvokedDerivedState', + 'value' => 'state.nested.derivedProp', + ) + ); + $this->evaluate( + array( + 'namespace' => 'pluginWithInvokedDerivedState', + 'value' => 'state.nested.derivedProp', + ) + ); // Only the path part that points to a derived state prop should be serialized. $this->set_internal_namespace_stack( 'pluginWithInvokedDerivedStateReturningArray' ); - $this->evaluate( 'state.nested.derivedProp.prop' ); + $this->evaluate( + array( + 'namespace' => 'pluginWithInvokedDerivedStateReturningArray', + 'value' => 'state.nested.derivedProp', + ) + ); } ); @@ -810,24 +835,301 @@ public function test_extract_directive_value_invalid_json() { * Tests the ability to extract prefix and suffix from a directive attribute * name. * - * @ticket 60356 + * @ticket 64106 + * + * @covers ::parse_directive_name + */ + public function test_parse_directive_name() { + $parse_directive_name = new ReflectionMethod( $this->interactivity, 'parse_directive_name' ); + if ( PHP_VERSION_ID < 80100 ) { + $parse_directive_name->setAccessible( true ); + } + + // Should parse directives without suffix or unique ID. + $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test' ); + $this->assertSame( 'test', $result['prefix'] ); + $this->assertNull( $result['suffix'] ); + $this->assertNull( $result['unique_id'] ); + + // Should parse directives with suffix only. + $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test--one' ); + $this->assertSame( 'test', $result['prefix'] ); + $this->assertSame( 'one', $result['suffix'] ); + $this->assertNull( $result['unique_id'] ); + + // Should parse directives with unique ID only. + $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test---unique-id' ); + $this->assertSame( 'test', $result['prefix'] ); + $this->assertNull( $result['suffix'] ); + $this->assertSame( 'unique-id', $result['unique_id'] ); + + // Should parse directives with suffix and unique ID. + $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test--suffix---unique-id' ); + $this->assertSame( 'test', $result['prefix'] ); + $this->assertSame( 'suffix', $result['suffix'] ); + $this->assertSame( 'unique-id', $result['unique_id'] ); + + // Should handle empty suffix (just two dashes). + $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test--' ); + $this->assertSame( 'test', $result['prefix'] ); + $this->assertNull( $result['suffix'] ); + $this->assertNull( $result['unique_id'] ); + + // Should handle empty unique ID (just three dashes). + $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test---' ); + $this->assertSame( 'test', $result['prefix'] ); + $this->assertNull( $result['suffix'] ); + $this->assertNull( $result['unique_id'] ); + + // Should handle only dashes (4 or more dashes). + $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test----' ); + $this->assertSame( 'test', $result['prefix'] ); + $this->assertSame( '--', $result['suffix'] ); + $this->assertNull( $result['unique_id'] ); + + // Should handle suffix starting with 4 or more dashes but containing valid characters. + $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test------custom-suffix' ); + $this->assertSame( 'test', $result['prefix'] ); + $this->assertSame( '----custom-suffix', $result['suffix'] ); + $this->assertNull( $result['unique_id'] ); + + // Should handle complex pattern with multiple dashes. + $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test--complex--suffix---complex--unique---id' ); + $this->assertSame( 'test', $result['prefix'] ); + $this->assertSame( 'complex--suffix', $result['suffix'] ); + $this->assertSame( 'complex--unique---id', $result['unique_id'] ); + + // Should handle suffix with dashes followed by unique ID. + $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test----suffix---unique-id' ); + $this->assertSame( 'test', $result['prefix'] ); + $this->assertSame( '--suffix', $result['suffix'] ); + $this->assertSame( 'unique-id', $result['unique_id'] ); + + // Should handle unique IDs followed by suffix in wrong order. + $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test---unique-id--wrong-suffix' ); + $this->assertSame( 'test', $result['prefix'] ); + $this->assertNull( $result['suffix'] ); + $this->assertSame( 'unique-id--wrong-suffix', $result['unique_id'] ); + } + + /** + * Tests the ability to get the valid entries of a specific directive in an HTML element. * - * @covers ::extract_prefix_and_suffix + * @ticket 64106 + * + * @covers ::get_directive_entries */ - public function test_extract_prefix_and_suffix() { - $extract_prefix_and_suffix = new ReflectionMethod( $this->interactivity, 'extract_prefix_and_suffix' ); + public function test_get_directive_entries() { + $get_directive_entries = new ReflectionMethod( $this->interactivity, 'get_directive_entries' ); if ( PHP_VERSION_ID < 80100 ) { - $extract_prefix_and_suffix->setAccessible( true ); + $get_directive_entries->setAccessible( true ); } + $this->set_internal_namespace_stack( 'myPlugin' ); + + // Should process simple directives. + $html = '
'; + $p = new WP_Interactivity_API_Directives_Processor( $html ); + $p->next_tag(); + $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' ); + $this->assertCount( 1, $results ); + $result = $results[0]; + $this->assertSame( 'myPlugin', $result['namespace'] ); + $this->assertSame( 'test value', $result['value'] ); + $this->assertNull( $result['suffix'] ); + $this->assertNull( $result['unique_id'] ); + + // Should process directives without value. + $html = '
'; + $p = new WP_Interactivity_API_Directives_Processor( $html ); + $p->next_tag(); + $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' ); + $this->assertNull( $results[0]['value'] ); + + // Should parse JSON values in directives. + $html = '
'; + $p = new WP_Interactivity_API_Directives_Processor( $html ); + $p->next_tag(); + $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' ); + $this->assertSame( array( 'key' => 'value' ), $results[0]['value'] ); + + // Should handle malformed JSON and keep as string. + $html = '
'; + $p = new WP_Interactivity_API_Directives_Processor( $html ); + $p->next_tag(); + $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' ); + $this->assertSame( '{malformed: json}', $results[0]['value'] ); + + // Should process directives with a custom namespace. + $html = '
'; + $p = new WP_Interactivity_API_Directives_Processor( $html ); + $p->next_tag(); + $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' ); + $this->assertSame( 'my-namespace', $results[0]['namespace'] ); + $this->assertSame( 'test value', $results[0]['value'] ); + + // Should parse JSON values with a custom namespace. + $html = '
'; + $p = new WP_Interactivity_API_Directives_Processor( $html ); + $p->next_tag(); + $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' ); + $this->assertSame( 'my-namespace', $results[0]['namespace'] ); + $this->assertSame( array( 'key' => 'value' ), $results[0]['value'] ); + + // Should handle multiple directives with different unique IDs. + $html = ' +
'; + $p = new WP_Interactivity_API_Directives_Processor( $html ); + $p->next_tag(); + $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' ); + $this->assertCount( 3, $results ); + $this->assertSame( + array( + 'namespace' => 'myPlugin', + 'value' => 'value-a', + 'suffix' => null, + 'unique_id' => 'plugin-a', + ), + $results[0] + ); + $this->assertSame( + array( + 'namespace' => 'myPlugin', + 'value' => 'value-b', + 'suffix' => null, + 'unique_id' => 'plugin-b', + ), + $results[1] + ); + $this->assertSame( + array( + 'namespace' => 'myPlugin', + 'value' => 'value-c', + 'suffix' => null, + 'unique_id' => 'plugin-c', + ), + $results[2] + ); - $result = $extract_prefix_and_suffix->invoke( $this->interactivity, 'data-wp-interactive' ); - $this->assertSame( array( 'data-wp-interactive' ), $result ); + // Should handle mix of different suffixes and unique IDs. + $html = ' +
'; + $p = new WP_Interactivity_API_Directives_Processor( $html ); + $p->next_tag(); + $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' ); + $this->assertCount( 4, $results ); + $this->assertSame( + array( + 'namespace' => 'myPlugin', + 'value' => 'value1', + 'suffix' => 'suffix-a', + 'unique_id' => 'id-1', + ), + $results[0] + ); + $this->assertSame( + array( + 'namespace' => 'myPlugin', + 'value' => 'value2', + 'suffix' => 'suffix-a', + 'unique_id' => 'id-2', + ), + $results[1] + ); + $this->assertSame( + array( + 'namespace' => 'myPlugin', + 'value' => 'value3', + 'suffix' => 'suffix-b', + 'unique_id' => 'id-1', + ), + $results[2] + ); + $this->assertSame( + array( + 'namespace' => 'myPlugin', + 'value' => 'value4', + 'suffix' => 'suffix-c', + 'unique_id' => 'id-1', + ), + $results[3] + ); - $result = $extract_prefix_and_suffix->invoke( $this->interactivity, 'data-wp-bind--src' ); - $this->assertSame( array( 'data-wp-bind', 'src' ), $result ); + // Should handle unique ID with namespace. + $html = '
'; + $p = new WP_Interactivity_API_Directives_Processor( $html ); + $p->next_tag(); + $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' ); + $this->assertSame( 'my-namespace', $results[0]['namespace'] ); + $this->assertSame( 'test value', $results[0]['value'] ); + $this->assertSame( 'unique-id', $results[0]['unique_id'] ); - $result = $extract_prefix_and_suffix->invoke( $this->interactivity, 'data-wp-foo--and--bar' ); - $this->assertSame( array( 'data-wp-foo', 'and--bar' ), $result ); + // Should handle multiple directives with different namespaces and unique IDs. + $html = ' +
'; + $p = new WP_Interactivity_API_Directives_Processor( $html ); + $p->next_tag(); + $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' ); + $this->assertCount( 2, $results ); + $this->assertSame( + array( + 'namespace' => 'namespace-a', + 'value' => 'value1', + 'suffix' => null, + 'unique_id' => 'id-a', + ), + $results[0] + ); + $this->assertSame( + array( + 'namespace' => 'namespace-b', + 'value' => 'value2', + 'suffix' => null, + 'unique_id' => 'id-b', + ), + $results[1] + ); + // Should sort directives by suffix and uniqueId for stable ordering. + $html = ' +
'; + $p = new WP_Interactivity_API_Directives_Processor( $html ); + $p->next_tag(); + $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' ); + $this->assertCount( 6, $results ); + $this->assertEquals( + array( + array( null, null ), + array( null, 'a' ), + array( null, 'z' ), + array( 'a', null ), + array( 'b', 'a' ), + array( 'b', 'z' ), + ), + array_map( + function ( $d ) { + return array( $d['suffix'], $d['unique_id'] ); + }, + $results + ) + ); } /** @@ -915,8 +1217,6 @@ public function test_process_directives_process_the_directives_in_the_correct_or * * @dataProvider data_html_with_unbalanced_tags * - * @expectedIncorrectUsage WP_Interactivity_API::_process_directives - * * @param string $html HTML containing unbalanced tags and also a directive. */ public function test_process_directives_doesnt_change_html_if_contains_unbalanced_tags( $html ) { @@ -1078,10 +1378,10 @@ public function test_process_directives_does_not_change_inner_html_in_math() { /** * Invokes the private `evaluate` method of WP_Interactivity_API class. * - * @param string $directive_value The directive attribute value to evaluate. + * @param string $entry The entry array containing namespace, value, suffix, and unique ID. * @return mixed The result of the evaluate method. */ - private function evaluate( $directive_value ) { + private function evaluate( $entry ) { /* * The global WP_Interactivity_API instance is momentarily replaced to * make global functions like `wp_interactivity_state` and @@ -1096,7 +1396,7 @@ private function evaluate( $directive_value ) { $evaluate->setAccessible( true ); } - $result = $evaluate->invokeArgs( $this->interactivity, array( $directive_value ) ); + $result = $evaluate->invokeArgs( $this->interactivity, array( $entry ) ); // Restore the original WP_Interactivity_API instance. $wp_interactivity = $wp_interactivity_prev; @@ -1142,24 +1442,55 @@ public function offsetUnset( $offset ): void {} 'otherPlugin' => array( 'key' => 'otherPlugin-context' ), ) ); - $this->set_internal_namespace_stack( 'myPlugin' ); + $default_ns = 'myPlugin'; + $this->set_internal_namespace_stack( $default_ns ); - $result = $this->evaluate( 'state.key' ); + $result = $this->evaluate( + array( + 'namespace' => $default_ns, + 'value' => 'state.key', + ) + ); $this->assertSame( 'myPlugin-state', $result ); - $result = $this->evaluate( 'context.key' ); + $result = $this->evaluate( + array( + 'namespace' => $default_ns, + 'value' => 'context.key', + ) + ); $this->assertSame( 'myPlugin-context', $result ); - $result = $this->evaluate( 'otherPlugin::state.key' ); + $result = $this->evaluate( + array( + 'namespace' => 'otherPlugin', + 'value' => 'state.key', + ) + ); $this->assertSame( 'otherPlugin-state', $result ); - $result = $this->evaluate( 'otherPlugin::context.key' ); + $result = $this->evaluate( + array( + 'namespace' => 'otherPlugin', + 'value' => 'context.key', + ) + ); $this->assertSame( 'otherPlugin-context', $result ); - $result = $this->evaluate( 'state.obj.prop' ); + $result = $this->evaluate( + array( + 'namespace' => $default_ns, + 'value' => 'state.obj.prop', + ) + ); $this->assertSame( 'object property', $result ); - $result = $this->evaluate( 'state.arrAccess.1' ); + $result = $this->evaluate( + array( + 'namespace' => $default_ns, + 'value' => 'state.arrAccess.1', + ) + ); $this->assertSame( '1', $result ); } @@ -1180,18 +1511,39 @@ public function test_evaluate_value_negation() { 'otherPlugin' => array( 'key' => 'otherPlugin-context' ), ) ); - $this->set_internal_namespace_stack( 'myPlugin' ); + $default_ns = 'myPlugin'; + $this->set_internal_namespace_stack( $default_ns ); - $result = $this->evaluate( '!state.key' ); + $result = $this->evaluate( + array( + 'namespace' => $default_ns, + 'value' => '!state.key', + ) + ); $this->assertFalse( $result ); - $result = $this->evaluate( '!context.key' ); + $result = $this->evaluate( + array( + 'namespace' => $default_ns, + 'value' => '!context.key', + ) + ); $this->assertFalse( $result ); - $result = $this->evaluate( 'otherPlugin::!state.key' ); + $result = $this->evaluate( + array( + 'namespace' => 'otherPlugin', + 'value' => '!state.key', + ) + ); $this->assertFalse( $result ); - $result = $this->evaluate( 'otherPlugin::!context.key' ); + $result = $this->evaluate( + array( + 'namespace' => 'otherPlugin', + 'value' => '!context.key', + ) + ); $this->assertFalse( $result ); } @@ -1212,18 +1564,39 @@ public function test_evaluate_value_negation_non_existent_path() { 'otherPlugin' => array(), ) ); - $this->set_internal_namespace_stack( 'myPlugin' ); + $default_ns = 'myPlugin'; + $this->set_internal_namespace_stack( $default_ns ); - $result = $this->evaluate( '!state.missing' ); + $result = $this->evaluate( + array( + 'namespace' => $default_ns, + 'value' => '!state.missing', + ) + ); $this->assertTrue( $result ); - $result = $this->evaluate( '!context.missing' ); + $result = $this->evaluate( + array( + 'namespace' => $default_ns, + 'value' => '!context.missing', + ) + ); $this->assertTrue( $result ); - $result = $this->evaluate( 'otherPlugin::!state.deeply.nested.missing' ); + $result = $this->evaluate( + array( + 'namespace' => 'otherPlugin', + 'value' => '!state.deeply.nested.missing', + ) + ); $this->assertTrue( $result ); - $result = $this->evaluate( 'otherPlugin::!context.deeply.nested.missing' ); + $result = $this->evaluate( + array( + 'namespace' => 'otherPlugin', + 'value' => '!context.deeply.nested.missing', + ) + ); $this->assertTrue( $result ); } @@ -1243,24 +1616,55 @@ public function test_evaluate_non_existent_path() { 'otherPlugin' => array( 'key' => 'otherPlugin-context' ), ) ); - $this->set_internal_namespace_stack( 'myPlugin' ); + $default_ns = 'myPlugin'; + $this->set_internal_namespace_stack( $default_ns ); - $result = $this->evaluate( 'state.nonExistentKey' ); + $result = $this->evaluate( + array( + 'namespace' => $default_ns, + 'value' => 'state.nonExistentKey', + ) + ); $this->assertNull( $result ); - $result = $this->evaluate( 'context.nonExistentKey' ); + $result = $this->evaluate( + array( + 'namespace' => $default_ns, + 'value' => 'context.nonExistentKey', + ) + ); $this->assertNull( $result ); - $result = $this->evaluate( 'otherPlugin::state.nonExistentKey' ); + $result = $this->evaluate( + array( + 'namespace' => 'otherPlugin', + 'value' => 'state.nonExistentKey', + ) + ); $this->assertNull( $result ); - $result = $this->evaluate( 'otherPlugin::context.nonExistentKey' ); + $result = $this->evaluate( + array( + 'namespace' => 'otherPlugin', + 'value' => 'context.nonExistentKey', + ) + ); $this->assertNull( $result ); - $result = $this->evaluate( ' state.key' ); // Extra space. + $result = $this->evaluate( + array( + 'namespace' => $default_ns, + 'value' => ' state.key', // Extra space. + ) + ); $this->assertNull( $result ); - $result = $this->evaluate( 'otherPlugin:: state.key' ); // Extra space. + $result = $this->evaluate( + array( + 'namespace' => 'otherPlugin', + 'value' => ' state.key', // Extra space. + ) + ); $this->assertNull( $result ); } @@ -1294,18 +1698,39 @@ public function test_evaluate_nested_value() { ), ) ); - $this->set_internal_namespace_stack( 'myPlugin' ); + $default_ns = 'myPlugin'; + $this->set_internal_namespace_stack( $default_ns ); - $result = $this->evaluate( 'state.nested.key' ); + $result = $this->evaluate( + array( + 'namespace' => $default_ns, + 'value' => 'state.nested.key', + ) + ); $this->assertSame( 'myPlugin-state-nested', $result ); - $result = $this->evaluate( 'context.nested.key' ); + $result = $this->evaluate( + array( + 'namespace' => $default_ns, + 'value' => 'context.nested.key', + ) + ); $this->assertSame( 'myPlugin-context-nested', $result ); - $result = $this->evaluate( 'otherPlugin::state.nested.key' ); + $result = $this->evaluate( + array( + 'namespace' => 'otherPlugin', + 'value' => 'state.nested.key', + ) + ); $this->assertSame( 'otherPlugin-state-nested', $result ); - $result = $this->evaluate( 'otherPlugin::context.nested.key' ); + $result = $this->evaluate( + array( + 'namespace' => 'otherPlugin', + 'value' => 'context.nested.key', + ) + ); $this->assertSame( 'otherPlugin-context-nested', $result ); } @@ -1321,13 +1746,28 @@ public function test_evaluate_unvalid_namespaces() { $this->set_internal_context_stack( array() ); $this->set_internal_namespace_stack(); - $result = $this->evaluate( 'path', 'null' ); + $result = $this->evaluate( + array( + 'namespace' => 'null', + 'value' => 'path', + ) + ); $this->assertNull( $result ); - $result = $this->evaluate( 'path', '' ); + $result = $this->evaluate( + array( + 'namespace' => '', + 'value' => 'path', + ) + ); $this->assertNull( $result ); - $result = $this->evaluate( 'path', '{}' ); + $result = $this->evaluate( + array( + 'namespace' => '{}', + 'value' => 'path', + ) + ); $this->assertNull( $result ); } @@ -1365,7 +1805,12 @@ public function test_evaluate_derived_state() { ); $this->set_internal_namespace_stack( 'myPlugin' ); - $result = $this->evaluate( 'state.derived' ); + $result = $this->evaluate( + array( + 'namespace' => 'myPlugin', + 'value' => 'state.derived', + ) + ); $this->assertSame( "Derived state: myPlugin-state\nDerived context: myPlugin-context", $result ); } @@ -1408,7 +1853,12 @@ public function test_evaluate_derived_state_accessing_different_namespace() { ); $this->set_internal_namespace_stack( 'myPlugin' ); - $result = $this->evaluate( 'state.derived' ); + $result = $this->evaluate( + array( + 'namespace' => 'myPlugin', + 'value' => 'state.derived', + ) + ); $this->assertSame( "Derived state: otherPlugin-state\nDerived context: otherPlugin-context", $result ); } @@ -1451,7 +1901,12 @@ public function test_evaluate_derived_state_defined_in_different_namespace() { ); $this->set_internal_namespace_stack( 'myPlugin' ); - $result = $this->evaluate( 'otherPlugin::state.derived' ); + $result = $this->evaluate( + array( + 'namespace' => 'otherPlugin', + 'value' => 'state.derived', + ) + ); $this->assertSame( "Derived state: otherPlugin-state\nDerived context: otherPlugin-context", $result ); } @@ -1475,7 +1930,12 @@ public function test_evaluate_derived_state_that_throws() { $this->set_internal_context_stack(); $this->set_internal_namespace_stack( 'myPlugin' ); - $result = $this->evaluate( 'state.derivedThatThrows' ); + $result = $this->evaluate( + array( + 'namespace' => 'myPlugin', + 'value' => 'state.derivedThatThrows', + ) + ); $this->assertNull( $result ); } @@ -1498,7 +1958,12 @@ public function test_evaluate_derived_state_intermediate() { $this->set_internal_context_stack(); $this->set_internal_namespace_stack( 'myPlugin' ); - $result = $this->evaluate( 'state.derivedState.property' ); + $result = $this->evaluate( + array( + 'namespace' => 'myPlugin', + 'value' => 'state.derivedState.property', + ) + ); $this->assertSame( 'value', $result ); }