Skip to content

Commit 6eb9603

Browse files
committed
fix(cv-header): fix crash when focusing out of right panels
when focusing out of right panels, the emitted event contains a ref to the panel, but the access was not using '.value' to get the actual DOM element, crashing the rest of the logic that relied on it tests were also added to cover the focus processing.
1 parent 1c05423 commit 6eb9603

File tree

3 files changed

+98
-3
lines changed

3 files changed

+98
-3
lines changed

src/components/CvUIShell/CvHeader.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,10 @@ function onCvPanelFocusout(payload) {
192192
item => item.ariaControls === srcComponent.id
193193
);
194194
if (
195-
srcComponent.el !== srcEvent.relatedTarget &&
196-
!srcComponent.el.contains(srcEvent.relatedTarget) &&
195+
srcComponent.el.value !== srcEvent.relatedTarget &&
196+
!srcComponent.el.value.contains(srcEvent.relatedTarget) &&
197197
found &&
198-
found.el !== srcEvent.relatedTarget
198+
found.el.value !== srcEvent.relatedTarget
199199
) {
200200
onCvPanelControlToggle(found, false);
201201
}

src/components/CvUIShell/__tests__/CvHeader.spec.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import CvSideNavItems from '../CvSideNavItems.vue';
99
import CvSideNavMenu from '../CvSideNavMenu.vue';
1010
import CvSideNavMenuItem from '../CvSideNavMenuItem.vue';
1111
import CvSideNavLink from '../CvSideNavLink.vue';
12+
import PanelFocusTestComponent from './PanelFocusTestComponent.vue';
13+
import { nextTick } from 'vue';
1214

1315
const globalHeader = {
1416
components: { CvHeaderGlobalAction },
@@ -101,6 +103,52 @@ describe('CvHeader', () => {
101103
expect(onResize.mock.calls.length).toBe(2);
102104
});
103105

106+
it('should allow user to focus between elements in the right panel', async () => {
107+
const user = userEvent.setup();
108+
const component = render(PanelFocusTestComponent);
109+
110+
const testPanel = await component.findByTestId('links-panel');
111+
expect(testPanel.getAttribute('aria-hidden')).toBe('true'); // panel is closed
112+
113+
const actionButton = await component.findByTestId('action-button');
114+
await user.click(actionButton);
115+
116+
expect(testPanel.getAttribute('aria-hidden')).toBe('false'); // panel is open
117+
118+
const link1 = await component.findByTestId('link-1');
119+
const link2 = await component.findByTestId('link-2');
120+
121+
link1.focus();
122+
expect(testPanel.getAttribute('aria-hidden')).toBe('false');
123+
124+
link2.focus();
125+
expect(testPanel.getAttribute('aria-hidden')).toBe('false');
126+
});
127+
128+
it('should close the right panel when focusing out of its elements', async () => {
129+
const user = userEvent.setup();
130+
const component = render(PanelFocusTestComponent);
131+
132+
const testPanel = await component.findByTestId('links-panel');
133+
expect(testPanel.getAttribute('aria-hidden')).toBe('true'); // panel is closed
134+
135+
const actionButton = await component.findByTestId('action-button');
136+
await user.click(actionButton);
137+
138+
expect(testPanel.getAttribute('aria-hidden')).toBe('false'); // panel is open
139+
140+
const link1 = await component.findByTestId('link-1');
141+
link1.focus();
142+
expect(testPanel.getAttribute('aria-hidden')).toBe('false');
143+
144+
const outerInput = await component.findByTestId('outer-input');
145+
outerInput.focus();
146+
147+
await nextTick();
148+
149+
expect(testPanel.getAttribute('aria-hidden')).toBe('true'); // panel is closed
150+
});
151+
104152
it('CvHeader - side nav rail', async () => {
105153
const result = render(CvHeader, {
106154
slots: {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<script setup>
2+
import CvHeader from '../CvHeader.vue';
3+
import CvHeaderGlobalAction from '../CvHeaderGlobalAction.vue';
4+
import CvHeaderPanel from '../CvHeaderPanel.vue';
5+
import CvSwitcher from '../CvSwitcher.vue';
6+
import CvSwitcherItem from '../CvSwitcherItem.vue';
7+
import CvSwitcherItemLink from '../CvSwitcherItemLink.vue';
8+
</script>
9+
10+
<template>
11+
<cv-header>
12+
<template #header-global>
13+
<cv-header-global-action
14+
data-testid="action-button"
15+
aria-label="List links"
16+
aria-controls="links-panel"
17+
label="Links"
18+
>
19+
Links
20+
</cv-header-global-action>
21+
</template>
22+
<template #right-panels>
23+
<cv-header-panel id="links-panel" data-testid="links-panel">
24+
<cv-switcher>
25+
<cv-switcher-item>
26+
<cv-switcher-item-link
27+
href="javascript:void(0)"
28+
data-testid="link-1"
29+
selected
30+
>
31+
Selected app
32+
</cv-switcher-item-link>
33+
</cv-switcher-item>
34+
<cv-switcher-item>
35+
<cv-switcher-item-link
36+
href="javascript:void(0)"
37+
data-testid="link-2"
38+
>
39+
Other app
40+
</cv-switcher-item-link>
41+
</cv-switcher-item>
42+
</cv-switcher>
43+
</cv-header-panel>
44+
</template>
45+
</cv-header>
46+
<input type="text" data-testid="outer-input" />
47+
</template>

0 commit comments

Comments
 (0)