Ticket #34893: 34893.7.diff
File 34893.7.diff, 32.0 KB (added by , 8 years ago) |
---|
-
src/wp-admin/css/customize-controls.css
diff --git src/wp-admin/css/customize-controls.css src/wp-admin/css/customize-controls.css index 0a62611..47af403 100644
p.customize-section-description { 494 494 .customize-control input[type="search"], 495 495 .customize-control input[type="tel"], 496 496 .customize-control input[type="url"] { 497 width: 98%;497 width: 100%; 498 498 line-height: 18px; 499 499 margin: 0; 500 500 } … … p.customize-section-description { 622 622 border-right: 1px solid #ddd; 623 623 } 624 624 625 626 /** 627 * Notifications 628 */ 629 630 #customize-controls .customize-control-notifications-container { /* Scoped to #customize-controls for specificity over notification styles in common.css. */ 631 margin: 4px 0 8px 0; 632 padding: 5px 0; 633 display: none; 634 cursor: default; 635 } 636 637 .customize-control-widget_form.has-notifications .widget .widget-top, 638 .customize-control-nav_menu_item.has-notifications .menu-item-bar .menu-item-handle { 639 background-color: #ffe0e0; 640 } 641 642 .customize-control-notifications-container li { 643 list-style: disc inside; 644 margin-bottom: 0; 645 margin-left: 0; 646 border-left: solid 4px transparent; 647 padding-left: 8px; 648 padding-top: 4px; 649 padding-bottom: 4px; 650 } 651 .customize-control-notifications-container li[data-type="error"] { 652 border-left-color: #dc3232; 653 } 654 .customize-control-notifications-container li:only-child { 655 list-style: none; 656 margin-left: 0; 657 } 658 659 625 660 /* Style for custom settings */ 626 661 627 662 /** -
src/wp-admin/js/customize-controls.js
diff --git src/wp-admin/js/customize-controls.js src/wp-admin/js/customize-controls.js index ef01674..b992d2c 100644
27 27 this.id = id; 28 28 this.transport = this.transport || 'refresh'; 29 29 this._dirty = options.dirty || false; 30 this.notifications = new api.Values({ defaultConstructor: api.Notification }); 30 31 31 32 // Whenever the setting's value changes, refresh the preview. 32 33 this.bind( this.preview ); … … 1478 1479 control.priority = new api.Value(); 1479 1480 control.active = new api.Value(); 1480 1481 control.activeArgumentsQueue = []; 1482 control.notifications = new api.Values({ defaultConstructor: api.Notification }); 1481 1483 1482 1484 control.elements = []; 1483 1485 … … 1541 1543 1542 1544 control.setting = control.settings['default'] || null; 1543 1545 1546 _.each( control.settings, function( setting ) { 1547 setting.notifications.bind( 'add', function( settingNotification ) { 1548 var controlNotification = new api.Notification( setting.id + ':' + settingNotification.code, settingNotification ); 1549 control.notifications.add( controlNotification.code, controlNotification ); 1550 } ); 1551 setting.notifications.bind( 'remove', function( settingNotification ) { 1552 control.notifications.remove( setting.id + ':' + settingNotification.code ); 1553 } ); 1554 } ); 1555 1544 1556 control.embed(); 1545 1557 }) ); 1546 1558 } 1547 1559 1548 1560 // After the control is embedded on the page, invoke the "ready" method. 1549 1561 control.deferred.embedded.done( function () { 1562 /* 1563 * Note that this debounced/deferred rendering is needed for two reasons: 1564 * 1) The 'remove' event is triggered just _before_ the notification is actually removed. 1565 * 2) Improve performance when adding/removing multiple notifications at a time. 1566 */ 1567 var debouncedRenderNotifications = _.debounce( function renderNotifications() { 1568 control.renderNotifications(); 1569 } ); 1570 control.notifications.bind( 'add', debouncedRenderNotifications ); 1571 control.notifications.bind( 'remove', debouncedRenderNotifications ); 1572 control.renderNotifications(); 1573 1550 1574 control.ready(); 1551 1575 }); 1552 1576 }, … … 1589 1613 ready: function() {}, 1590 1614 1591 1615 /** 1616 * Get the element inside of a control's container that contains the validation error message. 1617 * 1618 * Control subclasses may override this to return the proper container to render notifications into. 1619 * Injects the notification container for existing controls that lack the necessary container, 1620 * including special handling for nav menu items and widgets. 1621 * 1622 * @returns {jQuery} Setting validation message element. 1623 * @this {wp.customize.Control} 1624 */ 1625 getNotificationsContainerElement: function() { 1626 var control = this, controlTitle, notificationsContainer; 1627 1628 notificationsContainer = control.container.find( '.customize-control-notifications-container:first' ); 1629 if ( notificationsContainer.length ) { 1630 return notificationsContainer; 1631 } 1632 1633 notificationsContainer = $( '<div class="customize-control-notifications-container" aria-live="assertive"></div>' ); 1634 1635 if ( control.container.hasClass( 'customize-control-nav_menu_item' ) ) { 1636 control.container.find( '.menu-item-settings:first' ).prepend( notificationsContainer ); 1637 } else if ( control.container.hasClass( 'customize-control-widget_form' ) ) { 1638 control.container.find( '.widget-inside:first' ).prepend( notificationsContainer ); 1639 } else { 1640 controlTitle = control.container.find( '.customize-control-title' ); 1641 if ( controlTitle.length ) { 1642 controlTitle.after( notificationsContainer ); 1643 } else { 1644 control.container.prepend( notificationsContainer ); 1645 } 1646 } 1647 return notificationsContainer; 1648 }, 1649 1650 /** 1651 * Render notifications. 1652 * 1653 * Renders the `control.notifications` into the control's container. 1654 * Control subclasses may override this method to do their own handling 1655 * of rendering notifications. 1656 */ 1657 renderNotifications: function() { 1658 var control = this, container, notifications; 1659 container = control.getNotificationsContainerElement(); 1660 if ( ! container || ! container.length ) { 1661 return; 1662 } 1663 notifications = []; 1664 control.notifications.each( function( notification ) { 1665 notifications.push( notification ); 1666 } ); 1667 1668 if ( 0 === notifications.length ) { 1669 container.stop().slideUp( 'fast' ); 1670 } else { 1671 container.stop().slideDown( 'fast', null, function() { 1672 $( this ).css( 'height', 'auto' ); 1673 } ); 1674 } 1675 1676 if ( ! control.notificationsTemplate ) { 1677 control.notificationsTemplate = wp.template( 'customize-control-notifications' ); 1678 } 1679 1680 control.container.toggleClass( 'has-notifications', 0 !== notifications.length ); 1681 container.empty().append( $.trim( 1682 control.notificationsTemplate( { notifications: notifications } ) 1683 ) ); 1684 }, 1685 1686 /** 1592 1687 * Normal controls do not expand, so just expand its parent 1593 1688 * 1594 1689 * @param {Object} [params] … … 3223 3318 } 3224 3319 }); 3225 3320 3321 api.settingConstructor = {}; 3226 3322 api.controlConstructor = { 3227 3323 color: api.ColorControl, 3228 3324 media: api.MediaControl, … … 3323 3419 }; 3324 3420 }, 3325 3421 3422 _handleInvalidSettingsError: function( response ) { 3423 var invalidControls = [], wasFocused = false; 3424 if ( ! response.invalid_settings || 0 === response.invalid_settings.length ) { 3425 return; 3426 } 3427 3428 // Find the controls that correspond to each invalid setting. 3429 _.each( response.invalid_settings, function( notifications, settingId ) { 3430 var setting = api( settingId ); 3431 if ( setting ) { 3432 _.each( notifications, function( notificationParams, code ) { 3433 var notification = new api.Notification( code, notificationParams ); 3434 setting.notifications.add( code, notification ); 3435 } ); 3436 } 3437 3438 api.control.each( function( control ) { 3439 _.each( control.settings, function( controlSetting ) { 3440 if ( controlSetting.id === settingId ) { 3441 invalidControls.push( control ); 3442 } 3443 } ); 3444 } ); 3445 } ); 3446 3447 // Focus on the first control that is inside of an expanded section (one that is visible). 3448 _( invalidControls ).find( function( control ) { 3449 var isExpanded = control.section() && api.section.has( control.section() ) && api.section( control.section() ).expanded(); 3450 if ( isExpanded && control.expanded ) { 3451 isExpanded = control.expanded(); 3452 } 3453 if ( isExpanded ) { 3454 control.focus(); 3455 wasFocused = true; 3456 } 3457 return wasFocused; 3458 } ); 3459 3460 // Focus on the first invalid control. 3461 if ( ! wasFocused && invalidControls[0] ) { 3462 invalidControls[0].focus(); 3463 } 3464 }, 3465 3326 3466 save: function() { 3327 3467 var self = this, 3328 3468 processing = api.state( 'processing' ), … … 3349 3489 3350 3490 api.trigger( 'save', request ); 3351 3491 3492 // Remove all setting notifications prior to save, allowing server to respond with new notifications. 3493 api.each( function( setting ) { 3494 setting.notifications.each( function( notification ) { 3495 setting.notifications.remove( notification.code ); 3496 } ); 3497 } ); 3498 3352 3499 request.always( function () { 3353 3500 body.removeClass( 'saving' ); 3354 3501 saveBtn.prop( 'disabled', false ); … … 3372 3519 self.preview.iframe.show(); 3373 3520 } ); 3374 3521 } 3522 3523 self._handleInvalidSettingsError( response ); 3524 3375 3525 api.trigger( 'error', response ); 3376 3526 } ); 3377 3527 … … 3424 3574 3425 3575 // Create Settings 3426 3576 $.each( api.settings.settings, function( id, data ) { 3427 api.create( id, id, data.value, { 3577 var constructor = api.settingConstructor[ data.type ] || api.Setting, 3578 setting; 3579 3580 setting = new constructor( id, data.value, { 3428 3581 transport: data.transport, 3429 3582 previewer: api.previewer, 3430 3583 dirty: !! data.dirty 3431 3584 } ); 3585 api.add( id, setting ); 3432 3586 }); 3433 3587 3434 3588 // Create Panels -
src/wp-includes/class-wp-customize-manager.php
diff --git src/wp-includes/class-wp-customize-manager.php src/wp-includes/class-wp-customize-manager.php index 0052a1a..b94c3ce 100644
final class WP_Customize_Manager { 654 654 * Return the sanitized value for a given setting from the request's POST data. 655 655 * 656 656 * @since 3.4.0 657 * @since 4.1.1 Introduced 'default' parameter. 657 * @since 4.1.1 Introduced `$default` parameter. 658 * @since 4.6.0 Return `$default` when setting post value is invalid. 659 * @see WP_REST_Server::dispatch() 660 * @see WP_Rest_Request::sanitize_params() 661 * @see WP_Rest_Request::has_valid_params() 658 662 * 659 * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object 660 * @param mixed $default value returned $setting has no post value (added in 4.2.0). 661 * @return string|mixed $post_value Sanitized value or the $default provided 663 * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object. 664 * @param mixed $default Value returned $setting has no post value (added in 4.2.0) 665 * or the post value is invalid (added in 4.6.0). 666 * @return string|mixed $post_value Sanitized value or the $default provided. 662 667 */ 663 668 public function post_value( $setting, $default = null ) { 664 669 $post_values = $this->unsanitized_post_values(); 665 if ( array_key_exists( $setting->id, $post_values ) ) { 666 return $setting->sanitize( $post_values[ $setting->id ] ); 667 } else { 670 if ( ! array_key_exists( $setting->id, $post_values ) ) { 671 return $default; 672 } 673 $value = $setting->sanitize( $post_values[ $setting->id ] ); 674 if ( is_null( $value ) || is_wp_error( $value ) ) { 675 return $default; 676 } 677 $valid = $setting->validate( $value ); 678 if ( is_wp_error( $valid ) ) { 668 679 return $default; 669 680 } 681 return $value; 670 682 } 671 683 672 684 /** … … final class WP_Customize_Manager { 970 982 } 971 983 972 984 /** 985 * Validate setting values. 986 * 987 * Sanitization is applied to the values before being passed for validation. 988 * Validation is skipped for unregistered settings or for values that are 989 * already null since they will be skipped anyway. 990 * 991 * @since 4.6.0 992 * @access public 993 * @see WP_REST_Request::has_valid_params() 994 * 995 * @param array $setting_values Mapping of setting IDs to values to sanitize and validate. 996 * @return array Empty array if all settings were valid. One or more instances of `WP_Error` if any were invalid. 997 */ 998 public function validate_setting_values( $setting_values ) { 999 $validity_errors = array(); 1000 foreach ( $setting_values as $setting_id => $unsanitized_value ) { 1001 $setting = $this->get_setting( $setting_id ); 1002 if ( ! $setting || is_null( $unsanitized_value ) ) { 1003 continue; 1004 } 1005 $validity = $setting->validate( $setting->sanitize( $unsanitized_value ) ); 1006 if ( false === $validity || null === $validity ) { 1007 $validity = new WP_Error( 'invalid_value', __( 'Invalid value.' ) ); 1008 } 1009 if ( is_wp_error( $validity ) ) { 1010 $validity_errors[ $setting_id ] = $validity; 1011 } 1012 } 1013 return $validity_errors; 1014 } 1015 1016 /** 973 1017 * Switch the theme and trigger the save() method on each setting. 974 1018 * 975 1019 * @since 3.4.0 … … final class WP_Customize_Manager { 984 1028 wp_send_json_error( 'invalid_nonce' ); 985 1029 } 986 1030 1031 /** 1032 * Fires before save validation happens. 1033 * 1034 * Plugins can add just-in-time `customize_validate_{$setting_id}` filters 1035 * at this point to catch any settings registered after `customize_register`. 1036 * 1037 * @since 4.6.0 1038 * 1039 * @param WP_Customize_Manager $this WP_Customize_Manager instance. 1040 */ 1041 do_action( 'customize_save_validation_before', $this ); 1042 1043 // Validate settings. 1044 $validity_errors = $this->validate_setting_values( $this->unsanitized_post_values() ); 1045 $invalid_count = count( $validity_errors ); 1046 if ( $invalid_count > 0 ) { 1047 $settings_errors = array(); 1048 foreach ( $validity_errors as $setting_id => $validity_error ) { 1049 $settings_errors[ $setting_id ] = array(); 1050 foreach ( $validity_error->errors as $error_code => $error_message ) { 1051 $settings_errors[ $setting_id ][ $error_code ] = array( 1052 'message' => join( ' ', $error_message ), 1053 'data' => $validity_error->get_error_data( $error_code ), 1054 ); 1055 } 1056 } 1057 $response = array( 1058 'invalid_settings' => $settings_errors, 1059 'message' => sprintf( _n( 'There is %d invalid setting.', 'There are %d invalid settings.', $invalid_count ), $invalid_count ), 1060 ); 1061 1062 /** This filter is documented in wp-includes/class-wp-customize-manager.php */ 1063 $response = apply_filters( 'customize_save_response', $response, $this ); 1064 wp_send_json_error( $response ); 1065 } 1066 987 1067 // Do we have to switch themes? 988 1068 if ( ! $this->is_theme_active() ) { 989 1069 // Temporarily stop previewing the theme to allow switch_themes() … … final class WP_Customize_Manager { 1403 1483 ) ); 1404 1484 $control->print_template(); 1405 1485 } 1486 ?> 1487 <script type="text/html" id="tmpl-customize-control-notifications"> 1488 <ul> 1489 <# _.each( data.notifications, function( notification ) { #> 1490 <li data-code="{{ notification.code }}" data-type="{{ notification.type }}">{{ notification.message || notification.code }}</li> 1491 <# } ); #> 1492 </ul> 1493 </script> 1494 <?php 1406 1495 } 1407 1496 1408 1497 /** … … final class WP_Customize_Manager { 1763 1852 printf( 1764 1853 "s[%s] = %s;\n", 1765 1854 wp_json_encode( $setting->id ), 1766 wp_json_encode( array( 1767 'value' => $setting->js_value(), 1768 'transport' => $setting->transport, 1769 'dirty' => $setting->dirty, 1770 ) ) 1855 wp_json_encode( $setting->json() ) 1771 1856 ); 1772 1857 } 1773 1858 } -
src/wp-includes/class-wp-customize-setting.php
diff --git src/wp-includes/class-wp-customize-setting.php src/wp-includes/class-wp-customize-setting.php index 94e7ded..9679883 100644
class WP_Customize_Setting { 59 59 * 60 60 * @var callback 61 61 */ 62 public $validate_callback = ''; 62 63 public $sanitize_callback = ''; 63 64 public $sanitize_js_callback = ''; 64 65 … … class WP_Customize_Setting { 142 143 $this->id .= '[' . implode( '][', $this->id_data['keys'] ) . ']'; 143 144 } 144 145 146 if ( $this->validate_callback ) { 147 add_filter( "customize_validate_{$this->id}", $this->validate_callback, 10, 3 ); 148 } 145 149 if ( $this->sanitize_callback ) { 146 add_filter( "customize_sanitize_{$this->id}", $this->sanitize_callback, 10, 2);150 add_filter( "customize_sanitize_{$this->id}", $this->sanitize_callback, 10, 3 ); 147 151 } 148 152 if ( $this->sanitize_js_callback ) { 149 153 add_filter( "customize_sanitize_js_{$this->id}", $this->sanitize_js_callback, 10, 2 ); … … class WP_Customize_Setting { 464 468 * the value of the setting. 465 469 * 466 470 * @since 3.4.0 471 * @since 4.6.0 Return the result of updating the value. 467 472 * 468 * @return false|void False if cap check fails or value isn't set .473 * @return false|void False if cap check fails or value isn't set or is invalid. 469 474 */ 470 475 final public function save() { 471 476 $value = $this->post_value(); 472 477 473 if ( ! $this->check_capabilities() || ! isset( $value ) ) 478 if ( ! $this->check_capabilities() || ! isset( $value ) ) { 474 479 return false; 480 } 475 481 476 482 /** 477 483 * Fires when the WP_Customize_Setting::save() method is called. … … class WP_Customize_Setting { 483 489 * 484 490 * @param WP_Customize_Setting $this WP_Customize_Setting instance. 485 491 */ 486 do_action( 'customize_save_' . $this->id_data[ 'base'], $this );492 do_action( 'customize_save_' . $this->id_data['base'], $this ); 487 493 488 494 $this->update( $value ); 489 495 } … … class WP_Customize_Setting { 494 500 * @since 3.4.0 495 501 * 496 502 * @param mixed $default A default value which is used as a fallback. Default is null. 497 * @return mixed The default value on failure, otherwise the sanitized value.503 * @return mixed The default value on failure, otherwise the sanitized and validated value. 498 504 */ 499 505 final public function post_value( $default = null ) { 500 506 return $this->manager->post_value( $this, $default ); … … class WP_Customize_Setting { 505 511 * 506 512 * @since 3.4.0 507 513 * 508 * @param string|array $value The value to sanitize.509 * @return string|array|null Null if an input isn't valid, otherwise the sanitized value.514 * @param string|array $value The value to sanitize. 515 * @return string|array|null|WP_Error Sanitized value, or `null`/`WP_Error` if invalid. 510 516 */ 511 517 public function sanitize( $value ) { 512 518 … … class WP_Customize_Setting { 522 528 } 523 529 524 530 /** 531 * Validate an input. 532 * 533 * @since 4.6.0 534 * @access public 535 * @see WP_REST_Request::has_valid_params() 536 * 537 * @param mixed $value Value to validate. 538 * @return true|WP_Error 539 */ 540 public function validate( $value ) { 541 if ( is_wp_error( $value ) ) { 542 return $value; 543 } 544 if ( is_null( $value ) ) { 545 return new WP_Error( 'invalid_value', __( 'Invalid value.' ) ); 546 } 547 548 $validity = new WP_Error(); 549 550 /** 551 * Validate a Customize setting value. 552 * 553 * Plugins should amend the `$validity` object via its `WP_Error::add()` method. 554 * 555 * @since 4.6.0 556 * 557 * @param WP_Error $validity Filtered from `true` to `WP_Error` when invalid. 558 * @param mixed $value Value of the setting. 559 * @param WP_Customize_Setting $this WP_Customize_Setting instance. 560 */ 561 $validity = apply_filters( "customize_validate_{$this->id}", $validity, $value, $this ); 562 563 if ( is_wp_error( $validity ) && empty( $validity->errors ) ) { 564 $validity = true; 565 } 566 return $validity; 567 } 568 569 /** 525 570 * Get the root value for a setting, especially for multidimensional ones. 526 571 * 527 572 * @since 4.4.0 … … class WP_Customize_Setting { 700 745 } 701 746 702 747 /** 748 * Get the data to export to the client via JSON. 749 * 750 * @since 4.6.0 751 * 752 * @return array Array of parameters passed to JavaScript. 753 */ 754 public function json() { 755 return array( 756 'value' => $this->js_value(), 757 'transport' => $this->transport, 758 'dirty' => $this->dirty, 759 'type' => $this->type, 760 ); 761 } 762 763 /** 703 764 * Validate user capabilities whether the theme supports the setting. 704 765 * 705 766 * @since 3.4.0 -
src/wp-includes/js/customize-base.js
diff --git src/wp-includes/js/customize-base.js src/wp-includes/js/customize-base.js index b4b7279..eb7fa44 100644
window.wp = window.wp || {}; 755 755 // Add the Events mixin to api.Messenger. 756 756 $.extend( api.Messenger.prototype, api.Events ); 757 757 758 /** 759 * Notification. 760 * 761 * @class 762 * @augments wp.customize.Class 763 * 764 * @param {string} code The error code. 765 * @param {object} params Params. 766 * @param {string} params.message The error message. 767 * @param {string} [params.type=error] The notification type. 768 * @param {*} [params.data] Any additional data. 769 */ 770 api.Notification = api.Class.extend({ 771 initialize: function( code, params ) { 772 this.code = code; 773 this.message = params.message; 774 this.type = params.type || 'error'; 775 this.data = params.data || null; 776 } 777 }); 778 758 779 // The main API object is also a collection of all customizer settings. 759 780 api = $.extend( new api.Values(), api ); 760 781 -
tests/phpunit/tests/customize/setting.php
diff --git tests/phpunit/tests/customize/setting.php tests/phpunit/tests/customize/setting.php index c2ff7f5..98990c5 100644
class Tests_WP_Customize_Setting extends WP_UnitTestCase { 42 42 $this->assertEquals( 'refresh', $setting->transport ); 43 43 $this->assertEquals( '', $setting->sanitize_callback ); 44 44 $this->assertEquals( '', $setting->sanitize_js_callback ); 45 $this->assertFalse( has_filter( "customize_validate_{$setting->id}" ) ); 45 46 $this->assertFalse( has_filter( "customize_sanitize_{$setting->id}" ) ); 46 47 $this->assertFalse( has_filter( "customize_sanitize_js_{$setting->id}" ) ); 47 48 $this->assertEquals( false, $setting->dirty ); … … class Tests_WP_Customize_Setting extends WP_UnitTestCase { 54 55 'theme_supports' => 'widgets', 55 56 'default' => 'barbar', 56 57 'transport' => 'postMessage', 58 'validate_callback' => create_function( '$value', 'return $value . ":validate_callback";' ), 57 59 'sanitize_callback' => create_function( '$value', 'return $value . ":sanitize_callback";' ), 58 60 'sanitize_js_callback' => create_function( '$value', 'return $value . ":sanitize_js_callback";' ), 59 61 ); … … class Tests_WP_Customize_Setting extends WP_UnitTestCase { 62 64 foreach ( $args as $key => $value ) { 63 65 $this->assertEquals( $value, $setting->$key ); 64 66 } 67 $this->assertEquals( 10, has_filter( "customize_validate_{$setting->id}", $args['validate_callback'] ) ); 65 68 $this->assertEquals( 10, has_filter( "customize_sanitize_{$setting->id}", $args['sanitize_callback'] ) ); 66 69 $this->assertEquals( 10, has_filter( "customize_sanitize_js_{$setting->id}" ), $args['sanitize_js_callback'] ); 67 70 } … … class Tests_WP_Customize_Setting extends WP_UnitTestCase { 90 93 91 94 /** 92 95 * Run assertions on non-multidimensional standard settings. 96 * 97 * @see WP_Customize_Setting::value() 93 98 */ 94 99 function test_preview_standard_types_non_multidimensional() { 95 100 $_POST['customized'] = wp_slash( wp_json_encode( $this->post_data_overrides ) ); … … class Tests_WP_Customize_Setting extends WP_UnitTestCase { 167 172 * Run assertions on multidimensional standard settings. 168 173 * 169 174 * @see WP_Customize_Setting::preview() 175 * @see WP_Customize_Setting::value() 170 176 */ 171 177 function test_preview_standard_types_multidimensional() { 172 178 $_POST['customized'] = wp_slash( wp_json_encode( $this->post_data_overrides ) ); … … class Tests_WP_Customize_Setting extends WP_UnitTestCase { 569 575 $autoload = $wpdb->get_var( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name = %s", $id_base ) ); 570 576 $this->assertEquals( 'no', $autoload, 'Even though setting1 did not indicate autoload (thus normally true), since another multidimensional option setting of the base did say autoload=false, it should be autoload=no' ); 571 577 } 578 579 /** 580 * Test js_value and json methods. 581 * 582 * @see WP_Customize_Setting::js_value() 583 * @see WP_Customize_Setting::json() 584 */ 585 public function test_js_value() { 586 $default = "\x00"; 587 $args = array( 588 'type' => 'binary', 589 'default' => $default, 590 'transport' => 'postMessage', 591 'dirty' => true, 592 'sanitize_js_callback' => create_function( '$value', 'return base64_encode( $value );' ), 593 ); 594 $setting = new WP_Customize_Setting( $this->manager, 'name', $args ); 595 596 $this->assertEquals( $default, $setting->value() ); 597 $this->assertEquals( base64_encode( $default ), $setting->js_value() ); 598 599 $exported = $setting->json(); 600 $this->assertArrayHasKey( 'type', $exported ); 601 $this->assertArrayHasKey( 'value', $exported ); 602 $this->assertArrayHasKey( 'transport', $exported ); 603 $this->assertArrayHasKey( 'dirty', $exported ); 604 $this->assertEquals( $setting->js_value(), $exported['value'] ); 605 $this->assertEquals( $args['type'], $setting->type ); 606 $this->assertEquals( $args['transport'], $setting->transport ); 607 $this->assertEquals( $args['dirty'], $setting->dirty ); 608 } 609 610 /** 611 * Test validate. 612 * 613 * @see WP_Customize_Setting::validate() 614 */ 615 public function test_validate() { 616 $setting = new WP_Customize_Setting( $this->manager, 'name', array( 617 'type' => 'key', 618 'validate_callback' => array( $this, 'filter_validate_for_test_validate' ), 619 ) ); 620 $validity = $setting->validate( 'BAD!' ); 621 $this->assertInstanceOf( 'WP_Error', $validity ); 622 $this->assertEquals( 'invalid_key', $validity->get_error_code() ); 623 } 624 625 /** 626 * Validate callback. 627 * 628 * @see Tests_WP_Customize_Setting::test_validate() 629 * 630 * @param WP_Error $validity Validity. 631 * @param string $value Value. 632 * 633 * @return WP_Error 634 */ 635 public function filter_validate_for_test_validate( $validity, $value ) { 636 $this->assertInstanceOf( 'WP_Error', $validity ); 637 $this->assertInternalType( 'string', $value ); 638 if ( sanitize_key( $value ) !== $value ) { 639 $validity->add( 'invalid_key', 'Invalid key' ); 640 } 641 return $validity; 642 } 572 643 } 573 644 -
tests/qunit/fixtures/customize-settings.js
diff --git tests/qunit/fixtures/customize-settings.js tests/qunit/fixtures/customize-settings.js index 85bd1ab..670f783 100644
window._wpCustomizeSettings = { 112 112 'fixture-setting': { 113 113 'transport': 'postMessage', 114 114 'value': 'Lorem Ipsum' 115 }, 116 'fixture-setting-abbr': { 117 'transport': 'postMessage', 118 'value': 'NASA', 119 'type': 'abbreviation' 115 120 } 116 121 }, 117 122 'theme': { -
tests/qunit/index.html
diff --git tests/qunit/index.html tests/qunit/index.html index aa454a9..d9739fa 100644
126 126 <# } #> 127 127 </li> 128 128 </script> 129 <script type="text/html" id="tmpl-customize-control-notifications"> 130 <ul> 131 <# _.each( data.notifications, function( notification ) { #> 132 <li data-code="{{ notification.code }}" data-type="{{ notification.type }}">{{ notification.message || notification.code }}</li> 133 <# } ); #> 134 </ul> 135 </script> 129 136 130 137 <!-- Templates for Customizer Menus --> 131 138 <script type="text/html" id="tmpl-customize-control-nav_menu-content"> -
tests/qunit/wp-admin/js/customize-base.js
diff --git tests/qunit/wp-admin/js/customize-base.js tests/qunit/wp-admin/js/customize-base.js index c19ea80..b7aca0d 100644
1 /* global wp */1 /* global wp, test, ok, equal, module */ 2 2 3 3 jQuery( function( $ ) { 4 4 var FooSuperClass, BarSubClass, foo, bar, ConstructorTestClass, newConstructor, constructorTest, $mockElement, mockString, … … jQuery( function( $ ) { 158 158 firstValueInstance.set( 'newValue' ); 159 159 ok( wasCallbackFired ); 160 160 }); 161 162 module( 'Customize Base: Notification' ); 163 test( 'Notification object exists and has expected properties', function ( assert ) { 164 var notification = new wp.customize.Notification( 'mycode', { 165 'message': 'Hello World', 166 'type': 'update', 167 'data': { 'foo': 'bar' } 168 } ); 169 170 assert.equal( 'mycode', notification.code ); 171 assert.equal( 'Hello World', notification.message ); 172 assert.equal( 'update', notification.type ); 173 assert.deepEqual( { 'foo': 'bar' }, notification.data ); 174 175 notification = new wp.customize.Notification( 'mycode2', { 176 'message': 'Hello Space' 177 } ); 178 assert.equal( 'mycode2', notification.code ); 179 assert.equal( 'Hello Space', notification.message ); 180 assert.equal( 'error', notification.type ); 181 assert.equal( null, notification.data ); 182 } ); 161 183 }); -
tests/qunit/wp-admin/js/customize-controls.js
diff --git tests/qunit/wp-admin/js/customize-controls.js tests/qunit/wp-admin/js/customize-controls.js index 47d695c..23ca9ac 100644
1 /* global wp */ 1 /* global wp, test, ok, equal, module */ 2 3 wp.customize.settingConstructor.abbreviation = wp.customize.Setting.extend({ 4 validate: function( value ) { 5 return value.toUpperCase(); 6 } 7 }); 2 8 3 9 jQuery( window ).load( function (){ 4 10 'use strict'; … … jQuery( window ).load( function (){ 85 91 test( 'Setting has fixture value', function () { 86 92 equal( wp.customize( 'fixture-setting' )(), 'Lorem Ipsum' ); 87 93 } ); 94 test( 'Setting has notifications', function () { 95 var setting = wp.customize( 'fixture-setting' ); 96 ok( setting.notifications.extended( wp.customize.Values ) ); 97 equal( wp.customize.Notification, setting.notifications.prototype.constructor.defaultConstructor ); 98 } ); 99 test( 'Setting constructor object exists', function( assert ) { 100 assert.ok( _.isObject( wp.customize.settingConstructor ) ); 101 } ); 102 test( 'Custom setting constructor is used', function( assert ) { 103 var setting = wp.customize( 'fixture-setting-abbr' ); 104 assert.ok( setting.extended( wp.customize.settingConstructor.abbreviation ) ); 105 setting.set( 'usa' ); 106 assert.equal( 'USA', setting.get() ); 107 } ); 88 108 89 109 module( 'Customizer Control in Fixture' ); 90 110 test( 'Control exists', function () { … … jQuery( window ).load( function (){ 99 119 var control = wp.customize.control( 'fixture-control' ); 100 120 equal( control.section(), 'fixture-section' ); 101 121 } ); 122 test( 'Control has notifications', function ( assert ) { 123 var control = wp.customize.control( 'fixture-control' ), settingNotification, controlOnlyNotification, doneEmbedded; 124 assert.ok( control.notifications.extended( wp.customize.Values ) ); 125 assert.equal( wp.customize.Notification, control.notifications.prototype.constructor.defaultConstructor ); 126 assert.ok( _.isFunction( control.getNotificationsContainerElement ) ); 127 assert.ok( _.isFunction( control.renderNotifications ) ); 128 129 doneEmbedded = assert.async(); 130 control.deferred.embedded.done( function() { 131 var notificationContainerElement; 132 133 assert.equal( 0, _.size( control.notifications._value ) ); 134 assert.equal( 0, _.size( control.settings['default'].notifications._value ) ); 135 136 notificationContainerElement = control.getNotificationsContainerElement(); 137 assert.equal( 1, notificationContainerElement.length ); 138 assert.ok( notificationContainerElement.is( '.customize-control-notifications-container' ) ); 139 assert.equal( 0, notificationContainerElement.find( '> ul > li' ).length ); 140 assert.equal( 'none', notificationContainerElement.css( 'display' ) ); 141 142 settingNotification = new wp.customize.Notification( 'setting_invalidity', 'Invalid setting' ); 143 controlOnlyNotification = new wp.customize.Notification( 'control_invalidity', 'Invalid control' ); 144 control.settings['default'].notifications.add( settingNotification.code, settingNotification ); 145 control.notifications.add( controlOnlyNotification.code, controlOnlyNotification ); 146 147 // Note that renderNotifications is being called manually here since rendering normally happens asynchronously. 148 control.renderNotifications(); 149 150 assert.equal( 2, notificationContainerElement.find( '> ul > li' ).length ); 151 assert.notEqual( 'none', notificationContainerElement.css( 'display' ) ); 152 assert.equal( 2, _.size( control.notifications._value ) ); 153 assert.equal( 1, _.size( control.settings['default'].notifications._value ) ); 154 155 control.notifications.remove( controlOnlyNotification.code ); 156 control.renderNotifications(); 157 assert.equal( 1, notificationContainerElement.find( '> ul > li' ).length ); 158 assert.notEqual( 'none', notificationContainerElement.css( 'display' ) ); 159 160 control.settings['default'].notifications.remove( settingNotification.code ); 161 control.renderNotifications(); 162 assert.equal( 0, notificationContainerElement.find( '> ul > li' ).length ); 163 assert.ok( notificationContainerElement.is( ':animated' ) ); // It is being slid down. 164 notificationContainerElement.stop().hide(); // Clean up. 165 166 doneEmbedded(); 167 } ); 168 } ); 102 169 103 170 module( 'Customizer control without associated settings' ); 104 171 test( 'Control can be created without settings', function() {