WordPress.org

Make WordPress Core

Changeset 30716


Ignore:
Timestamp:
12/02/14 22:55:48 (3 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.