Update frontend test for ci
CI / backend (push) Canceled after 19s
CI / frontend (push) Successful in 4m39s

This commit is contained in:
voltsrage
2026-08-05 22:20:05 +08:00
parent d307b915cb
commit 943d41339c
3 changed files with 79 additions and 74 deletions
@@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest' import { describe, it, expect, vi, beforeEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia' import { setActivePinia, createPinia } from 'pinia'
import { useAlertQualityStore } from './alertQuality' import { useAlertQualityStore } from '@/stores/alertQuality'
vi.mock('@/api/alertQuality', () => ({ vi.mock('@/api/alertQuality', () => ({
submitAlertFeedback: vi.fn().mockResolvedValue({ id: 'fb-1', createdAt: '2026-06-23T00:00:00Z' }), submitAlertFeedback: vi.fn().mockResolvedValue({ id: 'fb-1', createdAt: '2026-06-23T00:00:00Z' }),
@@ -1,98 +1,98 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { describe, it, expect, beforeEach, vi } from 'vitest'
import { createPinia, setActivePinia } from 'pinia' import { createPinia, setActivePinia } from 'pinia'
import { useFeedbackStore } from '@/stores/feedback' import { useFeedbackStore } from '@/stores/feedback'
describe('useFeedbackStore', () => { const { submitAlertFeedback, fetchQualityMetricsSummary, fetchQualityMetrics } = vi.hoisted(() => ({
submitAlertFeedback: vi.fn().mockResolvedValue({
id: 'fb-1',
createdAt: '2026-06-23T00:00:00Z',
feedbackType: 'Useful',
}),
fetchQualityMetricsSummary: vi.fn().mockResolvedValue({
totalAlerts: 10,
totalFeedback: 4,
acknowledgementRate: 0.8,
usefulRate: 0.75,
falsePositiveRate: 0.1,
wouldActRate: 0.6,
avgSecondsToAcknowledge: 300,
avgSecondsToResolution: 1200,
}),
fetchQualityMetrics: vi.fn().mockResolvedValue({
items: [
{ alertType: 'SEPSIS_WARNING', usefulRate: 0.75, falsePositiveRate: 0.1, acknowledgementRate: 0.8, totalAlerts: 5 },
{ alertType: 'WARNING_HEART_RATE', usefulRate: 0.5, falsePositiveRate: 0.2, acknowledgementRate: 0.7, totalAlerts: 5 },
],
}),
}))
vi.mock('@/api/alertQuality', () => ({
submitAlertFeedback,
fetchQualityMetricsSummary,
fetchQualityMetrics,
FEEDBACK_TYPE_MAP: {
useful: 'Useful',
'false-positive': 'FalsePositive',
'would-act': 'WouldAct',
'too-early': 'TooEarly',
},
}))
describe('useFeedbackStore (compat shim)', () => {
beforeEach(() => { beforeEach(() => {
localStorage.clear() vi.clearAllMocks()
setActivePinia(createPinia()) setActivePinia(createPinia())
}) })
afterEach(() => { it('addFeedback_delegatesToAlertQualityAndCaches', async () => {
vi.restoreAllMocks() const store = useFeedbackStore()
await store.addFeedback('a1', 'SEPSIS_WARNING', 'CRITICAL', 'useful')
expect(submitAlertFeedback).toHaveBeenCalledWith('a1', 'useful', '')
expect(store.getFeedback('a1').rating).toBe('useful')
}) })
it('addFeedback_createsEntry', () => { it('addFeedback_updatesCachedRating', async () => {
const store = useFeedbackStore() submitAlertFeedback
store.addFeedback('a1', 'SEPSIS_WARNING', 'CRITICAL', 'useful') .mockResolvedValueOnce({ id: 'fb-1', createdAt: '2026-06-23T00:00:00Z', feedbackType: 'Useful' })
.mockResolvedValueOnce({ id: 'fb-2', createdAt: '2026-06-23T00:01:00Z', feedbackType: 'FalsePositive' })
expect(store.entries).toHaveLength(1) const store = useFeedbackStore()
expect(store.entries[0].alertId).toBe('a1') await store.addFeedback('a1', 'SEPSIS_WARNING', 'CRITICAL', 'useful')
expect(store.entries[0].rating).toBe('useful') await store.addFeedback('a1', 'SEPSIS_WARNING', 'CRITICAL', 'false-positive')
expect(store.getFeedback('a1').rating).toBe('false-positive')
}) })
it('addFeedback_updatesExisting', () => { it('stats_mapsFromQualitySummary', async () => {
const store = useFeedbackStore() const store = useFeedbackStore()
store.addFeedback('a1', 'SEPSIS_WARNING', 'CRITICAL', 'useful') const { useAlertQualityStore } = await import('@/stores/alertQuality')
store.addFeedback('a1', 'SEPSIS_WARNING', 'CRITICAL', 'false-positive') await useAlertQualityStore().loadDashboard()
expect(store.entries).toHaveLength(1) expect(store.stats.value.total).toBe(4)
expect(store.entries[0].rating).toBe('false-positive') expect(store.stats.value.usefulPct).toBe(75)
expect(store.stats.value.fpPct).toBe(10)
}) })
it('stats_computesCorrectly', () => { it('byAlertType_groupsSnapshots', async () => {
const store = useFeedbackStore() const store = useFeedbackStore()
store.addFeedback('a1', 'SEPSIS_WARNING', 'CRITICAL', 'useful') const { useAlertQualityStore } = await import('@/stores/alertQuality')
store.addFeedback('a2', 'SEPSIS_WARNING', 'CRITICAL', 'would-act') await useAlertQualityStore().loadDashboard()
store.addFeedback('a3', 'WARNING_HEART_RATE', 'WARNING', 'false-positive')
store.addFeedback('a4', 'WARNING_HEART_RATE', 'WARNING', 'too-early')
expect(store.stats.total).toBe(4) expect(Object.keys(store.byAlertType.value)).toHaveLength(2)
expect(store.stats.useful).toBe(2) expect(store.byAlertType.value.SEPSIS_WARNING).toHaveLength(1)
expect(store.stats.falsePositive).toBe(1) expect(store.byAlertType.value.WARNING_HEART_RATE).toHaveLength(1)
expect(store.stats.usefulPct).toBe(50)
expect(store.stats.fpPct).toBe(25)
}) })
it('byAlertType_groupsCorrectly', () => { it('entries_isEmptyCompatStub', () => {
const store = useFeedbackStore() const store = useFeedbackStore()
store.addFeedback('a1', 'SEPSIS_WARNING', 'CRITICAL', 'useful') expect(store.entries.value).toEqual([])
store.addFeedback('a2', 'WARNING_HEART_RATE', 'WARNING', 'false-positive')
store.addFeedback('a3', 'SEPSIS_WARNING', 'CRITICAL', 'too-early')
expect(Object.keys(store.byAlertType)).toHaveLength(2)
expect(store.byAlertType.SEPSIS_WARNING).toHaveLength(2)
expect(store.byAlertType.WARNING_HEART_RATE).toHaveLength(1)
}) })
it('exportAsJson_generatesValidJson', () => { it('exportAndClearAreNoOps', () => {
const store = useFeedbackStore() const store = useFeedbackStore()
store.addFeedback('a1', 'SEPSIS_WARNING', 'CRITICAL', 'useful')
let capturedBlob = null
const click = vi.fn()
const originalCreateElement = document.createElement.bind(document)
URL.createObjectURL = vi.fn((blob) => {
capturedBlob = blob
return 'blob:url'
})
URL.revokeObjectURL = vi.fn()
vi.spyOn(document, 'createElement').mockImplementation((tag) => {
if (tag === 'a') return { href: '', download: '', click }
return originalCreateElement(tag)
})
expect(() => store.exportAsJson()).not.toThrow() expect(() => store.exportAsJson()).not.toThrow()
expect(capturedBlob).toBeInstanceOf(Blob) expect(() => store.exportAsCsv()).not.toThrow()
expect(capturedBlob.type).toBe('application/json') expect(() => store.clearAll()).not.toThrow()
expect(click).toHaveBeenCalled()
})
it('clearAll_emptiesEntries', () => {
const store = useFeedbackStore()
store.addFeedback('a1', 'SEPSIS_WARNING', 'CRITICAL', 'useful')
store.clearAll()
expect(store.entries).toHaveLength(0)
})
it('persistsToLocalStorage', () => {
const store = useFeedbackStore()
store.addFeedback('a1', 'SEPSIS_WARNING', 'CRITICAL', 'useful')
const stored = JSON.parse(localStorage.getItem('vigilcare-feedback'))
expect(stored).toHaveLength(1)
expect(stored[0].rating).toBe('useful')
}) })
}) })
+7 -2
View File
@@ -1,6 +1,11 @@
import { computed } from 'vue' import { computed } from 'vue'
import { useAlertQualityStore } from './alertQuality' import { useAlertQualityStore } from './alertQuality'
/**
* Compatibility shim for prePhase 33 feedback consumers.
* Feedback submission and analytics now live in useAlertQualityStore;
* /feedback redirects to /analytics/alerts.
*/
export function useFeedbackStore() { export function useFeedbackStore() {
const store = useAlertQualityStore() const store = useAlertQualityStore()
return { return {
@@ -10,7 +15,7 @@ export function useFeedbackStore() {
usefulPct: store.summaryStats.usefulRate, usefulPct: store.summaryStats.usefulRate,
fpPct: store.summaryStats.falsePositiveRate, fpPct: store.summaryStats.falsePositiveRate,
})), })),
byAlertType: store.byAlertType, byAlertType: computed(() => store.byAlertType),
addFeedback: (alertId, _type, _severity, rating, notes) => addFeedback: (alertId, _type, _severity, rating, notes) =>
store.submitFeedback(alertId, rating, notes), store.submitFeedback(alertId, rating, notes),
getFeedback: store.getFeedback, getFeedback: store.getFeedback,
@@ -18,4 +23,4 @@ export function useFeedbackStore() {
exportAsCsv: () => {}, exportAsCsv: () => {},
clearAll: () => {}, clearAll: () => {},
} }
} }