diff --git a/src/renderer/stores/editor-store.ts b/src/renderer/stores/editor-store.ts new file mode 100644 index 0000000..92fb5e1 --- /dev/null +++ b/src/renderer/stores/editor-store.ts @@ -0,0 +1,63 @@ +import { create } from 'zustand'; +import { immer } from 'zustand/middleware/immer'; +import { enableMapSet } from 'immer'; + +enableMapSet(); + +export interface Buffer { + id: string; + path: string; + content: string; + dirty: boolean; + cursor?: { line: number; column: number }; +} + +interface EditorState { + buffers: Map; + activeId: string | null; + openBuffer: (id: string, path: string, content: string) => void; + updateContent: (id: string, content: string) => void; + markSaved: (id: string) => void; + setCursor: (id: string, line: number, column: number) => void; + closeBuffer: (id: string) => void; + setActive: (id: string) => void; +} + +export const useEditorStore = create()( + immer((set) => ({ + buffers: new Map(), + activeId: null, + openBuffer: (id, path, content) => + set((s) => { + s.buffers.set(id, { id, path, content, dirty: false }); + s.activeId = id; + }), + updateContent: (id, content) => + set((s) => { + const buf = s.buffers.get(id); + if (buf) { + buf.content = content; + buf.dirty = true; + } + }), + markSaved: (id) => + set((s) => { + const buf = s.buffers.get(id); + if (buf) buf.dirty = false; + }), + setCursor: (id, line, column) => + set((s) => { + const buf = s.buffers.get(id); + if (buf) buf.cursor = { line, column }; + }), + closeBuffer: (id) => + set((s) => { + s.buffers.delete(id); + if (s.activeId === id) s.activeId = null; + }), + setActive: (id) => + set((s) => { + s.activeId = id; + }), + })) +); \ No newline at end of file diff --git a/tests/unit/stores/editor-store.test.ts b/tests/unit/stores/editor-store.test.ts new file mode 100644 index 0000000..0a5e59a --- /dev/null +++ b/tests/unit/stores/editor-store.test.ts @@ -0,0 +1,36 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { useEditorStore } from '@/stores/editor-store'; + +describe('useEditorStore', () => { + beforeEach(() => { + useEditorStore.setState({ buffers: new Map(), activeId: null }); + }); + + it('creates a new buffer with the given content', () => { + useEditorStore.getState().openBuffer('file-1', '/foo.md', '# hello'); + const buf = useEditorStore.getState().buffers.get('file-1'); + expect(buf?.content).toBe('# hello'); + expect(buf?.path).toBe('/foo.md'); + }); + + it('updates content and marks dirty', () => { + useEditorStore.getState().openBuffer('file-1', '/foo.md', ''); + useEditorStore.getState().updateContent('file-1', 'new content'); + const buf = useEditorStore.getState().buffers.get('file-1'); + expect(buf?.content).toBe('new content'); + expect(buf?.dirty).toBe(true); + }); + + it('marks a buffer clean after save', () => { + useEditorStore.getState().openBuffer('file-1', '/foo.md', ''); + useEditorStore.getState().updateContent('file-1', 'x'); + useEditorStore.getState().markSaved('file-1'); + expect(useEditorStore.getState().buffers.get('file-1')?.dirty).toBe(false); + }); + + it('closes a buffer and removes it', () => { + useEditorStore.getState().openBuffer('file-1', '/foo.md', ''); + useEditorStore.getState().closeBuffer('file-1'); + expect(useEditorStore.getState().buffers.has('file-1')).toBe(false); + }); +}); \ No newline at end of file