Make WordPress Core

Changeset 61020


Ignore:
Timestamp:
10/21/2025 12:07:16 PM (3 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.