Make WordPress Core

Changeset 61020


Ignore:
Timestamp:
10/21/2025 12:07:16 PM (10 months ago)
Author:
luisherranz
Message:

Interactivity API: Support unique IDs in server-side directives processing.

Server-side logic to support unique IDs in the Interactivity API directives to match what the client is doing (https://github.com/WordPress/gutenberg/pull/72161).

Props santosguillamot, darerodz, luisherranz.
Fixes #64106.

Location:
trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/interactivity-api/class-wp-interactivity-api.php

    r61019 r61020  
    533533                                        // Checks if there is a server directive processor registered for each directive.
    534534                                        foreach ( $p->get_attribute_names_with_prefix( 'data-wp-' ) as $attribute_name ) {
    535                                                 if ( ! preg_match(
    536                                                         /*
    537                                                          * This must align with the client-side regex used by the interactivity API.
    538                                                          * @see https://github.com/WordPress/gutenberg/blob/ca616014255efbb61f34c10917d52a2d86c1c660/packages/interactivity/src/vdom.ts#L20-L32
    539                                                          */
    540                                                         '/' .
    541                                                         '^data-wp-' .
    542                                                         // Match alphanumeric characters including hyphen-separated
    543                                                         // segments. It excludes underscore intentionally to prevent confusion.
    544                                                         // E.g., "custom-directive".
    545                                                         '([a-z0-9]+(?:-[a-z0-9]+)*)' .
    546                                                         // (Optional) Match '--' followed by any alphanumeric charachters. It
    547                                                         // excludes underscore intentionally to prevent confusion, but it can
    548                                                         // contain multiple hyphens. E.g., "--custom-prefix--with-more-info".
    549                                                         '(?:--([a-z0-9_-]+))?$' .
    550                                                         '/i',
    551                                                         $attribute_name
    552                                                 ) ) {
     535                                                $parsed_directive = $this->parse_directive_name( $attribute_name );
     536                                                if ( empty( $parsed_directive ) ) {
    553537                                                        continue;
    554538                                                }
    555                                                 list( $directive_prefix ) = $this->extract_prefix_and_suffix( $attribute_name );
     539                                                $directive_prefix = 'data-wp-' . $parsed_directive['prefix'];
    556540                                                if ( array_key_exists( $directive_prefix, self::$directive_processors ) ) {
    557541                                                        $directives_prefixes[] = $directive_prefix;
     
    630614                 * It returns null if the HTML is unbalanced because unbalanced HTML is
    631615                 * not safe to process. In that case, the Interactivity API runtime will
    632                  * update the HTML on the client side during the hydration. It will also
    633                  * display a notice to the developer to inform them about the issue.
     616                 * update the HTML on the client side during the hydration. It will display
     617                 * a notice to the developer in the console to inform them about the issue.
    634618                 */
    635619                if ( $unbalanced || 0 < count( $tag_stack ) ) {
    636                         $tag_errored = 0 < count( $tag_stack ) ? end( $tag_stack )[0] : $tag_name;
    637                         /* translators: %1s: Namespace processed, %2s: The tag that caused the error; could be any HTML tag.  */
    638                         $message = sprintf( __( 'Interactivity directives failed to process in "%1$s" due to a missing "%2$s" end tag.' ), end( $this->namespace_stack ), $tag_errored );
    639                         _doing_it_wrong( __METHOD__, $message, '6.6.0' );
    640620                        return null;
    641621                }
     
    652632         * @since 6.6.0 Removed `default_namespace` and `context` arguments.
    653633         * @since 6.6.0 Add support for derived state.
    654          *
    655          * @param string|true $directive_value The directive attribute value string or `true` when it's a boolean attribute.
     634         * @since 6.9.0 Recieve $entry as an argument instead of the directive value string.
     635         *
     636         * @param array $entry An array containing a whole directive entry with its namespace, value, suffix, or unique ID.
    656637         * @return mixed|null The result of the evaluation. Null if the reference path doesn't exist or the namespace is falsy.
    657638         */
    658         private function evaluate( $directive_value ) {
    659                 $default_namespace = end( $this->namespace_stack );
    660                 $context           = end( $this->context_stack );
    661 
    662                 list( $ns, $path ) = $this->extract_directive_value( $directive_value, $default_namespace );
     639        private function evaluate( $entry ) {
     640                $context                               = end( $this->context_stack );
     641                ['namespace' => $ns, 'value' => $path] = $entry;
     642
    663643                if ( ! $ns || ! $path ) {
    664644                        /* translators: %s: The directive value referenced. */
    665                         $message = sprintf( __( 'Namespace or reference path cannot be empty. Directive value referenced: %s' ), $directive_value );
     645                        $message = sprintf( __( 'Namespace or reference path cannot be empty. Directive value referenced: %s' ), json_encode( $entry ) );
    666646                        _doing_it_wrong( __METHOD__, $message, '6.6.0' );
    667647                        return null;
     
    767747
    768748        /**
    769          * Extracts the directive attribute name to separate and return the directive
    770          * prefix and an optional suffix.
    771          *
    772          * The suffix is the string after the first double hyphen and the prefix is
    773          * everything that comes before the suffix.
    774          *
    775          * Example:
    776          *
    777          *     extract_prefix_and_suffix( 'data-wp-interactive' )   => array( 'data-wp-interactive', null )
    778          *     extract_prefix_and_suffix( 'data-wp-bind--src' )     => array( 'data-wp-bind', 'src' )
    779          *     extract_prefix_and_suffix( 'data-wp-foo--and--bar' ) => array( 'data-wp-foo', 'and--bar' )
    780          *
    781          * @since 6.5.0
     749         * Parse the directive name to extract the following parts:
     750         * - Prefix: The main directive name without "data-wp-".
     751         * - Suffix: An optional suffix used during directive processing, extracted after the first double hyphen "--".
     752         * - Unique ID: An optional unique identifier, extracted after the first triple hyphen "---".
     753         *
     754         * This function has an equivalent version for the client side.
     755         * See `parseDirectiveName` in https://github.com/WordPress/gutenberg/blob/trunk/packages/interactivity/src/vdom.ts.:
     756         *
     757         * See examples in the function unit tests `test_parse_directive_name`.
     758         *
     759         * @since 6.9.0
    782760         *
    783761         * @param string $directive_name The directive attribute name.
    784          * @return array An array containing the directive prefix and optional suffix.
    785          */
    786         private function extract_prefix_and_suffix( string $directive_name ): array {
    787                 return explode( '--', $directive_name, 2 );
     762         * @return array An array containing the directive prefix, optional suffix, and optional unique ID.
     763         */
     764        private function parse_directive_name( string $directive_name ): ?array {
     765                // Remove the first 8 characters (assumes "data-wp-" prefix)
     766                $name = substr( $directive_name, 8 );
     767
     768                // Check for invalid characters (anything not a-z, 0-9, -, or _)
     769                if ( preg_match( '/[^a-z0-9\-_]/i', $name ) ) {
     770                        return null;
     771                }
     772
     773                // Find the first occurrence of '--' to separate the prefix
     774                $suffix_index = strpos( $name, '--' );
     775
     776                if ( false === $suffix_index ) {
     777                        return array(
     778                                'prefix'    => $name,
     779                                'suffix'    => null,
     780                                'unique_id' => null,
     781                        );
     782                }
     783
     784                $prefix    = substr( $name, 0, $suffix_index );
     785                $remaining = substr( $name, $suffix_index );
     786
     787                // If remaining starts with '---' but not '----', it's a unique_id
     788                if ( '---' === substr( $remaining, 0, 3 ) && '-' !== ( $remaining[3] ?? '' ) ) {
     789                        return array(
     790                                'prefix'    => $prefix,
     791                                'suffix'    => null,
     792                                'unique_id' => '---' !== $remaining ? substr( $remaining, 3 ) : null,
     793                        );
     794                }
     795
     796                // Otherwise, remove the first two dashes for a potential suffix
     797                $suffix = substr( $remaining, 2 );
     798
     799                // Look for '---' in the suffix for a unique_id
     800                $unique_id_index = strpos( $suffix, '---' );
     801
     802                if ( false !== $unique_id_index && '-' !== ( $suffix[ $unique_id_index + 3 ] ?? '' ) ) {
     803                        $unique_id = substr( $suffix, $unique_id_index + 3 );
     804                        $suffix    = substr( $suffix, 0, $unique_id_index );
     805                        return array(
     806                                'prefix'    => $prefix,
     807                                'suffix'    => empty( $suffix ) ? null : $suffix,
     808                                'unique_id' => empty( $unique_id ) ? null : $unique_id,
     809                        );
     810                }
     811
     812                return array(
     813                        'prefix'    => $prefix,
     814                        'suffix'    => empty( $suffix ) ? null : $suffix,
     815                        'unique_id' => null,
     816                );
    788817        }
    789818
     
    835864
    836865                return array( $default_namespace, $directive_value );
     866        }
     867
     868        /**
     869         * Parse the HTML element and get all the valid directives with the given prefix.
     870         *
     871         * @since 6.9.0
     872         *
     873         * @param WP_Interactivity_API_Directives_Processor $p      The directives processor instance.
     874         * @param string                                    $prefix The directive prefix to filter by.
     875         * @return array An array of entries containing the directive namespace, value, suffix, and unique ID.
     876         */
     877        private function get_directive_entries( WP_Interactivity_API_Directives_Processor $p, string $prefix ) {
     878                $directive_attributes = $p->get_attribute_names_with_prefix( 'data-wp-' . $prefix );
     879                $entries              = array();
     880                foreach ( $directive_attributes as $attribute_name ) {
     881                        [ 'prefix' => $attr_prefix, 'suffix' => $suffix, 'unique_id' => $unique_id] = $this->parse_directive_name( $attribute_name );
     882                        // Ensure it is the desired directive.
     883                        if ( $prefix !== $attr_prefix ) {
     884                                continue;
     885                        }
     886                        list( $namespace, $value ) = $this->extract_directive_value( $p->get_attribute( $attribute_name ), end( $this->namespace_stack ) );
     887                        $entries[]                 = array(
     888                                'namespace' => $namespace,
     889                                'value'     => $value,
     890                                'suffix'    => $suffix,
     891                                'unique_id' => $unique_id,
     892                        );
     893                }
     894                // Sort directive entries to ensure stable ordering with the client.
     895                // Put nulls first, then sort by suffix and finally by uniqueIds.
     896                usort(
     897                        $entries,
     898                        function ( $a, $b ) {
     899                                $a_suffix = $a['suffix'] ?? '';
     900                                $b_suffix = $b['suffix'] ?? '';
     901                                if ( $a_suffix !== $b_suffix ) {
     902                                        return $a_suffix < $b_suffix ? -1 : 1;
     903                                }
     904                                $a_id = $a['unique_id'] ?? '';
     905                                $b_id = $b['unique_id'] ?? '';
     906                                if ( $a_id === $b_id ) {
     907                                        return 0;
     908                                }
     909                                return $a_id > $b_id ? 1 : -1;
     910                        }
     911                );
     912                return $entries;
    837913        }
    838914
     
    918994                }
    919995
    920                 $attribute_value = $p->get_attribute( 'data-wp-context' );
    921                 $namespace_value = end( $this->namespace_stack );
    922 
    923                 // Separates the namespace from the context JSON object.
    924                 list( $namespace_value, $decoded_json ) = is_string( $attribute_value ) && ! empty( $attribute_value )
    925                         ? $this->extract_directive_value( $attribute_value, $namespace_value )
    926                         : array( $namespace_value, null );
    927 
    928                 /*
    929                  * If there is a namespace, it adds a new context to the stack merging the
    930                  * previous context with the new one.
    931                  */
    932                 if ( is_string( $namespace_value ) ) {
    933                         $this->context_stack[] = array_replace_recursive(
    934                                 end( $this->context_stack ) !== false ? end( $this->context_stack ) : array(),
    935                                 array( $namespace_value => is_array( $decoded_json ) ? $decoded_json : array() )
     996                $entries = $this->get_directive_entries( $p, 'context' );
     997                $context = end( $this->context_stack ) !== false ? end( $this->context_stack ) : array();
     998                foreach ( $entries as $entry ) {
     999                        if ( null !== $entry['suffix'] ) {
     1000                                continue;
     1001                        }
     1002
     1003                        $context = array_replace_recursive(
     1004                                $context,
     1005                                array( $entry['namespace'] => is_array( $entry['value'] ) ? $entry['value'] : array() )
    9361006                        );
    937                 } else {
    938                         /*
    939                          * If there is no namespace, it pushes the current context to the stack.
    940                          * It needs to do so because the function pops out the current context
    941                          * from the stack whenever it finds a `data-wp-context`'s closing tag.
    942                          */
    943                         $this->context_stack[] = end( $this->context_stack );
    944                 }
     1007                }
     1008                $this->context_stack[] = $context;
    9451009        }
    9461010
     
    9581022        private function data_wp_bind_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) {
    9591023                if ( 'enter' === $mode ) {
    960                         $all_bind_directives = $p->get_attribute_names_with_prefix( 'data-wp-bind--' );
    961 
    962                         foreach ( $all_bind_directives as $attribute_name ) {
    963                                 list( , $bound_attribute ) = $this->extract_prefix_and_suffix( $attribute_name );
    964                                 if ( empty( $bound_attribute ) ) {
    965                                         return;
    966                                 }
    967 
    968                                 $attribute_value = $p->get_attribute( $attribute_name );
    969                                 $result          = $this->evaluate( $attribute_value );
     1024                        $entries = $this->get_directive_entries( $p, 'bind' );
     1025                        foreach ( $entries as $entry ) {
     1026                                if ( empty( $entry['suffix'] ) || null !== $entry['unique_id'] ) {
     1027                                                return;
     1028                                }
     1029
     1030                                $result = $this->evaluate( $entry );
    9701031
    9711032                                if (
     
    9731034                                        (
    9741035                                                false !== $result ||
    975                                                 ( strlen( $bound_attribute ) > 5 && '-' === $bound_attribute[4] )
     1036                                                ( strlen( $entry['suffix'] ) > 5 && '-' === $entry['suffix'][4] )
    9761037                                        )
    9771038                                ) {
     
    9851046                                        if (
    9861047                                                is_bool( $result ) &&
    987                                                 ( strlen( $bound_attribute ) > 5 && '-' === $bound_attribute[4] )
     1048                                                ( strlen( $entry['suffix'] ) > 5 && '-' === $entry['suffix'][4] )
    9881049                                        ) {
    9891050                                                $result = $result ? 'true' : 'false';
    9901051                                        }
    991                                         $p->set_attribute( $bound_attribute, $result );
     1052                                        $p->set_attribute( $entry['suffix'], $result );
    9921053                                } else {
    993                                         $p->remove_attribute( $bound_attribute );
     1054                                        $p->remove_attribute( $entry['suffix'] );
    9941055                                }
    9951056                        }
     
    10111072                if ( 'enter' === $mode ) {
    10121073                        $all_class_directives = $p->get_attribute_names_with_prefix( 'data-wp-class--' );
    1013 
    1014                         foreach ( $all_class_directives as $attribute_name ) {
    1015                                 list( , $class_name ) = $this->extract_prefix_and_suffix( $attribute_name );
     1074                        $entries              = $this->get_directive_entries( $p, 'class' );
     1075                        foreach ( $entries as $entry ) {
     1076                                if ( empty( $entry['suffix'] ) ) {
     1077                                        continue;
     1078                                }
     1079                                $class_name = isset( $entry['unique_id'] ) && $entry['unique_id']
     1080                                        ? "{$entry['suffix']}---{$entry['unique_id']}"
     1081                                        : $entry['suffix'];
     1082
    10161083                                if ( empty( $class_name ) ) {
    10171084                                        return;
    10181085                                }
    10191086
    1020                                 $attribute_value = $p->get_attribute( $attribute_name );
    1021                                 $result          = $this->evaluate( $attribute_value );
     1087                                $result = $this->evaluate( $entry );
    10221088
    10231089                                if ( $result ) {
     
    10431109        private function data_wp_style_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) {
    10441110                if ( 'enter' === $mode ) {
    1045                         $all_style_attributes = $p->get_attribute_names_with_prefix( 'data-wp-style--' );
    1046 
    1047                         foreach ( $all_style_attributes as $attribute_name ) {
    1048                                 list( , $style_property ) = $this->extract_prefix_and_suffix( $attribute_name );
    1049                                 if ( empty( $style_property ) ) {
     1111                        $entries = $this->get_directive_entries( $p, 'style' );
     1112                        foreach ( $entries as $entry ) {
     1113                                $style_property = $entry['suffix'];
     1114                                if ( empty( $style_property ) || null !== $entry['unique_id'] ) {
    10501115                                        continue;
    10511116                                }
    10521117
    1053                                 $directive_attribute_value = $p->get_attribute( $attribute_name );
    1054                                 $style_property_value      = $this->evaluate( $directive_attribute_value );
    1055                                 $style_attribute_value     = $p->get_attribute( 'style' );
    1056                                 $style_attribute_value     = ( $style_attribute_value && ! is_bool( $style_attribute_value ) ) ? $style_attribute_value : '';
     1118                                $style_property_value  = $this->evaluate( $entry );
     1119                                $style_attribute_value = $p->get_attribute( 'style' );
     1120                                $style_attribute_value = ( $style_attribute_value && ! is_bool( $style_attribute_value ) ) ? $style_attribute_value : '';
    10571121
    10581122                                /*
     
    11341198        private function data_wp_text_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) {
    11351199                if ( 'enter' === $mode ) {
    1136                         $attribute_value = $p->get_attribute( 'data-wp-text' );
    1137                         $result          = $this->evaluate( $attribute_value );
     1200                        $entries     = $this->get_directive_entries( $p, 'text' );
     1201                        $valid_entry = null;
     1202                        // Get the first valid `data-wp-text` entry without suffix or unique ID.
     1203                        foreach ( $entries as $entry ) {
     1204                                if ( null === $entry['suffix'] && null === $entry['unique_id'] && ! empty( $entry['value'] ) ) {
     1205                                        $valid_entry = $entry;
     1206                                        break;
     1207                                }
     1208                        }
     1209                        if ( null === $valid_entry ) {
     1210                                return;
     1211                        }
     1212                        $result = $this->evaluate( $valid_entry );
    11381213
    11391214                        /*
     
    12641339        private function data_wp_each_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$tag_stack ) {
    12651340                if ( 'enter' === $mode && 'TEMPLATE' === $p->get_tag() ) {
    1266                         $attribute_name   = $p->get_attribute_names_with_prefix( 'data-wp-each' )[0];
    1267                         $extracted_suffix = $this->extract_prefix_and_suffix( $attribute_name );
    1268                         $item_name        = isset( $extracted_suffix[1] ) ? $this->kebab_to_camel_case( $extracted_suffix[1] ) : 'item';
    1269                         $attribute_value  = $p->get_attribute( $attribute_name );
    1270                         $result           = $this->evaluate( $attribute_value );
     1341                        $entries = $this->get_directive_entries( $p, 'each' );
     1342                        if ( count( $entries ) > 1 || empty( $entries ) ) {
     1343                                // There should be only one `data-wp-each` directive per template tag.
     1344                                return;
     1345                        }
     1346                        $entry = $entries[0];
     1347                        if ( null !== $entry['unique_id'] ) {
     1348                                return;
     1349                        }
     1350                        $item_name = isset( $entry['suffix'] ) ? $this->kebab_to_camel_case( $entry['suffix'] ) : 'item';
     1351                        $result    = $this->evaluate( $entry );
    12711352
    12721353                        // Gets the content between the template tags and leaves the cursor in the closer tag.
     
    13011382                        }
    13021383
    1303                         // Extracts the namespace from the directive attribute value.
    1304                         $namespace_value                = end( $this->namespace_stack );
    1305                         list( $namespace_value, $path ) = is_string( $attribute_value ) && ! empty( $attribute_value )
    1306                                 ? $this->extract_directive_value( $attribute_value, $namespace_value )
    1307                                 : array( $namespace_value, null );
    1308 
    13091384                        // Processes the inner content for each item of the array.
    13101385                        $processed_content = '';
     
    13131388                                $this->context_stack[] = array_replace_recursive(
    13141389                                        end( $this->context_stack ) !== false ? end( $this->context_stack ) : array(),
    1315                                         array( $namespace_value => array( $item_name => $item ) )
     1390                                        array( $entry['namespace'] => array( $item_name => $item ) )
    13161391                                );
    13171392
     
    13381413                                $i = new WP_Interactivity_API_Directives_Processor( $processed_item );
    13391414                                while ( $i->next_tag() ) {
    1340                                         $i->set_attribute( 'data-wp-each-child', $namespace_value . '::' . $path );
     1415                                        $i->set_attribute( 'data-wp-each-child', $entry['namespace'] . '::' . $entry['value'] );
    13411416                                        $i->next_balanced_tag_closer_tag();
    13421417                                }
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-bind.php

    r58321 r61020  
    399399                $this->assertSame( true, $p->get_attribute( 'id' ) );
    400400        }
     401
     402        /**
     403         * Tests ignores unique IDs in bind directive.
     404         *
     405         * @ticket 64106
     406         *
     407         * @covers ::process_directives
     408         */
     409        public function test_wp_bind_ignores_unique_ids() {
     410                $html    = '<div data-wp-bind--id="myPlugin::state.trueValue"></div>';
     411                list($p) = $this->process_directives( $html );
     412                $this->assertSame( true, $p->get_attribute( 'id' ) );
     413
     414                $html    = '<div data-wp-bind--id---unique-id="myPlugin::state.trueValue"></div>';
     415                list($p) = $this->process_directives( $html );
     416                $this->assertNull( $p->get_attribute( 'id' ) );
     417                $this->assertNull( $p->get_attribute( 'id---unique-id' ) );
     418        }
    401419}
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-class.php

    r58594 r61020  
    7777                        >Text</div>';
    7878                list($p) = $this->process_directives( $html );
    79                 $this->assertSame( 'some-class other-class', $p->get_attribute( 'class' ) );
     79                $this->assertSame( 'other-class some-class', $p->get_attribute( 'class' ) );
    8080        }
    8181
     
    329329                $this->assertNull( $p->get_attribute( 'class' ) );
    330330        }
     331
     332        /**
     333         * Tests that classes with several dashes can be used.
     334         *
     335         * @ticket 64106
     336         *
     337         * @covers ::process_directives
     338         */
     339        public function test_wp_class_can_use_several_dashes() {
     340                $html    = '<div data-wp-class--main-bg--color="myPlugin::state.true">Text</div>';
     341                list($p) = $this->process_directives( $html );
     342                $this->assertSame( 'main-bg--color', $p->get_attribute( 'class' ) );
     343
     344                $html    = '<div data-wp-class--main-bg---color="myPlugin::state.true">Text</div>';
     345                list($p) = $this->process_directives( $html );
     346                $this->assertSame( 'main-bg---color', $p->get_attribute( 'class' ) );
     347
     348                $html    = '<div data-wp-class--main-bg----color="myPlugin::state.true">Text</div>';
     349                list($p) = $this->process_directives( $html );
     350                $this->assertSame( 'main-bg----color', $p->get_attribute( 'class' ) );
     351        }
    331352}
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-context.php

    r58594 r61020  
    523523                $this->assertSame( 'some-id-1', $p->get_attribute( 'id' ) );
    524524        }
     525
     526        /**
     527         * Tests supports multiple context directives in the same element.
     528         *
     529         * @ticket 64106
     530         *
     531         * @covers ::process_directives
     532         */
     533        public function test_wp_context_supports_multiple_directives_in_the_same_element() {
     534                $html    = '
     535                        <div
     536                                data-wp-interactive="directive-context/multiple"
     537                                data-wp-context=\'{ "prop": "parent", "parent": true }\'
     538                        >
     539                                <div
     540                                        data-wp-context---id2=\'other-namespace::{ "prop": true }\'
     541                                        data-wp-context=\'{ "prop": "default", "default": true }\'
     542                                        data-wp-context---id1=\'{ "prop": "id1", "id1": true }\'
     543                                >
     544                                        <span
     545                                                class="test"
     546                                                data-wp-bind--data-test-prop="context.prop"
     547                                                data-wp-bind--data-test-parent="context.parent"
     548                                                data-wp-bind--data-test-default="context.default"
     549                                                data-wp-bind--data-test-id1="context.id1"
     550                                                data-wp-bind--data-test-other="other-namespace::context.prop"
     551                                        ></span>
     552                                </div>
     553                        </div>
     554                ';
     555                list($p) = $this->process_directives( $html );
     556                $this->assertSame( 'id1', $p->get_attribute( 'data-test-prop' ) );
     557                foreach ( array( 'parent', 'default', 'id1', 'other' ) as $attribute ) {
     558                        $attr_name = "data-test-$attribute";
     559                        $this->assertSame(
     560                                'true',
     561                                $p->get_attribute( $attr_name ),
     562                                "Failed asserting that $attr_name equals 'true'"
     563                        );
     564                }
     565        }
    525566}
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-each.php

    r60953 r61020  
    581581         *
    582582         * @covers ::process_directives
    583          *
    584          * @expectedIncorrectUsage WP_Interactivity_API::_process_directives
    585583         */
    586584        public function test_wp_each_unbalanced_tags() {
     
    601599         *
    602600         * @covers ::process_directives
    603          *
    604          * @expectedIncorrectUsage WP_Interactivity_API::_process_directives
    605601         */
    606602        public function test_wp_each_unbalanced_tags_in_nested_template_tags() {
     
    685681                $this->assertSame( $expected, $new );
    686682        }
     683
     684        /**
     685         * Tests it doesn't support multiple directives.
     686         *
     687         * @ticket 64106
     688         *
     689         * @covers ::process_directives
     690         */
     691        public function test_wp_each_doesnt_support_multiple_directives() {
     692                $original = '' .
     693                        '<div data-wp-interactive="directive-each">' .
     694                                '<template data-wp-each="myPlugin::state.list" data-wp-each--item="myPlugin::state.list">' .
     695                                        '<span data-wp-text="myPlugin::context.item"></span>' .
     696                                '</template>' .
     697                                '<template data-wp-each---unique-id="myPlugin::state.list">' .
     698                                        '<span data-wp-text="myPlugin::context.item"></span>' .
     699                                '</template>' .
     700                                '<div data-wp-bind--id="myPlugin::state.after">Text</div>' .
     701                        '</div>';
     702                $expected = '' .
     703                        '<div data-wp-interactive="directive-each">' .
     704                                '<template data-wp-each="myPlugin::state.list" data-wp-each--item="myPlugin::state.list">' .
     705                                        '<span data-wp-text="myPlugin::context.item"></span>' .
     706                                '</template>' .
     707                                '<template data-wp-each---unique-id="myPlugin::state.list">' .
     708                                        '<span data-wp-text="myPlugin::context.item"></span>' .
     709                                '</template>' .
     710                                '<div id="after-wp-each" data-wp-bind--id="myPlugin::state.after">Text</div>' .
     711                        '</div>';
     712                $new      = $this->interactivity->process_directives( $original );
     713                $this->assertSame( $expected, $new );
     714        }
    687715}
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-style.php

    r60729 r61020  
    191191                        >Text</div>';
    192192                list($p) = $this->process_directives( $html );
    193                 $this->assertSame( 'color:green;background:green;', $p->get_attribute( 'style' ) );
     193                $this->assertSame( 'background:green;color:green;', $p->get_attribute( 'style' ) );
    194194        }
    195195
     
    449449                $this->assertNull( $p->get_attribute( 'style' ) );
    450450        }
     451
     452        /**
     453         * Tests it can use CSS variables.
     454         *
     455         * @ticket 64106
     456         *
     457         * @covers ::process_directives
     458         */
     459        public function test_wp_style_can_use_CSS_variables() {
     460                $html    = '<div data-wp-style----text-color="myPlugin::state.green">Text</div>';
     461                list($p) = $this->process_directives( $html );
     462                $this->assertSame( '--text-color:green;', $p->get_attribute( 'style' ) );
     463        }
     464
     465        /**
     466         * Tests it ignores unique IDs.
     467         *
     468         * @ticket 64106
     469         *
     470         * @covers ::process_directives
     471         */
     472        public function test_wp_style_ignores_unique_ids() {
     473                $html    = '<div data-wp-style--color---unique-id="myPlugin::state.green">Text</div>';
     474                list($p) = $this->process_directives( $html );
     475                $this->assertNull( $p->get_attribute( 'style' ) );
     476        }
    451477}
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-text.php

    r58594 r61020  
    1313 * @group interactivity-api
    1414 */
    15 class Tests_Interactivity_API_WpInteractivityAPIWPText extends WP_UnitTestCase {
     15class Tests_WP_Interactivity_API_WP_Text extends WP_UnitTestCase {
    1616        /**
    1717         * Instance of WP_Interactivity_API.
     
    132132         *
    133133         * @covers ::process_directives
    134          *
    135          * @expectedIncorrectUsage WP_Interactivity_API::_process_directives
    136134         */
    137135        public function test_wp_text_fails_with_unbalanced_and_same_tags_inside_content() {
     
    155153                $this->assertSame( '<div data-wp-text="myPlugin::state.text">&lt;span&gt;Updated&lt;/span&gt;</div>', $new_html );
    156154        }
     155
     156        /**
     157         * Tests it ignores suffixes and unique-ids.
     158         *
     159         * @ticket 64106
     160         *
     161         * @covers ::process_directives
     162         */
     163        public function test_wp_text_ignores_suffixes_and_unique_ids() {
     164                $html     = '<span data-wp-text--suffix="myPlugin::state.text">Text</span>';
     165                $new_html = $this->interactivity->process_directives( $html );
     166                $this->assertSame( $html, $new_html );
     167
     168                $html     = '<span data-wp-text---unique-id="myPlugin::state.text">Text</span>';
     169                $new_html = $this->interactivity->process_directives( $html );
     170                $this->assertSame( $html, $new_html );
     171        }
     172
     173        /**
     174         * Tests first `data-wp-text` works even when suffixes and unique-ids are included.
     175         *
     176         * @ticket 64106
     177         *
     178         * @covers ::process_directives
     179         */
     180        public function test_wp_text_works_even_when_suffixes_and_unique_ids_are_included() {
     181                $original = '<span data-wp-text--suffix="myPlugin::state.text" data-wp-text---unique-id="myPlugin::state.text" data-wp-text="myPlugin::state.text">Text</span>';
     182                $expected = '<span data-wp-text--suffix="myPlugin::state.text" data-wp-text---unique-id="myPlugin::state.text" data-wp-text="myPlugin::state.text">Updated</span>';
     183                $new_html = $this->interactivity->process_directives( $original );
     184                $this->assertSame( $expected, $new_html );
     185        }
    157186}
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php

    r61019 r61020  
    379379                                // Multiple evaluations should be serialized only once.
    380380                                $this->set_internal_namespace_stack( 'pluginWithInvokedDerivedState' );
    381                                 $this->evaluate( 'state.derivedProp' );
    382                                 $this->evaluate( 'state.derivedProp' );
    383                                 $this->evaluate( 'state.nested.derivedProp' );
    384                                 $this->evaluate( 'state.nested.derivedProp' );
     381                                $this->evaluate(
     382                                        array(
     383                                                'namespace' => 'pluginWithInvokedDerivedState',
     384                                                'value'     => 'state.derivedProp',
     385                                        )
     386                                );
     387                                $this->evaluate(
     388                                        array(
     389                                                'namespace' => 'pluginWithInvokedDerivedState',
     390                                                'value'     => 'state.derivedProp',
     391                                        )
     392                                );
     393                                $this->evaluate(
     394                                        array(
     395                                                'namespace' => 'pluginWithInvokedDerivedState',
     396                                                'value'     => 'state.nested.derivedProp',
     397                                        )
     398                                );
     399                                $this->evaluate(
     400                                        array(
     401                                                'namespace' => 'pluginWithInvokedDerivedState',
     402                                                'value'     => 'state.nested.derivedProp',
     403                                        )
     404                                );
    385405
    386406                                // Only the path part that points to a derived state prop should be serialized.
    387407                                $this->set_internal_namespace_stack( 'pluginWithInvokedDerivedStateReturningArray' );
    388                                 $this->evaluate( 'state.nested.derivedProp.prop' );
     408                                $this->evaluate(
     409                                        array(
     410                                                'namespace' => 'pluginWithInvokedDerivedStateReturningArray',
     411                                                'value'     => 'state.nested.derivedProp',
     412                                        )
     413                                );
    389414                        }
    390415                );
     
    820845         * name.
    821846         *
    822          * @ticket 60356
    823          *
    824          * @covers ::extract_prefix_and_suffix
    825          */
    826         public function test_extract_prefix_and_suffix() {
    827                 $extract_prefix_and_suffix = new ReflectionMethod( $this->interactivity, 'extract_prefix_and_suffix' );
     847         * @ticket 64106
     848         *
     849         * @covers ::parse_directive_name
     850         */
     851        public function test_parse_directive_name() {
     852                $parse_directive_name = new ReflectionMethod( $this->interactivity, 'parse_directive_name' );
    828853                if ( PHP_VERSION_ID < 80100 ) {
    829                         $extract_prefix_and_suffix->setAccessible( true );
     854                        $parse_directive_name->setAccessible( true );
    830855                }
    831856
    832                 $result = $extract_prefix_and_suffix->invoke( $this->interactivity, 'data-wp-interactive' );
    833                 $this->assertSame( array( 'data-wp-interactive' ), $result );
    834 
    835                 $result = $extract_prefix_and_suffix->invoke( $this->interactivity, 'data-wp-bind--src' );
    836                 $this->assertSame( array( 'data-wp-bind', 'src' ), $result );
    837 
    838                 $result = $extract_prefix_and_suffix->invoke( $this->interactivity, 'data-wp-foo--and--bar' );
    839                 $this->assertSame( array( 'data-wp-foo', 'and--bar' ), $result );
     857                // Should parse directives without suffix or unique ID.
     858                $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test' );
     859                $this->assertSame( 'test', $result['prefix'] );
     860                $this->assertNull( $result['suffix'] );
     861                $this->assertNull( $result['unique_id'] );
     862
     863                // Should parse directives with suffix only.
     864                $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test--one' );
     865                $this->assertSame( 'test', $result['prefix'] );
     866                $this->assertSame( 'one', $result['suffix'] );
     867                $this->assertNull( $result['unique_id'] );
     868
     869                // Should parse directives with unique ID only.
     870                $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test---unique-id' );
     871                $this->assertSame( 'test', $result['prefix'] );
     872                $this->assertNull( $result['suffix'] );
     873                $this->assertSame( 'unique-id', $result['unique_id'] );
     874
     875                // Should parse directives with suffix and unique ID.
     876                $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test--suffix---unique-id' );
     877                $this->assertSame( 'test', $result['prefix'] );
     878                $this->assertSame( 'suffix', $result['suffix'] );
     879                $this->assertSame( 'unique-id', $result['unique_id'] );
     880
     881                // Should handle empty suffix (just two dashes).
     882                $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test--' );
     883                $this->assertSame( 'test', $result['prefix'] );
     884                $this->assertNull( $result['suffix'] );
     885                $this->assertNull( $result['unique_id'] );
     886
     887                // Should handle empty unique ID (just three dashes).
     888                $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test---' );
     889                $this->assertSame( 'test', $result['prefix'] );
     890                $this->assertNull( $result['suffix'] );
     891                $this->assertNull( $result['unique_id'] );
     892
     893                // Should handle only dashes (4 or more dashes).
     894                $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test----' );
     895                $this->assertSame( 'test', $result['prefix'] );
     896                $this->assertSame( '--', $result['suffix'] );
     897                $this->assertNull( $result['unique_id'] );
     898
     899                // Should handle suffix starting with 4 or more dashes but containing valid characters.
     900                $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test------custom-suffix' );
     901                $this->assertSame( 'test', $result['prefix'] );
     902                $this->assertSame( '----custom-suffix', $result['suffix'] );
     903                $this->assertNull( $result['unique_id'] );
     904
     905                // Should handle complex pattern with multiple dashes.
     906                $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test--complex--suffix---complex--unique---id' );
     907                $this->assertSame( 'test', $result['prefix'] );
     908                $this->assertSame( 'complex--suffix', $result['suffix'] );
     909                $this->assertSame( 'complex--unique---id', $result['unique_id'] );
     910
     911                // Should handle suffix with dashes followed by unique ID.
     912                $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test----suffix---unique-id' );
     913                $this->assertSame( 'test', $result['prefix'] );
     914                $this->assertSame( '--suffix', $result['suffix'] );
     915                $this->assertSame( 'unique-id', $result['unique_id'] );
     916
     917                // Should handle unique IDs followed by suffix in wrong order.
     918                $result = $parse_directive_name->invoke( $this->interactivity, 'data-wp-test---unique-id--wrong-suffix' );
     919                $this->assertSame( 'test', $result['prefix'] );
     920                $this->assertNull( $result['suffix'] );
     921                $this->assertSame( 'unique-id--wrong-suffix', $result['unique_id'] );
     922        }
     923
     924        /**
     925         * Tests the ability to get the valid entries of a specific directive in an HTML element.
     926         *
     927         * @ticket 64106
     928         *
     929         * @covers ::get_directive_entries
     930         */
     931        public function test_get_directive_entries() {
     932                $get_directive_entries = new ReflectionMethod( $this->interactivity, 'get_directive_entries' );
     933                if ( PHP_VERSION_ID < 80100 ) {
     934                        $get_directive_entries->setAccessible( true );
     935                }
     936                $this->set_internal_namespace_stack( 'myPlugin' );
     937
     938                // Should process simple directives.
     939                $html = '<div data-wp-test="test value"></div>';
     940                $p    = new WP_Interactivity_API_Directives_Processor( $html );
     941                $p->next_tag();
     942                $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' );
     943                $this->assertCount( 1, $results );
     944                $result = $results[0];
     945                $this->assertSame( 'myPlugin', $result['namespace'] );
     946                $this->assertSame( 'test value', $result['value'] );
     947                $this->assertNull( $result['suffix'] );
     948                $this->assertNull( $result['unique_id'] );
     949
     950                // Should process directives without value.
     951                $html = '<div data-wp-test></div>';
     952                $p    = new WP_Interactivity_API_Directives_Processor( $html );
     953                $p->next_tag();
     954                $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' );
     955                $this->assertNull( $results[0]['value'] );
     956
     957                // Should parse JSON values in directives.
     958                $html = '<div data-wp-test=\'{"key": "value"}\'></div>';
     959                $p    = new WP_Interactivity_API_Directives_Processor( $html );
     960                $p->next_tag();
     961                $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' );
     962                $this->assertSame( array( 'key' => 'value' ), $results[0]['value'] );
     963
     964                // Should handle malformed JSON and keep as string.
     965                $html = '<div data-wp-test="{malformed: json}"></div>';
     966                $p    = new WP_Interactivity_API_Directives_Processor( $html );
     967                $p->next_tag();
     968                $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' );
     969                $this->assertSame( '{malformed: json}', $results[0]['value'] );
     970
     971                // Should process directives with a custom namespace.
     972                $html = '<div data-wp-test="my-namespace::test value"></div>';
     973                $p    = new WP_Interactivity_API_Directives_Processor( $html );
     974                $p->next_tag();
     975                $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' );
     976                $this->assertSame( 'my-namespace', $results[0]['namespace'] );
     977                $this->assertSame( 'test value', $results[0]['value'] );
     978
     979                // Should parse JSON values with a custom namespace.
     980                $html = '<div data-wp-test=\'my-namespace::{"key": "value"}\'></div>';
     981                $p    = new WP_Interactivity_API_Directives_Processor( $html );
     982                $p->next_tag();
     983                $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' );
     984                $this->assertSame( 'my-namespace', $results[0]['namespace'] );
     985                $this->assertSame( array( 'key' => 'value' ), $results[0]['value'] );
     986
     987                // Should handle multiple directives with different unique IDs.
     988                $html = '
     989                        <div
     990                                data-wp-test---plugin-a="value-a"
     991                                data-wp-test---plugin-b="value-b"
     992                                data-wp-test---plugin-c="value-c"
     993                        ></div>';
     994                $p    = new WP_Interactivity_API_Directives_Processor( $html );
     995                $p->next_tag();
     996                $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' );
     997                $this->assertCount( 3, $results );
     998                $this->assertSame(
     999                        array(
     1000                                'namespace' => 'myPlugin',
     1001                                'value'     => 'value-a',
     1002                                'suffix'    => null,
     1003                                'unique_id' => 'plugin-a',
     1004                        ),
     1005                        $results[0]
     1006                );
     1007                $this->assertSame(
     1008                        array(
     1009                                'namespace' => 'myPlugin',
     1010                                'value'     => 'value-b',
     1011                                'suffix'    => null,
     1012                                'unique_id' => 'plugin-b',
     1013                        ),
     1014                        $results[1]
     1015                );
     1016                $this->assertSame(
     1017                        array(
     1018                                'namespace' => 'myPlugin',
     1019                                'value'     => 'value-c',
     1020                                'suffix'    => null,
     1021                                'unique_id' => 'plugin-c',
     1022                        ),
     1023                        $results[2]
     1024                );
     1025
     1026                // Should handle mix of different suffixes and unique IDs.
     1027                $html = '
     1028                        <div
     1029                                data-wp-test--suffix-a---id-1="value1"
     1030                                data-wp-test--suffix-a---id-2="value2"
     1031                                data-wp-test--suffix-b---id-1="value3"
     1032                                data-wp-test--suffix-c---id-1="value4"
     1033                        ></div>';
     1034                $p    = new WP_Interactivity_API_Directives_Processor( $html );
     1035                $p->next_tag();
     1036                $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' );
     1037                $this->assertCount( 4, $results );
     1038                $this->assertSame(
     1039                        array(
     1040                                'namespace' => 'myPlugin',
     1041                                'value'     => 'value1',
     1042                                'suffix'    => 'suffix-a',
     1043                                'unique_id' => 'id-1',
     1044                        ),
     1045                        $results[0]
     1046                );
     1047                $this->assertSame(
     1048                        array(
     1049                                'namespace' => 'myPlugin',
     1050                                'value'     => 'value2',
     1051                                'suffix'    => 'suffix-a',
     1052                                'unique_id' => 'id-2',
     1053                        ),
     1054                        $results[1]
     1055                );
     1056                $this->assertSame(
     1057                        array(
     1058                                'namespace' => 'myPlugin',
     1059                                'value'     => 'value3',
     1060                                'suffix'    => 'suffix-b',
     1061                                'unique_id' => 'id-1',
     1062                        ),
     1063                        $results[2]
     1064                );
     1065                $this->assertSame(
     1066                        array(
     1067                                'namespace' => 'myPlugin',
     1068                                'value'     => 'value4',
     1069                                'suffix'    => 'suffix-c',
     1070                                'unique_id' => 'id-1',
     1071                        ),
     1072                        $results[3]
     1073                );
     1074
     1075                // Should handle unique ID with namespace.
     1076                $html = '<div data-wp-test---unique-id="my-namespace::test value"></div>';
     1077                $p    = new WP_Interactivity_API_Directives_Processor( $html );
     1078                $p->next_tag();
     1079                $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' );
     1080                $this->assertSame( 'my-namespace', $results[0]['namespace'] );
     1081                $this->assertSame( 'test value', $results[0]['value'] );
     1082                $this->assertSame( 'unique-id', $results[0]['unique_id'] );
     1083
     1084                // Should handle multiple directives with different namespaces and unique IDs.
     1085                $html = '
     1086                        <div
     1087                                data-wp-test---id-a="namespace-a::value1"
     1088                                data-wp-test---id-b="namespace-b::value2"
     1089                        ></div>';
     1090                $p    = new WP_Interactivity_API_Directives_Processor( $html );
     1091                $p->next_tag();
     1092                $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' );
     1093                $this->assertCount( 2, $results );
     1094                $this->assertSame(
     1095                        array(
     1096                                'namespace' => 'namespace-a',
     1097                                'value'     => 'value1',
     1098                                'suffix'    => null,
     1099                                'unique_id' => 'id-a',
     1100                        ),
     1101                        $results[0]
     1102                );
     1103                $this->assertSame(
     1104                        array(
     1105                                'namespace' => 'namespace-b',
     1106                                'value'     => 'value2',
     1107                                'suffix'    => null,
     1108                                'unique_id' => 'id-b',
     1109                        ),
     1110                        $results[1]
     1111                );
     1112                // Should sort directives by suffix and uniqueId for stable ordering.
     1113                $html = '
     1114                        <div
     1115                                data-wp-test---z
     1116                                data-wp-test---a
     1117                                data-wp-test--b---z
     1118                                data-wp-test--b---a
     1119                                data-wp-test--a
     1120                                data-wp-test
     1121                        ></div>';
     1122                $p    = new WP_Interactivity_API_Directives_Processor( $html );
     1123                $p->next_tag();
     1124                $results = $get_directive_entries->invoke( $this->interactivity, $p, 'test' );
     1125                $this->assertCount( 6, $results );
     1126                $this->assertEquals(
     1127                        array(
     1128                                array( null, null ),
     1129                                array( null, 'a' ),
     1130                                array( null, 'z' ),
     1131                                array( 'a', null ),
     1132                                array( 'b', 'a' ),
     1133                                array( 'b', 'z' ),
     1134                        ),
     1135                        array_map(
     1136                                function ( $d ) {
     1137                                        return array( $d['suffix'], $d['unique_id'] );
     1138                                },
     1139                                $results
     1140                        )
     1141                );
    8401142        }
    8411143
     
    9251227         * @dataProvider data_html_with_unbalanced_tags
    9261228         *
    927          * @expectedIncorrectUsage WP_Interactivity_API::_process_directives
    928          *
    9291229         * @param string $html HTML containing unbalanced tags and also a directive.
    9301230         */
     
    10881388         * Invokes the private `evaluate` method of WP_Interactivity_API class.
    10891389         *
    1090          * @param string $directive_value   The directive attribute value to evaluate.
     1390         * @param string $entry The entry array containing namespace, value, suffix, and unique ID.
    10911391         * @return mixed The result of the evaluate method.
    10921392         */
    1093         private function evaluate( $directive_value ) {
     1393        private function evaluate( $entry ) {
    10941394                /*
    10951395                 * The global WP_Interactivity_API instance is momentarily replaced to
     
    11061406                }
    11071407
    1108                 $result = $evaluate->invokeArgs( $this->interactivity, array( $directive_value ) );
     1408                $result = $evaluate->invokeArgs( $this->interactivity, array( $entry ) );
    11091409
    11101410                // Restore the original WP_Interactivity_API instance.
     
    11521452                        )
    11531453                );
    1154                 $this->set_internal_namespace_stack( 'myPlugin' );
    1155 
    1156                 $result = $this->evaluate( 'state.key' );
     1454                $default_ns = 'myPlugin';
     1455                $this->set_internal_namespace_stack( $default_ns );
     1456
     1457                $result = $this->evaluate(
     1458                        array(
     1459                                'namespace' => $default_ns,
     1460                                'value'     => 'state.key',
     1461                        )
     1462                );
    11571463                $this->assertSame( 'myPlugin-state', $result );
    11581464
    1159                 $result = $this->evaluate( 'context.key' );
     1465                $result = $this->evaluate(
     1466                        array(
     1467                                'namespace' => $default_ns,
     1468                                'value'     => 'context.key',
     1469                        )
     1470                );
    11601471                $this->assertSame( 'myPlugin-context', $result );
    11611472
    1162                 $result = $this->evaluate( 'otherPlugin::state.key' );
     1473                $result = $this->evaluate(
     1474                        array(
     1475                                'namespace' => 'otherPlugin',
     1476                                'value'     => 'state.key',
     1477                        )
     1478                );
    11631479                $this->assertSame( 'otherPlugin-state', $result );
    11641480
    1165                 $result = $this->evaluate( 'otherPlugin::context.key' );
     1481                $result = $this->evaluate(
     1482                        array(
     1483                                'namespace' => 'otherPlugin',
     1484                                'value'     => 'context.key',
     1485                        )
     1486                );
    11661487                $this->assertSame( 'otherPlugin-context', $result );
    11671488
    1168                 $result = $this->evaluate( 'state.obj.prop' );
     1489                $result = $this->evaluate(
     1490                        array(
     1491                                'namespace' => $default_ns,
     1492                                'value'     => 'state.obj.prop',
     1493                        )
     1494                );
    11691495                $this->assertSame( 'object property', $result );
    11701496
    1171                 $result = $this->evaluate( 'state.arrAccess.1' );
     1497                $result = $this->evaluate(
     1498                        array(
     1499                                'namespace' => $default_ns,
     1500                                'value'     => 'state.arrAccess.1',
     1501                        )
     1502                );
    11721503                $this->assertSame( '1', $result );
    11731504        }
     
    11901521                        )
    11911522                );
    1192                 $this->set_internal_namespace_stack( 'myPlugin' );
    1193 
    1194                 $result = $this->evaluate( '!state.key' );
     1523                $default_ns = 'myPlugin';
     1524                $this->set_internal_namespace_stack( $default_ns );
     1525
     1526                $result = $this->evaluate(
     1527                        array(
     1528                                'namespace' => $default_ns,
     1529                                'value'     => '!state.key',
     1530                        )
     1531                );
    11951532                $this->assertFalse( $result );
    11961533
    1197                 $result = $this->evaluate( '!context.key' );
     1534                $result = $this->evaluate(
     1535                        array(
     1536                                'namespace' => $default_ns,
     1537                                'value'     => '!context.key',
     1538                        )
     1539                );
    11981540                $this->assertFalse( $result );
    11991541
    1200                 $result = $this->evaluate( 'otherPlugin::!state.key' );
     1542                $result = $this->evaluate(
     1543                        array(
     1544                                'namespace' => 'otherPlugin',
     1545                                'value'     => '!state.key',
     1546                        )
     1547                );
    12011548                $this->assertFalse( $result );
    12021549
    1203                 $result = $this->evaluate( 'otherPlugin::!context.key' );
     1550                $result = $this->evaluate(
     1551                        array(
     1552                                'namespace' => 'otherPlugin',
     1553                                'value'     => '!context.key',
     1554                        )
     1555                );
    12041556                $this->assertFalse( $result );
    12051557        }
     
    12221574                        )
    12231575                );
    1224                 $this->set_internal_namespace_stack( 'myPlugin' );
    1225 
    1226                 $result = $this->evaluate( '!state.missing' );
     1576                $default_ns = 'myPlugin';
     1577                $this->set_internal_namespace_stack( $default_ns );
     1578
     1579                $result = $this->evaluate(
     1580                        array(
     1581                                'namespace' => $default_ns,
     1582                                'value'     => '!state.missing',
     1583                        )
     1584                );
    12271585                $this->assertTrue( $result );
    12281586
    1229                 $result = $this->evaluate( '!context.missing' );
     1587                $result = $this->evaluate(
     1588                        array(
     1589                                'namespace' => $default_ns,
     1590                                'value'     => '!context.missing',
     1591                        )
     1592                );
    12301593                $this->assertTrue( $result );
    12311594
    1232                 $result = $this->evaluate( 'otherPlugin::!state.deeply.nested.missing' );
     1595                $result = $this->evaluate(
     1596                        array(
     1597                                'namespace' => 'otherPlugin',
     1598                                'value'     => '!state.deeply.nested.missing',
     1599                        )
     1600                );
    12331601                $this->assertTrue( $result );
    12341602
    1235                 $result = $this->evaluate( 'otherPlugin::!context.deeply.nested.missing' );
     1603                $result = $this->evaluate(
     1604                        array(
     1605                                'namespace' => 'otherPlugin',
     1606                                'value'     => '!context.deeply.nested.missing',
     1607                        )
     1608                );
    12361609                $this->assertTrue( $result );
    12371610        }
     
    12531626                        )
    12541627                );
    1255                 $this->set_internal_namespace_stack( 'myPlugin' );
    1256 
    1257                 $result = $this->evaluate( 'state.nonExistentKey' );
     1628                $default_ns = 'myPlugin';
     1629                $this->set_internal_namespace_stack( $default_ns );
     1630
     1631                $result = $this->evaluate(
     1632                        array(
     1633                                'namespace' => $default_ns,
     1634                                'value'     => 'state.nonExistentKey',
     1635                        )
     1636                );
    12581637                $this->assertNull( $result );
    12591638
    1260                 $result = $this->evaluate( 'context.nonExistentKey' );
     1639                $result = $this->evaluate(
     1640                        array(
     1641                                'namespace' => $default_ns,
     1642                                'value'     => 'context.nonExistentKey',
     1643                        )
     1644                );
    12611645                $this->assertNull( $result );
    12621646
    1263                 $result = $this->evaluate( 'otherPlugin::state.nonExistentKey' );
     1647                $result = $this->evaluate(
     1648                        array(
     1649                                'namespace' => 'otherPlugin',
     1650                                'value'     => 'state.nonExistentKey',
     1651                        )
     1652                );
    12641653                $this->assertNull( $result );
    12651654
    1266                 $result = $this->evaluate( 'otherPlugin::context.nonExistentKey' );
     1655                $result = $this->evaluate(
     1656                        array(
     1657                                'namespace' => 'otherPlugin',
     1658                                'value'     => 'context.nonExistentKey',
     1659                        )
     1660                );
    12671661                $this->assertNull( $result );
    12681662
    1269                 $result = $this->evaluate( ' state.key' ); // Extra space.
     1663                $result = $this->evaluate(
     1664                        array(
     1665                                'namespace' => $default_ns,
     1666                                'value'     => ' state.key',  // Extra space.
     1667                        )
     1668                );
    12701669                $this->assertNull( $result );
    12711670
    1272                 $result = $this->evaluate( 'otherPlugin:: state.key' ); // Extra space.
     1671                $result = $this->evaluate(
     1672                        array(
     1673                                'namespace' => 'otherPlugin',
     1674                                'value'     => ' state.key',  // Extra space.
     1675                        )
     1676                );
    12731677                $this->assertNull( $result );
    12741678        }
     
    13041708                        )
    13051709                );
    1306                 $this->set_internal_namespace_stack( 'myPlugin' );
    1307 
    1308                 $result = $this->evaluate( 'state.nested.key' );
     1710                $default_ns = 'myPlugin';
     1711                $this->set_internal_namespace_stack( $default_ns );
     1712
     1713                $result = $this->evaluate(
     1714                        array(
     1715                                'namespace' => $default_ns,
     1716                                'value'     => 'state.nested.key',
     1717                        )
     1718                );
    13091719                $this->assertSame( 'myPlugin-state-nested', $result );
    13101720
    1311                 $result = $this->evaluate( 'context.nested.key' );
     1721                $result = $this->evaluate(
     1722                        array(
     1723                                'namespace' => $default_ns,
     1724                                'value'     => 'context.nested.key',
     1725                        )
     1726                );
    13121727                $this->assertSame( 'myPlugin-context-nested', $result );
    13131728
    1314                 $result = $this->evaluate( 'otherPlugin::state.nested.key' );
     1729                $result = $this->evaluate(
     1730                        array(
     1731                                'namespace' => 'otherPlugin',
     1732                                'value'     => 'state.nested.key',
     1733                        )
     1734                );
    13151735                $this->assertSame( 'otherPlugin-state-nested', $result );
    13161736
    1317                 $result = $this->evaluate( 'otherPlugin::context.nested.key' );
     1737                $result = $this->evaluate(
     1738                        array(
     1739                                'namespace' => 'otherPlugin',
     1740                                'value'     => 'context.nested.key',
     1741                        )
     1742                );
    13181743                $this->assertSame( 'otherPlugin-context-nested', $result );
    13191744        }
     
    13311756                $this->set_internal_namespace_stack();
    13321757
    1333                 $result = $this->evaluate( 'path', 'null' );
     1758                $result = $this->evaluate(
     1759                        array(
     1760                                'namespace' => 'null',
     1761                                'value'     => 'path',
     1762                        )
     1763                );
    13341764                $this->assertNull( $result );
    13351765
    1336                 $result = $this->evaluate( 'path', '' );
     1766                $result = $this->evaluate(
     1767                        array(
     1768                                'namespace' => '',
     1769                                'value'     => 'path',
     1770                        )
     1771                );
    13371772                $this->assertNull( $result );
    13381773
    1339                 $result = $this->evaluate( 'path', '{}' );
     1774                $result = $this->evaluate(
     1775                        array(
     1776                                'namespace' => '{}',
     1777                                'value'     => 'path',
     1778                        )
     1779                );
    13401780                $this->assertNull( $result );
    13411781        }
     
    13751815                $this->set_internal_namespace_stack( 'myPlugin' );
    13761816
    1377                 $result = $this->evaluate( 'state.derived' );
     1817                $result = $this->evaluate(
     1818                        array(
     1819                                'namespace' => 'myPlugin',
     1820                                'value'     => 'state.derived',
     1821                        )
     1822                );
    13781823                $this->assertSame( "Derived state: myPlugin-state\nDerived context: myPlugin-context", $result );
    13791824        }
     
    14181863                $this->set_internal_namespace_stack( 'myPlugin' );
    14191864
    1420                 $result = $this->evaluate( 'state.derived' );
     1865                $result = $this->evaluate(
     1866                        array(
     1867                                'namespace' => 'myPlugin',
     1868                                'value'     => 'state.derived',
     1869                        )
     1870                );
    14211871                $this->assertSame( "Derived state: otherPlugin-state\nDerived context: otherPlugin-context", $result );
    14221872        }
     
    14611911                $this->set_internal_namespace_stack( 'myPlugin' );
    14621912
    1463                 $result = $this->evaluate( 'otherPlugin::state.derived' );
     1913                $result = $this->evaluate(
     1914                        array(
     1915                                'namespace' => 'otherPlugin',
     1916                                'value'     => 'state.derived',
     1917                        )
     1918                );
    14641919                $this->assertSame( "Derived state: otherPlugin-state\nDerived context: otherPlugin-context", $result );
    14651920        }
     
    14851940                $this->set_internal_namespace_stack( 'myPlugin' );
    14861941
    1487                 $result = $this->evaluate( 'state.derivedThatThrows' );
     1942                $result = $this->evaluate(
     1943                        array(
     1944                                'namespace' => 'myPlugin',
     1945                                'value'     => 'state.derivedThatThrows',
     1946                        )
     1947                );
    14881948                $this->assertNull( $result );
    14891949        }
     
    15081968                $this->set_internal_namespace_stack( 'myPlugin' );
    15091969
    1510                 $result = $this->evaluate( 'state.derivedState.property' );
     1970                $result = $this->evaluate(
     1971                        array(
     1972                                'namespace' => 'myPlugin',
     1973                                'value'     => 'state.derivedState.property',
     1974                        )
     1975                );
    15111976                $this->assertSame( 'value', $result );
    15121977        }
Note: See TracChangeset for help on using the changeset viewer.