diff --git a/packages/customize-widgets/src/components/sidebar-block-editor/sidebar-adapter.js b/packages/customize-widgets/src/components/sidebar-block-editor/sidebar-adapter.js index 5a5e06d57936be..bbc2d6fd75efed 100644 --- a/packages/customize-widgets/src/components/sidebar-block-editor/sidebar-adapter.js +++ b/packages/customize-widgets/src/components/sidebar-block-editor/sidebar-adapter.js @@ -131,6 +131,8 @@ export default class SidebarAdapter { number = widgetModel.get( 'multi_number' ); } + widget.instance.is_widget_customizer_js_value = true; + const settingId = number ? `widget_${ widget.idBase }[${ number }]` : `widget_${ widget.idBase }`; @@ -158,6 +160,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 ); } @@ -235,7 +244,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 ); diff --git a/packages/e2e-tests/plugins/widgets.php b/packages/e2e-tests/plugins/widgets.php new file mode 100644 index 00000000000000..69be2590b11a28 --- /dev/null +++ b/packages/e2e-tests/plugins/widgets.php @@ -0,0 +1,48 @@ + 'Test widget.' ) + ); + } + + 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']; + } + + public function form( $instance ) { + ?> +
+ + +
+ { 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' ); } ); @@ -510,6 +513,141 @@ describe( 'Widgets Customizer', () => { "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." ); } ); + + 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( 1000 ); + 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 expect( publishButton ).toMatchQuery( { + disabled: true, + } ); + await page.waitForResponse( createURL( '/wp-admin/admin-ajax.php' ) ); + + 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(); + } ); } ); /** @@ -562,6 +700,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 ); + const blockOption = await find( { role: 'option', name: blockName,