Ticket #35243: 35243.diff
File 35243.diff, 23.5 KB (added by , 8 years ago) |
---|
-
Gruntfile.js
456 456 dest: BUILD_DIR, 457 457 ext: '.min.js', 458 458 src: [ 459 'wp-admin/js/* .js',459 'wp-admin/js/**/*.js', 460 460 'wp-includes/js/*.js', 461 461 'wp-includes/js/mediaelement/wp-mediaelement.js', 462 462 'wp-includes/js/mediaelement/wp-playlist.js', -
src/wp-admin/css/customize-widgets.css
213 213 display: block; 214 214 } 215 215 216 /* Text Widget */ 217 .wp-customizer div.mce-inline-toolbar-grp, 218 .wp-customizer div.mce-tooltip { 219 z-index: 500100 !important; 220 } 221 .wp-customizer .ui-autocomplete.wplink-autocomplete { 222 z-index: 500110; /* originally 100110, but z-index of .wp-full-overlay is 500000 */ 223 } 224 .wp-customizer #wp-link-backdrop { 225 z-index: 500100; /* originally 100100, but z-index of .wp-full-overlay is 500000 */ 226 } 227 .wp-customizer #wp-link-wrap { 228 z-index: 500105; /* originally 100105, but z-index of .wp-full-overlay is 500000 */ 229 } 230 216 231 /** 217 232 * Styles for new widget addition panel 218 233 */ -
src/wp-admin/js/widgets/text-widgets.js
1 /* global tinymce, switchEditors */ 2 /* eslint consistent-this: [ "error", "control" ] */ 3 wp.textWidgets = ( function( $ ) { 4 'use strict'; 5 6 var component = {}; 7 8 /** 9 * Text widget control. 10 * 11 * @class TextWidgetControl 12 * @constructor 13 * @abstract 14 */ 15 component.TextWidgetControl = Backbone.View.extend({ 16 17 /** 18 * View events. 19 * 20 * @type {Object} 21 */ 22 events: {}, 23 24 /** 25 * Initialize. 26 * 27 * @param {Object} options - Options. 28 * @param {Backbone.Model} options.model - Model. 29 * @param {jQuery} options.el - Control container element. 30 * @returns {void} 31 */ 32 initialize: function initialize( options ) { 33 var control = this; 34 35 if ( ! options.el ) { 36 throw new Error( 'Missing options.el' ); 37 } 38 39 Backbone.View.prototype.initialize.call( control, options ); 40 41 /* 42 * Create a container element for the widget control fields. 43 * This is inserted into the DOM immediately before the the .widget-content 44 * element because the contents of this element are essentially "managed" 45 * by PHP, where each widget update cause the entire element to be emptied 46 * and replaced with the rendered output of WP_Widget::form() which is 47 * sent back in Ajax request made to save/update the widget instance. 48 * To prevent a "flash of replaced DOM elements and re-initialized JS 49 * components", the JS template is rendered outside of the normal form 50 * container. 51 */ 52 control.fieldContainer = $( '<div class="text-widget-fields"></div>' ); 53 control.fieldContainer.html( wp.template( 'widget-text-control-fields' ) ); 54 control.widgetContentContainer = control.$el.find( '.widget-content:first' ); 55 control.widgetContentContainer.before( control.fieldContainer ); 56 57 control.fields = { 58 title: control.fieldContainer.find( '.title' ), 59 text: control.fieldContainer.find( '.text' ) 60 }; 61 62 // Sync input fields to hidden sync fields which actually get sent to the server. 63 _.each( control.fields, function( fieldInput, fieldName ) { 64 fieldInput.on( 'input change', function updateSyncField() { 65 var syncInput = control.widgetContentContainer.find( 'input[type=hidden].' + fieldName ); 66 if ( syncInput.val() !== $( this ).val() ) { 67 syncInput.val( $( this ).val() ); 68 syncInput.trigger( 'change' ); 69 } 70 }); 71 72 // Note that syncInput cannot be re-used because it will be destroyed with each widget-updated event. 73 fieldInput.val( control.widgetContentContainer.find( 'input[type=hidden].' + fieldName ).val() ); 74 }); 75 }, 76 77 /** 78 * Update input fields from the sync fields. 79 * 80 * This function is called at the widget-updated and widget-synced events. 81 * A field will only be updated if it is not currently focused, to avoid 82 * overwriting content that the user is entering. 83 * 84 * @returns {void} 85 */ 86 updateFields: function updateFields() { 87 var control = this, syncInput; 88 89 if ( ! control.fields.title.is( document.activeElement ) ) { 90 syncInput = control.widgetContentContainer.find( 'input[type=hidden].title' ); 91 control.fields.title.val( syncInput.val() ); 92 } 93 94 syncInput = control.widgetContentContainer.find( 'input[type=hidden].text' ); 95 if ( control.fields.text.is( ':visible' ) ) { 96 if ( ! control.fields.text.is( document.activeElement ) ) { 97 control.fields.text.val( syncInput.val() ); 98 } 99 } else if ( control.editor && ! control.editorFocused && syncInput.val() !== control.fields.text.val() ) { 100 control.editor.setContent( wp.editor.autop( syncInput.val() ) ); 101 } 102 }, 103 104 /** 105 * Initialize editor. 106 * 107 * @returns {void} 108 */ 109 initializeEditor: function initializeEditor() { 110 var control = this, changeDebounceDelay = 1000, iframeKeepAliveInterval = 1000, id, textarea, restoreTextMode = false; 111 textarea = control.fields.text; 112 id = textarea.attr( 'id' ); 113 114 /** 115 * Build (or re-build) the visual editor. 116 * 117 * @returns {void} 118 */ 119 function buildEditor() { 120 var editor, triggerChangeIfDirty, onInit; 121 122 // Destroy any existing editor so that it can be re-initialized after a widget-updated event. 123 if ( tinymce.get( id ) ) { 124 restoreTextMode = tinymce.get( id ).isHidden(); 125 wp.editor.remove( id ); 126 } 127 128 wp.editor.initialize( id, { 129 tinymce: { 130 wpautop: true 131 }, 132 quicktags: true 133 } ); 134 135 editor = window.tinymce.get( id ); 136 if ( ! editor ) { 137 throw new Error( 'Failed to initialize editor' ); 138 } 139 onInit = function() { 140 watchForDestroyedBody( control.$el.find( 'iframe' )[0] ); 141 142 // If a prior mce instance was replaced, and it was in text mode, toggle to text mode. 143 if ( restoreTextMode ) { 144 switchEditors.go( id, 'toggle' ); 145 } 146 }; 147 148 if ( editor.initialized ) { 149 onInit(); 150 } else { 151 editor.on( 'init', onInit ); 152 } 153 154 control.editorFocused = false; 155 triggerChangeIfDirty = function() { 156 if ( editor.isDirty() ) { 157 editor.save(); 158 textarea.trigger( 'change' ); 159 } 160 }; 161 editor.on( 'focus', function() { 162 control.editorFocused = true; 163 } ); 164 editor.on( 'NodeChange', _.debounce( triggerChangeIfDirty, changeDebounceDelay ) ); 165 editor.on( 'blur', function() { 166 control.editorFocused = false; 167 triggerChangeIfDirty(); 168 } ); 169 170 control.editor = editor; 171 } 172 173 /** 174 * Watch an iframe for the destruction of its TinyMCE contenteditable contents. 175 * 176 * @todo There may be a better way to listen for an iframe being destroyed. 177 * @param {HTMLIFrameElement} iframe - TinyMCE iframe. 178 * @returns {void} 179 */ 180 function watchForDestroyedBody( iframe ) { 181 var timeoutId = setInterval( function() { 182 if ( ! iframe.contentWindow || iframe.contentWindow.document.body.id ) { 183 return; 184 } 185 clearInterval( timeoutId ); 186 buildEditor(); 187 }, iframeKeepAliveInterval ); 188 } 189 190 buildEditor(); 191 } 192 }); 193 194 /** 195 * Mapping of widget ID to instances of TextWidgetControl subclasses. 196 * 197 * @type {Object.<string, wp.textWidgets.TextWidgetControl>} 198 */ 199 component.widgetControls = {}; 200 201 /** 202 * Handle widget being added or initialized for the first time at the widget-added event. 203 * 204 * @param {jQuery.Event} event - Event. 205 * @param {jQuery} widgetContainer - Widget container element. 206 * @returns {void} 207 */ 208 component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) { 209 var widgetForm, idBase, widgetControl, widgetId, animatedCheckDelay = 50, widgetInside, renderWhenAnimationDone; 210 widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); // Note: '.form' appears in the customizer, whereas 'form' on the widgets admin screen. 211 212 idBase = widgetForm.find( '> .id_base' ).val(); 213 if ( 'text' !== idBase ) { 214 return; 215 } 216 217 // Prevent initializing already-added widgets. 218 widgetId = widgetForm.find( '> .widget-id' ).val(); 219 if ( component.widgetControls[ widgetId ] ) { 220 return; 221 } 222 223 widgetControl = new component.TextWidgetControl({ 224 el: widgetContainer 225 }); 226 227 component.widgetControls[ widgetId ] = widgetControl; 228 229 /* 230 * Render the widget once the widget parent's container finishes animating, 231 * as the widget-added event fires with a slideDown of the container. 232 * This ensures that the textarea is visible and an iframe can be embedded 233 * with TinyMCE being able to set contenteditable on it. 234 */ 235 widgetInside = widgetContainer.parent(); 236 renderWhenAnimationDone = function() { 237 if ( widgetInside.is( ':animated' ) ) { 238 setTimeout( renderWhenAnimationDone, animatedCheckDelay ); 239 } else { 240 widgetControl.initializeEditor(); 241 } 242 }; 243 renderWhenAnimationDone(); 244 }; 245 246 /** 247 * Sync widget instance data sanitized from server back onto widget model. 248 * 249 * This gets called via the 'widget-updated' event when saving a widget from 250 * the widgets admin screen and also via the 'widget-synced' event when making 251 * a change to a widget in the customizer. 252 * 253 * @param {jQuery.Event} event - Event. 254 * @param {jQuery} widgetContainer - Widget container element. 255 * @returns {void} 256 */ 257 component.handleWidgetUpdated = function handleWidgetUpdated( event, widgetContainer ) { 258 var widgetForm, widgetId, widgetControl, idBase; 259 widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); 260 261 idBase = widgetForm.find( '> .id_base' ).val(); 262 if ( 'text' !== idBase ) { 263 return; 264 } 265 266 widgetId = widgetForm.find( '> .widget-id' ).val(); 267 widgetControl = component.widgetControls[ widgetId ]; 268 if ( ! widgetControl ) { 269 return; 270 } 271 272 widgetControl.updateFields(); 273 }; 274 275 /** 276 * Initialize functionality. 277 * 278 * This function exists to prevent the JS file from having to boot itself. 279 * When WordPress enqueues this script, it should have an inline script 280 * attached which calls wp.textWidgets.init(). 281 * 282 * @returns {void} 283 */ 284 component.init = function init() { 285 var $document = $( document ); 286 $document.on( 'widget-added', component.handleWidgetAdded ); 287 $document.on( 'widget-synced widget-updated', component.handleWidgetUpdated ); 288 289 /* 290 * Manually trigger widget-added events for media widgets on the admin 291 * screen once they are expanded. The widget-added event is not triggered 292 * for each pre-existing widget on the widgets admin screen like it is 293 * on the customizer. Likewise, the customizer only triggers widget-added 294 * when the widget is expanded to just-in-time construct the widget form 295 * when it is actually going to be displayed. So the following implements 296 * the same for the widgets admin screen, to invoke the widget-added 297 * handler when a pre-existing media widget is expanded. 298 */ 299 $( function initializeExistingWidgetContainers() { 300 var widgetContainers; 301 if ( 'widgets' !== window.pagenow ) { 302 return; 303 } 304 widgetContainers = $( '.widgets-holder-wrap:not(#available-widgets)' ).find( 'div.widget' ); 305 widgetContainers.one( 'click.toggle-widget-expanded', function toggleWidgetExpanded() { 306 var widgetContainer = $( this ); 307 component.handleWidgetAdded( new jQuery.Event( 'widget-added' ), widgetContainer ); 308 }); 309 }); 310 }; 311 312 return component; 313 })( jQuery ); -
src/wp-content/themes/twentyten/style.css
841 841 padding: 4px; 842 842 text-align: center; 843 843 } 844 .widget-container .wp-caption { 845 max-width: 100% !important; 846 } 844 847 .wp-caption img { 845 848 margin: 5px 5px 0; 846 849 max-width: 622px; /* caption width - 10px */ -
src/wp-includes/default-filters.php
164 164 165 165 add_filter( 'wp_sprintf', 'wp_sprintf_l', 10, 2 ); 166 166 167 add_filter( 'widget_text', 'balanceTags' ); 167 add_filter( 'widget_text', 'balanceTags' ); 168 add_filter( 'widget_text_content', 'capital_P_dangit', 11 ); 169 add_filter( 'widget_text_content', 'wptexturize' ); 170 add_filter( 'widget_text_content', 'convert_smilies', 20 ); 171 add_filter( 'widget_text_content', 'wpautop' ); 168 172 169 173 add_filter( 'date_i18n', 'wp_maybe_decline_date' ); 170 174 -
src/wp-includes/script-loader.php
602 602 $scripts->add( 'admin-gallery', "/wp-admin/js/gallery$suffix.js", array( 'jquery-ui-sortable' ) ); 603 603 604 604 $scripts->add( 'admin-widgets', "/wp-admin/js/widgets$suffix.js", array( 'jquery-ui-sortable', 'jquery-ui-draggable', 'jquery-ui-droppable' ), false, 1 ); 605 $scripts->add( 'text-widgets', "/wp-admin/js/widgets/text-widgets$suffix.js", array( 'jquery', 'backbone', 'editor', 'wp-util' ) ); 606 $scripts->add_inline_script( 'text-widgets', 'wp.textWidgets.init();', 'after' ); 605 607 606 608 $scripts->add( 'theme', "/wp-admin/js/theme$suffix.js", array( 'wp-backbone', 'wp-a11y' ), false, 1 ); 607 609 -
src/wp-includes/widgets/class-wp-widget-text.php
28 28 'description' => __( 'Arbitrary text or HTML.' ), 29 29 'customize_selective_refresh' => true, 30 30 ); 31 $control_ops = array( 'width' => 400, 'height' => 350 ); 31 $control_ops = array( 32 'width' => 400, 33 'height' => 350, 34 ); 32 35 parent::__construct( 'text', __( 'Text' ), $widget_ops, $control_ops ); 33 36 } 34 37 35 38 /** 39 * Add hooks for enqueueing assets when registering all widget instances of this widget class. 40 * 41 * @since 4.8.0 42 * @access public 43 */ 44 public function _register() { 45 46 // Note that the widgets component in the customizer will also do the 'admin_print_scripts-widgets.php' action in WP_Customize_Widgets::print_scripts(). 47 add_action( 'admin_print_scripts-widgets.php', array( $this, 'enqueue_admin_scripts' ) ); 48 49 // Note that the widgets component in the customizer will also do the 'admin_footer-widgets.php' action in WP_Customize_Widgets::print_footer_scripts(). 50 add_action( 'admin_footer-widgets.php', array( $this, 'render_control_template_scripts' ) ); 51 52 parent::_register(); 53 } 54 55 /** 36 56 * Outputs the content for the current Text widget instance. 37 57 * 38 58 * @since 2.8.0 … … 61 81 */ 62 82 $text = apply_filters( 'widget_text', $widget_text, $instance, $this ); 63 83 84 if ( isset( $instance['filter'] ) ) { 85 if ( 'content' === $instance['filter'] ) { 86 87 /** 88 * Filters the content of the Text widget to apply changes expected from the visual (TinyMCE) editor. 89 * 90 * By default a subset of the_content filters are applied, including wpautop and wptexturize. 91 * 92 * @since 4.8.0 93 * 94 * @param string $widget_text The widget content. 95 * @param array $instance Array of settings for the current widget. 96 * @param WP_Widget_Text $this Current Text widget instance. 97 */ 98 $text = apply_filters( 'widget_text_content', $widget_text, $instance, $this ); 99 100 } elseif ( $instance['filter'] ) { 101 $text = wpautop( $text ); // Back-compat for instances prior to 4.8. 102 } 103 } 104 64 105 echo $args['before_widget']; 65 106 if ( ! empty( $title ) ) { 66 107 echo $args['before_title'] . $title . $args['after_title']; 67 } ?> 68 <div class="textwidget"><?php echo !empty( $instance['filter'] ) ? wpautop( $text ) : $text; ?></div> 108 } 109 110 ?> 111 <div class="textwidget"><?php echo $text; ?></div> 69 112 <?php 70 113 echo $args['after_widget']; 71 114 } … … 89 132 } else { 90 133 $instance['text'] = wp_kses_post( $new_instance['text'] ); 91 134 } 92 $instance['filter'] = ! empty( $new_instance['filter'] ); 135 136 /* 137 * Re-use legacy 'filter' (wpautop) property to now indicate content filters will always apply. 138 * Prior to 4.8, this is a boolean value used to indicate whether or not wpautop should be 139 * applied. By re-using this property, downgrading WordPress from 4.8 to 4.7 will ensure 140 * that the content for Text widgets created with TinyMCE will continue to get wpautop. 141 */ 142 $instance['filter'] = 'content'; 143 93 144 return $instance; 94 145 } 95 146 96 147 /** 148 * Loads the required scripts and styles for the widget control. 149 * 150 * @since 4.8.0 151 * @access public 152 */ 153 public function enqueue_admin_scripts() { 154 wp_enqueue_editor(); 155 wp_enqueue_script( 'text-widgets' ); 156 } 157 158 /** 97 159 * Outputs the Text widget settings form. 98 160 * 99 161 * @since 2.8.0 162 * @since 4.8.0 Form only contains hidden inputs which are synced with JS template. 100 163 * @access public 164 * @see WP_Widget_Visual_Text::render_control_template_scripts() 101 165 * 102 166 * @param array $instance Current settings. 167 * @return void 103 168 */ 104 169 public function form( $instance ) { 105 $instance = wp_parse_args( (array) $instance, array( 'title' => '', 'text' => '' ) ); 106 $filter = isset( $instance['filter'] ) ? $instance['filter'] : 0; 107 $title = sanitize_text_field( $instance['title'] ); 170 $instance = wp_parse_args( 171 (array) $instance, 172 array( 173 'title' => '', 174 'text' => '', 175 ) 176 ); 108 177 ?> 109 <p><label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('Title:'); ?></label> 110 <input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr($title); ?>" /></p> 111 112 <p><label for="<?php echo $this->get_field_id( 'text' ); ?>"><?php _e( 'Content:' ); ?></label> 113 <textarea class="widefat" rows="16" cols="20" id="<?php echo $this->get_field_id('text'); ?>" name="<?php echo $this->get_field_name('text'); ?>"><?php echo esc_textarea( $instance['text'] ); ?></textarea></p> 178 <input id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" class="title" type="hidden" value="<?php echo esc_attr( $instance['title'] ); ?>"> 179 <input id="<?php echo $this->get_field_id( 'text' ); ?>" name="<?php echo $this->get_field_name( 'text' ); ?>" class="text" type="hidden" value="<?php echo esc_attr( $instance['text'] ); ?>"> 180 <?php 181 } 114 182 115 <p><input id="<?php echo $this->get_field_id('filter'); ?>" name="<?php echo $this->get_field_name('filter'); ?>" type="checkbox"<?php checked( $filter ); ?> /> <label for="<?php echo $this->get_field_id('filter'); ?>"><?php _e('Automatically add paragraphs'); ?></label></p> 183 /** 184 * Render form template scripts. 185 * 186 * @since 4.8.0 187 * @access public 188 */ 189 public function render_control_template_scripts() { 190 ?> 191 <script type="text/html" id="tmpl-widget-text-control-fields"> 192 <# var elementIdPrefix = 'el' + String( Math.random() ).replace( /\D/g, '' ) + '_' #> 193 <p> 194 <label for="{{ elementIdPrefix }}title"><?php esc_html_e( 'Title:' ); ?></label> 195 <input id="{{ elementIdPrefix }}title" type="text" class="widefat title"> 196 </p> 197 <p> 198 <label for="{{ elementIdPrefix }}text" class="screen-reader-text"><?php esc_html_e( 'Content:' ); ?></label> 199 <textarea id="{{ elementIdPrefix }}text" class="widefat text" style="height: 200px" rows="16" cols="20"></textarea> 200 </p> 201 </script> 116 202 <?php 117 203 } 118 204 } -
tests/phpunit/tests/widgets/test-class-wp-widget-text.php
1 <?php 2 /** 3 * Unit tests covering WP_Widget_Text functionality. 4 * 5 * @package WordPress 6 * @subpackage widgets 7 */ 8 9 /** 10 * Test wp-includes/widgets/class-wp-widget-text.php 11 * 12 * @group widgets 13 */ 14 class Test_WP_Widget_Text extends WP_UnitTestCase { 15 16 /** 17 * Test enqueue_admin_scripts method. 18 * 19 * @covers WP_Widget_Text::_register 20 */ 21 function test__register() { 22 set_current_screen( 'widgets.php' ); 23 $widget = new WP_Widget_Text(); 24 $widget->_register(); 25 26 $this->assertEquals( 10, has_action( 'admin_print_scripts-widgets.php', array( $widget, 'enqueue_admin_scripts' ) ) ); 27 $this->assertEquals( 10, has_action( 'admin_footer-widgets.php', array( $widget, 'render_control_template_scripts' ) ) ); 28 } 29 30 /** 31 * Test widget method. 32 * 33 * @covers WP_Widget_Text::widget 34 */ 35 function test_widget() { 36 $widget = new WP_Widget_Text(); 37 $text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n Praesent ut turpis consequat lorem volutpat bibendum vitae vitae ante."; 38 39 $args = array( 40 'before_title' => '<h2>', 41 'after_title' => "</h2>\n", 42 'before_widget' => '<section>', 43 'after_widget' => "</section>\n", 44 ); 45 $instance = array( 46 'title' => 'Foo', 47 'text' => $text, 48 'filter' => false, 49 ); 50 51 ob_start(); 52 $widget->widget( $args, $instance ); 53 $output = ob_get_clean(); 54 $this->assertNotContains( '<p>', $output ); 55 $this->assertNotContains( '<br />', $output ); 56 57 $instance['filter'] = true; 58 ob_start(); 59 $widget->widget( $args, $instance ); 60 $output = ob_get_clean(); 61 $this->assertContains( '<p>', $output ); 62 $this->assertContains( '<br />', $output ); 63 64 $instance['filter'] = 'content'; 65 ob_start(); 66 $widget->widget( $args, $instance ); 67 $output = ob_get_clean(); 68 $this->assertContains( '<p>', $output ); 69 $this->assertContains( '<br />', $output ); 70 } 71 72 /** 73 * Test update method. 74 * 75 * @covers WP_Widget_Text::update 76 */ 77 function test_update() { 78 $widget = new WP_Widget_Text(); 79 $instance = array( 80 'text' => '', 81 'title' => '', 82 ); 83 84 wp_set_current_user( $this->factory()->user->create( array( 85 'role' => 'author', 86 ) ) ); 87 88 // Should return valid instance. 89 $expected = array( 90 'text' => '', 91 'title' => '', 92 'filter' => 'content', 93 ); 94 $result = $widget->update( $expected, $instance ); 95 $this->assertSame( $result, $expected ); 96 97 // Back-compat with pre-4.8. 98 $this->assertTrue( ! empty( $expected['filter'] ) ); 99 100 wp_get_current_user()->add_cap( 'unfiltered_html' ); 101 $expected['text'] = '<script>alert( "Howdy!" );</script>'; 102 $result = $widget->update( $expected, $instance ); 103 $this->assertSame( $result, $expected ); 104 } 105 106 /** 107 * Test enqueue_admin_scripts method. 108 * 109 * @covers WP_Widget_Text::enqueue_admin_scripts 110 */ 111 function test_enqueue_admin_scripts() { 112 set_current_screen( 'widgets.php' ); 113 $widget = new WP_Widget_Text(); 114 $widget->enqueue_admin_scripts(); 115 116 $this->assertTrue( wp_script_is( 'text-widgets' ) ); 117 } 118 119 /** 120 * Test render_control_template_scripts method. 121 * 122 * @covers WP_Widget_Text::render_control_template_scripts 123 */ 124 function test_render_control_template_scripts() { 125 $widget = new WP_Widget_Text(); 126 127 ob_start(); 128 $widget->render_control_template_scripts(); 129 $output = ob_get_clean(); 130 131 $this->assertContains( '<script type="text/html" id="tmpl-widget-text-control-fields">', $output ); 132 } 133 }