From 17b92837f86e1a2a11ff1dbc4d64c597ec4494af Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Sun, 5 May 2019 18:35:59 -0400 Subject: [PATCH 01/17] automatic update to readme for @wordpress/data --- packages/data/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/data/README.md b/packages/data/README.md index 0e117eca77d876..8b92cd6c10145c 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -421,6 +421,10 @@ const App = ( { props } ) => { } ``` +# **RegistryContext** + +Undocumented declaration. + # **RegistryProvider** A custom Context provider for exposing the provided `registry` to children From 4d7a9c2df0f6f573a8930ab21986e21d1183b4eb Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Sun, 5 May 2019 18:36:25 -0400 Subject: [PATCH 02/17] no idea why this is getting changes --- packages/core-data/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-data/README.md b/packages/core-data/README.md index a2fb271017fcd7..8b760483d950b3 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -175,6 +175,7 @@ _Returns_ - `Object`: Updated record. + ## Selectors @@ -419,7 +420,6 @@ _Returns_ - `boolean`: Whether a request is in progress for an embed preview. -

Code is Poetry.

From ee2f8aaea47c084e525c8bce4927d896264a831b Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Sun, 5 May 2019 18:39:41 -0400 Subject: [PATCH 03/17] add new EditorInitialization data component (and tests) --- .../components/editor-initialization/index.js | 48 ++++++ .../editor-initialization/listeners.js | 87 ++++++++++ .../editor-initialization/test/listeners.js | 159 ++++++++++++++++++ .../components/editor-initialization/utils.js | 23 +++ 4 files changed, 317 insertions(+) create mode 100644 packages/edit-post/src/components/editor-initialization/index.js create mode 100644 packages/edit-post/src/components/editor-initialization/listeners.js create mode 100644 packages/edit-post/src/components/editor-initialization/test/listeners.js create mode 100644 packages/edit-post/src/components/editor-initialization/utils.js diff --git a/packages/edit-post/src/components/editor-initialization/index.js b/packages/edit-post/src/components/editor-initialization/index.js new file mode 100644 index 00000000000000..acecf2da594c1f --- /dev/null +++ b/packages/edit-post/src/components/editor-initialization/index.js @@ -0,0 +1,48 @@ +/** + * WordPress dependencies + */ +import { useEffect, useContext } from '@wordpress/element'; +import { RegistryContext } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { + blockSelectionListener, + adjustSidebarListener, + viewPostLinkUpdateListener, +} from './listeners'; + +/** + * Data component used for initializing the editor and re-initializes + * when postId changes or unmount. + * + * @param {number} postId + * @return {null} This is a data component so does not render any ui. + */ +export default function( { postId } ) { + const registry = useContext( RegistryContext ); + useEffect( () => { + const blockSelectionUnsubscribe = registry.subscribe( + blockSelectionListener( registry ) + ); + const adjustSidebarUnsubscribe = registry.subscribe( + adjustSidebarListener( registry ) + ); + const viewPostLinkUpdateUnsubscribe = registry.subscribe( + viewPostLinkUpdateListener( registry ) + ); + registry.dispatch( 'core/nux' ).triggerGuide( [ + 'core/editor.inserter', + 'core/editor.settings', + 'core/editor.preview', + 'core/editor.publish', + ] ); + return () => { + blockSelectionUnsubscribe(); + adjustSidebarUnsubscribe(); + viewPostLinkUpdateUnsubscribe(); + }; + }, [ registry, postId ] ); + return null; +} diff --git a/packages/edit-post/src/components/editor-initialization/listeners.js b/packages/edit-post/src/components/editor-initialization/listeners.js new file mode 100644 index 00000000000000..ab3a428c34bb53 --- /dev/null +++ b/packages/edit-post/src/components/editor-initialization/listeners.js @@ -0,0 +1,87 @@ +/** + * Internal dependencies + */ +import { STORE_KEY, VIEW_AS_LINK_SELECTOR } from '../../store/constants'; +import { onChangeListener } from './utils'; + +/** + * This listener is used to handle block selection triggering the appropriate + * sidebar state. + * + * @param {Object} registry An instance of the store registry. + * + * @return {Function} A function to pass in as a wp.data.subscribe callback. + */ +export const blockSelectionListener = ( registry ) => onChangeListener( + () => !! registry.select( 'core/block-editor' ) + .getBlockSelectionStart(), + ( hasBlockSelection ) => { + if ( ! registry.select( 'core/edit-post' ).isEditorSidebarOpened() ) { + return; + } + if ( hasBlockSelection ) { + registry.dispatch( STORE_KEY ) + .openGeneralSidebar( 'edit-post/block' ); + } else { + registry.dispatch( STORE_KEY ) + .openGeneralSidebar( 'edit-post/document' ); + } + } +); + +/** + * This listener is used to handle adjusting the sidebar according to viewport + * viewport size. + * + * @param {Object} registry An instance of the store registry. + * + * @return {Function} A function to pass in as a wp.data.subscribe callback. + */ +export const adjustSidebarListener = ( registry ) => onChangeListener( + () => registry.select( 'core/viewport' ) + .isViewportMatch( '< medium' ), + ( () => { + let sidebarToReOpenOnExpand = null; + return ( isSmall ) => { + const { getActiveGeneralSidebarName } = registry.select( STORE_KEY ); + const { + closeGeneralSidebar: closeSidebar, + openGeneralSidebar: openSidebar, + } = registry.dispatch( STORE_KEY ); + if ( isSmall ) { + sidebarToReOpenOnExpand = getActiveGeneralSidebarName(); + if ( sidebarToReOpenOnExpand ) { + closeSidebar(); + } + } else if ( + sidebarToReOpenOnExpand && + ! getActiveGeneralSidebarName() + ) { + openSidebar( sidebarToReOpenOnExpand ); + } + }; + } )(), + true +); + +/** + * This listener is used to handle updating the "view as" link selector when the + * post link is changed. + * + * @param {Object} registry An instance of the store registry + * + * @return {Function} A function to pass in as a wp.data.subscribe callback + */ +export const viewPostLinkUpdateListener = ( registry ) => onChangeListener( + () => registry.select( 'core/editor' ).getCurrentPost().link, + ( newPermalink ) => { + if ( ! newPermalink ) { + return; + } + const nodeToUpdate = document.querySelector( VIEW_AS_LINK_SELECTOR ); + if ( ! nodeToUpdate ) { + return; + } + nodeToUpdate.setAttribute( 'href', newPermalink ); + } +); diff --git a/packages/edit-post/src/components/editor-initialization/test/listeners.js b/packages/edit-post/src/components/editor-initialization/test/listeners.js new file mode 100644 index 00000000000000..e3122942f7c3a6 --- /dev/null +++ b/packages/edit-post/src/components/editor-initialization/test/listeners.js @@ -0,0 +1,159 @@ +/** + * Internal dependencies + */ +import { + blockSelectionListener, + adjustSidebarListener, + viewPostLinkUpdateListener, +} from '../listeners'; +import { STORE_KEY, VIEW_AS_LINK_SELECTOR } from '../../../store/constants'; + +const registryMock = { select: {}, dispatch: {} }; + +describe( 'blockSelectionListener', () => { + const isEditorSidebarOpened = jest.fn(); + const getBlockSelectionStart = jest.fn(); + const openSidebar = jest.fn(); + beforeEach( () => { + registryMock.select = ( store ) => { + const stores = { + 'core/block-editor': { getBlockSelectionStart }, + 'core/edit-post': { isEditorSidebarOpened }, + }; + return stores[ store ]; + }; + registryMock.dispatch = () => ( { openGeneralSidebar: openSidebar } ); + } ); + afterEach( () => { + isEditorSidebarOpened.mockClear(); + getBlockSelectionStart.mockClear(); + openSidebar.mockClear(); + } ); + it( 'does nothing if sidebar is not opened', () => { + getBlockSelectionStart.mockReturnValue( true ); + isEditorSidebarOpened.mockReturnValue( false ); + const listener = blockSelectionListener( registryMock ); + getBlockSelectionStart.mockReturnValue( false ); + listener(); + expect( getBlockSelectionStart ).toHaveBeenCalled(); + expect( isEditorSidebarOpened ).toHaveBeenCalled(); + expect( openSidebar ).not.toHaveBeenCalled(); + } ); + it( 'opens block sidebar if block is selected', () => { + isEditorSidebarOpened.mockReturnValue( true ); + getBlockSelectionStart.mockReturnValue( false ); + const listener = blockSelectionListener( registryMock ); + getBlockSelectionStart.mockReturnValue( true ); + listener(); + expect( openSidebar ).toHaveBeenCalledWith( 'edit-post/block' ); + } ); + it( 'opens document sidebar if block is not selected', () => { + isEditorSidebarOpened.mockReturnValue( true ); + getBlockSelectionStart.mockReturnValue( true ); + const listener = blockSelectionListener( registryMock ); + getBlockSelectionStart.mockReturnValue( false ); + listener(); + expect( openSidebar ).toHaveBeenCalledWith( 'edit-post/document' ); + } ); +} ); +describe( 'adjustSidebarListener', () => { + const isViewportMatch = jest.fn(); + const getActiveGeneralSidebarName = jest.fn(); + const willCloseGeneralSidebar = jest.fn(); + const willOpenGeneralSidebar = jest.fn(); + beforeEach( () => { + registryMock.select = ( store ) => { + const stores = { + 'core/viewport': { isViewportMatch }, + [ STORE_KEY ]: { getActiveGeneralSidebarName }, + }; + return stores[ store ]; + }; + registryMock.dispatch = ( store ) => { + const stores = { + [ STORE_KEY ]: { + closeGeneralSidebar: willCloseGeneralSidebar, + openGeneralSidebar: willOpenGeneralSidebar, + }, + }; + return stores[ store ]; + }; + registryMock.subscribe = jest.fn(); + isViewportMatch.mockReturnValue( true ); + } ); + afterEach( () => { + isViewportMatch.mockClear(); + getActiveGeneralSidebarName.mockClear(); + willCloseGeneralSidebar.mockClear(); + willOpenGeneralSidebar.mockClear(); + } ); + it( 'initializes and does nothing when viewport is not small', () => { + isViewportMatch.mockReturnValue( false ); + adjustSidebarListener( registryMock )(); + expect( isViewportMatch ).toHaveBeenCalled(); + expect( getActiveGeneralSidebarName ).not.toHaveBeenCalled(); + } ); + it( 'does not close sidebar if viewport is small and there is no ' + + 'active sidebar name available', () => { + getActiveGeneralSidebarName.mockReturnValue( false ); + adjustSidebarListener( registryMock )(); + expect( willCloseGeneralSidebar ).not.toHaveBeenCalled(); + expect( willOpenGeneralSidebar ).not.toHaveBeenCalled(); + } ); + it( 'closes sidebar if viewport is small and there is an active ' + + 'sidebar name available', () => { + getActiveGeneralSidebarName.mockReturnValue( 'someSidebar' ); + adjustSidebarListener( registryMock )(); + expect( willCloseGeneralSidebar ).toHaveBeenCalled(); + expect( willOpenGeneralSidebar ).not.toHaveBeenCalled(); + } ); + it( 'opens sidebar if viewport is not small, there is a cached sidebar to ' + + 'reopen on expand, and there is no current sidebar name available', () => { + getActiveGeneralSidebarName.mockReturnValue( 'someSidebar' ); + const listener = adjustSidebarListener( registryMock ); + listener(); + isViewportMatch.mockReturnValue( false ); + getActiveGeneralSidebarName.mockReturnValue( false ); + listener(); + expect( willCloseGeneralSidebar ).toHaveBeenCalledTimes( 1 ); + expect( willOpenGeneralSidebar ).toHaveBeenCalledTimes( 1 ); + } ); +} ); +describe( 'viewPostLinkUpdateListener', () => { + const getCurrentPost = jest.fn(); + const setAttribute = jest.fn(); + beforeEach( () => { + document.querySelector = jest.fn().mockReturnValue( { setAttribute } ); + getCurrentPost.mockReturnValue( { link: 'foo' } ); + registryMock.select = ( store ) => { + const stores = { 'core/editor': { getCurrentPost } }; + return stores[ store ]; + }; + } ); + afterEach( () => { + setAttribute.mockClear(); + getCurrentPost.mockClear(); + } ); + it( 'updates nothing if there is no new permalink', () => { + const listener = viewPostLinkUpdateListener( registryMock ); + listener(); + expect( getCurrentPost ).toHaveBeenCalledTimes( 2 ); + expect( document.querySelector ).not.toHaveBeenCalled(); + expect( setAttribute ).not.toHaveBeenCalled(); + } ); + it( 'does not do anything if the node is not found', () => { + const listener = viewPostLinkUpdateListener( registryMock ); + getCurrentPost.mockReturnValue( { link: 'bar' } ); + document.querySelector.mockReturnValue( false ); + listener(); + expect( document.querySelector ) + .toHaveBeenCalledWith( VIEW_AS_LINK_SELECTOR ); + expect( setAttribute ).not.toHaveBeenCalled(); + } ); + it( 'updates with the new permalink when node is found', () => { + const listener = viewPostLinkUpdateListener( registryMock ); + getCurrentPost.mockReturnValue( { link: 'bar' } ); + listener(); + expect( setAttribute ).toHaveBeenCalledWith( 'href', 'bar' ); + } ); +} ); diff --git a/packages/edit-post/src/components/editor-initialization/utils.js b/packages/edit-post/src/components/editor-initialization/utils.js new file mode 100644 index 00000000000000..7dfd1ea08ed892 --- /dev/null +++ b/packages/edit-post/src/components/editor-initialization/utils.js @@ -0,0 +1,23 @@ +/** + * Given a selector returns a functions that returns the listener only + * if the returned value from the selector changes. + * + * @param {function} selector Selector. + * @param {function} listener Listener. + * @param {boolean} initial Flags whether listener should be invoked on + * initial call. + * @return {function} Listener creator. + */ +export const onChangeListener = ( selector, listener, initial = false ) => { + let previousValue = selector(); + if ( initial ) { + listener( selector() ); + } + return () => { + const selectedValue = selector(); + if ( selectedValue !== previousValue ) { + previousValue = selectedValue; + listener( selectedValue ); + } + }; +}; From 8f81af4167953d47857b52da3e5e8c4d77d9544b Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Sun, 5 May 2019 18:40:53 -0400 Subject: [PATCH 04/17] implement new component in the Editor setup --- packages/edit-post/src/editor.js | 20 +++++++++++--------- packages/edit-post/src/index.js | 9 --------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index 88eb7c4a6113fd..eb2b36ed685423 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -21,6 +21,7 @@ import { */ import preventEventDiscovery from './prevent-event-discovery'; import Layout from './components/layout'; +import EditorInitialization from './components/editor-initialization'; class Editor extends Component { constructor() { @@ -93,15 +94,16 @@ class Editor extends Component { - - - + + + + diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index cf8070c8c7b4e4..66d8c753b46ad4 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -9,7 +9,6 @@ import '@wordpress/viewport'; import '@wordpress/notices'; import { registerCoreBlocks } from '@wordpress/block-library'; import { render, unmountComponentAtNode } from '@wordpress/element'; -import { dispatch } from '@wordpress/data'; /** * Internal dependencies @@ -76,14 +75,6 @@ export function initializeEditor( id, postType, postId, settings, initialEdits ) console.warn( "Your browser is using Quirks Mode. \nThis can cause rendering issues such as blocks overlaying meta boxes in the editor. Quirks Mode can be triggered by PHP errors or HTML code appearing before the opening . Try checking the raw page source or your site's PHP error log and resolving errors there, removing any HTML before the doctype, or disabling plugins." ); } - dispatch( 'core/edit-post' ).__unstableInitialize(); - dispatch( 'core/nux' ).triggerGuide( [ - 'core/editor.inserter', - 'core/editor.settings', - 'core/editor.preview', - 'core/editor.publish', - ] ); - render( Date: Sun, 5 May 2019 18:42:24 -0400 Subject: [PATCH 05/17] remove obsolete code replaced by new component --- packages/edit-post/CHANGELOG.md | 1 + packages/edit-post/src/store/actions.js | 79 -------- packages/edit-post/src/store/controls.js | 16 -- packages/edit-post/src/store/test/actions.js | 178 ------------------- packages/edit-post/src/store/utils.js | 23 --- 5 files changed, 1 insertion(+), 296 deletions(-) delete mode 100644 packages/edit-post/src/store/utils.js diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index 55d3affac68770..22dbdcd8d93805 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -7,6 +7,7 @@ ### Refactor - convert `INIT` effect to controls & actions [#14740](https://github.com/WordPress/gutenberg/pull/14740) +- Create EditorInitializor component and implement for various things to initialize as the editor is loaded. This replaces the `__unstableInitialize` refactor done in #14740. ## 3.2.0 (2019-03-06) diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index e3538cccddc64f..d771dc840d8049 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -3,13 +3,6 @@ */ import { castArray } from 'lodash'; -/** - * Internal dependencies - */ -import { __unstableSubscribe } from './controls'; -import { onChangeListener } from './utils'; -import { STORE_KEY, VIEW_AS_LINK_SELECTOR } from './constants'; - /** * Returns an action object used in signalling that the user opened an editor sidebar. * @@ -238,75 +231,3 @@ export function metaBoxUpdatesSuccess() { type: 'META_BOX_UPDATES_SUCCESS', }; } - -/** - * Returns an action generator used to initialize some subscriptions for the - * post editor: - * - * - subscription for toggling the `edit-post/block` general sidebar when a - * block is selected. - * - subscription for hiding/showing the sidebar depending on size of viewport. - * - subscription for updating the "View Post" link in the admin bar when - * permalink is updated. - */ -export function* __unstableInitialize() { - // Select the block settings tab when the selected block changes - yield __unstableSubscribe( ( registry ) => onChangeListener( - () => !! registry.select( 'core/block-editor' ) - .getBlockSelectionStart(), - ( hasBlockSelection ) => { - if ( ! registry.select( 'core/edit-post' ).isEditorSidebarOpened() ) { - return; - } - if ( hasBlockSelection ) { - registry.dispatch( STORE_KEY ) - .openGeneralSidebar( 'edit-post/block' ); - } else { - registry.dispatch( STORE_KEY ) - .openGeneralSidebar( 'edit-post/document' ); - } - } - ) ); - // hide/show the sidebar depending on size of viewport. - yield __unstableSubscribe( ( registry ) => onChangeListener( - () => registry.select( 'core/viewport' ) - .isViewportMatch( '< medium' ), - ( () => { - let sidebarToReOpenOnExpand = null; - return ( isSmall ) => { - const { getActiveGeneralSidebarName } = registry.select( STORE_KEY ); - const { - closeGeneralSidebar: closeSidebar, - openGeneralSidebar: openSidebar, - } = registry.dispatch( STORE_KEY ); - if ( isSmall ) { - sidebarToReOpenOnExpand = getActiveGeneralSidebarName(); - if ( sidebarToReOpenOnExpand ) { - closeSidebar(); - } - } else if ( - sidebarToReOpenOnExpand && - ! getActiveGeneralSidebarName() - ) { - openSidebar( sidebarToReOpenOnExpand ); - } - }; - } )(), - true - ) ); - // Update View Post link in the admin bar when permalink is updated. - yield __unstableSubscribe( ( registry ) => onChangeListener( - () => registry.select( 'core/editor' ).getCurrentPost().link, - ( newPermalink ) => { - if ( ! newPermalink ) { - return; - } - const nodeToUpdate = document.querySelector( VIEW_AS_LINK_SELECTOR ); - if ( ! nodeToUpdate ) { - return; - } - nodeToUpdate.setAttribute( 'href', newPermalink ); - } - ) ); -} - diff --git a/packages/edit-post/src/store/controls.js b/packages/edit-post/src/store/controls.js index 462741a1734075..3a64e045f69666 100644 --- a/packages/edit-post/src/store/controls.js +++ b/packages/edit-post/src/store/controls.js @@ -21,28 +21,12 @@ export function select( storeName, selectorName, ...args ) { }; } -/** - * Calls a subscriber using the current state. - * - * @param {function} listenerCallback A callback for the subscriber that - * receives the registry. - * @return {Object} control descriptor. - */ -export function __unstableSubscribe( listenerCallback ) { - return { type: 'SUBSCRIBE', listenerCallback }; -} - const controls = { SELECT: createRegistryControl( ( registry ) => ( { storeName, selectorName, args } ) => { return registry.select( storeName )[ selectorName ]( ...args ); } ), - SUBSCRIBE: createRegistryControl( - ( registry ) => ( { listenerCallback } ) => { - return registry.subscribe( listenerCallback( registry ) ); - } - ), }; export default controls; diff --git a/packages/edit-post/src/store/test/actions.js b/packages/edit-post/src/store/test/actions.js index a16e58f9cc18d8..07dc4b81ece682 100644 --- a/packages/edit-post/src/store/test/actions.js +++ b/packages/edit-post/src/store/test/actions.js @@ -15,9 +15,7 @@ import { toggleFeature, togglePinnedPluginItem, requestMetaBoxUpdates, - __unstableInitialize, } from '../actions'; -import { STORE_KEY, VIEW_AS_LINK_SELECTOR } from '../constants'; describe( 'actions', () => { describe( 'openGeneralSidebar', () => { @@ -135,180 +133,4 @@ describe( 'actions', () => { } ); } ); } ); - - describe( '__unstableInitialize', () => { - let fulfillment; - const reset = () => fulfillment = __unstableInitialize(); - const registryMock = { select: {}, dispatch: {} }; - describe( 'yields subscribe control descriptor for block settings', () => { - reset(); - const { value } = fulfillment.next(); - const listenerCallback = value.listenerCallback; - const isEditorSidebarOpened = jest.fn(); - const getBlockSelectionStart = jest.fn(); - const openSidebar = jest.fn(); - beforeEach( () => { - registryMock.select = ( store ) => { - const stores = { - 'core/block-editor': { getBlockSelectionStart }, - 'core/edit-post': { isEditorSidebarOpened }, - }; - return stores[ store ]; - }; - registryMock.dispatch = () => ( { openGeneralSidebar: openSidebar } ); - } ); - afterEach( () => { - isEditorSidebarOpened.mockClear(); - getBlockSelectionStart.mockClear(); - openSidebar.mockClear(); - } ); - it( 'returns subscribe control descriptor', () => { - expect( value.type ).toBe( 'SUBSCRIBE' ); - } ); - it( 'does nothing if sidebar is not opened', () => { - getBlockSelectionStart.mockReturnValue( true ); - isEditorSidebarOpened.mockReturnValue( false ); - const listener = listenerCallback( registryMock ); - getBlockSelectionStart.mockReturnValue( false ); - listener(); - expect( getBlockSelectionStart ).toHaveBeenCalled(); - expect( isEditorSidebarOpened ).toHaveBeenCalled(); - expect( openSidebar ).not.toHaveBeenCalled(); - } ); - it( 'opens block sidebar if block is selected', () => { - isEditorSidebarOpened.mockReturnValue( true ); - getBlockSelectionStart.mockReturnValue( false ); - const listener = listenerCallback( registryMock ); - getBlockSelectionStart.mockReturnValue( true ); - listener(); - expect( openSidebar ).toHaveBeenCalledWith( 'edit-post/block' ); - } ); - it( 'opens document sidebar if block is not selected', () => { - isEditorSidebarOpened.mockReturnValue( true ); - getBlockSelectionStart.mockReturnValue( true ); - const listener = listenerCallback( registryMock ); - getBlockSelectionStart.mockReturnValue( false ); - listener(); - expect( openSidebar ).toHaveBeenCalledWith( 'edit-post/document' ); - } ); - } ); - describe( 'yields subscribe control descriptor for adjusting the ' + - 'sidebar', () => { - reset(); - fulfillment.next(); - const { value } = fulfillment.next(); - const listenerCallback = value.listenerCallback; - const isViewportMatch = jest.fn(); - const getActiveGeneralSidebarName = jest.fn(); - const willCloseGeneralSidebar = jest.fn(); - const willOpenGeneralSidebar = jest.fn(); - beforeEach( () => { - registryMock.select = ( store ) => { - const stores = { - 'core/viewport': { isViewportMatch }, - [ STORE_KEY ]: { getActiveGeneralSidebarName }, - }; - return stores[ store ]; - }; - registryMock.dispatch = ( store ) => { - const stores = { - [ STORE_KEY ]: { - closeGeneralSidebar: willCloseGeneralSidebar, - openGeneralSidebar: willOpenGeneralSidebar, - }, - }; - return stores[ store ]; - }; - registryMock.subscribe = jest.fn(); - isViewportMatch.mockReturnValue( true ); - } ); - afterEach( () => { - isViewportMatch.mockClear(); - getActiveGeneralSidebarName.mockClear(); - willCloseGeneralSidebar.mockClear(); - willOpenGeneralSidebar.mockClear(); - } ); - it( 'returns subscribe control descriptor', () => { - expect( value.type ).toBe( 'SUBSCRIBE' ); - } ); - it( 'initializes and does nothing when viewport is not small', () => { - isViewportMatch.mockReturnValue( false ); - listenerCallback( registryMock )(); - expect( isViewportMatch ).toHaveBeenCalled(); - expect( getActiveGeneralSidebarName ).not.toHaveBeenCalled(); - } ); - it( 'does not close sidebar if viewport is small and there is no ' + - 'active sidebar name available', () => { - getActiveGeneralSidebarName.mockReturnValue( false ); - listenerCallback( registryMock )(); - expect( willCloseGeneralSidebar ).not.toHaveBeenCalled(); - expect( willOpenGeneralSidebar ).not.toHaveBeenCalled(); - } ); - it( 'closes sidebar if viewport is small and there is an active ' + - 'sidebar name available', () => { - getActiveGeneralSidebarName.mockReturnValue( 'someSidebar' ); - listenerCallback( registryMock )(); - expect( willCloseGeneralSidebar ).toHaveBeenCalled(); - expect( willOpenGeneralSidebar ).not.toHaveBeenCalled(); - } ); - it( 'opens sidebar if viewport is not small, there is a cached sidebar to ' + - 'reopen on expand, and there is no current sidebar name available', () => { - getActiveGeneralSidebarName.mockReturnValue( 'someSidebar' ); - const listener = listenerCallback( registryMock ); - listener(); - isViewportMatch.mockReturnValue( false ); - getActiveGeneralSidebarName.mockReturnValue( false ); - listener(); - expect( willCloseGeneralSidebar ).toHaveBeenCalledTimes( 1 ); - expect( willOpenGeneralSidebar ).toHaveBeenCalledTimes( 1 ); - } ); - } ); - describe( 'yields subscribe control descriptor for updating the ' + - 'view post link when the permalink changes', () => { - reset(); - fulfillment.next(); - fulfillment.next(); - const { value } = fulfillment.next(); - const listenerCallback = value.listenerCallback; - const getCurrentPost = jest.fn(); - const setAttribute = jest.fn(); - beforeEach( () => { - document.querySelector = jest.fn().mockReturnValue( { setAttribute } ); - getCurrentPost.mockReturnValue( { link: 'foo' } ); - registryMock.select = ( store ) => { - const stores = { 'core/editor': { getCurrentPost } }; - return stores[ store ]; - }; - } ); - afterEach( () => { - setAttribute.mockClear(); - getCurrentPost.mockClear(); - } ); - it( 'returns expected control descriptor', () => { - expect( value.type ).toBe( 'SUBSCRIBE' ); - } ); - it( 'updates nothing if there is no new permalink', () => { - const listener = listenerCallback( registryMock ); - listener(); - expect( getCurrentPost ).toHaveBeenCalledTimes( 2 ); - expect( document.querySelector ).not.toHaveBeenCalled(); - expect( setAttribute ).not.toHaveBeenCalled(); - } ); - it( 'does not do anything if the node is not found', () => { - const listener = listenerCallback( registryMock ); - getCurrentPost.mockReturnValue( { link: 'bar' } ); - document.querySelector.mockReturnValue( false ); - listener(); - expect( document.querySelector ) - .toHaveBeenCalledWith( VIEW_AS_LINK_SELECTOR ); - expect( setAttribute ).not.toHaveBeenCalled(); - } ); - it( 'updates with the new permalink when node is found', () => { - const listener = listenerCallback( registryMock ); - getCurrentPost.mockReturnValue( { link: 'bar' } ); - listener(); - expect( setAttribute ).toHaveBeenCalledWith( 'href', 'bar' ); - } ); - } ); - } ); } ); diff --git a/packages/edit-post/src/store/utils.js b/packages/edit-post/src/store/utils.js deleted file mode 100644 index 7dfd1ea08ed892..00000000000000 --- a/packages/edit-post/src/store/utils.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Given a selector returns a functions that returns the listener only - * if the returned value from the selector changes. - * - * @param {function} selector Selector. - * @param {function} listener Listener. - * @param {boolean} initial Flags whether listener should be invoked on - * initial call. - * @return {function} Listener creator. - */ -export const onChangeListener = ( selector, listener, initial = false ) => { - let previousValue = selector(); - if ( initial ) { - listener( selector() ); - } - return () => { - const selectedValue = selector(); - if ( selectedValue !== previousValue ) { - previousValue = selectedValue; - listener( selectedValue ); - } - }; -}; From 03acbf3c88bd9e6d034c6a0a3aa28fe55f11733f Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Sun, 5 May 2019 18:42:47 -0400 Subject: [PATCH 06/17] dunno why this keeps reporting changes --- packages/core-data/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-data/README.md b/packages/core-data/README.md index 8b760483d950b3..a2fb271017fcd7 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -175,7 +175,6 @@ _Returns_ - `Object`: Updated record. - ## Selectors @@ -420,6 +419,7 @@ _Returns_ - `boolean`: Whether a request is in progress for an embed preview. +

Code is Poetry.

From 84ab1f4a9c273b6dbbc9bfcd824b9b6c1ad06362 Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Sun, 5 May 2019 19:09:02 -0400 Subject: [PATCH 07/17] use the postId prop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit important on the offchance the post is not retrieved (prevents undefined errors in the case `post.id` doesn’t exist). --- packages/edit-post/src/editor.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index eb2b36ed685423..d6a1cdc5a80823 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -71,6 +71,7 @@ class Editor extends Component { hasFixedToolbar, focusMode, post, + postId, initialEdits, onError, hiddenBlockTypes, @@ -102,7 +103,7 @@ class Editor extends Component { { ...props } > - + From 973f7f83202231378c5e6adf587ac2fc876f1b41 Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Sun, 5 May 2019 19:46:03 -0400 Subject: [PATCH 08/17] update changelog with link to pull. --- packages/edit-post/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index 22dbdcd8d93805..5940a222ad9884 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -7,7 +7,7 @@ ### Refactor - convert `INIT` effect to controls & actions [#14740](https://github.com/WordPress/gutenberg/pull/14740) -- Create EditorInitializor component and implement for various things to initialize as the editor is loaded. This replaces the `__unstableInitialize` refactor done in #14740. +- Create EditorInitializer component and implement for various things to initialize as the editor is loaded. This replaces the `__unstableInitialize` refactor done in #14740. ([#15444](https://github.com/WordPress/gutenberg/pull/15444)) ## 3.2.0 (2019-03-06) From 4a602b801049ae2afd6cb3e68c7d42ba89db2e16 Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Tue, 11 Jun 2019 19:32:38 -0400 Subject: [PATCH 09/17] add constant for preview link selector --- packages/edit-post/src/store/constants.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/edit-post/src/store/constants.js b/packages/edit-post/src/store/constants.js index 60f80d914a5c7b..35acac0c5633a8 100644 --- a/packages/edit-post/src/store/constants.js +++ b/packages/edit-post/src/store/constants.js @@ -9,3 +9,9 @@ export const STORE_KEY = 'core/edit-post'; * @type {string} */ export const VIEW_AS_LINK_SELECTOR = '#wp-admin-bar-view a'; + +/** + * CSS selector string for the admin bar preview post link anchor tag. + * @type {string} + */ +export const VIEW_AS_PREVIEW_LINK_SELECTOR = '#wp-admin-bar-preview a'; From 301e67524e65db7f4d22552a95669db1cd402397 Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Tue, 11 Jun 2019 19:50:38 -0400 Subject: [PATCH 10/17] add hooks for editor intitialization actions and implement in special initiatlization component --- packages/data/README.md | 4 - .../components/editor-initialization/index.js | 40 +-- .../editor-initialization/listener-hooks.js | 105 ++++++++ .../editor-initialization/listeners.js | 87 ------ .../test/listener-hooks.js | 255 ++++++++++++++++++ 5 files changed, 367 insertions(+), 124 deletions(-) create mode 100644 packages/edit-post/src/components/editor-initialization/listener-hooks.js delete mode 100644 packages/edit-post/src/components/editor-initialization/listeners.js create mode 100644 packages/edit-post/src/components/editor-initialization/test/listener-hooks.js diff --git a/packages/data/README.md b/packages/data/README.md index 8b92cd6c10145c..0e117eca77d876 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -421,10 +421,6 @@ const App = ( { props } ) => { } ``` -# **RegistryContext** - -Undocumented declaration. - # **RegistryProvider** A custom Context provider for exposing the provided `registry` to children diff --git a/packages/edit-post/src/components/editor-initialization/index.js b/packages/edit-post/src/components/editor-initialization/index.js index acecf2da594c1f..dc0b7418ad9846 100644 --- a/packages/edit-post/src/components/editor-initialization/index.js +++ b/packages/edit-post/src/components/editor-initialization/index.js @@ -1,17 +1,11 @@ -/** - * WordPress dependencies - */ -import { useEffect, useContext } from '@wordpress/element'; -import { RegistryContext } from '@wordpress/data'; - /** * Internal dependencies */ import { - blockSelectionListener, - adjustSidebarListener, - viewPostLinkUpdateListener, -} from './listeners'; + useAdjustSidebarListener, + useBlockSelectionListener, + useUpdatePostLinkListener, +} from './listener-hooks'; /** * Data component used for initializing the editor and re-initializes @@ -21,28 +15,8 @@ import { * @return {null} This is a data component so does not render any ui. */ export default function( { postId } ) { - const registry = useContext( RegistryContext ); - useEffect( () => { - const blockSelectionUnsubscribe = registry.subscribe( - blockSelectionListener( registry ) - ); - const adjustSidebarUnsubscribe = registry.subscribe( - adjustSidebarListener( registry ) - ); - const viewPostLinkUpdateUnsubscribe = registry.subscribe( - viewPostLinkUpdateListener( registry ) - ); - registry.dispatch( 'core/nux' ).triggerGuide( [ - 'core/editor.inserter', - 'core/editor.settings', - 'core/editor.preview', - 'core/editor.publish', - ] ); - return () => { - blockSelectionUnsubscribe(); - adjustSidebarUnsubscribe(); - viewPostLinkUpdateUnsubscribe(); - }; - }, [ registry, postId ] ); + useAdjustSidebarListener( postId ); + useBlockSelectionListener( postId ); + useUpdatePostLinkListener( postId ); return null; } diff --git a/packages/edit-post/src/components/editor-initialization/listener-hooks.js b/packages/edit-post/src/components/editor-initialization/listener-hooks.js new file mode 100644 index 00000000000000..8d1ecc35e88991 --- /dev/null +++ b/packages/edit-post/src/components/editor-initialization/listener-hooks.js @@ -0,0 +1,105 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { useEffect, useRef, useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { + STORE_KEY, + VIEW_AS_LINK_SELECTOR, + VIEW_AS_PREVIEW_LINK_SELECTOR, +} from '../../store/constants'; + +/** + * This listener hook monitors for block selection and triggers the appropriate + * sidebar state. + * + * @param {number} postId The current post id. + */ +export const useBlockSelectionListener = ( postId ) => { + const { + hasBlockSelection, + isEditorSidebarOpened, + } = useSelect( + ( select ) => ( { + hasBlockSelection: !! select( + 'core/block-editor' + ).getBlockSelectionStart(), + isEditorSidebarOpened: select( STORE_KEY ).isEditorSidebarOpened(), + } ), + [ postId ] + ); + + const { openGeneralSidebar } = useDispatch( STORE_KEY ); + + useEffect( () => { + if ( ! isEditorSidebarOpened ) { + return; + } + if ( hasBlockSelection ) { + openGeneralSidebar( 'edit-post/block' ); + } else { + openGeneralSidebar( 'edit-post/document' ); + } + }, [ hasBlockSelection, isEditorSidebarOpened ] ); +}; + +/** + * This listener hook is used to monitor viewport size and adjust the sidebar + * accordingly. + * + * @param {number} postId The current post id. + */ +export const useAdjustSidebarListener = ( postId ) => { + const { isSmall, sidebarToReOpenOnExpand } = useSelect( + ( select ) => ( { + isSmall: select( 'core/viewport' ).isViewportMatch( '< medium' ), + sidebarToReOpenOnExpand: select( STORE_KEY ).getActiveGeneralSidebarName(), + } ), + [ postId ] + ); + + const { openGeneralSidebar, closeGeneralSidebar } = useDispatch( STORE_KEY ); + + const previousOpenedSidebar = useRef( '' ); + + useEffect( () => { + if ( isSmall && sidebarToReOpenOnExpand ) { + previousOpenedSidebar.current = sidebarToReOpenOnExpand; + closeGeneralSidebar(); + } else if ( ! isSmall && previousOpenedSidebar.current ) { + openGeneralSidebar( previousOpenedSidebar.current ); + previousOpenedSidebar.current = ''; + } + }, [ isSmall, sidebarToReOpenOnExpand ] ); +}; + +/** + * This listener hook monitors any change in permalink and updates the view + * post link in the admin bar. + * + * @param {number} postId + */ +export const useUpdatePostLinkListener = ( postId ) => { + const { newPermalink } = useSelect( + ( select ) => ( { + newPermalink: select( 'core/editor' ).getCurrentPost().link, + } ), + [ postId ] + ); + + const nodeToUpdate = useMemo( + () => document.querySelector( VIEW_AS_PREVIEW_LINK_SELECTOR ) || + document.querySelector( VIEW_AS_LINK_SELECTOR ) + ); + + useEffect( () => { + if ( ! newPermalink || ! nodeToUpdate ) { + return; + } + nodeToUpdate.setAttribute( 'href', newPermalink ); + }, [ newPermalink ] ); +}; diff --git a/packages/edit-post/src/components/editor-initialization/listeners.js b/packages/edit-post/src/components/editor-initialization/listeners.js deleted file mode 100644 index ab3a428c34bb53..00000000000000 --- a/packages/edit-post/src/components/editor-initialization/listeners.js +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Internal dependencies - */ -import { STORE_KEY, VIEW_AS_LINK_SELECTOR } from '../../store/constants'; -import { onChangeListener } from './utils'; - -/** - * This listener is used to handle block selection triggering the appropriate - * sidebar state. - * - * @param {Object} registry An instance of the store registry. - * - * @return {Function} A function to pass in as a wp.data.subscribe callback. - */ -export const blockSelectionListener = ( registry ) => onChangeListener( - () => !! registry.select( 'core/block-editor' ) - .getBlockSelectionStart(), - ( hasBlockSelection ) => { - if ( ! registry.select( 'core/edit-post' ).isEditorSidebarOpened() ) { - return; - } - if ( hasBlockSelection ) { - registry.dispatch( STORE_KEY ) - .openGeneralSidebar( 'edit-post/block' ); - } else { - registry.dispatch( STORE_KEY ) - .openGeneralSidebar( 'edit-post/document' ); - } - } -); - -/** - * This listener is used to handle adjusting the sidebar according to viewport - * viewport size. - * - * @param {Object} registry An instance of the store registry. - * - * @return {Function} A function to pass in as a wp.data.subscribe callback. - */ -export const adjustSidebarListener = ( registry ) => onChangeListener( - () => registry.select( 'core/viewport' ) - .isViewportMatch( '< medium' ), - ( () => { - let sidebarToReOpenOnExpand = null; - return ( isSmall ) => { - const { getActiveGeneralSidebarName } = registry.select( STORE_KEY ); - const { - closeGeneralSidebar: closeSidebar, - openGeneralSidebar: openSidebar, - } = registry.dispatch( STORE_KEY ); - if ( isSmall ) { - sidebarToReOpenOnExpand = getActiveGeneralSidebarName(); - if ( sidebarToReOpenOnExpand ) { - closeSidebar(); - } - } else if ( - sidebarToReOpenOnExpand && - ! getActiveGeneralSidebarName() - ) { - openSidebar( sidebarToReOpenOnExpand ); - } - }; - } )(), - true -); - -/** - * This listener is used to handle updating the "view as" link selector when the - * post link is changed. - * - * @param {Object} registry An instance of the store registry - * - * @return {Function} A function to pass in as a wp.data.subscribe callback - */ -export const viewPostLinkUpdateListener = ( registry ) => onChangeListener( - () => registry.select( 'core/editor' ).getCurrentPost().link, - ( newPermalink ) => { - if ( ! newPermalink ) { - return; - } - const nodeToUpdate = document.querySelector( VIEW_AS_LINK_SELECTOR ); - if ( ! nodeToUpdate ) { - return; - } - nodeToUpdate.setAttribute( 'href', newPermalink ); - } -); diff --git a/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js b/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js new file mode 100644 index 00000000000000..350a23f5b0ca39 --- /dev/null +++ b/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js @@ -0,0 +1,255 @@ +/** + * External dependencies + */ +import TestRenderer, { act } from 'react-test-renderer'; + +/** + * WordPress dependencies + */ +import { RegistryProvider } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { + useBlockSelectionListener, + useAdjustSidebarListener, + useUpdatePostLinkListener, +} from '../listener-hooks'; +import { STORE_KEY } from '../../../store/constants'; + +describe( 'listener hook tests', () => { + const mockStores = { + 'core/block-editor': { + getBlockSelectionStart: jest.fn(), + }, + 'core/editor': { + getCurrentPost: jest.fn(), + }, + 'core/viewport': { + isViewportMatch: jest.fn(), + }, + [ STORE_KEY ]: { + isEditorSidebarOpened: jest.fn(), + openGeneralSidebar: jest.fn(), + closeGeneralSidebar: jest.fn(), + getActiveGeneralSidebarName: jest.fn(), + }, + }; + let subscribeTrigger; + const registry = { + select: jest.fn().mockImplementation( + ( storeName ) => mockStores[ storeName ] + ), + dispatch: jest.fn().mockImplementation( + ( storeName ) => mockStores[ storeName ] + ), + subscribe: ( subscription ) => { + subscribeTrigger = subscription; + }, + }; + const setMockReturnValue = ( store, functionName, value ) => { + mockStores[ store ][ functionName ] = jest.fn().mockReturnValue( value ); + }; + const getSpyedFunction = ( + store, + functionName + ) => mockStores[ store ][ functionName ]; + const renderComponent = ( testedHook, id, renderer = null ) => { + const TestComponent = ( { postId } ) => { + testedHook( postId ); + return null; + }; + const TestedOutput = + + ; + return renderer === null ? + TestRenderer.create( TestedOutput ) : + renderer.update( TestedOutput ); + }; + afterEach( () => { + Object.values( mockStores ).forEach( ( storeMocks ) => { + Object.values( storeMocks ).forEach( ( mock ) => { + mock.mockClear(); + } ); + } ); + subscribeTrigger = undefined; + } ); + describe( 'useBlockSelectionListener', () => { + it( 'does nothing when editor sidebar is not open', () => { + setMockReturnValue( STORE_KEY, 'isEditorSidebarOpened', false ); + act( () => { + renderComponent( useBlockSelectionListener, 10 ); + } ); + expect( + getSpyedFunction( STORE_KEY, 'isEditorSidebarOpened' ) + ).toHaveBeenCalled(); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).toHaveBeenCalledTimes( 0 ); + } ); + it( 'opens block sidebar if block is selected', () => { + setMockReturnValue( STORE_KEY, 'isEditorSidebarOpened', true ); + setMockReturnValue( 'core/block-editor', 'getBlockSelectionStart', true ); + act( () => { + renderComponent( useBlockSelectionListener, 10 ); + } ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).toHaveBeenCalledWith( 'edit-post/block' ); + } ); + it( 'opens document sidebar if block is not selected', () => { + setMockReturnValue( STORE_KEY, 'isEditorSidebarOpened', true ); + setMockReturnValue( 'core/block-editor', 'getBlockSelectionStart', false ); + act( () => { + renderComponent( useBlockSelectionListener, 10 ); + } ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).toHaveBeenCalledWith( 'edit-post/document' ); + } ); + } ); + describe( 'useAdjustSidebarListener', () => { + it( 'initializes and does nothing when viewport is not small', () => { + setMockReturnValue( 'core/viewport', 'isViewPortMatch', false ); + setMockReturnValue( STORE_KEY, 'getActiveGeneralSidebarName', 'edit-post/document' ); + act( () => { + renderComponent( useAdjustSidebarListener, 10 ); + } ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).not.toHaveBeenCalled(); + expect( + getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) + ).not.toHaveBeenCalled(); + } ); + it( 'does not close sidebar if viewport is small and there is no ' + + 'active sidebar name available', () => { + setMockReturnValue( 'core/viewport', 'isViewPortMatch', true ); + setMockReturnValue( STORE_KEY, 'getActiveGeneralSidebarName', null ); + act( () => { + renderComponent( useAdjustSidebarListener, 10 ); + } ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).not.toHaveBeenCalled(); + expect( + getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) + ).not.toHaveBeenCalled(); + } ); + it( 'closes sidebar if viewport is small and there is an active ' + + 'sidebar name available', () => { + setMockReturnValue( 'core/viewport', 'isViewportMatch', true ); + setMockReturnValue( STORE_KEY, 'getActiveGeneralSidebarName', 'foo' ); + act( () => { + renderComponent( useAdjustSidebarListener, 10 ); + } ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).not.toHaveBeenCalled(); + expect( + getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) + ).toHaveBeenCalledTimes( 1 ); + } ); + it( 'opens sidebar if viewport is not small, and there is a cached sidebar to ' + + 'reopen on expand', () => { + setMockReturnValue( 'core/viewport', 'isViewportMatch', true ); + setMockReturnValue( STORE_KEY, 'getActiveGeneralSidebarName', 'foo' ); + act( () => { + renderComponent( useAdjustSidebarListener, 10 ); + } ); + setMockReturnValue( 'core/viewport', 'isViewportMatch', false ); + act( () => { + subscribeTrigger(); + } ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).toHaveBeenCalledWith( 'foo' ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).toHaveBeenCalledTimes( 1 ); + expect( + getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) + ).toHaveBeenCalledTimes( 1 ); + } ); + } ); + describe( 'useUpdatePostLinkListener', () => { + const setAttribute = jest.fn(); + const mockSelector = jest.fn(); + beforeEach( () => { + document.querySelector = mockSelector.mockReturnValue( { setAttribute } ); + } ); + afterEach( () => { + setAttribute.mockClear(); + mockSelector.mockClear(); + } ); + it( 'updates nothing if there is no view link available', () => { + mockSelector.mockImplementation( () => null ); + setMockReturnValue( + 'core/editor', + 'getCurrentPost', + { link: 'foo' } + ); + act( () => { + renderComponent( useUpdatePostLinkListener, 10 ); + } ); + expect( setAttribute ).not.toHaveBeenCalled(); + } ); + it( 'updates nothing if there is no permalink', () => { + setMockReturnValue( + 'core/editor', + 'getCurrentPost', + { link: '' } + ); + act( () => { + renderComponent( useUpdatePostLinkListener, 10 ); + } ); + expect( setAttribute ).not.toHaveBeenCalled(); + } ); + it( 'only calls document query selector once across renders', () => { + act( () => { + const renderer = renderComponent( useUpdatePostLinkListener, 10 ); + renderComponent( useUpdatePostLinkListener, 20, renderer ); + } ); + expect( mockSelector ).toHaveBeenCalledTimes( 1 ); + act( () => { + subscribeTrigger(); + } ); + expect( mockSelector ).toHaveBeenCalledTimes( 1 ); + } ); + it( 'only updates the permalink when it changes', () => { + setMockReturnValue( + 'core/editor', + 'getCurrentPost', + { link: 'foo' } + ); + act( () => { + renderComponent( useUpdatePostLinkListener, 10 ); + } ); + act( () => { + subscribeTrigger(); + } ); + expect( setAttribute ).toHaveBeenCalledTimes( 1 ); + } ); + it( 'updates the permalink when it changes', () => { + setMockReturnValue( + 'core/editor', + 'getCurrentPost', + { link: 'foo' } + ); + act( () => { + renderComponent( useUpdatePostLinkListener, 10 ); + } ); + setMockReturnValue( + 'core/editor', + 'getCurrentPost', + { link: 'bar' } + ); + act( () => { + subscribeTrigger(); + } ); + expect( setAttribute ).toHaveBeenCalledTimes( 2 ); + expect( setAttribute ).toHaveBeenCalledWith( 'href', 'bar' ); + } ); + } ); +} ); From 4e11e871a2aa460147366ccec26435a32c3fdf5d Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Tue, 11 Jun 2019 19:52:57 -0400 Subject: [PATCH 11/17] update changelog --- packages/edit-post/CHANGELOG.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index 5940a222ad9884..52bbfd658b1b2c 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -1,3 +1,9 @@ +## Master + +### Refactor + +- Create EditorInitializer component and implement for various things to initialize as the editor is loaded. This replaces the `__unstableInitialize` refactor done in #14740. ([#15444](https://github.com/WordPress/gutenberg/pull/15444)) + ## 3.4.0 (2019-05-21) ### New Feature @@ -7,8 +13,6 @@ ### Refactor - convert `INIT` effect to controls & actions [#14740](https://github.com/WordPress/gutenberg/pull/14740) -- Create EditorInitializer component and implement for various things to initialize as the editor is loaded. This replaces the `__unstableInitialize` refactor done in #14740. ([#15444](https://github.com/WordPress/gutenberg/pull/15444)) - ## 3.2.0 (2019-03-06) From 508e22252ef5b9ab90d2440e54f062f12ec70762 Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Tue, 11 Jun 2019 20:01:43 -0400 Subject: [PATCH 12/17] expand jsdocs to hopefully trigger travis build. --- .../edit-post/src/components/editor-initialization/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-post/src/components/editor-initialization/index.js b/packages/edit-post/src/components/editor-initialization/index.js index dc0b7418ad9846..27f3a90c8dcc00 100644 --- a/packages/edit-post/src/components/editor-initialization/index.js +++ b/packages/edit-post/src/components/editor-initialization/index.js @@ -11,7 +11,7 @@ import { * Data component used for initializing the editor and re-initializes * when postId changes or unmount. * - * @param {number} postId + * @param {number} postId The id of the post. * @return {null} This is a data component so does not render any ui. */ export default function( { postId } ) { From d5e9efdf96ac901ef6ea901558b60ced1b94f2c4 Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Tue, 11 Jun 2019 20:16:43 -0400 Subject: [PATCH 13/17] remove obsolete tests --- .../editor-initialization/test/listeners.js | 159 ------------------ 1 file changed, 159 deletions(-) delete mode 100644 packages/edit-post/src/components/editor-initialization/test/listeners.js diff --git a/packages/edit-post/src/components/editor-initialization/test/listeners.js b/packages/edit-post/src/components/editor-initialization/test/listeners.js deleted file mode 100644 index e3122942f7c3a6..00000000000000 --- a/packages/edit-post/src/components/editor-initialization/test/listeners.js +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Internal dependencies - */ -import { - blockSelectionListener, - adjustSidebarListener, - viewPostLinkUpdateListener, -} from '../listeners'; -import { STORE_KEY, VIEW_AS_LINK_SELECTOR } from '../../../store/constants'; - -const registryMock = { select: {}, dispatch: {} }; - -describe( 'blockSelectionListener', () => { - const isEditorSidebarOpened = jest.fn(); - const getBlockSelectionStart = jest.fn(); - const openSidebar = jest.fn(); - beforeEach( () => { - registryMock.select = ( store ) => { - const stores = { - 'core/block-editor': { getBlockSelectionStart }, - 'core/edit-post': { isEditorSidebarOpened }, - }; - return stores[ store ]; - }; - registryMock.dispatch = () => ( { openGeneralSidebar: openSidebar } ); - } ); - afterEach( () => { - isEditorSidebarOpened.mockClear(); - getBlockSelectionStart.mockClear(); - openSidebar.mockClear(); - } ); - it( 'does nothing if sidebar is not opened', () => { - getBlockSelectionStart.mockReturnValue( true ); - isEditorSidebarOpened.mockReturnValue( false ); - const listener = blockSelectionListener( registryMock ); - getBlockSelectionStart.mockReturnValue( false ); - listener(); - expect( getBlockSelectionStart ).toHaveBeenCalled(); - expect( isEditorSidebarOpened ).toHaveBeenCalled(); - expect( openSidebar ).not.toHaveBeenCalled(); - } ); - it( 'opens block sidebar if block is selected', () => { - isEditorSidebarOpened.mockReturnValue( true ); - getBlockSelectionStart.mockReturnValue( false ); - const listener = blockSelectionListener( registryMock ); - getBlockSelectionStart.mockReturnValue( true ); - listener(); - expect( openSidebar ).toHaveBeenCalledWith( 'edit-post/block' ); - } ); - it( 'opens document sidebar if block is not selected', () => { - isEditorSidebarOpened.mockReturnValue( true ); - getBlockSelectionStart.mockReturnValue( true ); - const listener = blockSelectionListener( registryMock ); - getBlockSelectionStart.mockReturnValue( false ); - listener(); - expect( openSidebar ).toHaveBeenCalledWith( 'edit-post/document' ); - } ); -} ); -describe( 'adjustSidebarListener', () => { - const isViewportMatch = jest.fn(); - const getActiveGeneralSidebarName = jest.fn(); - const willCloseGeneralSidebar = jest.fn(); - const willOpenGeneralSidebar = jest.fn(); - beforeEach( () => { - registryMock.select = ( store ) => { - const stores = { - 'core/viewport': { isViewportMatch }, - [ STORE_KEY ]: { getActiveGeneralSidebarName }, - }; - return stores[ store ]; - }; - registryMock.dispatch = ( store ) => { - const stores = { - [ STORE_KEY ]: { - closeGeneralSidebar: willCloseGeneralSidebar, - openGeneralSidebar: willOpenGeneralSidebar, - }, - }; - return stores[ store ]; - }; - registryMock.subscribe = jest.fn(); - isViewportMatch.mockReturnValue( true ); - } ); - afterEach( () => { - isViewportMatch.mockClear(); - getActiveGeneralSidebarName.mockClear(); - willCloseGeneralSidebar.mockClear(); - willOpenGeneralSidebar.mockClear(); - } ); - it( 'initializes and does nothing when viewport is not small', () => { - isViewportMatch.mockReturnValue( false ); - adjustSidebarListener( registryMock )(); - expect( isViewportMatch ).toHaveBeenCalled(); - expect( getActiveGeneralSidebarName ).not.toHaveBeenCalled(); - } ); - it( 'does not close sidebar if viewport is small and there is no ' + - 'active sidebar name available', () => { - getActiveGeneralSidebarName.mockReturnValue( false ); - adjustSidebarListener( registryMock )(); - expect( willCloseGeneralSidebar ).not.toHaveBeenCalled(); - expect( willOpenGeneralSidebar ).not.toHaveBeenCalled(); - } ); - it( 'closes sidebar if viewport is small and there is an active ' + - 'sidebar name available', () => { - getActiveGeneralSidebarName.mockReturnValue( 'someSidebar' ); - adjustSidebarListener( registryMock )(); - expect( willCloseGeneralSidebar ).toHaveBeenCalled(); - expect( willOpenGeneralSidebar ).not.toHaveBeenCalled(); - } ); - it( 'opens sidebar if viewport is not small, there is a cached sidebar to ' + - 'reopen on expand, and there is no current sidebar name available', () => { - getActiveGeneralSidebarName.mockReturnValue( 'someSidebar' ); - const listener = adjustSidebarListener( registryMock ); - listener(); - isViewportMatch.mockReturnValue( false ); - getActiveGeneralSidebarName.mockReturnValue( false ); - listener(); - expect( willCloseGeneralSidebar ).toHaveBeenCalledTimes( 1 ); - expect( willOpenGeneralSidebar ).toHaveBeenCalledTimes( 1 ); - } ); -} ); -describe( 'viewPostLinkUpdateListener', () => { - const getCurrentPost = jest.fn(); - const setAttribute = jest.fn(); - beforeEach( () => { - document.querySelector = jest.fn().mockReturnValue( { setAttribute } ); - getCurrentPost.mockReturnValue( { link: 'foo' } ); - registryMock.select = ( store ) => { - const stores = { 'core/editor': { getCurrentPost } }; - return stores[ store ]; - }; - } ); - afterEach( () => { - setAttribute.mockClear(); - getCurrentPost.mockClear(); - } ); - it( 'updates nothing if there is no new permalink', () => { - const listener = viewPostLinkUpdateListener( registryMock ); - listener(); - expect( getCurrentPost ).toHaveBeenCalledTimes( 2 ); - expect( document.querySelector ).not.toHaveBeenCalled(); - expect( setAttribute ).not.toHaveBeenCalled(); - } ); - it( 'does not do anything if the node is not found', () => { - const listener = viewPostLinkUpdateListener( registryMock ); - getCurrentPost.mockReturnValue( { link: 'bar' } ); - document.querySelector.mockReturnValue( false ); - listener(); - expect( document.querySelector ) - .toHaveBeenCalledWith( VIEW_AS_LINK_SELECTOR ); - expect( setAttribute ).not.toHaveBeenCalled(); - } ); - it( 'updates with the new permalink when node is found', () => { - const listener = viewPostLinkUpdateListener( registryMock ); - getCurrentPost.mockReturnValue( { link: 'bar' } ); - listener(); - expect( setAttribute ).toHaveBeenCalledWith( 'href', 'bar' ); - } ); -} ); From 68f680f24f51c718c6c91bda15f5c0be13d74d51 Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Tue, 11 Jun 2019 20:17:11 -0400 Subject: [PATCH 14/17] switch away from useMemo --- .../editor-initialization/listener-hooks.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/edit-post/src/components/editor-initialization/listener-hooks.js b/packages/edit-post/src/components/editor-initialization/listener-hooks.js index 8d1ecc35e88991..56d7761270eac4 100644 --- a/packages/edit-post/src/components/editor-initialization/listener-hooks.js +++ b/packages/edit-post/src/components/editor-initialization/listener-hooks.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; -import { useEffect, useRef, useMemo } from '@wordpress/element'; +import { useEffect, useRef } from '@wordpress/element'; /** * Internal dependencies @@ -90,16 +90,17 @@ export const useUpdatePostLinkListener = ( postId ) => { } ), [ postId ] ); + const nodeToUpdate = useRef(); - const nodeToUpdate = useMemo( - () => document.querySelector( VIEW_AS_PREVIEW_LINK_SELECTOR ) || - document.querySelector( VIEW_AS_LINK_SELECTOR ) - ); + useEffect( () => { + nodeToUpdate.current = document.querySelector( VIEW_AS_PREVIEW_LINK_SELECTOR ) || + document.querySelector( VIEW_AS_LINK_SELECTOR ); + }, [ postId ] ); useEffect( () => { - if ( ! newPermalink || ! nodeToUpdate ) { + if ( ! newPermalink || ! nodeToUpdate.current ) { return; } - nodeToUpdate.setAttribute( 'href', newPermalink ); + nodeToUpdate.current.setAttribute( 'href', newPermalink ); }, [ newPermalink ] ); }; From 09db379b3d8f15989f64f89c297857f245500f27 Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Tue, 11 Jun 2019 22:09:17 -0400 Subject: [PATCH 15/17] doh! Add back in initial trigger of nux guides! --- .../components/editor-initialization/index.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/edit-post/src/components/editor-initialization/index.js b/packages/edit-post/src/components/editor-initialization/index.js index 27f3a90c8dcc00..2577a6d14ea9da 100644 --- a/packages/edit-post/src/components/editor-initialization/index.js +++ b/packages/edit-post/src/components/editor-initialization/index.js @@ -7,9 +7,15 @@ import { useUpdatePostLinkListener, } from './listener-hooks'; +/** + * WordPress dependencies + */ +import { useEffect } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; + /** * Data component used for initializing the editor and re-initializes - * when postId changes or unmount. + * when postId changes or on unmount. * * @param {number} postId The id of the post. * @return {null} This is a data component so does not render any ui. @@ -18,5 +24,14 @@ export default function( { postId } ) { useAdjustSidebarListener( postId ); useBlockSelectionListener( postId ); useUpdatePostLinkListener( postId ); + const { triggerGuide } = useDispatch( 'core/nux' ); + useEffect( () => { + triggerGuide( [ + 'core/editor.inserter', + 'core/editor.settings', + 'core/editor.preview', + 'core/editor.publish', + ] ); + }, [ triggerGuide ] ); return null; } From fd56104e4646586c1f09f52ebf06c002de12292f Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Tue, 25 Jun 2019 19:36:19 -0400 Subject: [PATCH 16/17] fix spacing issues after merge from master --- packages/edit-post/src/editor.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index d6a1cdc5a80823..92fad75ebef7e8 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -95,16 +95,16 @@ class Editor extends Component { - - - - + + + + From 521664cae2e2d637af4206bcc5d24d032b1f4e47 Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Tue, 25 Jun 2019 19:38:00 -0400 Subject: [PATCH 17/17] fix order of imports --- .../src/components/editor-initialization/index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/edit-post/src/components/editor-initialization/index.js b/packages/edit-post/src/components/editor-initialization/index.js index 2577a6d14ea9da..51cb90681f8ae8 100644 --- a/packages/edit-post/src/components/editor-initialization/index.js +++ b/packages/edit-post/src/components/editor-initialization/index.js @@ -1,3 +1,9 @@ +/** + * WordPress dependencies + */ +import { useEffect } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; + /** * Internal dependencies */ @@ -7,12 +13,6 @@ import { useUpdatePostLinkListener, } from './listener-hooks'; -/** - * WordPress dependencies - */ -import { useEffect } from '@wordpress/element'; -import { useDispatch } from '@wordpress/data'; - /** * Data component used for initializing the editor and re-initializes * when postId changes or on unmount.