@mozaic-ds/vue 2.13.0 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/dist/mozaic-vue.css +1 -1
  2. package/dist/mozaic-vue.d.ts +1088 -378
  3. package/dist/mozaic-vue.js +2662 -1854
  4. package/dist/mozaic-vue.js.map +1 -1
  5. package/dist/mozaic-vue.umd.cjs +5 -5
  6. package/dist/mozaic-vue.umd.cjs.map +1 -1
  7. package/package.json +4 -4
  8. package/src/components/actionlistbox/MActionListbox.spec.ts +53 -59
  9. package/src/components/actionlistbox/MActionListbox.stories.ts +22 -1
  10. package/src/components/actionlistbox/MActionListbox.vue +91 -28
  11. package/src/components/actionlistbox/README.md +15 -0
  12. package/src/components/breadcrumb/MBreadcrumb.vue +5 -0
  13. package/src/components/button/README.md +4 -0
  14. package/src/components/checkbox/README.md +2 -0
  15. package/src/components/divider/README.md +4 -0
  16. package/src/components/iconbutton/MIconButton.stories.ts +12 -0
  17. package/src/components/iconbutton/MIconButton.vue +13 -1
  18. package/src/components/iconbutton/README.md +27 -0
  19. package/src/components/loader/README.md +2 -0
  20. package/src/components/navigationindicator/MNavigationIndicator.spec.ts +152 -0
  21. package/src/components/navigationindicator/MNavigationIndicator.stories.ts +41 -0
  22. package/src/components/navigationindicator/MNavigationIndicator.vue +132 -0
  23. package/src/components/navigationindicator/README.md +37 -0
  24. package/src/components/pageheader/MPageHeader.spec.ts +142 -0
  25. package/src/components/pageheader/MPageHeader.stories.ts +125 -0
  26. package/src/components/pageheader/MPageHeader.vue +133 -0
  27. package/src/components/pageheader/README.md +46 -0
  28. package/src/components/popover/MPopover.spec.ts +106 -0
  29. package/src/components/popover/MPopover.stories.ts +126 -0
  30. package/src/components/popover/MPopover.vue +131 -0
  31. package/src/components/popover/README.md +42 -0
  32. package/src/components/radio/README.md +2 -0
  33. package/src/components/select/MSelect.spec.ts +2 -1
  34. package/src/components/select/MSelect.vue +30 -25
  35. package/src/components/sidebar/MSidebar.const.ts +6 -0
  36. package/src/components/sidebar/MSidebar.spec.ts +110 -0
  37. package/src/components/sidebar/MSidebar.stories.ts +108 -0
  38. package/src/components/sidebar/MSidebar.vue +124 -0
  39. package/src/components/sidebar/README.md +59 -0
  40. package/src/components/sidebar/stories/DefaultCase.stories.vue +120 -0
  41. package/src/components/sidebar/stories/README.md +27 -0
  42. package/src/components/sidebar/stories/WithExpandOnly.stories.vue +112 -0
  43. package/src/components/sidebar/stories/WithProfileInfoOnly.stories.vue +119 -0
  44. package/src/components/sidebar/stories/WithSingleLevel.stories.vue +98 -0
  45. package/src/components/sidebar/use-floating-item.composable.ts +135 -0
  46. package/src/components/sidebar/use-floating-item.spec.ts +251 -0
  47. package/src/components/sidebarexpandableitem/MSidebarExpandableItem.spec.ts +151 -0
  48. package/src/components/sidebarexpandableitem/MSidebarExpandableItem.vue +113 -0
  49. package/src/components/sidebarexpandableitem/README.md +36 -0
  50. package/src/components/sidebarfooter/MSidebarFooter.spec.ts +276 -0
  51. package/src/components/sidebarfooter/MSidebarFooter.vue +201 -0
  52. package/src/components/sidebarfooter/README.md +52 -0
  53. package/src/components/sidebarfooter/_MSidebarFooterMenu.vue +64 -0
  54. package/src/components/sidebarheader/MSidebarHeader.vue +36 -0
  55. package/src/components/sidebarheader/README.md +31 -0
  56. package/src/components/sidebarnavitem/MSidebarNavItem.spec.ts +127 -0
  57. package/src/components/sidebarnavitem/MSidebarNavItem.vue +113 -0
  58. package/src/components/sidebarnavitem/README.md +56 -0
  59. package/src/components/sidebarshortcutitem/MSidebarShortcutItem.spec.ts +59 -0
  60. package/src/components/sidebarshortcutitem/MSidebarShortcutItem.vue +52 -0
  61. package/src/components/sidebarshortcutitem/README.md +32 -0
  62. package/src/components/sidebarshortcuts/MSidebarShortcuts.spec.ts +87 -0
  63. package/src/components/sidebarshortcuts/MSidebarShortcuts.vue +101 -0
  64. package/src/components/sidebarshortcuts/README.md +36 -0
  65. package/src/components/statusbadge/README.md +12 -0
  66. package/src/components/textinput/MTextInput.stories.ts +13 -1
  67. package/src/components/textinput/MTextInput.vue +12 -0
  68. package/src/components/textinput/README.md +3 -1
  69. package/src/components/tile/MTile.spec.ts +61 -0
  70. package/src/components/tile/MTile.stories.ts +102 -0
  71. package/src/components/tile/MTile.vue +68 -0
  72. package/src/components/tile/README.md +19 -0
  73. package/src/components/tileclickable/MTileClickable.spec.ts +130 -0
  74. package/src/components/tileclickable/MTileClickable.stories.ts +60 -0
  75. package/src/components/tileclickable/MTileClickable.vue +106 -0
  76. package/src/components/tileclickable/README.md +30 -0
  77. package/src/components/tileexpandable/MTileExpandable.spec.ts +121 -0
  78. package/src/components/tileexpandable/MTileExpandable.stories.ts +50 -0
  79. package/src/components/tileexpandable/MTileExpandable.vue +131 -0
  80. package/src/components/tileexpandable/README.md +36 -0
  81. package/src/components/tileselectable/MTileSelectable.spec.ts +177 -0
  82. package/src/components/tileselectable/MTileSelectable.stories.ts +55 -0
  83. package/src/components/tileselectable/MTileSelectable.vue +142 -0
  84. package/src/components/tileselectable/README.md +44 -0
  85. package/src/components/toaster/README.md +1 -1
  86. package/src/components/tooltip/MTooltip.vue +5 -0
  87. package/src/components/tooltip/README.md +16 -1
  88. package/src/main.ts +12 -2
  89. package/src/utils/use-is-mobile.composable.ts +20 -0
  90. package/src/utils/use-is-mobile.spec.ts +70 -0
@@ -0,0 +1,151 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
3
+ import { ref, nextTick } from 'vue';
4
+
5
+ const onTriggerKeydown = vi.fn();
6
+ const onListboxKeydown = vi.fn();
7
+ const hideFloatingItem = vi.fn();
8
+
9
+ vi.mock('../sidebar/use-floating-item.composable', () => {
10
+ return {
11
+ useFloatingItem: () => {
12
+ const floatingItemIsDisplayed = ref(false);
13
+ return {
14
+ floatingItemIsDisplayed,
15
+ showFloatingItem: () => (floatingItemIsDisplayed.value = true),
16
+ hideFloatingItem: () => {
17
+ floatingItemIsDisplayed.value = false;
18
+ hideFloatingItem();
19
+ },
20
+ onTriggerKeydown,
21
+ onListboxKeydown,
22
+ };
23
+ },
24
+ };
25
+ });
26
+
27
+ import MSidebarExpandableItem from './MSidebarExpandableItem.vue';
28
+ import { EXPANDED_SIDEBAR_KEY } from '../sidebar/MSidebar.const';
29
+
30
+ describe('MSidebarExpandableItem', () => {
31
+ beforeEach(() => {
32
+ vi.clearAllMocks();
33
+ });
34
+
35
+ it('renders expanded details when expanded is true', async () => {
36
+ const wrapper = mount(MSidebarExpandableItem, {
37
+ props: { label: 'My Label', menuLabel: 'Menu' },
38
+ global: {
39
+ provide: { [EXPANDED_SIDEBAR_KEY]: true },
40
+ },
41
+ slots: {
42
+ default: '<li>Child item</li>',
43
+ },
44
+ });
45
+
46
+ expect(wrapper.find('li.mc-sidebar__item').exists()).toBe(true);
47
+ expect(
48
+ wrapper.find('summary.mc-sidebar__link').attributes('aria-label'),
49
+ ).toBe('My Label');
50
+ expect(wrapper.text()).toContain('My Label');
51
+ expect(wrapper.find('ul.mc-sidebar__sublist').html()).toContain(
52
+ 'Child item',
53
+ );
54
+ });
55
+
56
+ it('renders trigger and toggles listbox when not expanded', async () => {
57
+ const wrapper = mount(MSidebarExpandableItem, {
58
+ props: { label: 'My Label', menuLabel: 'Overlay Menu' },
59
+ global: {
60
+ provide: { [EXPANDED_SIDEBAR_KEY]: false },
61
+ },
62
+ slots: {
63
+ default: '<li>Overlay item</li>',
64
+ },
65
+ });
66
+
67
+ const button = wrapper.find('button.mc-sidebar__trigger');
68
+ expect(button.exists()).toBe(true);
69
+ expect(button.attributes('aria-haspopup')).toBe('listbox');
70
+ expect(button.attributes('aria-expanded')).toBe('false');
71
+
72
+ await button.trigger('mouseenter');
73
+ await nextTick();
74
+ expect(button.attributes('aria-expanded')).toBe('true');
75
+
76
+ const floating = wrapper.find('.mc-sidebar__floating-item');
77
+ expect(floating!.text()).toContain('Overlay Menu');
78
+ expect(floating!.text()).toContain('Overlay item');
79
+
80
+ await button.trigger('mouseleave');
81
+ await nextTick();
82
+ expect(button.attributes('aria-expanded')).toBe('false');
83
+ expect(floating.classes()).toContain('mc-sidebar__floating-item--hidden');
84
+ });
85
+
86
+ it('handles listbox trigger with keyboard accessibility', async () => {
87
+ const wrapper = mount(MSidebarExpandableItem, {
88
+ props: { label: 'My Label', menuLabel: 'Overlay Menu' },
89
+ global: {
90
+ provide: { [EXPANDED_SIDEBAR_KEY]: false },
91
+ },
92
+ slots: {
93
+ default: `
94
+ <li>Overlay item</li>
95
+ <li>Overlay item 2</li>
96
+ `,
97
+ },
98
+ });
99
+
100
+ const button = wrapper.find('button.mc-sidebar__trigger');
101
+ expect(button.exists()).toBe(true);
102
+
103
+ await button.trigger('focus');
104
+ await nextTick();
105
+ expect(button.attributes('aria-expanded')).toBe('true');
106
+
107
+ await button.trigger('keydown', { key: 'ArrowDown' });
108
+ expect(onTriggerKeydown).toHaveBeenCalled();
109
+
110
+ const listboxEl = wrapper.find('.mc-sidebar__floating-item');
111
+
112
+ await listboxEl.trigger('keydown', { key: 'Escape' });
113
+
114
+ await button.trigger('blur');
115
+ await nextTick();
116
+ expect(button.attributes('aria-expanded')).toBe('false');
117
+ });
118
+
119
+ it('calls onListboxKeydown when keydown is emitted on the listbox', async () => {
120
+ const wrapper = mount(MSidebarExpandableItem, {
121
+ props: { label: 'Item', menuLabel: 'Menu' },
122
+ global: {
123
+ provide: { [EXPANDED_SIDEBAR_KEY]: false },
124
+ },
125
+ });
126
+
127
+ const listbox = wrapper.find('.mc-sidebar__floating-item');
128
+ expect(listbox.exists()).toBe(true);
129
+
130
+ await listbox.trigger('keydown');
131
+ expect(onListboxKeydown).toHaveBeenCalled();
132
+ });
133
+
134
+ it('calls hideFloatingItem on mouseleave and blur on the listbox', async () => {
135
+ const wrapper = mount(MSidebarExpandableItem, {
136
+ props: { label: 'Item', menuLabel: 'Menu' },
137
+ global: {
138
+ provide: { [EXPANDED_SIDEBAR_KEY]: false },
139
+ },
140
+ });
141
+
142
+ const listbox = wrapper.find('.mc-sidebar__floating-item');
143
+ expect(listbox.exists()).toBe(true);
144
+
145
+ await listbox.trigger('mouseleave');
146
+ expect(hideFloatingItem).toHaveBeenCalledTimes(1);
147
+
148
+ await listbox.trigger('blur');
149
+ expect(hideFloatingItem).toHaveBeenCalledTimes(2);
150
+ });
151
+ });
@@ -0,0 +1,113 @@
1
+ <template>
2
+ <li v-if="expanded" class="mc-sidebar__item">
3
+ <details class="mc-sidebar__link-expand" open>
4
+ <summary class="mc-sidebar__link" :aria-label="props.label">
5
+ <component
6
+ v-if="props.icon"
7
+ :is="props.icon"
8
+ class="mc-sidebar__icon"
9
+ />
10
+ <span class="mc-sidebar__text">{{ props.label }}</span>
11
+ <ChevronDown20 />
12
+ </summary>
13
+ <ul class="mc-sidebar__sublist">
14
+ <slot name="default" />
15
+ </ul>
16
+ </details>
17
+ </li>
18
+
19
+ <div v-else class="mc-sidebar__item">
20
+ <button
21
+ ref="trigger"
22
+ class="mc-sidebar__trigger mc-sidebar__link"
23
+ :id="id"
24
+ aria-haspopup="listbox"
25
+ :aria-expanded="floatingItemIsDisplayed"
26
+ :aria-controls="`listbox-${id}`"
27
+ @mouseenter="showFloatingItem"
28
+ @focus="showFloatingItem"
29
+ @keydown="onTriggerKeydown"
30
+ @mouseleave="hideFloatingItem"
31
+ @blur="hideFloatingItem"
32
+ >
33
+ <component v-if="props.icon" :is="props.icon" class="mc-sidebar__icon" />
34
+ </button>
35
+
36
+ <div
37
+ class="mc-sidebar__floating-item mc-sidebar__floating-item--hidden"
38
+ ref="listbox"
39
+ :id="`listbox-${id}`"
40
+ tabindex="0"
41
+ role="menu"
42
+ @keydown="onListboxKeydown"
43
+ @mouseleave="hideFloatingItem"
44
+ @blur="hideFloatingItem"
45
+ >
46
+ <div class="mc-sidebar__listbox">
47
+ <h3 class="mc-sidebar__listbox-title">{{ props.menuLabel }}</h3>
48
+ <ul class="mc-sidebar__list">
49
+ <slot name="default" />
50
+ </ul>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </template>
55
+
56
+ <script setup lang="ts">
57
+ import {
58
+ inject,
59
+ provide,
60
+ useId,
61
+ useTemplateRef,
62
+ type Component,
63
+ type VNode,
64
+ } from 'vue';
65
+ import { ChevronDown20 } from '@mozaic-ds/icons-vue';
66
+ import { useFloatingItem } from '../sidebar/use-floating-item.composable';
67
+ import { SUB_ITEM_KEY, EXPANDED_SIDEBAR_KEY } from '../sidebar/MSidebar.const';
68
+ /**
69
+ * SidebarExpandableItem is a component that renders a sidebar item which can either expand to show a nested submenu or display a floating menu when the sidebar is collapsed. It supports optional icons, keyboard navigation, and accessibility attributes, and allows for custom submenu items via slots.
70
+ */
71
+ const props = defineProps<{
72
+ /**
73
+ * The text displayed for the sidebar item in its expanded state.
74
+ */
75
+ label: string;
76
+ /**
77
+ * The title shown at the top of the floating menu when the sidebar is collapsed.
78
+ */
79
+ menuLabel: string;
80
+ /**
81
+ * A Vue component used to display an icon alongside the sidebar item.
82
+ */
83
+ icon?: Component;
84
+ }>();
85
+
86
+ defineSlots<{
87
+ /**
88
+ * Contains one or more MSidebarNavItem components used to render the submenu items.
89
+ */
90
+ default: VNode[];
91
+ }>();
92
+
93
+ const expanded = inject(EXPANDED_SIDEBAR_KEY);
94
+
95
+ const id = useId();
96
+
97
+ const trigger = useTemplateRef('trigger');
98
+ const listbox = useTemplateRef('listbox');
99
+
100
+ const {
101
+ floatingItemIsDisplayed,
102
+ hideFloatingItem,
103
+ showFloatingItem,
104
+ onTriggerKeydown,
105
+ onListboxKeydown,
106
+ } = useFloatingItem(trigger, listbox);
107
+
108
+ provide(SUB_ITEM_KEY, true);
109
+ </script>
110
+
111
+ <style lang="scss" scoped>
112
+ @use '@mozaic-ds/styles/components/sidebar';
113
+ </style>
@@ -0,0 +1,36 @@
1
+ # MSidebarExpandableItem
2
+
3
+ SidebarExpandableItem is a component that renders a sidebar item which can either expand to show a nested submenu or display a floating menu when the sidebar is collapsed. It supports optional icons, keyboard navigation, and accessibility attributes, and allows for custom submenu items via slots.
4
+
5
+
6
+ ## Props
7
+
8
+ | Name | Description | Type | Default |
9
+ | --- | --- | --- | --- |
10
+ | `label*` | The text displayed for the sidebar item in its expanded state. | `string` | - |
11
+ | `menuLabel*` | The title shown at the top of the floating menu when the sidebar is collapsed. | `string` | - |
12
+ | `icon` | A Vue component used to display an icon alongside the sidebar item. | `Component` | - |
13
+
14
+ ## Slots
15
+
16
+ | Name | Description |
17
+ | --- | --- |
18
+ | `default` | Contains one or more MSidebarNavItem components used to render the submenu items. |
19
+
20
+ ## Dependencies
21
+
22
+ ### Used By
23
+
24
+ - [DefaultCase.stories](../sidebar/stories)
25
+ - [WithExpandOnly.stories](../sidebar/stories)
26
+ - [WithProfileInfoOnly.stories](../sidebar/stories)
27
+
28
+ ### Graph
29
+
30
+ ```mermaid
31
+ graph TD;
32
+ DefaultCase.stories --> MSidebarExpandableItem
33
+ WithExpandOnly.stories --> MSidebarExpandableItem
34
+ WithProfileInfoOnly.stories --> MSidebarExpandableItem
35
+ style MSidebarExpandableItem fill:#008240,stroke:#333,stroke-width:4px
36
+ ```
@@ -0,0 +1,276 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import MSidebarFooter from './MSidebarFooter.vue';
4
+ import {
5
+ TOGGLE_SIDEBAR_KEY,
6
+ EXPANDED_SIDEBAR_KEY,
7
+ } from '../sidebar/MSidebar.const';
8
+ import { nextTick, ref } from 'vue';
9
+
10
+ const onTriggerKeydown = vi.fn();
11
+ const onListboxKeydown = vi.fn();
12
+ const hideFloatingItem = vi.fn();
13
+
14
+ vi.mock('../sidebar/use-floating-item.composable', () => {
15
+ return {
16
+ useFloatingItem: () => {
17
+ const floatingItemIsDisplayed = ref(false);
18
+ return {
19
+ floatingItemIsDisplayed,
20
+ showFloatingItem: () => (floatingItemIsDisplayed.value = true),
21
+ hideFloatingItem: () => {
22
+ floatingItemIsDisplayed.value = false;
23
+ hideFloatingItem();
24
+ },
25
+ onTriggerKeydown,
26
+ onListboxKeydown,
27
+ };
28
+ },
29
+ };
30
+ });
31
+
32
+ vi.mock('../../utils/use-is-mobile.composable.ts', () => {
33
+ return {
34
+ useIsMobile: () => {
35
+ return {
36
+ isMobile: false,
37
+ };
38
+ },
39
+ };
40
+ });
41
+
42
+ describe('MSidebarFooter', () => {
43
+ const toggleSidebar = vi.fn();
44
+
45
+ beforeEach(() => {
46
+ vi.clearAllMocks();
47
+ });
48
+
49
+ it('does not render profile when no img/title/subtitle provided', () => {
50
+ const wrapper = mount(MSidebarFooter, {
51
+ global: {
52
+ provide: {
53
+ [TOGGLE_SIDEBAR_KEY]: vi.fn(),
54
+ [EXPANDED_SIDEBAR_KEY]: false,
55
+ },
56
+ },
57
+ });
58
+
59
+ expect(wrapper.find('.mc-sidebar__footer-profile').exists()).toBe(false);
60
+ expect(wrapper.find('button').exists()).toBe(true);
61
+ });
62
+
63
+ it('renders img, title and subtitle when provided', () => {
64
+ const props = {
65
+ avatar: '/avatar.png',
66
+ title: 'John Doe',
67
+ subtitle: 'Designer',
68
+ href: '/profile',
69
+ };
70
+ const wrapper = mount(MSidebarFooter, {
71
+ props,
72
+ global: {
73
+ provide: {
74
+ [TOGGLE_SIDEBAR_KEY]: vi.fn(),
75
+ [EXPANDED_SIDEBAR_KEY]: true,
76
+ },
77
+ },
78
+ });
79
+
80
+ const profile = wrapper.find('.mc-sidebar__footer-profile');
81
+ expect(profile.exists()).toBe(true);
82
+
83
+ const img = wrapper.find('.mc-sidebar__footer-avatar');
84
+ expect(img.exists()).toBe(true);
85
+ expect(img.attributes('src')).toBe('/avatar.png');
86
+ expect(img.attributes('alt')).toBe('John Doe');
87
+
88
+ expect(wrapper.find('.mc-sidebar__footer-title').text()).toBe('John Doe');
89
+ expect(wrapper.find('.mc-sidebar__footer-subtitle').text()).toBe(
90
+ 'Designer',
91
+ );
92
+ });
93
+
94
+ it('hides expand button when expandable prop is false', () => {
95
+ const wrapper = mount(MSidebarFooter, {
96
+ props: { expendable: false },
97
+ global: {
98
+ provide: {
99
+ [TOGGLE_SIDEBAR_KEY]: vi.fn(),
100
+ [EXPANDED_SIDEBAR_KEY]: false,
101
+ },
102
+ },
103
+ });
104
+
105
+ expect(wrapper.find('.mc-sidebar__footer-expand').exists()).toBe(false);
106
+ });
107
+
108
+ it('binds aria-expanded and calls toggle function when clicked', async () => {
109
+ const toggle = vi.fn();
110
+ const wrapper = mount(MSidebarFooter, {
111
+ global: {
112
+ provide: {
113
+ [TOGGLE_SIDEBAR_KEY]: toggle,
114
+ [EXPANDED_SIDEBAR_KEY]: true,
115
+ },
116
+ },
117
+ });
118
+
119
+ const button = wrapper.find('button');
120
+ expect(button.attributes('aria-expanded')).toBe('true');
121
+
122
+ await button.trigger('click');
123
+ expect(toggle).toHaveBeenCalled();
124
+ });
125
+
126
+ it('emits "log-out" when log out button is clicked', async () => {
127
+ const wrapper = mount(MSidebarFooter, {
128
+ props: {
129
+ title: 'John Doe',
130
+ logOutLabel: 'Log out',
131
+ },
132
+ global: {
133
+ provide: {
134
+ [EXPANDED_SIDEBAR_KEY]: true,
135
+ [TOGGLE_SIDEBAR_KEY]: vi.fn(),
136
+ },
137
+ stubs: {
138
+ MDivider: true,
139
+ MButton: true,
140
+ MSidebarNavItem: {
141
+ template: `
142
+ <div data-test="logout" @click="$emit('click')">
143
+ Log out
144
+ </div>
145
+ `,
146
+ },
147
+ },
148
+ },
149
+ });
150
+
151
+ const logoutItem = wrapper.find('[data-test="logout"]');
152
+
153
+ expect(logoutItem.exists()).toBe(true);
154
+
155
+ await logoutItem.trigger('click');
156
+
157
+ expect(wrapper.emitted('log-out')).toBeTruthy();
158
+ });
159
+
160
+ it('shows floating item on mouseenter when collapsed', async () => {
161
+ const wrapper = mount(MSidebarFooter, {
162
+ props: { title: 'John Doe' },
163
+ global: {
164
+ provide: {
165
+ [EXPANDED_SIDEBAR_KEY]: false,
166
+ [TOGGLE_SIDEBAR_KEY]: toggleSidebar,
167
+ },
168
+ },
169
+ });
170
+
171
+ const trigger = wrapper.find('button.mc-sidebar__footer-trigger');
172
+ expect(trigger.exists()).toBe(true);
173
+ expect(trigger.attributes('aria-expanded')).toBe('false');
174
+
175
+ await trigger.trigger('mouseenter');
176
+ await nextTick();
177
+
178
+ expect(trigger.attributes('aria-expanded')).toBe('true');
179
+ });
180
+
181
+ it('hides floating item on mouseleave and blur when collapsed', async () => {
182
+ const wrapper = mount(MSidebarFooter, {
183
+ props: { title: 'John Doe' },
184
+ global: {
185
+ provide: {
186
+ [EXPANDED_SIDEBAR_KEY]: false,
187
+ [TOGGLE_SIDEBAR_KEY]: toggleSidebar,
188
+ },
189
+ },
190
+ });
191
+
192
+ const trigger = wrapper.find('button.mc-sidebar__footer-trigger');
193
+
194
+ await trigger.trigger('mouseenter');
195
+ await nextTick();
196
+
197
+ await trigger.trigger('mouseleave');
198
+ expect(hideFloatingItem).toHaveBeenCalledTimes(1);
199
+
200
+ await trigger.trigger('blur');
201
+ expect(hideFloatingItem).toHaveBeenCalledTimes(2);
202
+ });
203
+
204
+ it('calls onTriggerKeydown on keydown when collapsed', async () => {
205
+ const wrapper = mount(MSidebarFooter, {
206
+ props: { title: 'John Doe' },
207
+ global: {
208
+ provide: {
209
+ [EXPANDED_SIDEBAR_KEY]: false,
210
+ [TOGGLE_SIDEBAR_KEY]: toggleSidebar,
211
+ },
212
+ },
213
+ });
214
+
215
+ const trigger = wrapper.find('button.mc-sidebar__footer-trigger');
216
+
217
+ await trigger.trigger('keydown', { key: 'ArrowDown' });
218
+ expect(onTriggerKeydown).toHaveBeenCalled();
219
+ });
220
+
221
+ it('calls onListboxKeydown when keydown is emitted on the listbox', async () => {
222
+ const wrapper = mount(MSidebarFooter, {
223
+ props: { title: 'John Doe' },
224
+ global: {
225
+ provide: {
226
+ [EXPANDED_SIDEBAR_KEY]: false,
227
+ [TOGGLE_SIDEBAR_KEY]: toggleSidebar,
228
+ },
229
+ },
230
+ });
231
+
232
+ const listbox = wrapper.find('#listbox-footer');
233
+ expect(listbox.exists()).toBe(true);
234
+
235
+ await listbox.trigger('keydown');
236
+ expect(onListboxKeydown).toHaveBeenCalled();
237
+ });
238
+
239
+ it('calls hideFloatingItem on mouseleave and blur on the listbox', async () => {
240
+ const wrapper = mount(MSidebarFooter, {
241
+ props: { title: 'John Doe' },
242
+ global: {
243
+ provide: {
244
+ [EXPANDED_SIDEBAR_KEY]: false,
245
+ [TOGGLE_SIDEBAR_KEY]: toggleSidebar,
246
+ },
247
+ },
248
+ });
249
+
250
+ const listbox = wrapper.find('#listbox-footer');
251
+
252
+ await listbox.trigger('mouseleave');
253
+ expect(hideFloatingItem).toHaveBeenCalledTimes(1);
254
+
255
+ await listbox.trigger('blur');
256
+ expect(hideFloatingItem).toHaveBeenCalledTimes(2);
257
+ });
258
+
259
+ it('calls toggleSidebar when expand button is clicked', async () => {
260
+ const wrapper = mount(MSidebarFooter, {
261
+ props: { title: 'John Doe' },
262
+ global: {
263
+ provide: {
264
+ [EXPANDED_SIDEBAR_KEY]: false,
265
+ [TOGGLE_SIDEBAR_KEY]: toggleSidebar,
266
+ },
267
+ },
268
+ });
269
+
270
+ const expandButton = wrapper.find('.mc-sidebar__footer-expand');
271
+ expect(expandButton.exists()).toBe(true);
272
+
273
+ await expandButton.trigger('click');
274
+ expect(toggleSidebar).toHaveBeenCalledTimes(1);
275
+ });
276
+ });