fix: restore mobile navigation clearance when closing app playback
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
<!-- Play/Pause -->
|
||||
<button
|
||||
class="flex-shrink-0 w-9 h-9 rounded-full bg-white/10 hover:bg-white/20 flex items-center justify-center transition-colors"
|
||||
:aria-label="audioPlayer.playing.value ? 'Pause audio' : 'Play audio'"
|
||||
@click="togglePlay"
|
||||
>
|
||||
<svg v-if="audioPlayer.loading.value" class="w-5 h-5 animate-spin text-white" fill="none" viewBox="0 0 24 24">
|
||||
@@ -47,6 +48,7 @@
|
||||
<!-- Close -->
|
||||
<button
|
||||
class="flex-shrink-0 w-8 h-8 rounded-full hover:bg-white/10 flex items-center justify-center transition-colors"
|
||||
aria-label="Stop audio"
|
||||
@click="audioPlayer.stop()"
|
||||
>
|
||||
<svg class="w-4 h-4 text-white/60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
||||
@@ -203,6 +203,17 @@ function updateTabBarHeight() {
|
||||
}
|
||||
}
|
||||
|
||||
// Store-driven app sessions do not change the route. Their v-if removes and
|
||||
// recreates the navigation, so route/resize listeners alone leave its height
|
||||
// unset after closing an app and let the navigation cover the audio controls.
|
||||
watch(mobileTabBar, (element, _previous, onCleanup) => {
|
||||
updateTabBarHeight()
|
||||
if (!element || typeof ResizeObserver === 'undefined') return
|
||||
const observer = new ResizeObserver(updateTabBarHeight)
|
||||
observer.observe(element)
|
||||
onCleanup(() => observer.disconnect())
|
||||
}, { flush: 'post' })
|
||||
|
||||
function onResize() {
|
||||
viewportWidth.value = window.innerWidth
|
||||
updateTabBarHeight()
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { nextTick, reactive } from 'vue'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import DashboardMobileNav from '../DashboardMobileNav.vue'
|
||||
|
||||
const state = vi.hoisted(() => ({ launcher: null as any }))
|
||||
vi.mock('@/stores/appLauncher', () => ({ useAppLauncherStore: () => state.launcher }))
|
||||
vi.mock('@/stores/uiMode', () => ({ useUIModeStore: () => ({ isEasy: false, isChat: false }) }))
|
||||
vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (key: string) => key }) }))
|
||||
vi.mock('vue-router', () => ({
|
||||
useRoute: () => ({ path: '/dashboard/discover', name: 'discover', query: {} }),
|
||||
useRouter: () => ({ push: vi.fn() }),
|
||||
RouterLink: { template: '<a><slot /></a>' },
|
||||
}))
|
||||
|
||||
describe('mobile navigation clearance across app sessions', () => {
|
||||
let height = 72
|
||||
const observers: Array<{ callback: () => void; disconnect: ReturnType<typeof vi.fn> }> = []
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
state.launcher = reactive({ panelAppId: null })
|
||||
height = 72
|
||||
observers.length = 0
|
||||
vi.spyOn(HTMLElement.prototype, 'offsetHeight', 'get').mockImplementation(() => height)
|
||||
vi.stubGlobal('ResizeObserver', class {
|
||||
disconnect = vi.fn()
|
||||
observe = vi.fn()
|
||||
constructor(callback: () => void) { observers.push({ callback, disconnect: this.disconnect }) }
|
||||
})
|
||||
})
|
||||
afterEach(() => {
|
||||
vi.clearAllTimers()
|
||||
vi.useRealTimers()
|
||||
vi.restoreAllMocks()
|
||||
vi.unstubAllGlobals()
|
||||
document.documentElement.style.removeProperty('--mobile-tab-bar-height')
|
||||
})
|
||||
const clearance = () => document.documentElement.style.getPropertyValue('--mobile-tab-bar-height')
|
||||
|
||||
it('restores measured space without a route change after closing an app', async () => {
|
||||
const wrapper = mount(DashboardMobileNav, { props: { showZoomIn: false } })
|
||||
await nextTick()
|
||||
expect(clearance()).toBe('72px')
|
||||
state.launcher.panelAppId = 'node-demo-v4v'
|
||||
await nextTick()
|
||||
expect(wrapper.find('[data-mobile-tab-bar]').exists()).toBe(false)
|
||||
expect(clearance()).toBe('')
|
||||
expect(observers[0].disconnect).toHaveBeenCalledOnce()
|
||||
height = 88
|
||||
state.launcher.panelAppId = null
|
||||
await nextTick()
|
||||
expect(wrapper.find('[data-mobile-tab-bar]').exists()).toBe(true)
|
||||
expect(clearance()).toBe('88px')
|
||||
height = 104
|
||||
observers.at(-1)!.callback()
|
||||
expect(clearance()).toBe('104px')
|
||||
wrapper.unmount()
|
||||
expect(observers.at(-1)!.disconnect).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('does not reserve a visible navigation height on desktop', async () => {
|
||||
height = 0
|
||||
const wrapper = mount(DashboardMobileNav, { props: { showZoomIn: false } })
|
||||
await nextTick()
|
||||
expect(clearance()).toBe('')
|
||||
wrapper.unmount()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user