fix(gui-v2): mount Drawer only on mobile (v-if) + shared Tailwind breakpoint
CRITICAL: replace `lg:hidden` on PrimeVue Drawer with `v-if="isMobile"` so the
teleported portal/overlay is never created on desktop viewports regardless of
mobileOpen state. Replace useMediaQuery raw string in SidebarHeader with
useBreakpoints(breakpointsTailwind).smaller('lg') shared by both components.
Add desktop/mobile comments; adapt tests to useBreakpoints mock; add
Drawer-absent-on-desktop and aside w-16/w-64 width-class assertions (21 tests).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -7,18 +7,44 @@
|
||||
* 2. Passes `groups` prop to SidebarNav.
|
||||
* 3. Mobile Drawer v-model:visible wires to shell.mobileOpen (get path).
|
||||
* 4. Drawer close (v-model:visible = false) calls shell.setMobileOpen(false).
|
||||
* 5. Drawer is NOT rendered when isMobile=false (desktop); IS rendered when isMobile=true.
|
||||
* 6. Desktop <aside> applies correct width class based on sidebarCollapsed.
|
||||
*
|
||||
* Stubs: Drawer is stubbed with a simple slot passthrough so we can inspect
|
||||
* whether its `visible` prop is correctly bound to the store.
|
||||
*
|
||||
* @vueuse/core is mocked so we can control isMobile per test context.
|
||||
*/
|
||||
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { ref } from 'vue'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { useShellUiStore } from '@/stores/useShellUiStore'
|
||||
import AppSidebar from '@/components-v2/layout/AppSidebar.vue'
|
||||
import type { V2NavGroup } from '@/types/v2/nav'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mock @vueuse/core so we can control `isMobile` per test.
|
||||
// AppSidebar uses useBreakpoints(breakpointsTailwind).smaller('lg').
|
||||
// We return an object whose .smaller() method returns a controllable ref.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const mockIsMobileRef = ref(false)
|
||||
|
||||
vi.mock('@vueuse/core', () => ({
|
||||
breakpointsTailwind: {},
|
||||
useBreakpoints: () => ({
|
||||
smaller: (_bp: string) => mockIsMobileRef,
|
||||
}),
|
||||
}))
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Import component AFTER mock is set up
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// eslint-disable-next-line import/first -- intentional: mock must be declared first
|
||||
import AppSidebar from '@/components-v2/layout/AppSidebar.vue'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// A minimal nav group fixture
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -75,7 +101,12 @@ function mountSidebar(groups: V2NavGroup[] = testGroups) {
|
||||
}
|
||||
|
||||
describe('AppSidebar', () => {
|
||||
beforeEach(() => setActivePinia(createPinia()))
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
|
||||
// Default to desktop (isMobile=false) so most tests exercise the stable path
|
||||
mockIsMobileRef.value = false
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Child composition
|
||||
@@ -108,10 +139,67 @@ describe('AppSidebar', () => {
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Mobile Drawer wiring
|
||||
// Drawer v-if: mount/unmount based on isMobile
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
it('Drawer visible is true when shell.mobileOpen is true', async () => {
|
||||
it('desktop (isMobile=false): Drawer is NOT rendered', () => {
|
||||
mockIsMobileRef.value = false
|
||||
|
||||
const wrapper = mountSidebar()
|
||||
|
||||
// v-if="isMobile" means the Drawer stub must be absent on desktop
|
||||
expect(wrapper.find('.drawer-stub').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('mobile (isMobile=true): Drawer IS rendered', async () => {
|
||||
mockIsMobileRef.value = true
|
||||
|
||||
const wrapper = mountSidebar()
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.find('.drawer-stub').exists()).toBe(true)
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Desktop <aside> width class based on sidebarCollapsed
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
it('desktop: aside has w-64 class when sidebar is expanded', async () => {
|
||||
mockIsMobileRef.value = false
|
||||
|
||||
const wrapper = mountSidebar()
|
||||
const shell = useShellUiStore()
|
||||
|
||||
shell.sidebarCollapsed = false
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.find('aside').classes()).toContain('w-64')
|
||||
expect(wrapper.find('aside').classes()).not.toContain('w-16')
|
||||
})
|
||||
|
||||
it('desktop: aside has w-16 class when sidebar is collapsed', async () => {
|
||||
mockIsMobileRef.value = false
|
||||
|
||||
const wrapper = mountSidebar()
|
||||
const shell = useShellUiStore()
|
||||
|
||||
shell.sidebarCollapsed = true
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.find('aside').classes()).toContain('w-16')
|
||||
expect(wrapper.find('aside').classes()).not.toContain('w-64')
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Mobile Drawer wiring (only exercised when isMobile=true)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
it('mobile: Drawer visible is true when shell.mobileOpen is true', async () => {
|
||||
mockIsMobileRef.value = true
|
||||
|
||||
const wrapper = mountSidebar()
|
||||
const shell = useShellUiStore()
|
||||
|
||||
@@ -125,7 +213,9 @@ describe('AppSidebar', () => {
|
||||
expect(drawer.attributes('data-visible')).toBe('true')
|
||||
})
|
||||
|
||||
it('Drawer visible is false when shell.mobileOpen is false', async () => {
|
||||
it('mobile: Drawer visible is false when shell.mobileOpen is false', async () => {
|
||||
mockIsMobileRef.value = true
|
||||
|
||||
const wrapper = mountSidebar()
|
||||
const shell = useShellUiStore()
|
||||
|
||||
@@ -138,7 +228,9 @@ describe('AppSidebar', () => {
|
||||
expect(drawer.attributes('data-visible')).toBe('false')
|
||||
})
|
||||
|
||||
it('emitting update:visible false from Drawer calls shell.setMobileOpen(false)', async () => {
|
||||
it('mobile: emitting update:visible false from Drawer calls shell.setMobileOpen(false)', async () => {
|
||||
mockIsMobileRef.value = true
|
||||
|
||||
const wrapper = mountSidebar()
|
||||
const shell = useShellUiStore()
|
||||
|
||||
@@ -152,7 +244,9 @@ describe('AppSidebar', () => {
|
||||
expect(setMobileOpenSpy).toHaveBeenCalledWith(false)
|
||||
})
|
||||
|
||||
it('emitting update:visible true from Drawer calls shell.setMobileOpen(true)', async () => {
|
||||
it('mobile: emitting update:visible true from Drawer calls shell.setMobileOpen(true)', async () => {
|
||||
mockIsMobileRef.value = true
|
||||
|
||||
const wrapper = mountSidebar()
|
||||
const shell = useShellUiStore()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user