Skip to content

Commit

Permalink
feat(content-sidebar): Added panel selection preservation (#3753)
Browse files Browse the repository at this point in the history
* feat(content-sidebar): Added panel selection preservation

* feat(content-sidebar): Added panel selection preservation

* feat(content-sidebar): Added panel selection preservation

* feat(content-sidebar): Added panel selection preservation

* feat(content-sidebar): Added panel selection preservation

* feat(content-sidebar): Added panel selection preservation
  • Loading branch information
kkuliczkowski-box authored Nov 27, 2024
1 parent e0db445 commit 60b7109
Show file tree
Hide file tree
Showing 7 changed files with 375 additions and 37 deletions.
26 changes: 23 additions & 3 deletions src/elements/content-sidebar/Sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import SidebarNav from './SidebarNav';
import SidebarPanels from './SidebarPanels';
import SidebarUtils from './SidebarUtils';
import { withCurrentUser } from '../common/current-user';
import { withFeatureConsumer } from '../common/feature-checking';
import { isFeatureEnabled, withFeatureConsumer } from '../common/feature-checking';
import type { FeatureConfig } from '../common/feature-checking';
import type { ActivitySidebarProps } from './ActivitySidebar';
import type { DetailsSidebarProps } from './DetailsSidebar';
Expand Down Expand Up @@ -75,6 +75,7 @@ type State = {
export const SIDEBAR_FORCE_KEY: 'bcs.force' = 'bcs.force';
export const SIDEBAR_FORCE_VALUE_CLOSED: 'closed' = 'closed';
export const SIDEBAR_FORCE_VALUE_OPEN: 'open' = 'open';
export const SIDEBAR_SELECTED_PANEL_KEY: 'sidebar-selected-panel' = 'sidebar-selected-panel';

class Sidebar extends React.Component<Props, State> {
static defaultProps = {
Expand Down Expand Up @@ -252,6 +253,24 @@ class Sidebar extends React.Component<Props, State> {
}
}

getDefaultPanel(): string | typeof undefined {
const { features } = this.props;

if (!isFeatureEnabled(features, 'panelSelectionPreservation')) {
return undefined;
}

return this.store.getItem(SIDEBAR_SELECTED_PANEL_KEY) || undefined;
}

handlePanelChange = (name: string) => {
const { features, onPanelChange = noop } = this.props;
if (isFeatureEnabled(features, 'panelSelectionPreservation')) {
this.store.setItem(SIDEBAR_SELECTED_PANEL_KEY, name);
}
onPanelChange(name);
};

render() {
const {
activitySidebarProps,
Expand All @@ -274,7 +293,6 @@ class Sidebar extends React.Component<Props, State> {
metadataEditors,
metadataSidebarProps,
onAnnotationSelect,
onPanelChange,
onVersionChange,
versionsSidebarProps,
}: Props = this.props;
Expand All @@ -288,6 +306,7 @@ class Sidebar extends React.Component<Props, State> {
const styleClassName = classNames('be bcs', className, {
'bcs-is-open': isOpen,
});
const defaultPanel = this.getDefaultPanel();

return (
<aside id={this.id} className={styleClassName} data-testid="preview-sidebar">
Expand All @@ -310,7 +329,7 @@ class Sidebar extends React.Component<Props, State> {
hasSkills={hasSkills}
hasDocGen={docGenSidebarProps.isDocGenTemplate}
isOpen={isOpen}
onPanelChange={onPanelChange}
onPanelChange={this.handlePanelChange}
/>
)}
<SidebarPanels
Expand All @@ -319,6 +338,7 @@ class Sidebar extends React.Component<Props, State> {
currentUser={currentUser}
currentUserError={currentUserError}
elementId={this.id}
defaultPanel={defaultPanel}
detailsSidebarProps={detailsSidebarProps}
docGenSidebarProps={docGenSidebarProps}
file={file}
Expand Down
17 changes: 16 additions & 1 deletion src/elements/content-sidebar/SidebarPanels.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type Props = {
boxAISidebarProps: BoxAISidebarProps,
currentUser?: User,
currentUserError?: Errors,
defaultPanel?: string,
detailsSidebarProps: DetailsSidebarProps,
docGenSidebarProps: DocGenSidebarProps,
elementId: string,
Expand Down Expand Up @@ -184,6 +185,7 @@ class SidebarPanels extends React.Component<Props, State> {
boxAISidebarProps,
currentUser,
currentUserError,
defaultPanel = '',
detailsSidebarProps,
docGenSidebarProps,
elementId,
Expand Down Expand Up @@ -212,6 +214,17 @@ class SidebarPanels extends React.Component<Props, State> {
const isMetadataSidebarRedesignEnabled = isFeatureEnabled(features, 'metadata.redesign.enabled');
const isMetadataAiSuggestionsEnabled = isFeatureEnabled(features, 'metadata.aiSuggestions.enabled');

const panelsEligibility = {
[SIDEBAR_VIEW_BOXAI]: hasBoxAI,
[SIDEBAR_VIEW_DOCGEN]: hasDocGen,
[SIDEBAR_VIEW_SKILLS]: hasSkills,
[SIDEBAR_VIEW_ACTIVITY]: hasActivity,
[SIDEBAR_VIEW_DETAILS]: hasDetails,
[SIDEBAR_VIEW_METADATA]: hasMetadata,
};

const showDefaultPanel: boolean = !!(defaultPanel && panelsEligibility[defaultPanel]);

if (!isOpen || (!hasBoxAI && !hasActivity && !hasDetails && !hasMetadata && !hasSkills && !hasVersions)) {
return null;
}
Expand Down Expand Up @@ -369,7 +382,9 @@ class SidebarPanels extends React.Component<Props, State> {
render={() => {
let redirect = '';

if (hasBoxAI) {
if (showDefaultPanel) {
redirect = defaultPanel;
} else if (hasBoxAI) {
redirect = SIDEBAR_VIEW_BOXAI;
} else if (hasDocGen) {
redirect = SIDEBAR_VIEW_DOCGEN;
Expand Down
5 changes: 5 additions & 0 deletions src/elements/content-sidebar/__mocks__/SidebarUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ export default {
return <div data-testid="versions-sidebar" />;
}
},
docgen: class DocGenSidebar extends React.Component {
render() {
return <div data-testid="docgen-sidebar" />;
}
},
}[panelName];
}),
};
176 changes: 157 additions & 19 deletions src/elements/content-sidebar/__tests__/Sidebar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,21 @@ import {
SIDEBAR_FORCE_KEY,
SIDEBAR_FORCE_VALUE_CLOSED,
SIDEBAR_FORCE_VALUE_OPEN,
SIDEBAR_SELECTED_PANEL_KEY,
SidebarComponent as Sidebar,
} from '../Sidebar';
import SidebarNav from '../SidebarNav';
import SidebarPanels from '../SidebarPanels';
import LocalStore from '../../../utils/LocalStore';

jest.mock('../SidebarNav', () => ({
__esModule: true,
default: jest.fn(() => 'SidebarNav'),
}));
jest.mock('../SidebarPanels', () => ({
__esModule: true,
default: jest.fn(() => 'SidebarPanels'),
}));
jest.mock('../../common/async-load', () => () => 'LoadableComponent');
jest.mock('../../../utils/LocalStore');

Expand Down Expand Up @@ -41,6 +52,12 @@ describe('elements/content-sidebar/Sidebar', () => {

const getWrapper = props => shallow(<Sidebar {...defaultProps} {...props} />);

const getSidebar = props => (
<MemoryRouter initialEntries={['/']}>
<Sidebar {...defaultProps} {...props} />
</MemoryRouter>
);

beforeEach(() => {
LocalStore.mockClear();
});
Expand Down Expand Up @@ -176,19 +193,6 @@ describe('elements/content-sidebar/Sidebar', () => {
});
describe('open state change', () => {
const mockOnOpenChange = jest.fn();
const getSidebar = open => (
<MemoryRouter initialEntries={['/']}>
<Sidebar
{...defaultProps}
file={file}
location={{
pathname: '/',
state: { open },
}}
onOpenChange={mockOnOpenChange}
/>
</MemoryRouter>
);

afterEach(() => {
mockOnOpenChange.mockClear();
Expand All @@ -203,9 +207,25 @@ describe('elements/content-sidebar/Sidebar', () => {
`(
'given previous open state = $prevOpen and new open state = $open should call onOpenChange with $open',
({ prevOpen, open }) => {
const { rerender } = render(getSidebar(prevOpen));

rerender(getSidebar(open));
const { rerender } = render(
getSidebar({
location: {
pathname: '/',
state: { open: prevOpen },
},
onOpenChange: mockOnOpenChange,
}),
);

rerender(
getSidebar({
location: {
pathname: '/',
state: { open },
},
onOpenChange: mockOnOpenChange,
}),
);

expect(mockOnOpenChange).toBeCalledWith(open);
},
Expand All @@ -217,9 +237,25 @@ describe('elements/content-sidebar/Sidebar', () => {
`(
'given previous open state = $prevOpen and new open state = $open should not call onOpenChange',
({ prevOpen, open }) => {
const { rerender } = render(getSidebar(prevOpen));

rerender(getSidebar(open));
const { rerender } = render(
getSidebar({
location: {
pathname: '/',
state: { open: prevOpen },
},
onOpenChange: mockOnOpenChange,
}),
);

rerender(
getSidebar({
location: {
pathname: '/',
state: { open },
},
onOpenChange: mockOnOpenChange,
}),
);

expect(mockOnOpenChange).not.toBeCalled();
},
Expand Down Expand Up @@ -362,6 +398,46 @@ describe('elements/content-sidebar/Sidebar', () => {

expect(wrapper.exists('SidebarNav')).toBe(false);
});

describe('SidebarPanels', () => {
const mockGetItem = jest.fn();
const MockSidebarPanels = jest.fn(() => 'SidebarPanels');

beforeEach(() => {
LocalStore.mockImplementationOnce(() => ({
getItem: mockGetItem,
setItem: jest.fn(),
}));
SidebarPanels.mockImplementationOnce(MockSidebarPanels);
});

afterEach(() => {
mockGetItem.mockClear();
MockSidebarPanels.mockClear();
});
test.each`
panelSelectionPreservation | savedDefaultPanel | expected
${true} | ${'activity'} | ${'activity'}
${true} | ${'details'} | ${'details'}
${true} | ${null} | ${undefined}
${false} | ${'activity'} | ${undefined}
${undefined} | ${'activity'} | ${undefined}
`(
'should render SidebarPanels with defaultPanel prop = $defaultPanel, given sidebar selected panel saved in LocalStore is $defaultPanel and panelSelectionPreservation feature = $panelSelectionPreservation',
({ panelSelectionPreservation, savedDefaultPanel, expected }) => {
mockGetItem.mockReturnValue(savedDefaultPanel);
render(
getSidebar({
features: { panelSelectionPreservation },
}),
);
expect(MockSidebarPanels).toHaveBeenCalledWith(
expect.objectContaining({ defaultPanel: expected }),
{},
);
},
);
});
});

describe('refresh()', () => {
Expand All @@ -375,4 +451,66 @@ describe('elements/content-sidebar/Sidebar', () => {
expect(refresh).toHaveBeenCalledWith(shouldRefreshCache);
});
});

describe('on panel change', () => {
const mockSetItem = jest.fn();
const mockPanelName = 'activity';

beforeEach(() => {
LocalStore.mockImplementationOnce(() => ({
getItem: jest.fn(),
setItem: mockSetItem,
}));

SidebarNav.mockImplementationOnce(({ onPanelChange }) => {
onPanelChange(mockPanelName);
return 'SidebarNav';
});
});

afterEach(() => {
mockSetItem.mockClear();
});

test('should call onPanelChange prop', () => {
const mockOnPanelChange = jest.fn();
render(
getSidebar({
hasNav: true,
onPanelChange: mockOnPanelChange,
}),
);

expect(mockOnPanelChange).toHaveBeenCalledWith(mockPanelName);
});

test('given panelSelectionPreservation feature = true should save panel name in LocalStore', () => {
render(
getSidebar({
features: { panelSelectionPreservation: true },
hasNav: true,
}),
);

expect(mockSetItem).toHaveBeenCalledWith(SIDEBAR_SELECTED_PANEL_KEY, mockPanelName);
});

test.each`
panelSelectionPreservation
${undefined}
${false}
`(
'given panelSelectionPreservation feature = $panelSelectionPreservation should not save panel name in LocalStore',
({ panelSelectionPreservation }) => {
render(
getSidebar({
features: { panelSelectionPreservation },
hasNav: true,
}),
);

expect(mockSetItem).not.toHaveBeenCalled();
},
);
});
});
Loading

0 comments on commit 60b7109

Please sign in to comment.