Wordpress/tests/qunit/wp-admin/js/customize-base.js
Weston Ruter c7ff79df64 Customize: Add setting validation model and control notifications to augment setting sanitization.
When a setting is invalid, not only will it be blocked from being saved but all other settings will be blocked as well. This ensures that Customizer saves aren't partial but are more transactional. User will be displayed the error in a notification so that they can fix and re-attempt saving.

PHP changes:

* Introduces `WP_Customize_Setting::validate()`, `WP_Customize_Setting::$validate_callback`, and the `customize_validate_{$setting_id}` filter.
* Introduces `WP_Customize_Manager::validate_setting_values()` to do validation (and sanitization) for the setting values supplied, returning a list of `WP_Error` instances for invalid settings.
* Attempting to save settings that are invalid will result in the save being blocked entirely, with the errors being sent in the `customize_save_response`. Modifies `WP_Customize_Manager::save()` to check all settings for validity issues prior to calling their `save` methods.
* Introduces `WP_Customize_Setting::json()` for parity with the other Customizer classes. This includes exporting of the `type`.
* Modifies `WP_Customize_Manager::post_value()` to apply `validate` after `sanitize`, and if validation fails, to return the `$default`.
* Introduces `customize_save_validation_before` action which fires right before the validation checks are made prior to saving.

JS changes:

* Introduces `wp.customize.Notification` in JS which to represent `WP_Error` instances returned from the server when setting validation fails.
* Introduces `wp.customize.Setting.prototype.notifications`.
* Introduces `wp.customize.Control.prototype.notifications`, which are synced with a control's settings' notifications.
* Introduces `wp.customize.Control.prototype.renderNotifications()` to re-render a control's notifications in its notification area. This is called automatically when the notifications collection changes.
* Introduces `wp.customize.settingConstructor`, allowing custom setting types to be used in the same way that custom controls, panels, and sections can be made.
* Injects a notification area into existing controls which is populated in response to the control's `notifications` collection changing. A custom control can customize the placement of the notification area by overriding the new `getNotificationsContainerElement` method.
* When a save fails due to setting invalidity, the invalidity errors will be added to the settings to then populate in the controls' notification areas, and the first such invalid control will be focused.

Props westonruter, celloexpressions, mrahmadawais.
See #35210.
See #30937.
Fixes #34893.


git-svn-id: https://develop.svn.wordpress.org/trunk@37476 602fd350-edb4-49c9-b593-d223f7449a82
2016-05-20 21:09:40 +00:00

184 lines
5.7 KiB
JavaScript

/* global wp, test, ok, equal, module */
jQuery( function( $ ) {
var FooSuperClass, BarSubClass, foo, bar, ConstructorTestClass, newConstructor, constructorTest, $mockElement, mockString,
firstInitialValue, firstValueInstance, wasCallbackFired, mockValueCallback;
module( 'Customize Base: Class' );
FooSuperClass = wp.customize.Class.extend(
{
initialize: function ( instanceProps ) {
$.extend( this, instanceProps || {} );
},
protoProp: 'protoPropValue'
},
{
staticProp: 'staticPropValue'
}
);
test( 'FooSuperClass is a function ', function () {
equal( typeof FooSuperClass, 'function' );
});
test( 'FooSuperClass prototype has protoProp', function () {
equal( FooSuperClass.prototype.protoProp, 'protoPropValue' );
});
test( 'FooSuperClass does not have protoProp', function () {
equal( typeof FooSuperClass.protoProp, 'undefined' );
});
test( 'FooSuperClass has staticProp', function () {
equal( FooSuperClass.staticProp, 'staticPropValue' );
});
test( 'FooSuperClass prototype does not have staticProp', function () {
equal( typeof FooSuperClass.prototype.staticProp, 'undefined' );
});
foo = new FooSuperClass( { instanceProp: 'instancePropValue' } );
test( 'FooSuperClass instance foo extended Class', function () {
equal( foo.extended( wp.customize.Class ), true );
});
test( 'foo instance has protoProp', function () {
equal( foo.protoProp, 'protoPropValue' );
});
test( 'foo instance does not have staticProp', function () {
equal( typeof foo.staticProp, 'undefined' );
});
test( 'FooSuperClass instance foo ran initialize() and has supplied instanceProp', function () {
equal( foo.instanceProp, 'instancePropValue' );
});
// @todo Test Class.applicator?
// @todo do we test object.instance?
module( 'Customize Base: Subclass' );
BarSubClass = FooSuperClass.extend(
{
initialize: function ( instanceProps ) {
FooSuperClass.prototype.initialize.call( this, instanceProps );
this.subInstanceProp = 'subInstancePropValue';
},
subProtoProp: 'subProtoPropValue'
},
{
subStaticProp: 'subStaticPropValue'
}
);
test( 'BarSubClass prototype has subProtoProp', function () {
equal( BarSubClass.prototype.subProtoProp, 'subProtoPropValue' );
});
test( 'BarSubClass prototype has parent FooSuperClass protoProp', function () {
equal( BarSubClass.prototype.protoProp, 'protoPropValue' );
});
bar = new BarSubClass( { instanceProp: 'instancePropValue' } );
test( 'BarSubClass instance bar its initialize() and parent initialize() run', function () {
equal( bar.instanceProp, 'instancePropValue' );
equal( bar.subInstanceProp, 'subInstancePropValue' );
});
test( 'BarSubClass instance bar extended FooSuperClass', function () {
equal( bar.extended( FooSuperClass ), true );
});
// Implements todo : Test Class.constructor() manipulation
module( 'Customize Base: Constructor Manipulation' );
newConstructor = function ( instanceProps ) {
$.extend( this , instanceProps || {} );
};
ConstructorTestClass = wp.customize.Class.extend(
{
constructor : newConstructor,
protoProp: 'protoPropValue'
},
{
staticProp: 'staticPropValue'
}
);
test( 'New constructor added to class' , function () {
equal( ConstructorTestClass.prototype.constructor , newConstructor );
});
test( 'Class with new constructor has protoPropValue' , function () {
equal( ConstructorTestClass.prototype.protoProp , 'protoPropValue' );
});
constructorTest = new ConstructorTestClass( { instanceProp: 'instancePropValue' } );
test( 'ConstructorTestClass instance constructorTest has the new constructor', function () {
equal( constructorTest.constructor, newConstructor );
});
test( 'ConstructorTestClass instance constructorTest extended Class', function () {
equal( constructorTest.extended( wp.customize.Class ), true );
});
test( 'ConstructorTestClass instance constructorTest has the added instance property', function () {
equal( constructorTest.instanceProp , 'instancePropValue' );
});
module( 'Customize Base: wp.customizer.ensure' );
$mockElement = $( '<div id="mockElement"></div>' );
test( 'Handles jQuery argument' , function() {
equal( wp.customize.ensure( $mockElement ) , $mockElement );
});
mockString = '<div class="mockString"></div>';
test( 'Handles string argument' , function() {
ok( wp.customize.ensure( mockString ) instanceof jQuery );
});
module( 'Customize Base: Value Class' );
firstInitialValue = true;
firstValueInstance = new wp.customize.Value( firstInitialValue );
test( 'Initialized with the right value' , function() {
equal( firstValueInstance.get() , firstInitialValue );
});
test( '.set() works' , function() {
firstValueInstance.set( false );
equal( firstValueInstance.get() , false );
});
test( '.bind() adds new callback that fires on set()' , function() {
wasCallbackFired = false;
mockValueCallback = function() {
wasCallbackFired = true;
};
firstValueInstance.bind( mockValueCallback );
firstValueInstance.set( 'newValue' );
ok( wasCallbackFired );
});
module( 'Customize Base: Notification' );
test( 'Notification object exists and has expected properties', function ( assert ) {
var notification = new wp.customize.Notification( 'mycode', {
'message': 'Hello World',
'type': 'update',
'data': { 'foo': 'bar' }
} );
assert.equal( 'mycode', notification.code );
assert.equal( 'Hello World', notification.message );
assert.equal( 'update', notification.type );
assert.deepEqual( { 'foo': 'bar' }, notification.data );
notification = new wp.customize.Notification( 'mycode2', {
'message': 'Hello Space'
} );
assert.equal( 'mycode2', notification.code );
assert.equal( 'Hello Space', notification.message );
assert.equal( 'error', notification.type );
assert.equal( null, notification.data );
} );
});