Add showPopout opt-out to plugin RHS registerRightHandSidebarComponent (#37125) (#37134)

Automatic Merge
This commit is contained in:
Mattermost Build
2026-06-22 17:05:16 +02:00
committed by GitHub
parent 07db1f510c
commit 1e16b87ac6
5 changed files with 156 additions and 2 deletions
+5
View File
@@ -1095,6 +1095,7 @@ export default class PluginRegistry {
* Accepts the following:
* - component - A react component to display in the Right-Hand Sidebar.
* - title - A string or JSX element to display as a title for the RHS.
* - showPopout - Optional boolean (default: true). Set to false to hide the "Open in new window" button in the RHS header.
* Returns:
* - id: a unique identifier
* - showRHSPlugin: the action to dispatch that will open the RHS.
@@ -1104,12 +1105,15 @@ export default class PluginRegistry {
registerRightHandSidebarComponent = reArg([
'component',
'title',
'showPopout',
], ({
component,
title,
showPopout = true,
}: {
component: RightHandSidebarComponent['component'];
title: ReactResolvable;
showPopout?: boolean;
}) => {
const id = generateId();
@@ -1118,6 +1122,7 @@ export default class PluginRegistry {
pluginId: this.id,
component,
title: resolveReactElement(title),
showPopout,
});
return {id, showRHSPlugin: showRHSPlugin(id), hideRHSPlugin: hideRHSPlugin(id), toggleRHSPlugin: toggleRHSPlugin(id)};
@@ -21,6 +21,7 @@ function mapStateToProps(state: GlobalState) {
pluggableId,
title: pluginTitle,
pluginId,
showPopout: pluginComponent?.showPopout ?? true,
};
}
@@ -0,0 +1,146 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import type {DeepPartial} from '@mattermost/types/utilities';
import {renderWithContext, screen} from 'tests/react_testing_utils';
import type {GlobalState} from 'types/store';
import RhsPlugin from './rhs_plugin';
import ConnectedRhsPlugin from '.';
jest.mock('components/search_results_header', () => ({
__esModule: true,
default: ({children, newWindowHandler}: {children: React.ReactNode; newWindowHandler?: () => void}) => (
<div
data-testid='search-results-header'
data-has-window-handler={newWindowHandler === undefined ? 'false' : 'true'}
>
{children}
</div>
),
}));
jest.mock('plugins/pluggable', () => ({
__esModule: true,
default: () => <div data-testid='pluggable'/>,
}));
jest.mock('utils/popouts/popout_windows', () => ({
popoutRhsPlugin: jest.fn(),
}));
const baseState: DeepPartial<GlobalState> = {
entities: {
general: {config: {}},
teams: {
currentTeamId: 'team-id',
teams: {'team-id': {id: 'team-id', name: 'test-team'}},
},
channels: {
currentChannelId: 'channel-id',
channels: {'channel-id': {id: 'channel-id', name: 'test-channel'}},
},
preferences: {myPreferences: {}},
},
plugins: {
plugins: {'plugin-id': {name: 'Test Plugin'}},
components: {
RightHandSidebarComponent: [],
},
},
views: {
rhs: {pluggableId: ''},
},
};
describe('RhsPlugin', () => {
describe('component', () => {
it('passes newWindowHandler to SearchResultsHeader when showPopout is true', () => {
renderWithContext(
<RhsPlugin
showPluggable={true}
pluggableId='pluggable-id'
title='Test Title'
pluginId='plugin-id'
showPopout={true}
/>,
baseState,
);
expect(screen.getByTestId('search-results-header')).toHaveAttribute('data-has-window-handler', 'true');
});
it('passes undefined for newWindowHandler to SearchResultsHeader when showPopout is false', () => {
renderWithContext(
<RhsPlugin
showPluggable={true}
pluggableId='pluggable-id'
title='Test Title'
pluginId='plugin-id'
showPopout={false}
/>,
baseState,
);
expect(screen.getByTestId('search-results-header')).toHaveAttribute('data-has-window-handler', 'false');
});
it('defaults showPopout to true when the prop is omitted', () => {
renderWithContext(
<RhsPlugin
showPluggable={true}
pluggableId='pluggable-id'
title='Test Title'
pluginId='plugin-id'
/>,
baseState,
);
expect(screen.getByTestId('search-results-header')).toHaveAttribute('data-has-window-handler', 'true');
});
});
describe('mapStateToProps', () => {
const pluggableId = 'pluggable-id';
function stateWithRegisteredComponent(showPopout?: boolean): DeepPartial<GlobalState> {
const component: Record<string, unknown> = {
id: pluggableId,
pluginId: 'plugin-id',
title: 'Test Title',
};
if (showPopout !== undefined) {
component.showPopout = showPopout;
}
return {
...baseState,
plugins: {
...baseState.plugins,
components: {
RightHandSidebarComponent: [component as any],
},
},
views: {
rhs: {pluggableId},
},
};
}
it('defaults showPopout to true when component showPopout field is undefined', () => {
renderWithContext(<ConnectedRhsPlugin/>, stateWithRegisteredComponent(undefined));
expect(screen.getByTestId('search-results-header')).toHaveAttribute('data-has-window-handler', 'true');
});
it('passes showPopout: false through to hide the popout button', () => {
renderWithContext(<ConnectedRhsPlugin/>, stateWithRegisteredComponent(false));
expect(screen.getByTestId('search-results-header')).toHaveAttribute('data-has-window-handler', 'false');
});
});
});
@@ -21,9 +21,10 @@ export type Props = {
pluggableId: string;
title: React.ReactNode;
pluginId?: string;
showPopout?: boolean;
};
const RhsPlugin = ({showPluggable, pluggableId, title, pluginId}: Props) => {
const RhsPlugin = ({showPluggable, pluggableId, title, pluginId, showPopout = true}: Props) => {
const intl = useIntl();
const currentTeam = useSelector(getCurrentTeam);
const currentChannel = useSelector(getCurrentChannel);
@@ -51,7 +52,7 @@ const RhsPlugin = ({showPluggable, pluggableId, title, pluginId}: Props) => {
id='rhsContainer'
className='sidebar-right__body'
>
<SearchResultsHeader newWindowHandler={newWindowHandler}>
<SearchResultsHeader newWindowHandler={showPopout ? newWindowHandler : undefined}>
{title}
</SearchResultsHeader>
{
@@ -350,6 +350,7 @@ export type PostDropdownMenuItemComponent = PluginComponent & {
export type RightHandSidebarComponent = PluginComponent & {
title: PluggableText;
component: React.ComponentType<BasePluggableProps>;
showPopout?: boolean;
};
export type SearchHintsComponent = PluginComponent & {