Skip to content

Commit

Permalink
Fix creating and replacing legacy widgets in customizer (#32005)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevin940726 authored and youknowriad committed May 31, 2021
1 parent 5777d2b commit 05a2c87
Show file tree
Hide file tree
Showing 4 changed files with 253 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ function widgetIdToSettingId( widgetId ) {
return `widget_${ idBase }`;
}

/**
* This is a custom debounce function to call different callbacks depending on
* whether it's the _leading_ call or not.
*
* @param {Function} leading The callback that gets called first.
* @param {Function} callback The callback that gets called after the first time.
* @param {number} timeout The debounced time in milliseconds.
* @return {Function} The debounced function.
*/
function debounce( leading, callback, timeout ) {
let isLeading = false;
let timerID;
Expand Down Expand Up @@ -200,6 +209,13 @@ export default class SidebarAdapter {

_removeWidget( widget ) {
const settingId = widgetIdToSettingId( widget.id );
const setting = this.api( settingId );

if ( setting ) {
const instance = setting.get();
this.widgetsCache.delete( instance );
}

this.api.remove( settingId );
}

Expand Down Expand Up @@ -277,7 +293,10 @@ export default class SidebarAdapter {
return widgetId;
} );

// TODO: We should in theory also handle delete widgets here too.
const deletedWidgets = this.getWidgets().filter(
( widget ) => ! nextWidgetIds.includes( widget.id )
);
deletedWidgets.forEach( ( widget ) => this._removeWidget( widget ) );

this.setting.set( nextWidgetIds );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ function blockToWidget( block, existingWidget = null ) {
idBase: block.attributes.idBase,
instance: {
...existingWidget?.instance,
// Required only for the customizer.
is_widget_customizer_js_value: true,
encoded_serialized_instance: encoded,
instance_hash_key: hash,
raw_instance: raw,
Expand Down
79 changes: 79 additions & 0 deletions packages/e2e-tests/plugins/class-test-widget.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php
/**
* Plugin Name: Gutenberg Test Widgets
* Plugin URI: https://github.com/WordPress/gutenberg
* Author: Gutenberg Team
*
* @package gutenberg-test-widgets
*/

/**
* Test widget to be used in e2e tests.
*/
class Test_Widget extends WP_Widget {
/**
* Sets up a new test widget instance.
*/
function __construct() {
parent::__construct(
'test_widget',
'Test Widget',
array( 'description' => 'Test widget.' )
);
}

/**
* Outputs the content for the widget instance.
*
* @param array $args Display arguments including 'before_title', 'after_title',
* 'before_widget', and 'after_widget'.
* @param array $instance Settings for the current Block widget instance.
*/
public function widget( $args, $instance ) {
$title = apply_filters( 'widget_title', $instance['title'] );
echo $args['before_widget'];
if ( ! empty( $title ) ) {
echo $args['before_title'] . $title . $args['after_title'];
}
echo 'Hello Test Widget';
echo $args['after_widget'];
}

/**
* Outputs the widget settings form.
*
* @param array $instance Current instance.
*/
public function form( $instance ) {
?>
<p>
<label for="<?php echo $this->get_field_id( 'title' ); ?>">Title:</label>
<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( $instance['title'] ); ?>" />
</p>
<?php
}

/**
* Handles updating settings for the current widget instance.
*
* @param array $new_instance New settings for this instance as input by the user via
* WP_Widget::form().
*
* @return array Settings to save or bool false to cancel saving.
* @since 4.8.1
*/
public function update( $new_instance ) {
$instance = array();
$instance['title'] = ( ! empty( $new_instance['title'] ) ) ? strip_tags( $new_instance['title'] ) : '';
return $instance;
}
}

/**
* Register the widget.
*/
function load_test_widget() {
register_widget( 'Test_Widget' );
}

add_action( 'widgets_init', 'load_test_widget' );
152 changes: 152 additions & 0 deletions packages/e2e-tests/specs/widgets/customizing-widgets.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
showBlockToolbar,
clickBlockToolbarButton,
deleteAllWidgets,
createURL,
} from '@wordpress/e2e-test-utils';

/**
Expand Down Expand Up @@ -43,12 +44,14 @@ describe( 'Widgets Customizer', () => {
await deactivatePlugin(
'gutenberg-test-plugin-disables-the-css-animations'
);
await activatePlugin( 'gutenberg-test-widgets' );
} );

afterAll( async () => {
await activatePlugin(
'gutenberg-test-plugin-disables-the-css-animations'
);
await deactivatePlugin( 'gutenberg-test-widgets' );
await activateTheme( 'twentytwentyone' );
} );

Expand Down Expand Up @@ -512,6 +515,141 @@ describe( 'Widgets Customizer', () => {
);
} );

it( 'should handle legacy widgets', async () => {
const widgetsPanel = await find( {
role: 'heading',
name: /Widgets/,
level: 3,
} );
await widgetsPanel.click();

const footer1Section = await find( {
role: 'heading',
name: /^Footer #1/,
level: 3,
} );
await footer1Section.click();

const legacyWidgetBlock = await addBlock( 'Legacy Widget' );
const selectLegacyWidgets = await find( {
role: 'combobox',
name: 'Select a legacy widget to display:',
} );
await selectLegacyWidgets.select( 'test_widget' );

await expect( {
role: 'heading',
name: 'Test Widget',
level: 3,
} ).toBeFound( { root: legacyWidgetBlock } );

let titleInput = await find(
{
role: 'textbox',
name: 'Title:',
},
{
root: legacyWidgetBlock,
}
);

await titleInput.type( 'Hello Title' );

// Unfocus the current legacy widget.
await page.keyboard.press( 'Tab' );

// Disable reason: Sometimes the preview just doesn't fully load,
// it's the only way I know for now to ensure that the iframe is ready.
// eslint-disable-next-line no-restricted-syntax
await page.waitForTimeout( 2000 );
await waitForPreviewIframe();

// Expect the legacy widget to show in the site preview frame.
await expect( {
role: 'heading',
name: 'Hello Title',
} ).toBeFound( {
root: await find( {
name: 'Site Preview',
selector: 'iframe',
} ),
} );

// Expect the preview in block to show when unfocusing the legacy widget block.
await expect( {
role: 'heading',
name: 'Hello Title',
} ).toBeFound( {
root: await find( {
selector: 'iframe',
name: 'Legacy Widget Preview',
} ),
} );

await legacyWidgetBlock.focus();
await showBlockToolbar();

// Testing removing the block.
await clickBlockToolbarButton( 'Options' );
const removeBlockButton = await find( {
role: 'menuitem',
name: /Remove block/,
} );
await removeBlockButton.click();

// Add it back again using the variant.
const testWidgetBlock = await addBlock( 'Test Widget' );

titleInput = await find(
{
role: 'textbox',
name: 'Title:',
},
{
root: testWidgetBlock,
}
);

await titleInput.type( 'Hello again!' );
// Unfocus the current legacy widget.
await page.keyboard.press( 'Tab' );

// Expect the preview in block to show when unfocusing the legacy widget block.
await expect( {
role: 'heading',
name: 'Hello again!',
} ).toBeFound( {
root: await find( {
selector: 'iframe',
name: 'Legacy Widget Preview',
} ),
} );

const publishButton = await find( {
role: 'button',
name: 'Publish',
} );
await publishButton.click();

// Wait for publishing to finish.
await page.waitForResponse( createURL( '/wp-admin/admin-ajax.php' ) );
await expect( publishButton ).toMatchQuery( {
disabled: true,
} );

expect( console ).toHaveWarned(
"The page delivered both an 'X-Frame-Options' header and a 'Content-Security-Policy' header with a 'frame-ancestors' directive. Although the 'X-Frame-Options' header alone would have blocked embedding, it has been ignored."
);

await page.goto( createURL( '/' ) );

// Expect the saved widgets to show on frontend.
await expect( {
role: 'heading',
name: 'Hello again!',
} ).toBeFound();
} );

it( 'should handle esc key events', async () => {
const widgetsPanel = await find( {
role: 'heading',
Expand Down Expand Up @@ -592,6 +730,20 @@ async function addBlock( blockName ) {
);
await addBlockButton.click();

const searchBox = await find( {
role: 'searchbox',
name: 'Search for blocks and patterns',
} );

// Clear the input.
await searchBox.evaluate( ( node ) => {
if ( node.value ) {
node.value = '';
}
} );

await searchBox.type( blockName );

// TODO - remove this timeout when the test plugin for disabling CSS
// animations in tests works properly.
//
Expand Down

0 comments on commit 05a2c87

Please sign in to comment.