Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/block-editor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

### Bug Fixes

- Block inspector: Restore the Settings tab for a block whose only advanced tool is "Allowed blocks" (e.g. a container with `allowedBlocks` and no anchor), by counting the allowed-blocks fills when deciding whether to show the tab. The control is also now hidden while editing a section (pattern) block, matching the other inspector controls ([#84067](https://github.com/WordPress/gutenberg/pull/84067)).
- Fix the translator comments for the "blocks moved" and "blocks added" screen reader messages, which described the count as a block name ([#83555](https://github.com/WordPress/gutenberg/pull/83555)).
- `BlockCompare`: Show whitespace differences in the Resolve Block dialog. The markup's spaces and blank lines are kept instead of collapsed by the browser, and each added or removed line break gets a visible marker, so a diff made only of whitespace no longer looks identical on both sides. The dialog also shows the markup of a converted Custom HTML block, which is kept in its `innerContent` rather than produced by `save`, instead of an empty diff ([#82397](https://github.com/WordPress/gutenberg/pull/82397)).
- Inserter: Stop re-rendering the block list when the block selection changes while the inserter is open ([#83284](https://github.com/WordPress/gutenberg/pull/83284)).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, expect, it, vi } from 'vitest';
import { renderHook } from '@testing-library/react';
import { SlotFillProvider } from '@wordpress/components';
import type { ReactNode } from 'react';
import useInspectorControlsTabs from '../use-inspector-controls-tabs';
import { TAB_LIST_VIEW, TAB_SETTINGS, TAB_STYLES } from '../utils';
import groups, {
PrivateInspectorControlsAllowedBlocks,
} from '../../inspector-controls/groups';

vi.mock( import( '@wordpress/data' ), async ( importOriginal ) => {
const original = await importOriginal();
return {
...original,
useSelect: vi
.fn( original.useSelect )
.mockReturnValue( { tabSettings: {}, isPreviewMode: false } ),
};
} );

describe( 'useInspectorControlsTabs', () => {
it( 'includes Settings when Allowed blocks is the only settings control', () => {
const { result } = renderHook(
() =>
useInspectorControlsTabs( 'test/container', [], false, false ),
{
wrapper: ( { children }: { children: ReactNode } ) => (
<SlotFillProvider>
<groups.list.Fill>List View</groups.list.Fill>
<groups.layout.Fill>Layout</groups.layout.Fill>
<PrivateInspectorControlsAllowedBlocks.Fill>
Allowed blocks
</PrivateInspectorControlsAllowedBlocks.Fill>
{ children }
</SlotFillProvider>
),
}
);

expect( result.current ).toEqual( [
TAB_LIST_VIEW,
TAB_SETTINGS,
TAB_STYLES,
] );
} );
} );
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { __experimentalUseSlotFills as useSlotFills } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import InspectorControlsGroups from '../inspector-controls/groups';
import InspectorControlsGroups, {
PrivateInspectorControlsAllowedBlocks,
} from '../inspector-controls/groups';
import { InspectorAdvancedControls } from '../inspector-controls';
import { TAB_LIST_VIEW, TAB_SETTINGS, TAB_STYLES, TAB_CONTENT } from './utils';
import { store as blockEditorStore } from '../../store';
Expand Down Expand Up @@ -69,9 +71,14 @@ export default function useInspectorControlsTabs(
// (i.e. both list view and styles), check only the default
// InspectorControls slots. If we have multiple tabs, we'll need to check
// the advanced controls slot as well to ensure they are rendered.
//
// The allowed-blocks control renders inside the advanced panel via its own
// private slot, so its fills are counted here too — otherwise a block whose
// only advanced tool is Allowed blocks would lose its Settings tab.
const advancedFills = [
...( useSlotFills( InspectorAdvancedControls.slotName ) || [] ),
...( useSlotFills( bindingsGroup.name ) || [] ),
...( useSlotFills( PrivateInspectorControlsAllowedBlocks.name ) || [] ),
];

const settingsFills = [
Expand Down
5 changes: 5 additions & 0 deletions packages/block-editor/src/hooks/allowed-blocks.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ function BlockEditAllowedBlocksControlPure( { clientId } ) {
export default {
edit: BlockEditAllowedBlocksControlPure,
attributeKeys: [ 'allowedBlocks' ],
// The control fills its own private slot rather than going through
// InspectorControls, so it isn't hidden from section (pattern) blocks by
// InspectorControlsFill. Opt out of rendering during pattern editing so a
// section doesn't gain a Settings tab holding only Manage allowed blocks.
supportsPatternEditing: false,
hasSupport( name ) {
return hasBlockSupport( name, 'allowedBlocks' );
},
Expand Down
60 changes: 60 additions & 0 deletions packages/block-editor/src/hooks/test/utils.jsdom.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { afterEach, describe, expect, it } from 'vitest';
import { render, screen } from '@testing-library/react';
import { applyFilters, removeFilter } from '@wordpress/hooks';
import type { ComponentType } from 'react';
import { createBlockEditFilter } from '../utils';
import {
BlockEditContextProvider,
mayDisplayControlsKey,
mayDisplayPatternEditingControlsKey,
} from '../../components/block-edit/context';

describe( 'createBlockEditFilter', () => {
afterEach( () => {
removeFilter( 'editor.BlockEdit', 'core/editor/hooks' );
} );

it.each( [
{ supportsPatternEditing: true, isSelected: false, renders: true },
{ supportsPatternEditing: true, isSelected: true, renders: true },
{
supportsPatternEditing: undefined,
isSelected: false,
renders: false,
},
{ supportsPatternEditing: undefined, isSelected: true, renders: true },
{ supportsPatternEditing: false, isSelected: false, renders: false },
{ supportsPatternEditing: false, isSelected: true, renders: false },
] )(
'with supportsPatternEditing=$supportsPatternEditing and selection=$isSelected, renders=$renders during pattern editing',
( { supportsPatternEditing, isSelected, renders } ) => {
createBlockEditFilter( [
{
edit: () => <span>Feature control</span>,
hasSupport: () => true,
supportsPatternEditing,
},
] );
const FilteredEdit = applyFilters(
'editor.BlockEdit',
() => null
) as ComponentType< { name: string; attributes: object } >;
const context = {
name: 'test/container',
isSelected,
[ mayDisplayControlsKey ]: isSelected,
[ mayDisplayPatternEditingControlsKey ]: true,
};

render(
<BlockEditContextProvider value={ context }>
<FilteredEdit name="test/container" attributes={ {} } />
</BlockEditContextProvider>
);

expect( screen.queryByText( 'Feature control' ) !== null ).toBe(
renders
);
}
);
} );
13 changes: 12 additions & 1 deletion packages/block-editor/src/hooks/utils.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -565,8 +565,19 @@ export function createBlockEditFilter( features ) {
shareWithChildBlocks,
supportsPatternEditing,
} = feature;
// Explicit `false` hides the feature during pattern editing,
// even when the block is selected.
if (
context[ mayDisplayPatternEditingControlsKey ] &&
supportsPatternEditing === false
) {
return null;
}

const shouldDisplayControls =
( supportsPatternEditing &&
// Explicit `true` also shows controls for unselected blocks
// in the section. `undefined` uses the selection/parent rules.
( supportsPatternEditing === true &&
context[
mayDisplayPatternEditingControlsKey
] ) ||
Expand Down
8 changes: 5 additions & 3 deletions packages/block-editor/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@
{ "path": "../warning" }
],
/*
* The package has no TypeScript tests, and its two stories import
* components the build allowlist does not type yet, so nothing is checked.
* These dev files import sources outside the build's TypeScript allowlist.
* TypeScript unit tests are checked separately by test:unit:conventions.
*/
"exclude": [
"**/benchmark",
"src/components/block-icon/stories/index.story.ts",
"src/components/contrast-checker/stories/index.story.ts"
"src/components/contrast-checker/stories/index.story.ts",
"src/components/inspector-controls-tabs/test/use-inspector-controls-tabs.jsdom.test.tsx",
"src/hooks/test/utils.jsdom.test.tsx"
]
}
Loading