From 8e3d75b57b59b6bf8e9e695af27dab70ac360109 Mon Sep 17 00:00:00 2001 From: "chihao.ou" Date: Fri, 9 Oct 2026 20:05:17 +0800 Subject: [PATCH] fix(monitor): refresh cached availability on automatic reload --- .../src/views/user/ChannelStatusV1View.vue | 8 +- .../ChannelStatusV1View.details.spec.ts | 77 +++++++++++++++++++ 2 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 frontend/src/views/user/__tests__/ChannelStatusV1View.details.spec.ts diff --git a/frontend/src/views/user/ChannelStatusV1View.vue b/frontend/src/views/user/ChannelStatusV1View.vue index 041650c6c1..8d36f9c8a6 100644 --- a/frontend/src/views/user/ChannelStatusV1View.vue +++ b/frontend/src/views/user/ChannelStatusV1View.vue @@ -7,7 +7,7 @@ :loading="loading" :auto-refresh="autoRefresh" @update:window="handleWindowChange" - @refresh="manualReload" + @refresh="refresh()" /> reload(true), + onRefresh: () => refresh(true), shouldPause: () => document.hidden || loading.value, }) const countdown = autoRefresh.countdown @@ -109,8 +109,8 @@ async function reload(silent = false) { } } -async function manualReload() { - await reload(false) +async function refresh(silent = false) { + await reload(silent) // After base reload, refresh any cached detail records so non-7d availability // values stay in sync without forcing the user to switch tabs again. if (currentWindow.value !== '7d') { diff --git a/frontend/src/views/user/__tests__/ChannelStatusV1View.details.spec.ts b/frontend/src/views/user/__tests__/ChannelStatusV1View.details.spec.ts new file mode 100644 index 0000000000..7c5273edff --- /dev/null +++ b/frontend/src/views/user/__tests__/ChannelStatusV1View.details.spec.ts @@ -0,0 +1,77 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { enableAutoUnmount, flushPromises, shallowMount } from '@vue/test-utils' +import ChannelStatusV1View from '../ChannelStatusV1View.vue' + +const mocks = vi.hoisted(() => ({ list: vi.fn(), status: vi.fn() })) +vi.mock('@/api/channelMonitor', () => mocks) +vi.mock('@/stores/app', () => ({ useAppStore: () => ({ cachedPublicSettings: { channel_monitor_enabled: true }, showError: vi.fn() }) })) +vi.mock('vue-i18n', async () => ({ + ...await vi.importActual('vue-i18n'), + useI18n: () => ({ t: (key: string) => key }) +})) +enableAutoUnmount(afterEach) +afterEach(() => { vi.useRealTimers(); localStorage.clear() }) + +function detail(availability: number) { + return { id: 1, models: [{ model: 'model', availability_15d: availability, availability_30d: availability }] } +} +beforeEach(() => { + vi.useFakeTimers() + vi.resetAllMocks() + localStorage.clear() + localStorage.setItem('channel-status-auto-refresh', JSON.stringify({ enabled: true, interval_seconds: 30 })) + mocks.list.mockImplementation(async () => ({ items: [{ id: 1, name: 'Monitor', primary_model: 'model', primary_status: 'operational', availability_7d: 99 }] })) + mocks.status.mockResolvedValue(detail(90)) +}) + +function mountView() { + return shallowMount(ChannelStatusV1View, { + global: { stubs: { + AppLayout: { template: '
' }, + MonitorHero: { props: ['loading'], emits: ['update:window', 'refresh'], template: `
+ + + +
` }, + MonitorCardGrid: false, + MonitorCard: { props: ['availabilityValue'], template: '{{ availabilityValue }}' } + } } + }) +} + +describe('channel monitor automatic detail refresh', () => { + it.each(['fifteen', 'thirty'])('refreshes the cached %s-day availability on timer ticks', async (tab) => { + const wrapper = mountView() + await flushPromises() + await wrapper.get('.' + tab).trigger('click') + await flushPromises() + expect(wrapper.get('output').text()).toBe('90') + mocks.status.mockResolvedValue(detail(98)) + await vi.advanceTimersByTimeAsync(30000) + await flushPromises() + expect(wrapper.get('output').text()).toBe('98') + expect(mocks.status).toHaveBeenCalledTimes(2) + expect(wrapper.get('[data-loading]').attributes('data-loading')).toBe('false') + }) + + it('keeps the seven-day view on the list endpoint without extra detail requests', async () => { + const wrapper = mountView() + await flushPromises() + await vi.advanceTimersByTimeAsync(30000) + await flushPromises() + expect(mocks.list).toHaveBeenCalledTimes(2) + expect(mocks.status).not.toHaveBeenCalled() + expect(wrapper.get('output').text()).toBe('99') + }) + + it('still refreshes cached availability on manual reload', async () => { + const wrapper = mountView() + await flushPromises() + await wrapper.get('.fifteen').trigger('click') + await flushPromises() + mocks.status.mockResolvedValue(detail(98)) + await wrapper.get('.refresh').trigger('click') + await flushPromises() + expect(wrapper.get('output').text()).toBe('98') + }) +})