diff --git a/src/app-layout/__tests__/multi-layout-props.test.tsx b/src/app-layout/__tests__/multi-layout-props.test.tsx new file mode 100644 index 0000000000..1c92492c3c --- /dev/null +++ b/src/app-layout/__tests__/multi-layout-props.test.tsx @@ -0,0 +1,109 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React from 'react'; + +import { + mergeMultiAppLayoutProps, + SharedMultiAppLayoutProps, +} from '../../../lib/components/app-layout/visual-refresh-toolbar/multi-layout'; + +describe('mergeMultiAppLayoutProps', () => { + const mockParentNavigationToggle = jest.fn(); + const mockParentActiveDrawerChange = jest.fn(); + const mockParentSplitPanelToggle = jest.fn(); + const ownProps: SharedMultiAppLayoutProps = { + forceDeduplicationType: 'primary', + ariaLabels: { + navigation: 'Navigation', + drawers: 'Drawers', + }, + navigation:
Navigation
, + navigationOpen: true, + navigationFocusRef: React.createRef(), + onNavigationToggle: mockParentNavigationToggle, + breadcrumbs:
Breadcrumbs
, + activeDrawerId: 'drawer1', + drawers: [ + { + id: 'drawer1', + ariaLabels: { drawerName: 'Drawer 1' }, + content:
Drawer 1 Content
, + }, + ], + onActiveDrawerChange: mockParentActiveDrawerChange, + drawersFocusRef: React.createRef(), + splitPanel:
Split Panel
, + splitPanelToggleProps: { + displayed: false, + active: false, + position: 'bottom', + controlId: 'test', + ariaLabel: 'test', + }, + splitPanelFocusRef: React.createRef(), + onSplitPanelToggle: mockParentSplitPanelToggle, + }; + + const additionalPropsBase: Partial[] = [ + { + ariaLabels: { + navigation: 'New Navigation', + }, + drawers: [ + { + id: 'drawer2', + ariaLabels: { drawerName: 'Drawer 2' }, + content:
Drawer 2 Content
, + }, + ], + activeDrawerId: 'drawer2', + }, + { + splitPanelToggleProps: { + displayed: false, + active: false, + position: 'bottom', + controlId: 'test', + ariaLabel: 'test', + }, + }, + ]; + + it('should merge ownProps and additionalProps correctly', () => { + const result = mergeMultiAppLayoutProps(ownProps, additionalPropsBase); + + expect(result).toEqual({ + //asserting new aria labels overwrite existing yet preserve others + ariaLabels: { + navigation: 'New Navigation', + drawers: 'Drawers', + }, + hasNavigation: true, + navigationOpen: true, + navigationFocusRef: ownProps.navigationFocusRef, + onNavigationToggle: mockParentNavigationToggle, + hasBreadcrumbsPortal: true, + hasSplitPanel: true, + splitPanelToggleProps: { + displayed: false, + active: false, + position: 'bottom', + controlId: 'test', + ariaLabel: 'test', + }, + splitPanelFocusRef: ownProps.splitPanelFocusRef, + onSplitPanelToggle: mockParentSplitPanelToggle, + //asserting the ownProps drawer is not overwritten + activeDrawerId: ownProps.activeDrawerId, + drawers: ownProps.drawers, + drawersFocusRef: ownProps.drawersFocusRef, + onActiveDrawerChange: mockParentActiveDrawerChange, + }); + }); + + it('should return null if no fields are defined, except ariaLabels', () => { + const result = mergeMultiAppLayoutProps({ ariaLabels: {} } as SharedMultiAppLayoutProps, []); + + expect(result).toBeNull(); + }); +}); diff --git a/src/app-layout/__tests__/multi-layout.test.tsx b/src/app-layout/__tests__/multi-layout.test.tsx index 68f46c209e..f44aaf10de 100644 --- a/src/app-layout/__tests__/multi-layout.test.tsx +++ b/src/app-layout/__tests__/multi-layout.test.tsx @@ -75,7 +75,26 @@ describeEachAppLayout({ themes: ['refresh-toolbar'], sizes: ['desktop'] }, () => expect(isDrawerClosed(firstLayout.findNavigation())).toEqual(false); }); - test('merges tools from two instances', async () => { + test('navigationHide in primary is respected when navigation is defined when merging from two instances', async () => { + const { firstLayout, secondLayout } = await renderAsync( + + } + /> + ); + expect(firstLayout.findNavigation()).toBeFalsy(); + expect(firstLayout.findNavigationToggle()).toBeFalsy(); + expect(secondLayout.findNavigation()).toBeFalsy(); + expect(secondLayout.findNavigationToggle()).toBeFalsy(); + }); + + test('merges tools from two instances with where navigationHide is true in secondary', async () => { const { firstLayout, secondLayout } = await renderAsync( expect(createWrapper().findAllByClassName(testUtilStyles.tools)).toHaveLength(1); firstLayout.findToolsToggle().click(); + expect(secondLayout.findNavigation()).toBeFalsy(); + expect(secondLayout.findNavigationToggle()).toBeFalsy(); expect(isDrawerClosed(secondLayout.findTools())).toEqual(false); }); diff --git a/src/app-layout/visual-refresh-toolbar/index.tsx b/src/app-layout/visual-refresh-toolbar/index.tsx index 6af2cccfa2..0f7d94c76b 100644 --- a/src/app-layout/visual-refresh-toolbar/index.tsx +++ b/src/app-layout/visual-refresh-toolbar/index.tsx @@ -235,8 +235,11 @@ const AppLayoutVisualRefreshToolbar = React.forwardRef splitPanelFocusControl.refs.slider.current?.focus(), })); - const resolvedNavigation = navigationHide ? null : navigation ?? <>; const resolvedStickyNotifications = !!stickyNotifications && !isMobile; + //navigation must be null if hidden so toolbar knows to hide the toggle button + const resolvedNavigation = navigationHide ? null : navigation ?? <>; + //navigation must not be open if navigationHide is true + const resolvedNavigationOpen = !!resolvedNavigation && navigationOpen; const { maxDrawerSize, maxSplitPanelSize, @@ -248,7 +251,7 @@ const AppLayoutVisualRefreshToolbar = React.forwardRef} - navigationOpen={navigationOpen} + navigationOpen={resolvedNavigationOpen} navigationWidth={navigationWidth} tools={drawers && drawers.length > 0 && } globalTools={ diff --git a/src/app-layout/visual-refresh-toolbar/multi-layout.ts b/src/app-layout/visual-refresh-toolbar/multi-layout.ts index 5c62017f98..835458d8b7 100644 --- a/src/app-layout/visual-refresh-toolbar/multi-layout.ts +++ b/src/app-layout/visual-refresh-toolbar/multi-layout.ts @@ -10,7 +10,7 @@ import { AppLayoutProps } from '../interfaces'; import { Focusable } from '../utils/use-focus-control'; import { SplitPanelToggleProps, ToolbarProps } from './toolbar'; -interface SharedProps { +export interface SharedMultiAppLayoutProps { forceDeduplicationType?: 'primary' | 'secondary'; ariaLabels: AppLayoutProps.Labels | undefined; navigation: React.ReactNode; @@ -39,7 +39,10 @@ function checkAlreadyExists(value: boolean, propName: string) { return false; } -function mergeProps(ownProps: SharedProps, additionalProps: ReadonlyArray>): ToolbarProps | null { +export function mergeMultiAppLayoutProps( + ownProps: SharedMultiAppLayoutProps, + additionalProps: ReadonlyArray> +): ToolbarProps | null { const toolbar: ToolbarProps = {}; for (const props of [ownProps, ...additionalProps]) { toolbar.ariaLabels = Object.assign(toolbar.ariaLabels ?? {}, props.ariaLabels); @@ -50,6 +53,8 @@ function mergeProps(ownProps: SharedProps, additionalProps: ReadonlyArray key !== 'ariaLabels').length > 0 ? toolbar : null; } -export function useMultiAppLayout(props: SharedProps) { - const [registration, setRegistration] = useState | null>(null); +export function useMultiAppLayout(props: SharedMultiAppLayoutProps) { + const [registration, setRegistration] = useState | null>(null); const { forceDeduplicationType } = props; useLayoutEffect(() => { return awsuiPluginsInternal.appLayoutWidget.register(forceDeduplicationType, props => - setRegistration(props as RegistrationState) + setRegistration(props as RegistrationState) ); }, [forceDeduplicationType]); @@ -87,6 +92,7 @@ export function useMultiAppLayout(props: SharedProps) { return { registered: !!registration?.type, - toolbarProps: registration?.type === 'primary' ? mergeProps(props, registration.discoveredProps) : null, + toolbarProps: + registration?.type === 'primary' ? mergeMultiAppLayoutProps(props, registration.discoveredProps) : null, }; }