diff --git a/src/stores/__tests__/MessagesStore.test.ts b/src/stores/__tests__/MessagesStore.test.ts new file mode 100644 index 0000000..02d1a6f --- /dev/null +++ b/src/stores/__tests__/MessagesStore.test.ts @@ -0,0 +1,218 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { MessagesStore } from '../MessagesStore'; +import Message from '../../models/Message'; +import { getSnapshot } from 'mobx-state-tree'; + +describe('MessagesStore', () => { + let messagesStore; + + beforeEach(() => { + // Create a new instance of the store before each test + messagesStore = MessagesStore.create(); + }); + + describe('Initial state', () => { + it('should have empty items array initially', () => { + expect(messagesStore.items).toEqual([]); + }); + }); + + describe('add', () => { + it('should add a message to the items array', () => { + // Create a message + const message = Message.create({ + content: 'Hello, world!', + role: 'user', + }); + + // Add the message to the store + messagesStore.add(message); + + // Check that the message was added + expect(messagesStore.items.length).toBe(1); + expect(messagesStore.items[0].content).toBe('Hello, world!'); + expect(messagesStore.items[0].role).toBe('user'); + }); + + it('should add multiple messages to the items array', () => { + // Create messages + const message1 = Message.create({ + content: 'Hello', + role: 'user', + }); + + const message2 = Message.create({ + content: 'Hi there', + role: 'assistant', + }); + + // Add the messages to the store + messagesStore.add(message1); + messagesStore.add(message2); + + // Check that the messages were added + expect(messagesStore.items.length).toBe(2); + expect(messagesStore.items[0].content).toBe('Hello'); + expect(messagesStore.items[0].role).toBe('user'); + expect(messagesStore.items[1].content).toBe('Hi there'); + expect(messagesStore.items[1].role).toBe('assistant'); + }); + }); + + describe('updateLast', () => { + it('should update the content of the last message', () => { + // Add a message + const message = Message.create({ + content: 'Hello', + role: 'user', + }); + messagesStore.add(message); + + // Update the last message + messagesStore.updateLast('Updated content'); + + // Check that the message was updated + expect(messagesStore.items[0].content).toBe('Updated content'); + }); + + it('should do nothing if there are no messages', () => { + // Try to update the last message when there are no messages + messagesStore.updateLast('Updated content'); + + // Check that nothing happened + expect(messagesStore.items.length).toBe(0); + }); + }); + + describe('appendLast', () => { + it('should append content to the last message', () => { + // Add a message + const message = Message.create({ + content: 'Hello', + role: 'user', + }); + messagesStore.add(message); + + // Append to the last message + messagesStore.appendLast(', world!'); + + // Check that the message was updated + expect(messagesStore.items[0].content).toBe('Hello, world!'); + }); + + it('should do nothing if there are no messages', () => { + // Try to append to the last message when there are no messages + messagesStore.appendLast(', world!'); + + // Check that nothing happened + expect(messagesStore.items.length).toBe(0); + }); + }); + + describe('removeAfter', () => { + it('should remove all messages after the specified index', () => { + // Add messages + const message1 = Message.create({ + content: 'Message 1', + role: 'user', + }); + + const message2 = Message.create({ + content: 'Message 2', + role: 'assistant', + }); + + const message3 = Message.create({ + content: 'Message 3', + role: 'user', + }); + + messagesStore.add(message1); + messagesStore.add(message2); + messagesStore.add(message3); + + // Remove messages after index 0 + messagesStore.removeAfter(0); + + // Check that messages were removed + expect(messagesStore.items.length).toBe(1); + expect(messagesStore.items[0].content).toBe('Message 1'); + }); + + it('should do nothing if index is out of bounds (negative)', () => { + // Add messages + const message1 = Message.create({ + content: 'Message 1', + role: 'user', + }); + + const message2 = Message.create({ + content: 'Message 2', + role: 'assistant', + }); + + messagesStore.add(message1); + messagesStore.add(message2); + + // Try to remove messages with negative index + messagesStore.removeAfter(-1); + + // Check that nothing happened + expect(messagesStore.items.length).toBe(2); + }); + + it('should do nothing if index is out of bounds (too large)', () => { + // Add messages + const message1 = Message.create({ + content: 'Message 1', + role: 'user', + }); + + const message2 = Message.create({ + content: 'Message 2', + role: 'assistant', + }); + + messagesStore.add(message1); + messagesStore.add(message2); + + // Try to remove messages with index that's too large + messagesStore.removeAfter(2); + + // Check that nothing happened + expect(messagesStore.items.length).toBe(2); + }); + }); + + describe('reset', () => { + it('should remove all messages', () => { + // Add messages + const message1 = Message.create({ + content: 'Message 1', + role: 'user', + }); + + const message2 = Message.create({ + content: 'Message 2', + role: 'assistant', + }); + + messagesStore.add(message1); + messagesStore.add(message2); + + // Reset the store + messagesStore.reset(); + + // Check that all messages were removed + expect(messagesStore.items.length).toBe(0); + }); + + it('should do nothing if there are no messages', () => { + // Reset the store when there are no messages + messagesStore.reset(); + + // Check that nothing happened + expect(messagesStore.items.length).toBe(0); + }); + }); +}); \ No newline at end of file diff --git a/src/stores/__tests__/ModelStore.test.ts b/src/stores/__tests__/ModelStore.test.ts new file mode 100644 index 0000000..10053e7 --- /dev/null +++ b/src/stores/__tests__/ModelStore.test.ts @@ -0,0 +1,119 @@ +import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; +import { ModelStore } from '../ModelStore'; + +describe('ModelStore', () => { + let modelStore; + + // Mock localStorage + const localStorageMock = { + getItem: vi.fn(), + setItem: vi.fn(), + clear: vi.fn(), + }; + + beforeEach(() => { + // Create a new instance of the store before each test + modelStore = ModelStore.create(); + + // Setup localStorage mock + Object.defineProperty(window, 'localStorage', { + value: localStorageMock, + writable: true, + }); + + // Clear all mocks + vi.clearAllMocks(); + }); + + afterEach(() => { + // Restore all mocks + vi.restoreAllMocks(); + }); + + describe('Initial state', () => { + it('should have default model set initially', () => { + expect(modelStore.model).toBe('meta-llama/llama-4-scout-17b-16e-instruct'); + }); + + it('should have default image model set initially', () => { + expect(modelStore.imageModel).toBe('black-forest-labs/flux-1.1-pro'); + }); + + it('should have empty supportedModels array initially', () => { + expect(modelStore.supportedModels).toEqual([]); + }); + }); + + describe('setModel', () => { + it('should update the model value', () => { + modelStore.setModel('new-model'); + expect(modelStore.model).toBe('new-model'); + }); + + it('should save the model to localStorage', () => { + modelStore.setModel('new-model'); + expect(localStorageMock.setItem).toHaveBeenCalledWith('recentModel', 'new-model'); + }); + + it('should handle localStorage errors gracefully', () => { + // Make localStorage.setItem throw an error + localStorageMock.setItem.mockImplementation(() => { + throw new Error('localStorage error'); + }); + + // This should not throw an error + expect(() => modelStore.setModel('new-model')).not.toThrow(); + expect(modelStore.model).toBe('new-model'); + }); + }); + + describe('setImageModel', () => { + it('should update the imageModel value', () => { + modelStore.setImageModel('new-image-model'); + expect(modelStore.imageModel).toBe('new-image-model'); + }); + }); + + describe('setSupportedModels', () => { + it('should update the supportedModels array', () => { + const models = ['model1', 'model2', 'model3']; + modelStore.setSupportedModels(models); + expect(modelStore.supportedModels).toEqual(models); + }); + + it('should not change the current model if it is in the supported models list', () => { + // First set a model + modelStore.setModel('model2'); + + // Then set supported models including the current model + const models = ['model1', 'model2', 'model3']; + modelStore.setSupportedModels(models); + + // The model should remain the same + expect(modelStore.model).toBe('model2'); + }); + + it('should change the current model if it is not in the supported models list', () => { + // First set a model + modelStore.setModel('unsupported-model'); + + // Then set supported models not including the current model + const models = ['model1', 'model2', 'model3']; + modelStore.setSupportedModels(models); + + // The model should be changed to the last model in the list + expect(modelStore.model).toBe('model3'); + }); + + it('should handle empty supported models list', () => { + // First set a model + modelStore.setModel('current-model'); + + // Then set empty supported models + modelStore.setSupportedModels([]); + + // The model should remain the same since there's no alternative + expect(modelStore.model).toBe('current-model'); + }); + }); +}); \ No newline at end of file