Update frontend test for ci
This commit is contained in:
@@ -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')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useAlertQualityStore } from './alertQuality'
|
import { useAlertQualityStore } from './alertQuality'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compatibility shim for pre–Phase 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,
|
||||||
|
|||||||
Reference in New Issue
Block a user