2026-10-01 11:48:17 +08:00
|
|
|
import { createApp, defineComponent, h, nextTick, onMounted, onUnmounted } from 'vue'
|
|
|
|
|
import { createMemoryHistory, createRouter } from 'vue-router'
|
|
|
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
|
import AdminOperationsDashboard from '../AdminOperationsDashboard.vue'
|
2026-07-15 23:47:19 +08:00
|
|
|
|
2026-10-01 11:48:17 +08:00
|
|
|
vi.mock('@/features/overview/components/OverviewToolbar.vue', () => ({ default: { render: () => null } }))
|
|
|
|
|
const lifecycle = vi.hoisted(() => ({ mounted: [] as string[], unmounted: [] as string[] }))
|
|
|
|
|
function view(name: string) {
|
|
|
|
|
return defineComponent({
|
|
|
|
|
setup(_, { slots }) {
|
|
|
|
|
onMounted(() => lifecycle.mounted.push(name))
|
|
|
|
|
onUnmounted(() => lifecycle.unmounted.push(name))
|
|
|
|
|
return () => h('div', { 'data-view': name }, [name, slots.default?.({ snapshot: { node_id: 'live-node' } }), slots.details?.(), slots.diagnostics?.()])
|
|
|
|
|
},
|
2026-07-15 23:47:19 +08:00
|
|
|
})
|
2026-10-01 11:48:17 +08:00
|
|
|
}
|
|
|
|
|
vi.mock('@/features/overview/operations/RuntimeView.vue', () => ({ default: view('runtime') }))
|
|
|
|
|
vi.mock('@/features/overview/operations/PerformanceView.vue', () => ({ default: view('performance') }))
|
|
|
|
|
vi.mock('@/features/overview/operations/LiveMetrics.vue', () => ({ default: view('details') }))
|
|
|
|
|
vi.mock('@/features/overview/operations/RuntimeFocus.vue', () => ({ default: { render: () => null } }))
|
|
|
|
|
async function settle() { await Promise.resolve(); await Promise.resolve(); await nextTick(); await new Promise(resolve => setTimeout(resolve, 0)) }
|
2026-07-15 23:47:19 +08:00
|
|
|
|
2026-10-01 11:48:17 +08:00
|
|
|
describe('operations view lifecycle', () => {
|
|
|
|
|
afterEach(() => vi.useRealTimers())
|
2026-07-15 23:47:19 +08:00
|
|
|
|
2026-10-01 11:48:17 +08:00
|
|
|
it('keeps every section mounted regardless of legacy view query values or browser history', async () => {
|
|
|
|
|
lifecycle.mounted.length = 0
|
|
|
|
|
lifecycle.unmounted.length = 0
|
|
|
|
|
const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/admin/operations', component: AdminOperationsDashboard }] })
|
|
|
|
|
await router.push('/admin/operations?view=performance&from=2026-09-10T23:30:00Z&to=2026-09-11T00:30:00Z&timezone=UTC')
|
|
|
|
|
const root = document.createElement('div')
|
|
|
|
|
const app = createApp(AdminOperationsDashboard).use(router)
|
|
|
|
|
app.mount(root)
|
|
|
|
|
expect([...lifecycle.mounted].sort()).toEqual(['details', 'details', 'performance', 'runtime'])
|
|
|
|
|
expect(root.querySelector('[role="tablist"]')).toBeNull()
|
|
|
|
|
expect(root.querySelector('[role="tab"]')).toBeNull()
|
|
|
|
|
await router.push({ query: { ...router.currentRoute.value.query, view: 'resources' } })
|
|
|
|
|
await settle()
|
|
|
|
|
expect(lifecycle.mounted).toHaveLength(4)
|
|
|
|
|
expect(lifecycle.unmounted).toEqual([])
|
|
|
|
|
expect(router.currentRoute.value.query.from).toBe('2026-09-10T23:30:00Z')
|
|
|
|
|
router.back()
|
|
|
|
|
await settle()
|
|
|
|
|
for (const name of ['runtime', 'performance']) expect(root.querySelector(`[data-view="${name}"]`)).not.toBeNull()
|
|
|
|
|
expect(root.querySelectorAll('[data-view="details"]')).toHaveLength(2)
|
|
|
|
|
expect(lifecycle.unmounted).toEqual([])
|
|
|
|
|
app.unmount()
|
|
|
|
|
expect([...lifecycle.unmounted].sort()).toEqual(['details', 'details', 'performance', 'runtime'])
|
2026-07-15 23:47:19 +08:00
|
|
|
})
|
|
|
|
|
})
|