diff --git a/frontend/src/store/modules/account/index.js b/frontend/src/store/modules/account/index.js index 1ce72effe7..f6907abf46 100644 --- a/frontend/src/store/modules/account/index.js +++ b/frontend/src/store/modules/account/index.js @@ -7,10 +7,12 @@ import teamApi from '../../../api/team.js' import userApi from '../../../api/user.js' import { getTeamProperty } from '../../../composables/TeamProperties.js' import router from '../../../routes.js' + import product from '../../../services/product.js' import { useContextStore } from '@/stores/context.js' import { useProductAssistantStore } from '@/stores/product-assistant.js' +import { useProductExpertInsightsAgentStore } from '@/stores/product-expert-insights-agent.js' import { useUxDialogStore } from '@/stores/ux-dialog.js' import { useUxDrawersStore } from '@/stores/ux-drawers.js' import { useUxNavigationStore } from '@/stores/ux-navigation.js' @@ -528,7 +530,7 @@ const actions = { // Task 6: useProductTablesStore().$reset() // Task 7: useProductBrokersStore().$reset() useProductAssistantStore().$reset() - // Task 9: useProductExpertFfAgentStore().$reset() + useProductExpertInsightsAgentStore().$reset() // Task 10: useProductExpertOperatorAgentStore().$reset() // Task 11: useProductExpertStore().$reset() } diff --git a/frontend/src/stores/index.js b/frontend/src/stores/index.js index 3a8096bec8..3021099d86 100644 --- a/frontend/src/stores/index.js +++ b/frontend/src/stores/index.js @@ -6,3 +6,4 @@ export { useUxNavigationStore } from './ux-navigation.js' export { useUxStore } from './ux.js' export { useUxDialogStore } from './ux-dialog.js' export { useUxToursStore } from './ux-tours.js' +export { useProductExpertInsightsAgentStore } from './product-expert-insights-agent.js' diff --git a/frontend/src/stores/product-expert-insights-agent.js b/frontend/src/stores/product-expert-insights-agent.js new file mode 100644 index 0000000000..ff647f0302 --- /dev/null +++ b/frontend/src/stores/product-expert-insights-agent.js @@ -0,0 +1,30 @@ +import { defineStore } from 'pinia' +import { markRaw } from 'vue' + +export const useProductExpertInsightsAgentStore = defineStore('product-expert-insights-agent', { + state: () => ({ + context: null, + sessionId: null, + messages: [], + + // Session timing + abortController: null, + sessionStartTime: null, + sessionWarningShown: false, + sessionExpiredShown: false, + sessionCheckTimer: null + }), + actions: { + reset () { + if (this.sessionCheckTimer) clearInterval(this.sessionCheckTimer) + this.$reset() + }, + setSessionCheckTimer (timer) { + this.sessionCheckTimer = markRaw(timer) + } + }, + persist: { + pick: ['context'], + storage: localStorage + } +}) diff --git a/test/unit/frontend/stores/product-expert-insights-agent.spec.js b/test/unit/frontend/stores/product-expert-insights-agent.spec.js new file mode 100644 index 0000000000..80acccffdf --- /dev/null +++ b/test/unit/frontend/stores/product-expert-insights-agent.spec.js @@ -0,0 +1,157 @@ +import { createPinia, setActivePinia } from 'pinia' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { useProductExpertInsightsAgentStore } from '@/stores/product-expert-insights-agent.js' + +describe('useProductExpertInsightsAgentStore', () => { + beforeEach(() => { + setActivePinia(createPinia()) + }) + + describe('initial state', () => { + it('has null context', () => { + const store = useProductExpertInsightsAgentStore() + expect(store.context).toBeNull() + }) + + it('has null sessionId', () => { + const store = useProductExpertInsightsAgentStore() + expect(store.sessionId).toBeNull() + }) + + it('has empty messages array', () => { + const store = useProductExpertInsightsAgentStore() + expect(store.messages).toEqual([]) + }) + + it('has null sessionStartTime', () => { + const store = useProductExpertInsightsAgentStore() + expect(store.sessionStartTime).toBeNull() + }) + + it('has sessionWarningShown false', () => { + const store = useProductExpertInsightsAgentStore() + expect(store.sessionWarningShown).toBe(false) + }) + + it('has sessionExpiredShown false', () => { + const store = useProductExpertInsightsAgentStore() + expect(store.sessionExpiredShown).toBe(false) + }) + + it('has null abortController', () => { + const store = useProductExpertInsightsAgentStore() + expect(store.abortController).toBeNull() + }) + + it('has null sessionCheckTimer', () => { + const store = useProductExpertInsightsAgentStore() + expect(store.sessionCheckTimer).toBeNull() + }) + }) + + describe('setSessionCheckTimer', () => { + it('stores the timer reference', () => { + const store = useProductExpertInsightsAgentStore() + const fakeTimer = setInterval(() => {}, 10000) + store.setSessionCheckTimer(fakeTimer) + expect(store.sessionCheckTimer).toBe(fakeTimer) + clearInterval(fakeTimer) + }) + + it('replaces an existing timer', () => { + const store = useProductExpertInsightsAgentStore() + const timer1 = setInterval(() => {}, 10000) + const timer2 = setInterval(() => {}, 10000) + store.setSessionCheckTimer(timer1) + store.setSessionCheckTimer(timer2) + expect(store.sessionCheckTimer).toBe(timer2) + clearInterval(timer1) + clearInterval(timer2) + }) + }) + + describe('reset', () => { + it('clears all state back to initial values', () => { + const store = useProductExpertInsightsAgentStore() + store.context = { instanceId: 'abc' } + store.sessionId = 'session-123' + store.messages = [{ role: 'user', content: 'hello' }] + store.abortController = new AbortController() + store.sessionStartTime = Date.now() + store.sessionWarningShown = true + store.sessionExpiredShown = true + + store.reset() + + expect(store.context).toBeNull() + expect(store.sessionId).toBeNull() + expect(store.messages).toEqual([]) + expect(store.abortController).toBeNull() + expect(store.sessionStartTime).toBeNull() + expect(store.sessionWarningShown).toBe(false) + expect(store.sessionExpiredShown).toBe(false) + expect(store.sessionCheckTimer).toBeNull() + }) + + it('calls clearInterval when a timer is active', () => { + const store = useProductExpertInsightsAgentStore() + const clearIntervalSpy = vi.spyOn(globalThis, 'clearInterval') + const fakeTimer = setInterval(() => {}, 10000) + store.setSessionCheckTimer(fakeTimer) + + store.reset() + + expect(clearIntervalSpy).toHaveBeenCalledWith(fakeTimer) + clearIntervalSpy.mockRestore() + }) + + it('does not throw when no timer is set', () => { + const store = useProductExpertInsightsAgentStore() + expect(() => store.reset()).not.toThrow() + }) + + it('clears sessionCheckTimer after reset', () => { + const store = useProductExpertInsightsAgentStore() + const fakeTimer = setInterval(() => {}, 10000) + store.setSessionCheckTimer(fakeTimer) + + store.reset() + + expect(store.sessionCheckTimer).toBeNull() + }) + }) + + describe('state mutations', () => { + it('allows direct state assignment for context', () => { + const store = useProductExpertInsightsAgentStore() + store.context = { instanceId: 'test-id' } + expect(store.context).toEqual({ instanceId: 'test-id' }) + }) + + it('allows direct state assignment for sessionId', () => { + const store = useProductExpertInsightsAgentStore() + store.sessionId = 'abc-123' + expect(store.sessionId).toBe('abc-123') + }) + + it('allows pushing to messages', () => { + const store = useProductExpertInsightsAgentStore() + store.messages.push({ role: 'user', content: 'hello' }) + store.messages.push({ role: 'assistant', content: 'hi' }) + expect(store.messages).toHaveLength(2) + }) + + it('allows setting sessionWarningShown', () => { + const store = useProductExpertInsightsAgentStore() + store.sessionWarningShown = true + expect(store.sessionWarningShown).toBe(true) + }) + + it('allows setting sessionExpiredShown', () => { + const store = useProductExpertInsightsAgentStore() + store.sessionExpiredShown = true + expect(store.sessionExpiredShown).toBe(true) + }) + }) +}) diff --git a/test/unit/frontend/stores/ux-navigation.spec.js b/test/unit/frontend/stores/ux-navigation.spec.js index c365cb48dc..ca6f0640b5 100644 --- a/test/unit/frontend/stores/ux-navigation.spec.js +++ b/test/unit/frontend/stores/ux-navigation.spec.js @@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { useUxNavigationStore } from '@/stores/ux-navigation.js' -// Prevent _account-bridge from importing the real Vuex store +// Prevent _account_bridge from importing the real Vuex store vi.mock('@/stores/_account_bridge.js', () => ({ useAccountBridge: vi.fn(() => ({ team: null,