From 53c70a0f1a2614df8825f6151e93fca3e0a2fd3a Mon Sep 17 00:00:00 2001 From: Tony Tahmouch Date: Sun, 8 Aug 2021 17:21:51 -0400 Subject: [PATCH 1/8] TT | 3471 | "Introduce allow-list and disallow-list for blocks." MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is meant to be treated as a `draft`. I haven't spent a lot of time trying to document everything yet. I did spend some time writing type declarations in JSDoc until my comments ended up becoming larger than the actual implementation itself to which I started to question the value at that point. I can include them if it makes sense to do so. I haven't added tests that reflect the requirements defined below. I did look for existing tests for this module and wasn't able to find them, but I may just not be understanding the general testing strategy. The general idea behind this work is that there is a desire to allow the Gutenberg Block Editor to restrict the block types it offers in various contexts it is presented in to the customer. The changes found here should, in theory, only impact the blocks presented in the block picker, and not affect the blocks displayed within the editor if someone were to manually modify the markup. The "requirements" that I thought seemed reasonable to me would be: + If you pass in neither an allow list nor a disallow list, then proceed by using all core blocks. + If you pass in an allow list, and not a disallow list, then proceed by only including what is in the allow list from the entire set of core blocks. + If you pass in a disallow list, and not an allow list, then proceed by including the entire set of core blocks sans the blocks in the disallow list. + If you pass in both an allow and disallow list, then proceed by removing blocks named in the disallow list first, then keeping the blocks named in the allow list second. However, this use case isn't realistic in my opinion. The consumer should only provide an allow or disallow list whichever is more convenient for the context. An allow list is preferred when the amount of blocks we'd like to present to the customer is small. The disallow list is preferred when the amount of blocks we'd like to present is large. I made a previous attempt to implement the concept of allowing and disallowing block types. I tried ensuring that registration of the disallowed blocks never occured using `registerBlockType` if the block name *wasn't* included the provided allow list, or if the block name *was* provided in the disallow list. This attempt had some unintended side-effects.
 0. We have many different types of default blocks that we assume to be registered with the application for various reasons, e.g., the `BlockList.Footer` component assumes that the block type `core/paragraph` always exists as it provides a button to the customer to 'Add paragraph block', and the `core/paragraph` block type is also registered as the `setDefaultBlockName` in the editor initialization. Other examples that take place in the editor initialization are things like all social block variants being registered with `registerBlockVariations`, the classic block being registered as the `setFreeformContentHandlerName`, the missing block being registered as the `setUnregisteredTypeHandlerName`, and the group block being registered as the `setGroupingBlockName`. When `core/paragraph` was not registered with the editor, the editor would crash when attempting to `createBlock('core/paragraph')` in the `BlockList.Footer`. I was working on allowing various default block names to be registered for different use cases, i.e., default, missing, social, classic, and group, to allow other block types to replace those classifications if for example the `core/paragraph` block type is provided in a disallow list so that a different block type could become the application default, or if `core/missing` is not provided in an allow list that a different block type could be used to represent unregistered block types, or even possibly forcing a `core/missing` to never be disallowed/unregistered since would be ironic. 1. Jetpack block types are registered asynchronously after the core block types are registered in the "Jetpack Editor Setup" flow. It uses `capabilities` provided via props to the editor initialization to determine if certain block types should be shown or hidden. However, it *always registers* the blocks whether or not they should be shown or hidden based on the `capabilities`. So it was difficult to dependency inject the props provided to the editor from the consuming native app to supersede the `capabilities` if the `jetpack/*` components were disallowed. It would have required possibly selecting the allow list from the store in the latest possible registration spot, i.e., the `registerBlockType` function, to ensure that no matter where a block type was being registered that the allow list didn't need to consulted through all possible flows leading to block registration. So this sparked an idea that I shouldn't actually be messing with the possibility that a block type be registered with the application. Rather, just allow all existing block types to be registered as normal, and then use the same logic that the "Jetpack Editor Setup" uses in order to determine whether or not a block type should be shown or hidden in the `BlockList`. It seemed particularly perfect because it is literally the exact same concept except that in Jetpack's case it is using `capabilities` instead of explicit block names. The only drawback that I wasn't able to figure out when spiking this concept was how to listen for a completion of all block type registrations. There doesn't appear to be a specific event defined for that. So I just ensured that I waited until after all `core/*` and `jetpack/*` block types were registered in order to determine if the previously registered block types should be hidden. This unfortunately meant that I couldn't dispatch `SHOW` or `HIDE` actions to the Redux Store from the main initialization flow, but rather I had to `setTimeout` to ensure it happened at a future tick of the event loop after all possible block type registrations occured. This appeared to be the same basic flow that was used to determine if the `jetpack/story` or `jetypack/contact-info` block types should be hidden. I'm not fond of this implementation, but I don't think there is a roobust way to handle listening for all registrations being completed at the moment. + `src/allowed-blocks-setup.js` - I added a new `allowed-blocks-setup` module to be executed after the Core and Jetpack Block Registrations took place to hide blocks not included in provided allow list or provided in a disallow list. + `src/index.js` - I execute the module flow mentioned above after all block type registrations have taken place. + `src/jetpack-editor-setup.js` - The changes in this file are just cleanup to extract out the `toggleBlock` function to the module scope, and shift the initialization of the default `jetpackState` to this module where it is relevant. These changes are technically unnecessary for the scope of this work. --- src/allowed-blocks-setup.js | 54 +++++++++++++++++++++++++++++++++++++ src/index.js | 8 +++--- src/jetpack-editor-setup.js | 21 ++++++++------- 3 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 src/allowed-blocks-setup.js diff --git a/src/allowed-blocks-setup.js b/src/allowed-blocks-setup.js new file mode 100644 index 0000000000..f6320ff86e --- /dev/null +++ b/src/allowed-blocks-setup.js @@ -0,0 +1,54 @@ +/** + * External dependencies + */ +import { map as _map, without as _without } from 'lodash'; +/** + * WordPress dependencies + */ +import { dispatch as _dispatch } from '@wordpress/data'; +import { getBlockTypes as _getBlockTypes } from '@wordpress/blocks'; + +export default ( + props = {}, + dependencies = { + dispatch: _dispatch, + getBlockTypes: _getBlockTypes, + map: _map, + setTimeout, + without: _without, + } +) => { + const { dispatch, getBlockTypes, map, setTimeout, without } = dependencies; + const { + showBlocks = [ + // 'core/audio', + // 'jetpack/contact-info', + // 'jetpack/story', + // 'jetpack/layout-grid', + ], + hideBlocks = [ + // 'core/audio', + // 'jetpack/contact-info', + // 'jetpack/story', + // 'jetpack/layout-grid', + ], + } = props; + + setTimeout( () => { + const blocks = map( getBlockTypes(), 'name' ); + const uniqueHideBlocks = [ ...new Set( hideBlocks ) ]; + const uniqueShowBlocks = [ ...new Set( showBlocks ) ]; + const differenceHideBlocks = without( blocks, ...uniqueShowBlocks ); + const differenceShowBlocks = without( blocks, ...uniqueHideBlocks ); + + if ( uniqueHideBlocks.length > 0 ) { + dispatch( 'core/edit-post' ).hideBlockTypes( uniqueHideBlocks ); + dispatch( 'core/edit-post' ).showBlockTypes( differenceShowBlocks ); + } + + if ( uniqueShowBlocks.length > 0 ) { + dispatch( 'core/edit-post' ).showBlockTypes( uniqueShowBlocks ); + dispatch( 'core/edit-post' ).hideBlockTypes( differenceHideBlocks ); + } + } ); +}; diff --git a/src/index.js b/src/index.js index f1bbf1af93..95484d3e80 100644 --- a/src/index.js +++ b/src/index.js @@ -11,8 +11,9 @@ import { * Internal dependencies */ import correctTextFontWeight from './text-font-weight-correct'; -import setupJetpackEditor from './jetpack-editor-setup'; +import setupAllowedBlocks from './allowed-blocks-setup'; import setupBlockExperiments from './block-experiments-setup'; +import setupJetpackEditor from './jetpack-editor-setup'; import initialHtml from './initial-html'; addAction( 'native.pre-render', 'gutenberg-mobile', () => { @@ -21,11 +22,10 @@ addAction( 'native.pre-render', 'gutenberg-mobile', () => { } ); addAction( 'native.render', 'gutenberg-mobile', ( props ) => { - setupJetpackEditor( - props.jetpackState || { blogId: 1, isJetpackActive: true } - ); const capabilities = props.capabilities ?? {}; + setupJetpackEditor( props ); setupBlockExperiments( capabilities ); + setupAllowedBlocks( props ); } ); addFilter( 'native.block_editor_props', 'gutenberg-mobile', ( editorProps ) => { diff --git a/src/jetpack-editor-setup.js b/src/jetpack-editor-setup.js index f6405ddbd6..58f350123b 100644 --- a/src/jetpack-editor-setup.js +++ b/src/jetpack-editor-setup.js @@ -17,6 +17,14 @@ const supportedJetpackBlocks = { }, }; +const toggleBlock = ( capability, blockName ) => { + if ( capability !== true ) { + dispatch( 'core/edit-post' ).hideBlockTypes( [ blockName ] ); + } else { + dispatch( 'core/edit-post' ).showBlockTypes( [ blockName ] ); + } +}; + const setJetpackData = ( { isJetpackActive = false, userData = null, @@ -37,21 +45,16 @@ const setJetpackData = ( { return jetpackEditorInitialState; }; -export default ( jetpackState ) => { +export default ( props = {} ) => { + const { jetpackState: state } = props; + const jetpackState = state || { blogId: 1, isJetpackActive: true }; + if ( ! jetpackState.isJetpackActive ) { return; } const jetpackData = setJetpackData( jetpackState ); - const toggleBlock = ( capability, blockName ) => { - if ( capability !== true ) { - dispatch( 'core/edit-post' ).hideBlockTypes( [ blockName ] ); - } else { - dispatch( 'core/edit-post' ).showBlockTypes( [ blockName ] ); - } - }; - // Note on the use of setTimeout() here: // We observed the settings may not be ready exactly when the native.render hooks get run but rather // right after that execution cycle (because state hasn't changed yet). Hence, we're only checking for From a64514ceaca057f600171a3e222f5cda0d876448 Mon Sep 17 00:00:00 2001 From: Tony Tahmouch Date: Fri, 13 Aug 2021 15:36:59 -0400 Subject: [PATCH 2/8] TT | 3471 | "Introduce allow-list and disallow-list for blocks." + `src/allowed-blocks-setup.js` + Added `jetpack/layout-grid` as an "Available Jetpack Block." However, it's availability is irrelevant since it is not registered with `registerJetpackBlock` but rather `registerBlockType`. + Added a centralized entry point for registration and hiding of block types called `setupBlocks` that should be executed in the `gutenberg-mobile` `native.render` action as early as possible in the editor initialization. + Removed the redundant dispatching of the `SHOW_BLOCK_TYPES` action for the inverse of the block types that are hidden with the dispatching of the `HIDE_BLOCK_TYPES` action to @fluiddot 's point. + Ensured that all `jetpack/` block types export their own `registerBlock` entry point function to make registration conditional for non-`core/` block types. + Added `registerJetpackBlocksIfCapable` to ensure that `jetpack/contact-info`, `jetpack/layout-grid`, and `jetpack/story` are only registered if their respective `capabilities` passed through the editor intial `props` indicate they should be enabled for the customer. + Simplified the registration of all `jetpack/` block types previously done in `setupJetpackEditor` and `setJetpackData` in the new `setupJetpackBlocks` function. The `setTimeout` to ensure `jetpack/` blocks are conditionally hidden on next tick is no longer needed if not attempting to select the `capabilities` from the `core/block-editor` store, but rather just leverage the `props` passed directly into the initialization of the editor. The inline `require` is no longer needed since it now exports functions used to register lazily as opposed to implied registration when the module is loaded. `toggleBlock` is also no longer needed as the hiding of all block types, `core/`, `jetpack/`, or future types has been unified. + Removed the unnecessary `dependencies` argument from the `setupAllowedBlocks` function signature. We can just leverage Jest module mocking for dependency injection when necessary. + Removed the unnecessary `setTimeout` from the `setupAllowedBlocks` function as it no longer has to wait for `jetpack/` block types to be hidden first since that flow can now be done on the current tick of the event loop instead of a future tick waiting for the redux store state to have changed. + `src/block-experiments-setup.js` + Removed the module to consolidate block type registration and hiding into `src/allowed-blocks-setup.js`. + `src/block-support/supported-blocks.json` + Added `jetpack/layout-grid` and `jetpack/story` to the list of supported blocks. This appears to only be used in the iOS Swift React Native Bridge. + `src/index.js` + Consolidated the entry points used for registering and hiding `jetpack/contact-info`, `jetpack/layout-grid`, and `jetpack/story` to `setupBlocks` from `setupJetpackEditor` and `setupBlockExperiments`. + `src/jetpack-editor-setup.js` + Removed the module to consolidate block type registration and hiding into `src/allowed-blocks-setup.js`. + `src/test/index.js` + Updated the staging of the existing test that ensures `jetpack/` block types register successfully during the editor initialization. --- src/allowed-blocks-setup.js | 128 ++++++++++++++++-------- src/block-experiments-setup.js | 8 -- src/block-support/supported-blocks.json | 4 +- src/index.js | 9 +- src/jetpack-editor-setup.js | 78 --------------- src/test/index.js | 3 +- 6 files changed, 90 insertions(+), 140 deletions(-) delete mode 100644 src/block-experiments-setup.js delete mode 100644 src/jetpack-editor-setup.js diff --git a/src/allowed-blocks-setup.js b/src/allowed-blocks-setup.js index f6320ff86e..941eac0618 100644 --- a/src/allowed-blocks-setup.js +++ b/src/allowed-blocks-setup.js @@ -1,54 +1,94 @@ /** * External dependencies */ -import { map as _map, without as _without } from 'lodash'; +import { map, without } from 'lodash'; /** * WordPress dependencies */ -import { dispatch as _dispatch } from '@wordpress/data'; -import { getBlockTypes as _getBlockTypes } from '@wordpress/blocks'; - -export default ( - props = {}, - dependencies = { - dispatch: _dispatch, - getBlockTypes: _getBlockTypes, - map: _map, - setTimeout, - without: _without, - } -) => { - const { dispatch, getBlockTypes, map, setTimeout, without } = dependencies; +import { dispatch } from '@wordpress/data'; +import { getBlockTypes } from '@wordpress/blocks'; +/** + * Internal dependencies + */ +import { JETPACK_DATA_PATH } from '../jetpack/projects/plugins/jetpack/extensions/shared/get-jetpack-data'; +import { registerBlock as registerJetpackLayoutGridBlock } from '../block-experiments/blocks/layout-grid/src'; +import { + registerContactInfoBlock as registerJetpackContactInfoBlock, + registerStoryBlock as registerJetpackStoryBlock, +} from '../jetpack/projects/plugins/jetpack/extensions/editor.native'; + +// Please also consider updating ./block-support/supported-blocks.json +const availableJetpackBlocks = { + 'contact-info': { available: true }, + 'layout-grid': { available: true }, + story: { available: true }, +}; + +const setJetpackData = ( { + isJetpackActive = false, + userData: tracksUserData = null, + siteFragment = null, + blogId: wpcomBlogId = 1, +} ) => { + return { + siteFragment, + tracksUserData, + wpcomBlogId, + jetpack: { is_active: isJetpackActive }, + available_blocks: availableJetpackBlocks, + }; +}; + +const registerJetpackBlocksIfCapable = ( props = {} ) => { const { - showBlocks = [ - // 'core/audio', - // 'jetpack/contact-info', - // 'jetpack/story', - // 'jetpack/layout-grid', - ], - hideBlocks = [ - // 'core/audio', - // 'jetpack/contact-info', - // 'jetpack/story', - // 'jetpack/layout-grid', - ], + capabilities: { + layoutGridBlock = false, + mediaFilesCollectionBlock = false, + contactInfoBlock = false, + } = {}, } = props; - setTimeout( () => { - const blocks = map( getBlockTypes(), 'name' ); - const uniqueHideBlocks = [ ...new Set( hideBlocks ) ]; - const uniqueShowBlocks = [ ...new Set( showBlocks ) ]; - const differenceHideBlocks = without( blocks, ...uniqueShowBlocks ); - const differenceShowBlocks = without( blocks, ...uniqueHideBlocks ); - - if ( uniqueHideBlocks.length > 0 ) { - dispatch( 'core/edit-post' ).hideBlockTypes( uniqueHideBlocks ); - dispatch( 'core/edit-post' ).showBlockTypes( differenceShowBlocks ); - } - - if ( uniqueShowBlocks.length > 0 ) { - dispatch( 'core/edit-post' ).showBlockTypes( uniqueShowBlocks ); - dispatch( 'core/edit-post' ).hideBlockTypes( differenceHideBlocks ); - } - } ); + if ( layoutGridBlock ) { + registerJetpackLayoutGridBlock(); + } + + if ( mediaFilesCollectionBlock ) { + registerJetpackStoryBlock(); + } + + if ( contactInfoBlock ) { + registerJetpackContactInfoBlock(); + } +}; + +export const setupJetpackBlocks = ( props = {} ) => { + const { jetpackState = { blogId: 1, isJetpackActive: true } } = props; + const { isJetpackActive = false } = jetpackState; + + global.window[ JETPACK_DATA_PATH ] = setJetpackData( jetpackState ); + + if ( isJetpackActive ) { + registerJetpackBlocksIfCapable( props ); + } +}; + +export const setupAllowedBlocks = ( props = {} ) => { + const { showBlocks = [], hideBlocks = [] } = props; + const blocks = map( getBlockTypes(), 'name' ); + const uniqueHideBlocks = [ ...new Set( hideBlocks ) ]; + const uniqueShowBlocks = [ ...new Set( showBlocks ) ]; + const differenceHideBlocks = without( blocks, ...uniqueShowBlocks ); + + if ( uniqueHideBlocks.length > 0 ) { + dispatch( 'core/edit-post' ).hideBlockTypes( uniqueHideBlocks ); + } + + if ( uniqueShowBlocks.length > 0 ) { + dispatch( 'core/edit-post' ).hideBlockTypes( differenceHideBlocks ); + } +}; + +export const setupBlocks = ( props = {} ) => { + setupJetpackBlocks( props ); + setupAllowedBlocks( props ); }; diff --git a/src/block-experiments-setup.js b/src/block-experiments-setup.js deleted file mode 100644 index 037826718c..0000000000 --- a/src/block-experiments-setup.js +++ /dev/null @@ -1,8 +0,0 @@ -// This file is to set up the jetpack/layout-grid block that currently lives in block-experiments/blocks/layout-grid -import { registerBlock } from '../block-experiments/blocks/layout-grid/src'; - -export default function setupBlockExperiments( capabilities ) { - if ( capabilities.layoutGridBlock ) { - registerBlock(); - } -} diff --git a/src/block-support/supported-blocks.json b/src/block-support/supported-blocks.json index 072255d883..6c9baa0ff5 100644 --- a/src/block-support/supported-blocks.json +++ b/src/block-support/supported-blocks.json @@ -29,10 +29,12 @@ "core/file", "core/search", "core/block", + "jetpack/address", "jetpack/contact-info", "jetpack/email", + "jetpack/layout-grid", "jetpack/phone", - "jetpack/address" + "jetpack/story" ], "devOnly": [ "core/code" ], "iOSOnly": [], diff --git a/src/index.js b/src/index.js index 95484d3e80..91532a4b19 100644 --- a/src/index.js +++ b/src/index.js @@ -11,9 +11,7 @@ import { * Internal dependencies */ import correctTextFontWeight from './text-font-weight-correct'; -import setupAllowedBlocks from './allowed-blocks-setup'; -import setupBlockExperiments from './block-experiments-setup'; -import setupJetpackEditor from './jetpack-editor-setup'; +import { setupBlocks } from './allowed-blocks-setup'; import initialHtml from './initial-html'; addAction( 'native.pre-render', 'gutenberg-mobile', () => { @@ -22,10 +20,7 @@ addAction( 'native.pre-render', 'gutenberg-mobile', () => { } ); addAction( 'native.render', 'gutenberg-mobile', ( props ) => { - const capabilities = props.capabilities ?? {}; - setupJetpackEditor( props ); - setupBlockExperiments( capabilities ); - setupAllowedBlocks( props ); + setupBlocks( props ); } ); addFilter( 'native.block_editor_props', 'gutenberg-mobile', ( editorProps ) => { diff --git a/src/jetpack-editor-setup.js b/src/jetpack-editor-setup.js deleted file mode 100644 index 58f350123b..0000000000 --- a/src/jetpack-editor-setup.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Internal dependencies - */ -import { JETPACK_DATA_PATH } from '../jetpack/projects/plugins/jetpack/extensions/shared/get-jetpack-data'; -/** - * WordPress dependencies - */ -import { dispatch, select } from '@wordpress/data'; - -// When adding new blocks to this list please also consider updating ./block-support/supported-blocks.json -const supportedJetpackBlocks = { - 'contact-info': { - available: true, - }, - story: { - available: true, - }, -}; - -const toggleBlock = ( capability, blockName ) => { - if ( capability !== true ) { - dispatch( 'core/edit-post' ).hideBlockTypes( [ blockName ] ); - } else { - dispatch( 'core/edit-post' ).showBlockTypes( [ blockName ] ); - } -}; - -const setJetpackData = ( { - isJetpackActive = false, - userData = null, - siteFragment = null, - blogId, -} ) => { - const availableBlocks = supportedJetpackBlocks; - const jetpackEditorInitialState = { - available_blocks: availableBlocks, - jetpack: { - is_active: isJetpackActive, - }, - siteFragment, - tracksUserData: userData, - wpcomBlogId: blogId, - }; - global.window[ JETPACK_DATA_PATH ] = jetpackEditorInitialState; - return jetpackEditorInitialState; -}; - -export default ( props = {} ) => { - const { jetpackState: state } = props; - const jetpackState = state || { blogId: 1, isJetpackActive: true }; - - if ( ! jetpackState.isJetpackActive ) { - return; - } - - const jetpackData = setJetpackData( jetpackState ); - - // Note on the use of setTimeout() here: - // We observed the settings may not be ready exactly when the native.render hooks get run but rather - // right after that execution cycle (because state hasn't changed yet). Hence, we're only checking for - // the actual settings to be loaded by using setTimeout without a delay parameter. This ensures the - // settings are loaded onto the store and we can use the core/block-editor selector by the time we do - // the actual check. - - // eslint-disable-next-line @wordpress/react-no-unsafe-timeout - setTimeout( () => { - const capabilities = select( 'core/block-editor' ).getSettings( - 'capabilities' - ); - - toggleBlock( capabilities.mediaFilesCollectionBlock, 'jetpack/story' ); - toggleBlock( capabilities.contactInfoBlock, 'jetpack/contact-info' ); - } ); - - require( '../jetpack/projects/plugins/jetpack/extensions/editor' ); - - return jetpackData; -}; diff --git a/src/test/index.js b/src/test/index.js index f0bd82863e..db13c2e34a 100644 --- a/src/test/index.js +++ b/src/test/index.js @@ -18,8 +18,7 @@ describe( 'Test Jetpack blocks', () => { () => jest.fn() ); - const setupJetpackEditor = require( '../jetpack-editor-setup' ).default; - setupJetpackEditor( { blogId: 1, isJetpackActive: true } ); + require( '../allowed-blocks-setup' ); expect( mockRegisterBlockCollection.mock.calls[ 0 ][ 0 ] ).toBe( 'jetpack' From 52d5d0522def25e2033b0d70cd993037519ca35b Mon Sep 17 00:00:00 2001 From: Tony Tahmouch Date: Fri, 13 Aug 2021 21:32:02 -0400 Subject: [PATCH 3/8] TT | 3471 | Updating the `jetpack` submodule reference. --- jetpack | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetpack b/jetpack index 3ec4ba6abc..45bd3eb8e0 160000 --- a/jetpack +++ b/jetpack @@ -1 +1 @@ -Subproject commit 3ec4ba6abcdf76fb8c9b4750a4581e62efc5254a +Subproject commit 45bd3eb8e002552570a292551373478206b721c5 From 4dd35e1d96b1d88300e8acab2041f62d94a2b639 Mon Sep 17 00:00:00 2001 From: Tony Tahmouch Date: Tue, 17 Aug 2021 15:23:18 -0400 Subject: [PATCH 4/8] TT | 3471 | Renaming `setJetpackData` to `mapToJetpackData` as this function no longer does assignment, but rather simply maps one data model to another. --- src/allowed-blocks-setup.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/allowed-blocks-setup.js b/src/allowed-blocks-setup.js index 941eac0618..5c6e7e7100 100644 --- a/src/allowed-blocks-setup.js +++ b/src/allowed-blocks-setup.js @@ -24,7 +24,7 @@ const availableJetpackBlocks = { story: { available: true }, }; -const setJetpackData = ( { +const mapToJetpackData = ( { isJetpackActive = false, userData: tracksUserData = null, siteFragment = null, @@ -65,7 +65,7 @@ export const setupJetpackBlocks = ( props = {} ) => { const { jetpackState = { blogId: 1, isJetpackActive: true } } = props; const { isJetpackActive = false } = jetpackState; - global.window[ JETPACK_DATA_PATH ] = setJetpackData( jetpackState ); + global.window[ JETPACK_DATA_PATH ] = mapToJetpackData( jetpackState ); if ( isJetpackActive ) { registerJetpackBlocksIfCapable( props ); From 039d0acefbe88e2d27d889049b1c80d7388164d2 Mon Sep 17 00:00:00 2001 From: Tony Tahmouch Date: Tue, 17 Aug 2021 15:42:16 -0400 Subject: [PATCH 5/8] TT | 3471 | Moving the initialization of the global `JETPACK_DATA_PATH` field back to only be initialized if `isJetpackActive` is `true`. It is probably safe to be initialized regardless of if Jetpack is active because that global object has an `{jetpack: {is_active}}` field, but it's less likely to cause any side effects for the sake of this PR if it is reverted. --- src/allowed-blocks-setup.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/allowed-blocks-setup.js b/src/allowed-blocks-setup.js index 5c6e7e7100..243ba19967 100644 --- a/src/allowed-blocks-setup.js +++ b/src/allowed-blocks-setup.js @@ -65,9 +65,8 @@ export const setupJetpackBlocks = ( props = {} ) => { const { jetpackState = { blogId: 1, isJetpackActive: true } } = props; const { isJetpackActive = false } = jetpackState; - global.window[ JETPACK_DATA_PATH ] = mapToJetpackData( jetpackState ); - if ( isJetpackActive ) { + global.window[ JETPACK_DATA_PATH ] = mapToJetpackData( jetpackState ); registerJetpackBlocksIfCapable( props ); } }; From 8fdbde53876950a7115d3146245dfdb1bc5dbbe5 Mon Sep 17 00:00:00 2001 From: Tony Tahmouch Date: Tue, 17 Aug 2021 16:38:33 -0400 Subject: [PATCH 6/8] TT | 3471 | Eliminate the dependency on `lodash` as it is not currently a transitive dependency of `gutenberg-mobile` and there isn't a compelling enough reason to add it for simple `map` and `filter` array operations. --- src/allowed-blocks-setup.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/allowed-blocks-setup.js b/src/allowed-blocks-setup.js index 243ba19967..1e5cc89c5c 100644 --- a/src/allowed-blocks-setup.js +++ b/src/allowed-blocks-setup.js @@ -1,7 +1,3 @@ -/** - * External dependencies - */ -import { map, without } from 'lodash'; /** * WordPress dependencies */ @@ -73,17 +69,19 @@ export const setupJetpackBlocks = ( props = {} ) => { export const setupAllowedBlocks = ( props = {} ) => { const { showBlocks = [], hideBlocks = [] } = props; - const blocks = map( getBlockTypes(), 'name' ); const uniqueHideBlocks = [ ...new Set( hideBlocks ) ]; const uniqueShowBlocks = [ ...new Set( showBlocks ) ]; - const differenceHideBlocks = without( blocks, ...uniqueShowBlocks ); if ( uniqueHideBlocks.length > 0 ) { dispatch( 'core/edit-post' ).hideBlockTypes( uniqueHideBlocks ); } if ( uniqueShowBlocks.length > 0 ) { - dispatch( 'core/edit-post' ).hideBlockTypes( differenceHideBlocks ); + const blocks = getBlockTypes().map( ( { name } ) => name ); + const notShowBlocks = blocks.filter( + ( name ) => ! uniqueShowBlocks.includes( name ) + ); + dispatch( 'core/edit-post' ).hideBlockTypes( notShowBlocks ); } }; From b0080b08d0ed97e15e76479f96069058e9b3d89c Mon Sep 17 00:00:00 2001 From: Tony Tahmouch Date: Tue, 17 Aug 2021 16:46:29 -0400 Subject: [PATCH 7/8] TT | 3471 | Reverting `supported-blocks.json` to its previous state before `jetpack/layout-grid` and `jetpack/story` were added as supported blocks [per our conversation in GitHub](https://github.com/wordpress-mobile/gutenberg-mobile/pull/3811#discussion_r690036563). --- src/block-support/supported-blocks.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/block-support/supported-blocks.json b/src/block-support/supported-blocks.json index 6c9baa0ff5..072255d883 100644 --- a/src/block-support/supported-blocks.json +++ b/src/block-support/supported-blocks.json @@ -29,12 +29,10 @@ "core/file", "core/search", "core/block", - "jetpack/address", "jetpack/contact-info", "jetpack/email", - "jetpack/layout-grid", "jetpack/phone", - "jetpack/story" + "jetpack/address" ], "devOnly": [ "core/code" ], "iOSOnly": [], From a6fe7103497041bd37902d4415786618d4678c63 Mon Sep 17 00:00:00 2001 From: Tony Tahmouch Date: Sun, 22 Aug 2021 01:35:45 -0400 Subject: [PATCH 8/8] TT | 3471 | Ensure that the Jetpack initialization and Allowed Block Type initialization is comprehensively tested. --- src/allowed-blocks-setup.js | 55 +++- src/test/index.js | 496 ++++++++++++++++++++++++++++++++++-- 2 files changed, 517 insertions(+), 34 deletions(-) diff --git a/src/allowed-blocks-setup.js b/src/allowed-blocks-setup.js index 1e5cc89c5c..06457bed92 100644 --- a/src/allowed-blocks-setup.js +++ b/src/allowed-blocks-setup.js @@ -68,20 +68,49 @@ export const setupJetpackBlocks = ( props = {} ) => { }; export const setupAllowedBlocks = ( props = {} ) => { - const { showBlocks = [], hideBlocks = [] } = props; - const uniqueHideBlocks = [ ...new Set( hideBlocks ) ]; - const uniqueShowBlocks = [ ...new Set( showBlocks ) ]; - - if ( uniqueHideBlocks.length > 0 ) { - dispatch( 'core/edit-post' ).hideBlockTypes( uniqueHideBlocks ); - } - - if ( uniqueShowBlocks.length > 0 ) { - const blocks = getBlockTypes().map( ( { name } ) => name ); - const notShowBlocks = blocks.filter( - ( name ) => ! uniqueShowBlocks.includes( name ) + const registeredBlocks = getBlockTypes().map( ( { name } ) => name ); + const { showBlocks = registeredBlocks, hideBlocks = [] } = props; + const wereShowBlocksProvided = showBlocks !== registeredBlocks; + const uniqueRegisteredHideBlocks = hideBlocks.filter( ( name, index ) => { + return ( + // Is Unique? + hideBlocks.indexOf( name ) === index && + // Is Unambiguous? + ( ! wereShowBlocksProvided || ! showBlocks.includes( name ) ) && + // Is Registered? + registeredBlocks.includes( name ) ); - dispatch( 'core/edit-post' ).hideBlockTypes( notShowBlocks ); + } ); + const uniqueRegisteredShowBlocks = ! wereShowBlocksProvided + ? showBlocks + : showBlocks.filter( ( name, index ) => { + return ( + // Is Unique? + showBlocks.indexOf( name ) === index && + // Is Unambiguous? + ! hideBlocks.includes( name ) && + // Is Registered? + registeredBlocks.includes( name ) + ); + } ); + const wereShowBlocksFilteredDownToAnEmptySet = + wereShowBlocksProvided && + showBlocks.length > 0 && + uniqueRegisteredShowBlocks.length === 0; + const invertRegisteredShowBlocks = wereShowBlocksFilteredDownToAnEmptySet + ? [] + : registeredBlocks.filter( ( name ) => { + return ! uniqueRegisteredShowBlocks.includes( name ); + } ); + const hiddenBlockTypes = [ + ...uniqueRegisteredHideBlocks, + ...invertRegisteredShowBlocks, + ]; + + if ( hiddenBlockTypes.length > 0 ) { + dispatch( 'core/edit-post' ).hideBlockTypes( [ + ...new Set( hiddenBlockTypes ), + ] ); } }; diff --git a/src/test/index.js b/src/test/index.js index db13c2e34a..895a5d21c3 100644 --- a/src/test/index.js +++ b/src/test/index.js @@ -1,27 +1,481 @@ -describe( 'Test Jetpack blocks', () => { - it( 'should setup the editor for jetpack without failing', () => { - const mockRegisterBlockCollection = jest.fn(); - jest.mock( '@wordpress/blocks', () => { - return { - getCategories: () => [ { slug: 'media' } ], - setCategories: jest.fn(), - registerBlockCollection: mockRegisterBlockCollection, - withBlockContentContext: jest.fn(), +import { + getBlockTypes, + registerBlockType, + unregisterBlockType, +} from '@wordpress/blocks'; +import { select } from '@wordpress/data'; +import { defaultHooks } from '@wordpress/hooks'; +import { JETPACK_DATA_PATH } from '../../jetpack/projects/plugins/jetpack/extensions/shared/get-jetpack-data'; + +/** + * These are integration tests written with the intention of asserting the initialization of the Gutenberg Mobile + * editor. As they are integration tests, modules that were being mocked globally are now unmocked to use their + * real implementations. + * + * The intention of these integration tests is to ensure that: + * 0. Jetpack initialization is handled correctly by registering the Jetpack Block Collection, initializing Jetpack Data, + * and registering all Jetpack Block Types that you may be capable of using. + * 1. Allowed block types are displayed correctly in the block selector if a subset of blocks is provided to be shown + * or hidden. + * + * The tests are staged such that `require( '../index' )` mimics the entry point of the initialization of the Gutenberg + * Mobile editor, and `defaultHooks.doAction( 'native.render', props )` is the `native.render` hook that would normally be + * listened for when the editor is initializing. So all the tests ensure that when certain `props` are passed in from the + * `native.render` hook that the underlying Redux Store state is reduced expectedly. This is deliberately done to keep + * the tests as black-box as possible such that the implementation details of the application code may be changed more + * freely without a tight coupling on the code that tests it. + */ +describe( 'Gutenberg Mobile Initialization', () => { + beforeAll( () => { + jest.unmock( '@wordpress/api-fetch' ); + jest.unmock( '@wordpress/blocks' ); + } ); + + describe( 'Jetpack Blocks', () => { + it( `should register Jetpack block collection.`, () => { + // Arrange + const expectedCollectionTitle = 'Jetpack'; + const props = { jetpackState: { isJetpackActive: false } }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const { getCollections } = select( 'core/blocks' ); + const { + jetpack: { title: actualCollectionTitle = '' } = {}, + } = getCollections(); + expect( actualCollectionTitle ).toEqual( expectedCollectionTitle ); + } ); + + it( `should NOT register JETPACK_DATA_PATH when Jetpack is INACTIVE.`, () => { + // Arrange + const expectedJetpackData = undefined; + const props = { jetpackState: { isJetpackActive: false } }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const actualJetpackData = global.window[ JETPACK_DATA_PATH ]; + expect( actualJetpackData ).toEqual( expectedJetpackData ); + } ); + + it( `should register JETPACK_DATA_PATH with DEFAULT values when Jetpack is ACTIVE + AND Jetpack state is UNPROVIDED.`, () => { + // Arrange + const expectedJetpackData = { + available_blocks: { + 'contact-info': { available: true }, + 'layout-grid': { available: true }, + story: { available: true }, + }, + jetpack: { is_active: true }, + siteFragment: null, + tracksUserData: null, + wpcomBlogId: 1, + }; + const props = { jetpackState: { isJetpackActive: true } }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const actualJetpackData = global.window[ JETPACK_DATA_PATH ]; + expect( actualJetpackData ).toEqual( expectedJetpackData ); + } ); + + it( `should register JETPACK_DATA_PATH with CUSTOM values when Jetpack is ACTIVE + AND Jetpack state is PROVIDED.`, () => { + // Arrange + const expectedJetpackData = { + available_blocks: { + 'contact-info': { available: true }, + 'layout-grid': { available: true }, + story: { available: true }, + }, + jetpack: { is_active: true }, + siteFragment: '#fragment', + tracksUserData: {}, + wpcomBlogId: 1337, + }; + const props = { + jetpackState: { + isJetpackActive: true, + siteFragment: '#fragment', + userData: {}, + blogId: 1337, + }, + }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const actualJetpackData = global.window[ JETPACK_DATA_PATH ]; + expect( actualJetpackData ).toEqual( expectedJetpackData ); + } ); + + it( `should NOT register "jetpack/*" block types when Jetpack is INACTIVE + AND you are INCAPABLE.`, () => { + // Arrange + const expectedBlockTypes = []; + const props = { + jetpackState: { isJetpackActive: false }, + capabilities: { + layoutGridBlock: false, + mediaFilesCollectionBlock: false, + contactInfoBlock: false, + }, + }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const actualBlockTypes = getBlockTypes(); + expect( actualBlockTypes ).toEqual( expectedBlockTypes ); + } ); + + it( `should NOT register "jetpack/*" block types when Jetpack is INACTIVE + even if you are CAPABLE.`, () => { + // Arrange + const expectedBlockTypes = []; + const props = { + jetpackState: { isJetpackActive: false }, + capabilities: { + layoutGridBlock: true, + mediaFilesCollectionBlock: true, + contactInfoBlock: true, + }, + }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const actualBlockTypes = getBlockTypes(); + expect( actualBlockTypes ).toEqual( expectedBlockTypes ); + } ); + + it( `should NOT register "jetpack/*" block types when Jetpack is ACTIVE + AND you are INCAPABLE.`, () => { + // Arrange + const expectedBlockTypes = []; + const props = { + jetpackState: { isJetpackActive: true }, + capabilities: { + layoutGridBlock: false, + mediaFilesCollectionBlock: false, + contactInfoBlock: false, + }, + }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const actualBlockTypes = getBlockTypes(); + expect( actualBlockTypes ).toEqual( expectedBlockTypes ); + } ); + + it( `should register the "jetpack/*" block types when Jetpack is ACTIVE + AND you are CAPABLE.`, () => { + // Arrange + const expectedBlockTypes = [ + 'jetpack/address', + 'jetpack/contact-info', + 'jetpack/email', + 'jetpack/layout-grid', + 'jetpack/layout-grid-column', + 'jetpack/phone', + 'jetpack/story', + ].sort(); + const props = { + jetpackState: { isJetpackActive: true }, + capabilities: { + layoutGridBlock: true, + mediaFilesCollectionBlock: true, + contactInfoBlock: true, + }, }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const actualBlockTypes = getBlockTypes() + .map( ( { name } ) => name ) + .sort(); + expect( actualBlockTypes ).toEqual( expectedBlockTypes ); + } ); + + afterAll( () => { + [ + 'jetpack/address', + 'jetpack/contact-info', + 'jetpack/email', + 'jetpack/layout-grid', + 'jetpack/layout-grid-column', + 'jetpack/phone', + 'jetpack/story', + ].forEach( ( name ) => { + unregisterBlockType( name ); + } ); + } ); + } ); + + describe( 'Allowed Blocks', () => { + beforeAll( () => { + [ 'core/audio', 'jetpack/story' ].forEach( ( name ) => + registerBlockType( name, { title: name } ) + ); + } ); + + describe( 'Allow ALL REGISTERED Blocks', () => { + it( `should show ALL REGISTERED block types when NONE are provided.`, () => { + // Arrange + const expectedHiddenBlockTypes = []; + const props = {}; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const { getPreference } = select( 'core/edit-post' ); + const actualHiddenBlockTypes = getPreference( + 'hiddenBlockTypes' + ); + expect( actualHiddenBlockTypes ).toEqual( + expectedHiddenBlockTypes + ); + } ); + + it( `should NOT hide ALL OTHER block types when the provided block type to be shown is UNREGISTERED.`, () => { + // Arrange + const expectedHiddenBlockTypes = []; + const props = { showBlocks: [ 'jetpack/unregistered' ] }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const { getPreference } = select( 'core/edit-post' ); + const actualHiddenBlockTypes = getPreference( + 'hiddenBlockTypes' + ); + expect( actualHiddenBlockTypes ).toEqual( + expectedHiddenBlockTypes + ); + } ); + + it( `should show ALL REGISTERED block types when ALL REGISTERED block types are provided to be shown.`, () => { + // Arrange + const expectedHiddenBlockTypes = []; + const props = { + showBlocks: [ 'core/audio', 'jetpack/story' ], + }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const { getPreference } = select( 'core/edit-post' ); + const actualHiddenBlockTypes = getPreference( + 'hiddenBlockTypes' + ); + expect( actualHiddenBlockTypes ).toEqual( + expectedHiddenBlockTypes + ); + } ); + + it( `should NOT hide the provided block type when it is UNREGISTERED.`, () => { + // Arrange + const expectedHiddenBlockTypes = []; + const props = { hideBlocks: [ 'jetpack/unregistered' ] }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const { getPreference } = select( 'core/edit-post' ); + const actualHiddenBlockTypes = getPreference( + 'hiddenBlockTypes' + ); + expect( actualHiddenBlockTypes ).toEqual( + expectedHiddenBlockTypes + ); + } ); + + it( `should NOT hide ANY provided block types that are ambiguously asked to be shown.`, () => { + // Arrange + const expectedHiddenBlockTypes = []; + const props = { + hideBlocks: [ 'core/audio', 'jetpack/story' ], + showBlocks: [ 'jetpack/story', 'core/audio' ], + }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const { getPreference } = select( 'core/edit-post' ); + const actualHiddenBlockTypes = getPreference( + 'hiddenBlockTypes' + ); + expect( actualHiddenBlockTypes ).toEqual( + expectedHiddenBlockTypes + ); + } ); + + it( `should NOT hide ANY provided UNREGISTERED block type that is ambiguously asked to be shown.`, () => { + // Arrange + const expectedHiddenBlockTypes = []; + const props = { + hideBlocks: [ 'jetpack/unregistered' ], + showBlocks: [ 'jetpack/unregistered' ], + }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const { getPreference } = select( 'core/edit-post' ); + const actualHiddenBlockTypes = getPreference( + 'hiddenBlockTypes' + ); + expect( actualHiddenBlockTypes ).toEqual( + expectedHiddenBlockTypes + ); + } ); } ); - jest.mock( - '../../jetpack/projects/plugins/jetpack/extensions/blocks/contact-info/editor.js', - () => jest.fn() - ); - jest.mock( - '../../jetpack/projects/plugins/jetpack/extensions/blocks/story/editor.js', - () => jest.fn() - ); - require( '../allowed-blocks-setup' ); + describe( 'Allow SOME REGISTERED Blocks', () => { + it( `should hide ALL OTHER block types when the provided block type to be shown is REGISTERED.`, () => { + // Arrange + const expectedHiddenBlockTypes = [ 'jetpack/story' ]; + const props = { showBlocks: [ 'core/audio' ] }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const { getPreference } = select( 'core/edit-post' ); + const actualHiddenBlockTypes = getPreference( + 'hiddenBlockTypes' + ); + expect( actualHiddenBlockTypes ).toEqual( + expectedHiddenBlockTypes + ); + } ); + + it( `should hide ALL OTHER block types when the provided block types to be shown are REGISTERED AND UNREGISTERED.`, () => { + // Arrange + const expectedHiddenBlockTypes = [ 'jetpack/story' ]; + const props = { + showBlocks: [ 'core/audio', 'jetpack/unregistered' ], + }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const { getPreference } = select( 'core/edit-post' ); + const actualHiddenBlockTypes = getPreference( + 'hiddenBlockTypes' + ); + expect( actualHiddenBlockTypes ).toEqual( + expectedHiddenBlockTypes + ); + } ); - expect( mockRegisterBlockCollection.mock.calls[ 0 ][ 0 ] ).toBe( - 'jetpack' - ); + it( `should hide ANY REGISTERED block types when ANY REGISTERED block types are provided to be hidden.`, () => { + // Arrange + const expectedHiddenBlockTypes = [ 'jetpack/story' ]; + const props = { hideBlocks: [ 'jetpack/story' ] }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const { getPreference } = select( 'core/edit-post' ); + const actualHiddenBlockTypes = getPreference( + 'hiddenBlockTypes' + ); + expect( actualHiddenBlockTypes ).toEqual( + expectedHiddenBlockTypes + ); + } ); + + it( `should hide ANY REGISTERED block types when the provided block types to be hidden are REGISTERED AND UNREGISTERED.`, () => { + // Arrange + const expectedHiddenBlockTypes = [ 'jetpack/story' ]; + const props = { + hideBlocks: [ 'jetpack/story', 'jetpack/unregistered' ], + }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const { getPreference } = select( 'core/edit-post' ); + const actualHiddenBlockTypes = getPreference( + 'hiddenBlockTypes' + ); + expect( actualHiddenBlockTypes ).toEqual( + expectedHiddenBlockTypes + ); + } ); + + it( `should hide ANY provided REGISTERED block types that are also UNAMBIGUOUSLY asked to be NOT shown.`, () => { + // Arrange + const expectedHiddenBlockTypes = [ 'jetpack/story' ]; + const props = { + hideBlocks: [ 'jetpack/story' ], + showBlocks: [ 'core/audio' ], + }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const { getPreference } = select( 'core/edit-post' ); + const actualHiddenBlockTypes = getPreference( + 'hiddenBlockTypes' + ); + expect( actualHiddenBlockTypes ).toEqual( + expectedHiddenBlockTypes + ); + } ); + } ); + + describe( 'Allow NO REGISTERED Blocks', () => { + it( `should hide ALL REGISTERED block types when ALL REGISTERED block types are provided to be hidden.`, () => { + // Arrange + const expectedHiddenBlockTypes = [ + 'core/audio', + 'jetpack/story', + ].sort(); + const props = { hideBlocks: [ 'core/audio', 'jetpack/story' ] }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const { getPreference } = select( 'core/edit-post' ); + const actualHiddenBlockTypes = getPreference( + 'hiddenBlockTypes' + ).sort(); + expect( actualHiddenBlockTypes ).toEqual( + expectedHiddenBlockTypes + ); + } ); + + it( `should hide ALL REGISTERED block types when NO block types are provided to be shown.`, () => { + // Arrange + const expectedHiddenBlockTypes = [ + 'core/audio', + 'jetpack/story', + ].sort(); + const props = { showBlocks: [] }; + require( '../index' ); + // Act + defaultHooks.doAction( 'native.render', props ); + // Assert + const { getPreference } = select( 'core/edit-post' ); + const actualHiddenBlockTypes = getPreference( + 'hiddenBlockTypes' + ).sort(); + expect( actualHiddenBlockTypes ).toEqual( + expectedHiddenBlockTypes + ); + } ); + } ); + + afterAll( () => { + [ 'core/audio', 'jetpack/story' ].forEach( ( name ) => { + unregisterBlockType( name ); + } ); + } ); } ); } );