fix(client): restore titles for cached views

Restore the resolved entity title whenever a kept-alive view becomes active.
Prevent inactive cached views from replacing the current page title.

Fixes #1261
This commit is contained in:
neon
2026-09-05 09:15:16 +00:00
parent 16e63f2610
commit f5bb88f996
2 changed files with 92 additions and 3 deletions
@@ -0,0 +1,77 @@
import { defineComponent, h, KeepAlive, nextTick, ref } from 'vue'
import { mount } from '@vue/test-utils'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { usePageTitle } from '@/composables/usePageTitle'
vi.mock('vue-router', () => ({
useRoute: () => ({ matched: [] }),
}))
describe('usePageTitle', () => {
afterEach(() => {
document.title = ''
})
it('restores the resolved title when a kept-alive view is reactivated', async () => {
const active = ref(true)
const title = ref('Fiction')
const TitledView = defineComponent({
name: 'TitledView',
setup() {
usePageTitle(title)
return () => h('div')
},
})
const OtherView = defineComponent({
name: 'OtherView',
setup: () => () => h('div'),
})
const wrapper = mount(
defineComponent({
setup: () => () => h(KeepAlive, null, [h(active.value ? TitledView : OtherView)]),
}),
)
expect(document.title).toBe('Fiction · BookOrbit')
active.value = false
await nextTick()
document.title = 'Library #4 · BookOrbit'
active.value = true
await nextTick()
expect(document.title).toBe('Fiction · BookOrbit')
wrapper.unmount()
})
it('does not let a deactivated view overwrite the active page title', async () => {
const active = ref(true)
const title = ref('Fiction')
const TitledView = defineComponent({
name: 'TitledView',
setup() {
usePageTitle(title)
return () => h('div')
},
})
const OtherView = defineComponent({
name: 'OtherView',
setup: () => () => h('div'),
})
const wrapper = mount(
defineComponent({
setup: () => () => h(KeepAlive, null, [h(active.value ? TitledView : OtherView)]),
}),
)
active.value = false
await nextTick()
document.title = 'Authors · BookOrbit'
title.value = 'Renamed Fiction'
await nextTick()
expect(document.title).toBe('Authors · BookOrbit')
wrapper.unmount()
})
})
+15 -3
View File
@@ -1,4 +1,4 @@
import { onBeforeUnmount, toValue, watch, type MaybeRefOrGetter } from 'vue'
import { onActivated, onBeforeUnmount, onDeactivated, toValue, watch, type MaybeRefOrGetter } from 'vue'
import { useRoute } from 'vue-router'
import { formatPageTitle } from '@/lib/page-title'
import { resolveRouteTitle } from '@/router/title-resolver'
@@ -10,16 +10,28 @@ function setTitle(title: string | null | undefined) {
export function usePageTitle(title: MaybeRefOrGetter<string | null | undefined>) {
const route = useRoute()
let active = true
const stop = watch(
() => toValue(title),
(value) => setTitle(value),
(value) => {
if (active) setTitle(value)
},
{ immediate: true },
)
onActivated(() => {
active = true
setTitle(toValue(title))
})
onDeactivated(() => {
active = false
})
onBeforeUnmount(() => {
stop()
if (typeof document !== 'undefined') {
if (active && typeof document !== 'undefined') {
document.title = resolveRouteTitle(route)
}
})