From 943d41339cc517cbcdcb0a1b78531f9d0e6a4bed Mon Sep 17 00:00:00 2001 From: voltsrage Date: Wed, 5 Aug 2026 22:20:05 +0800 Subject: [PATCH] Update frontend test for ci --- .../src/__tests__/alertQuality.test.js | 2 +- .../src/__tests__/useFeedbackStore.test.js | 142 +++++++++--------- vigilcare-dashboard/src/stores/feedback.js | 9 +- 3 files changed, 79 insertions(+), 74 deletions(-) diff --git a/vigilcare-dashboard/src/__tests__/alertQuality.test.js b/vigilcare-dashboard/src/__tests__/alertQuality.test.js index bdc880a..cadf996 100644 --- a/vigilcare-dashboard/src/__tests__/alertQuality.test.js +++ b/vigilcare-dashboard/src/__tests__/alertQuality.test.js @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' import { setActivePinia, createPinia } from 'pinia' -import { useAlertQualityStore } from './alertQuality' +import { useAlertQualityStore } from '@/stores/alertQuality' vi.mock('@/api/alertQuality', () => ({ submitAlertFeedback: vi.fn().mockResolvedValue({ id: 'fb-1', createdAt: '2026-06-23T00:00:00Z' }), diff --git a/vigilcare-dashboard/src/__tests__/useFeedbackStore.test.js b/vigilcare-dashboard/src/__tests__/useFeedbackStore.test.js index 78e9c4b..cc9ee70 100644 --- a/vigilcare-dashboard/src/__tests__/useFeedbackStore.test.js +++ b/vigilcare-dashboard/src/__tests__/useFeedbackStore.test.js @@ -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 { 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(() => { - localStorage.clear() + vi.clearAllMocks() setActivePinia(createPinia()) }) - afterEach(() => { - vi.restoreAllMocks() + it('addFeedback_delegatesToAlertQualityAndCaches', async () => { + 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', () => { - const store = useFeedbackStore() - store.addFeedback('a1', 'SEPSIS_WARNING', 'CRITICAL', 'useful') + it('addFeedback_updatesCachedRating', async () => { + submitAlertFeedback + .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) - expect(store.entries[0].alertId).toBe('a1') - expect(store.entries[0].rating).toBe('useful') + const store = useFeedbackStore() + await store.addFeedback('a1', 'SEPSIS_WARNING', 'CRITICAL', '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() - store.addFeedback('a1', 'SEPSIS_WARNING', 'CRITICAL', 'useful') - store.addFeedback('a1', 'SEPSIS_WARNING', 'CRITICAL', 'false-positive') + const { useAlertQualityStore } = await import('@/stores/alertQuality') + await useAlertQualityStore().loadDashboard() - expect(store.entries).toHaveLength(1) - expect(store.entries[0].rating).toBe('false-positive') + expect(store.stats.value.total).toBe(4) + expect(store.stats.value.usefulPct).toBe(75) + expect(store.stats.value.fpPct).toBe(10) }) - it('stats_computesCorrectly', () => { + it('byAlertType_groupsSnapshots', async () => { const store = useFeedbackStore() - store.addFeedback('a1', 'SEPSIS_WARNING', 'CRITICAL', 'useful') - store.addFeedback('a2', 'SEPSIS_WARNING', 'CRITICAL', 'would-act') - store.addFeedback('a3', 'WARNING_HEART_RATE', 'WARNING', 'false-positive') - store.addFeedback('a4', 'WARNING_HEART_RATE', 'WARNING', 'too-early') + const { useAlertQualityStore } = await import('@/stores/alertQuality') + await useAlertQualityStore().loadDashboard() - expect(store.stats.total).toBe(4) - expect(store.stats.useful).toBe(2) - expect(store.stats.falsePositive).toBe(1) - expect(store.stats.usefulPct).toBe(50) - expect(store.stats.fpPct).toBe(25) + expect(Object.keys(store.byAlertType.value)).toHaveLength(2) + expect(store.byAlertType.value.SEPSIS_WARNING).toHaveLength(1) + expect(store.byAlertType.value.WARNING_HEART_RATE).toHaveLength(1) }) - it('byAlertType_groupsCorrectly', () => { + it('entries_isEmptyCompatStub', () => { const store = useFeedbackStore() - store.addFeedback('a1', 'SEPSIS_WARNING', 'CRITICAL', 'useful') - 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) + expect(store.entries.value).toEqual([]) }) - it('exportAsJson_generatesValidJson', () => { + it('exportAndClearAreNoOps', () => { 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(capturedBlob).toBeInstanceOf(Blob) - expect(capturedBlob.type).toBe('application/json') - 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') + expect(() => store.exportAsCsv()).not.toThrow() + expect(() => store.clearAll()).not.toThrow() }) }) diff --git a/vigilcare-dashboard/src/stores/feedback.js b/vigilcare-dashboard/src/stores/feedback.js index 974f1b6..42e1d27 100644 --- a/vigilcare-dashboard/src/stores/feedback.js +++ b/vigilcare-dashboard/src/stores/feedback.js @@ -1,6 +1,11 @@ import { computed } from 'vue' 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() { const store = useAlertQualityStore() return { @@ -10,7 +15,7 @@ export function useFeedbackStore() { usefulPct: store.summaryStats.usefulRate, fpPct: store.summaryStats.falsePositiveRate, })), - byAlertType: store.byAlertType, + byAlertType: computed(() => store.byAlertType), addFeedback: (alertId, _type, _severity, rating, notes) => store.submitFeedback(alertId, rating, notes), getFeedback: store.getFeedback, @@ -18,4 +23,4 @@ export function useFeedbackStore() { exportAsCsv: () => {}, clearAll: () => {}, } -} \ No newline at end of file +}