Make WordPress Core

Changeset 30716


Ignore:
Timestamp:
12/02/2014 10:55:48 PM (10 years ago)
Author:
ocean90
Message:

Customizer: Move private helper functions to wp.customize.utils so they can be unit tested.

Includes unit tests.

props ryankienstra, westonruter.
see #28709.

Location:
trunk
Files:
2 added
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/js/customize-controls.js

    r30712 r30716  
    11/* globals _wpCustomizeHeader, _wpMediaViewsL10n */
    22(function( exports, $ ){
    3     var bubbleChildValueChanges, Container, focus, isKeydownButNotEnterEvent, areElementListsEqual, prioritySort, api = wp.customize;
    4 
    5     // @todo Move private helper functions to wp.customize.utils so they can be unit tested
     3    var Container, focus, api = wp.customize;
    64
    75    /**
     
    3432
    3533    /**
     34     * Utility function namespace
     35     */
     36    api.utils = {};
     37
     38    /**
    3639     * Watch all changes to Value properties, and bubble changes to parent Values instance
    3740     *
     
    3942     * @param {Array} properties  The names of the Value instances to watch.
    4043     */
    41     bubbleChildValueChanges = function ( instance, properties ) {
     44    api.utils.bubbleChildValueChanges = function ( instance, properties ) {
    4245        $.each( properties, function ( i, key ) {
    4346            instance[ key ].bind( function ( to, from ) {
     
    8790     * @returns {Number}
    8891     */
    89     prioritySort = function ( a, b ) {
     92    api.utils.prioritySort = function ( a, b ) {
    9093        if ( a.priority() === b.priority() && typeof a.params.instanceNumber === 'number' && typeof b.params.instanceNumber === 'number' ) {
    9194            return a.params.instanceNumber - b.params.instanceNumber;
     
    101104     * @returns {boolean}
    102105     */
    103     isKeydownButNotEnterEvent = function ( event ) {
     106    api.utils.isKeydownButNotEnterEvent = function ( event ) {
    104107        return ( 'keydown' === event.type && 13 !== event.which );
    105108    };
     
    112115     * @returns {boolean}
    113116     */
    114     areElementListsEqual = function ( listA, listB ) {
     117    api.utils.areElementListsEqual = function ( listA, listB ) {
    115118        var equal = (
    116119            listA.length === listB.length && // if lists are different lengths, then naturally they are not equal
     
    143146
    144147            container.deferred = {
    145                 ready: new $.Deferred()
     148                embedded: new $.Deferred()
    146149            };
    147150            container.priority = new api.Value();
     
    156159                active = ( active && container.isContextuallyActive() );
    157160                container.onChangeActive( active, args );
    158                 // @todo trigger 'activated' and 'deactivated' events based on the expanded param?
    159161            });
    160162            container.expanded.bind( function ( expanded ) {
     
    162164                args = $.extend( {}, container.defaultExpandedArguments, args );
    163165                container.onChangeExpanded( expanded, args );
    164                 // @todo trigger 'expanded' and 'collapsed' events based on the expanded param?
    165166            });
    166167
    167168            container.attachEvents();
    168169
    169             bubbleChildValueChanges( container, [ 'priority', 'active' ] );
     170            api.utils.bubbleChildValueChanges( container, [ 'priority', 'active' ] );
    170171
    171172            container.priority.set( isNaN( container.params.priority ) ? 100 : container.params.priority );
    172173            container.active.set( container.params.active );
    173             container.expanded.set( false ); // @todo True if deeplinking?
     174            container.expanded.set( false );
    174175        },
    175176
     
    194195                }
    195196            } );
    196             children.sort( prioritySort );
     197            children.sort( api.utils.prioritySort );
    197198            return children;
    198199        },
     
    203204         */
    204205        isContextuallyActive: function () {
    205             throw new Error( 'Must override with subclass.' );
     206            throw new Error( 'Container.isContextuallyActive() must be overridden in a subclass.' );
    206207        },
    207208
     
    336337            });
    337338            section.panel.set( section.params.panel || '' );
    338             bubbleChildValueChanges( section, [ 'panel' ] );
     339            api.utils.bubbleChildValueChanges( section, [ 'panel' ] );
    339340
    340341            section.embed();
    341             section.deferred.ready.done( function () {
     342            section.deferred.embedded.done( function () {
    342343                section.ready();
    343344            });
     
    357358                    api.panel( panelId, function ( panel ) {
    358359                        // The panel has been registered, wait for it to become ready/initialized
    359                         panel.deferred.ready.done( function () {
     360                        panel.deferred.embedded.done( function () {
    360361                            parentContainer = panel.container.find( 'ul:first' );
    361362                            if ( ! section.container.parent().is( parentContainer ) ) {
    362363                                parentContainer.append( section.container );
    363364                            }
    364                             section.deferred.ready.resolve(); // @todo Better to use `embedded` instead of `ready`
     365                            section.deferred.embedded.resolve();
    365366                        });
    366367                    } );
     
    371372                        parentContainer.append( section.container );
    372373                    }
    373                     section.deferred.ready.resolve();
     374                    section.deferred.embedded.resolve();
    374375                }
    375376            };
     
    386387            // Expand/Collapse accordion sections on click.
    387388            section.container.find( '.accordion-section-title' ).on( 'click keydown', function( event ) {
    388                 if ( isKeydownButNotEnterEvent( event ) ) {
     389                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    389390                    return;
    390391                }
     
    480481            Container.prototype.initialize.call( panel, id, options );
    481482            panel.embed();
    482             panel.deferred.ready.done( function () {
     483            panel.deferred.embedded.done( function () {
    483484                panel.ready();
    484485            });
     
    495496                parentContainer.append( panel.container );
    496497            }
    497             panel.deferred.ready.resolve();
     498            panel.deferred.embedded.resolve();
    498499        },
    499500
     
    506507            // Expand/Collapse accordion sections on click.
    507508            panel.container.find( '.accordion-section-title' ).on( 'click keydown', function( event ) {
    508                 if ( isKeydownButNotEnterEvent( event ) ) {
     509                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    509510                    return;
    510511                }
     
    519520
    520521            meta.find( '> .accordion-section-title' ).on( 'click keydown', function( event ) {
    521                 if ( isKeydownButNotEnterEvent( event ) ) {
     522                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    522523                    return;
    523524                }
     
    677678
    678679            control.deferred = {
    679                 ready: new $.Deferred()
     680                embedded: new $.Deferred()
    680681            };
    681682            control.section = new api.Value();
     
    721722            control.active.set( control.params.active );
    722723
    723             bubbleChildValueChanges( control, [ 'section', 'priority', 'active' ] );
     724            api.utils.bubbleChildValueChanges( control, [ 'section', 'priority', 'active' ] );
    724725
    725726            // Associate this control with its settings when they are created
     
    740741            }) );
    741742
    742             control.deferred.ready.done( function () {
     743            control.deferred.embedded.done( function () {
    743744                control.ready();
    744745            });
     
    761762                api.section( sectionId, function ( section ) {
    762763                    // Wait for the section to be ready/initialized
    763                     section.deferred.ready.done( function () {
     764                    section.deferred.embedded.done( function () {
    764765                        parentContainer = section.container.find( 'ul:first' );
    765766                        if ( ! control.container.parent().is( parentContainer ) ) {
     
    767768                            control.renderContent();
    768769                        }
    769                         control.deferred.ready.resolve(); // @todo Better to use `embedded` instead of `ready`
     770                        control.deferred.embedded.resolve();
    770771                    });
    771772                });
     
    853854            // Support the .dropdown class to open/close complex elements
    854855            this.container.on( 'click keydown', '.dropdown', function( event ) {
    855                 if ( isKeydownButNotEnterEvent( event ) ) {
     856                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    856857                    return;
    857858                }
     
    936937         * set up internal event bindings.
    937938         */
    938         ready: function() {
     939        ready: function() {
    939940            var control = this;
    940941            // Shortcut so that we don't have to use _.bind every time we add a callback.
     
    955956         */
    956957        openFrame: function( event ) {
    957             if ( event.type === 'keydown' &&  13 !== event.which ) { // enter
     958            if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    958959                return;
    959             }
     960            }
    960961
    961962            event.preventDefault();
     
    985986                },
    986987                multiple: false
    987             });
     988            });
    988989
    989990            // When a file is selected, run a callback.
    990991            this.frame.on( 'select', this.select );
    991         },
     992        },
    992993
    993994        /**
     
    10091010         */
    10101011        restoreDefault: function( event ) {
    1011             if ( event.type === 'keydown' && 13 !== event.which ) { // enter
     1012            if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    10121013                return;
    10131014            }
     
    10241025         */
    10251026        removeFile: function( event ) {
    1026             if ( event.type === 'keydown' && 13 !== event.which ) { // enter
     1027            if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    10271028                return;
    1028             }
     1029            }
    10291030            event.preventDefault();
    10301031
     
    10321033            this.setting( '' );
    10331034            this.renderContent(); // Not bound to setting change when emptying.
    1034         },
     1035        },
    10351036
    10361037        // @deprecated
     
    10391040        // @deprecated
    10401041        removerVisibility: function() {}
    1041     });
     1042    });
    10421043
    10431044    /**
     
    17431744
    17441745        // Check if we can run the Customizer.
    1745         if ( ! api.settings )
     1746        if ( ! api.settings ) {
    17461747            return;
     1748        }
    17471749
    17481750        // Redirect to the fallback preview if any incompatibilities are found.
     
    17691771        // Expand/Collapse the main customizer customize info
    17701772        $( '#customize-info' ).find( '> .accordion-section-title' ).on( 'click keydown', function( event ) {
    1771             if ( isKeydownButNotEnterEvent( event ) ) {
     1773            if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    17721774                return;
    17731775            }
     
    19311933                instance = api[ type ]( id );
    19321934                // Wait until the element is embedded in the DOM
    1933                 instance.deferred.ready.done( function () {
     1935                instance.deferred.embedded.done( function () {
    19341936                    // Wait until the preview has activated and so active panels, sections, controls have been set
    19351937                    api.previewer.deferred.active.done( function () {
     
    19571959                rootNodes.push( panel );
    19581960                appendContainer = panel.container.find( 'ul:first' );
    1959                 if ( ! areElementListsEqual( sectionContainers, appendContainer.children( '[id]' ) ) ) {
     1961                if ( ! api.utils.areElementListsEqual( sectionContainers, appendContainer.children( '[id]' ) ) ) {
    19601962                    _( sections ).each( function ( section ) {
    19611963                        appendContainer.append( section.container );
     
    19731975                }
    19741976                appendContainer = section.container.find( 'ul:first' );
    1975                 if ( ! areElementListsEqual( controlContainers, appendContainer.children( '[id]' ) ) ) {
     1977                if ( ! api.utils.areElementListsEqual( controlContainers, appendContainer.children( '[id]' ) ) ) {
    19761978                    _( controls ).each( function ( control ) {
    19771979                        appendContainer.append( control.container );
     
    19821984
    19831985            // Sort the root panels and sections
    1984             rootNodes.sort( prioritySort );
     1986            rootNodes.sort( api.utils.prioritySort );
    19851987            rootContainers = _.pluck( rootNodes, 'container' );
    19861988            appendContainer = $( '#customize-theme-controls' ).children( 'ul' ); // @todo This should be defined elsewhere, and to be configurable
    1987             if ( ! areElementListsEqual( rootContainers, appendContainer.children() ) ) {
     1989            if ( ! api.utils.areElementListsEqual( rootContainers, appendContainer.children() ) ) {
    19881990                _( rootNodes ).each( function ( rootNode ) {
    19891991                    appendContainer.append( rootNode.container );
     
    20812083        // Go back to the top-level Customizer accordion.
    20822084        $( '#customize-header-actions' ).on( 'click keydown', '.control-panel-back', function( event ) {
    2083             if ( isKeydownButNotEnterEvent( event ) ) {
     2085            if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    20842086                return;
    20852087            }
     
    21002102
    21012103        $('.collapse-sidebar').on( 'click keydown', function( event ) {
    2102             if ( isKeydownButNotEnterEvent( event ) ) {
     2104            if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    21032105                return;
    21042106            }
  • trunk/tests/qunit/index.html

    r30102 r30716  
    3232    <script src="../../src/wp-includes/js/customize-models.js"></script>
    3333    <script src="../../src/wp-includes/js/shortcode.js"></script>
     34    <script src="../../src/wp-admin/js/customize-controls.js"></script>
    3435
    3536    <!-- Unit tests -->
     
    3839    <script src="wp-admin/js/customize-header.js"></script>
    3940    <script src="wp-includes/js/shortcode.js"></script>
     41    <script src="wp-admin/js/customize-controls.js"></script>
     42    <script src="wp-admin/js/customize-controls-utils.js"></script>
    4043  </div>
    4144</body>
  • trunk/tests/qunit/wp-admin/js/customize-base.js

    r30102 r30716  
    22
    33jQuery( function( $ ) {
    4     var FooSuperClass, BarSubClass, foo, bar;
     4    var FooSuperClass, BarSubClass, foo, bar, ConstructorTestClass, newConstructor, constructorTest, $mockElement, mockString,
     5    firstInitialValue, firstValueInstance, wasCallbackFired, mockValueCallback;
    56
    67    module( 'Customize Base: Class' );
     
    4748    });
    4849
    49     // @todo Test Class.constructor() manipulation
    5050    // @todo Test Class.applicator?
    5151    // @todo do we test object.instance?
    52 
    5352
    5453    module( 'Customize Base: Subclass' );
     
    8382    });
    8483
     84
     85    // Implements todo : Test Class.constructor() manipulation
     86    module( 'Customize Base: Constructor Manipulation' );
     87
     88    newConstructor = function ( instanceProps ) {
     89            $.extend( this , instanceProps || {} );
     90    };
     91
     92    ConstructorTestClass = wp.customize.Class.extend(
     93        {
     94            constructor : newConstructor,
     95            protoProp: 'protoPropValue'
     96        },
     97        {
     98            staticProp: 'staticPropValue'
     99        }
     100    );
     101
     102    test( 'New constructor added to class' , function () {
     103        equal( ConstructorTestClass.prototype.constructor , newConstructor );
     104    });
     105    test( 'Class with new constructor has protoPropValue' , function () {
     106        equal( ConstructorTestClass.prototype.protoProp , 'protoPropValue' );
     107    });
     108
     109    constructorTest = new ConstructorTestClass( { instanceProp: 'instancePropValue' } );
     110        test( 'ConstructorTestClass instance constructorTest has the new constructor', function () {
     111        equal( constructorTest.constructor, newConstructor );
     112    });
     113
     114    test( 'ConstructorTestClass instance constructorTest extended Class', function () {
     115        equal( constructorTest.extended( wp.customize.Class ), true );
     116    });
     117
     118    test( 'ConstructorTestClass instance constructorTest has the added instance property', function () {
     119        equal( constructorTest.instanceProp , 'instancePropValue' );
     120    });
     121
     122
     123    module( 'Customize Base: wp.customizer.ensure' );
     124
     125    $mockElement = $( '<div id="mockElement"></div>' );
     126
     127    test( 'Handles jQuery argument' , function() {
     128        equal( wp.customize.ensure( $mockElement ) , $mockElement );
     129    });
     130
     131    mockString = '<div class="mockString"></div>';
     132
     133    test( 'Handles string argument' , function() {
     134        ok( wp.customize.ensure( mockString ) instanceof jQuery );
     135    });
     136
     137
     138    module( 'Customize Base: Value Class' );
     139
     140    firstInitialValue = true;
     141    firstValueInstance = new wp.customize.Value( firstInitialValue );
     142
     143    test( 'Initialized with the right value' , function() {
     144        equal( firstValueInstance.get() , firstInitialValue );
     145    });
     146
     147    test( '.set() works' , function() {
     148        firstValueInstance.set( false );
     149        equal( firstValueInstance.get() , false );
     150    });
     151
     152    test( '.bind() adds new callback that fires on set()' , function() {
     153        wasCallbackFired = false;
     154        mockValueCallback = function() {
     155            wasCallbackFired = true;
     156        };
     157        firstValueInstance.bind( mockValueCallback );
     158        firstValueInstance.set( 'newValue' );
     159        ok( wasCallbackFired );
     160    });
    85161});
Note: See TracChangeset for help on using the changeset viewer.