feat(assistant): implement spec 021 chat assistant flow with semantic contracts
This commit is contained in:
59
frontend/src/lib/stores/__tests__/assistantChat.test.js
Normal file
59
frontend/src/lib/stores/__tests__/assistantChat.test.js
Normal file
@@ -0,0 +1,59 @@
|
||||
// [DEF:frontend.src.lib.stores.__tests__.assistantChat:Module]
|
||||
// @TIER: STANDARD
|
||||
// @SEMANTICS: test, store, assistant, toggle, conversation
|
||||
// @PURPOSE: Validate assistant chat store visibility and conversation binding transitions.
|
||||
// @LAYER: UI Tests
|
||||
// @RELATION: DEPENDS_ON -> assistantChatStore
|
||||
// @INVARIANT: Each test starts from default closed state.
|
||||
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { get } from 'svelte/store';
|
||||
import {
|
||||
assistantChatStore,
|
||||
toggleAssistantChat,
|
||||
openAssistantChat,
|
||||
closeAssistantChat,
|
||||
setAssistantConversationId,
|
||||
} from '../assistantChat.js';
|
||||
|
||||
// [DEF:assistantChatStore_tests:Function]
|
||||
// @TIER: STANDARD
|
||||
// @PURPOSE: Group store unit scenarios for assistant panel behavior.
|
||||
// @PRE: Store can be reset to baseline state in beforeEach hook.
|
||||
// @POST: Open/close/toggle/conversation transitions are validated.
|
||||
describe('assistantChatStore', () => {
|
||||
beforeEach(() => {
|
||||
assistantChatStore.set({
|
||||
isOpen: false,
|
||||
conversationId: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should open assistant panel', () => {
|
||||
openAssistantChat();
|
||||
const state = get(assistantChatStore);
|
||||
expect(state.isOpen).toBe(true);
|
||||
});
|
||||
|
||||
it('should close assistant panel', () => {
|
||||
openAssistantChat();
|
||||
closeAssistantChat();
|
||||
const state = get(assistantChatStore);
|
||||
expect(state.isOpen).toBe(false);
|
||||
});
|
||||
|
||||
it('should toggle assistant panel state', () => {
|
||||
toggleAssistantChat();
|
||||
expect(get(assistantChatStore).isOpen).toBe(true);
|
||||
toggleAssistantChat();
|
||||
expect(get(assistantChatStore).isOpen).toBe(false);
|
||||
});
|
||||
|
||||
it('should set conversation id', () => {
|
||||
setAssistantConversationId('conv-123');
|
||||
const state = get(assistantChatStore);
|
||||
expect(state.conversationId).toBe('conv-123');
|
||||
});
|
||||
});
|
||||
// [/DEF:assistantChatStore_tests:Function]
|
||||
// [/DEF:frontend.src.lib.stores.__tests__.assistantChat:Module]
|
||||
71
frontend/src/lib/stores/assistantChat.js
Normal file
71
frontend/src/lib/stores/assistantChat.js
Normal file
@@ -0,0 +1,71 @@
|
||||
// [DEF:assistantChat:Store]
|
||||
// @TIER: STANDARD
|
||||
// @SEMANTICS: assistant, store, ui-state, conversation
|
||||
// @PURPOSE: Control assistant chat panel visibility and active conversation binding.
|
||||
// @LAYER: UI
|
||||
// @RELATION: BINDS_TO -> AssistantChatPanel
|
||||
// @INVARIANT: conversationId persists while panel toggles unless explicitly reset.
|
||||
//
|
||||
// @UX_STATE: Closed -> Panel hidden.
|
||||
// @UX_STATE: Open -> Panel visible and interactive.
|
||||
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
const initialState = {
|
||||
isOpen: false,
|
||||
conversationId: null,
|
||||
};
|
||||
|
||||
export const assistantChatStore = writable(initialState);
|
||||
|
||||
// [DEF:toggleAssistantChat:Function]
|
||||
// @PURPOSE: Toggle assistant panel visibility.
|
||||
// @PRE: Store is initialized.
|
||||
// @POST: isOpen value inverted.
|
||||
export function toggleAssistantChat() {
|
||||
assistantChatStore.update((state) => {
|
||||
const next = { ...state, isOpen: !state.isOpen };
|
||||
console.log(`[assistantChat][${next.isOpen ? 'Open' : 'Closed'}] toggleAssistantChat`);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
// [/DEF:toggleAssistantChat:Function]
|
||||
|
||||
// [DEF:openAssistantChat:Function]
|
||||
// @PURPOSE: Open assistant panel.
|
||||
// @PRE: Store is initialized.
|
||||
// @POST: isOpen = true.
|
||||
export function openAssistantChat() {
|
||||
assistantChatStore.update((state) => {
|
||||
const next = { ...state, isOpen: true };
|
||||
console.log('[assistantChat][Open] openAssistantChat');
|
||||
return next;
|
||||
});
|
||||
}
|
||||
// [/DEF:openAssistantChat:Function]
|
||||
|
||||
// [DEF:closeAssistantChat:Function]
|
||||
// @PURPOSE: Close assistant panel.
|
||||
// @PRE: Store is initialized.
|
||||
// @POST: isOpen = false.
|
||||
export function closeAssistantChat() {
|
||||
assistantChatStore.update((state) => {
|
||||
const next = { ...state, isOpen: false };
|
||||
console.log('[assistantChat][Closed] closeAssistantChat');
|
||||
return next;
|
||||
});
|
||||
}
|
||||
// [/DEF:closeAssistantChat:Function]
|
||||
|
||||
// [DEF:setAssistantConversationId:Function]
|
||||
// @PURPOSE: Bind current conversation id in UI state.
|
||||
// @PRE: conversationId is string-like identifier.
|
||||
// @POST: store.conversationId updated.
|
||||
export function setAssistantConversationId(conversationId) {
|
||||
assistantChatStore.update((state) => {
|
||||
console.log('[assistantChat][ConversationBound] setAssistantConversationId');
|
||||
return { ...state, conversationId };
|
||||
});
|
||||
}
|
||||
// [/DEF:setAssistantConversationId:Function]
|
||||
// [/DEF:assistantChat:Store]
|
||||
Reference in New Issue
Block a user