From 7fa0965001b4f8b878ebe786cc7538ee76e8c987 Mon Sep 17 00:00:00 2001 From: Jan-Henrik Bruhn Date: Mon, 10 Nov 2025 12:26:34 +0100 Subject: [PATCH] there is a __tests__ folder and a test folder. That seems confusing, is that a SotA TypeScript/REact/Vite testing pattern? (vibe-kanban c7c8b21b) --- .../store-synchronization.test.tsx | 2 +- .../store-synchronization.test.tsx.bak | 360 ------------------ src/stores/historyStore.test.ts | 2 +- src/stores/workspaceStore.test.ts | 2 +- .../integration-utils.tsx | 0 src/{test => test-utils}/mocks.ts | 0 src/{test => test-utils}/setup.ts | 0 src/{test => test-utils}/test-helpers.ts | 0 vite.config.ts | 4 +- 9 files changed, 5 insertions(+), 365 deletions(-) delete mode 100644 src/__tests__/integration/store-synchronization.test.tsx.bak rename src/{test => test-utils}/integration-utils.tsx (100%) rename src/{test => test-utils}/mocks.ts (100%) rename src/{test => test-utils}/setup.ts (100%) rename src/{test => test-utils}/test-helpers.ts (100%) diff --git a/src/__tests__/integration/store-synchronization.test.tsx b/src/__tests__/integration/store-synchronization.test.tsx index e816c31..bbbb944 100644 --- a/src/__tests__/integration/store-synchronization.test.tsx +++ b/src/__tests__/integration/store-synchronization.test.tsx @@ -3,7 +3,7 @@ import { useWorkspaceStore } from '../../stores/workspaceStore'; import { useGraphStore } from '../../stores/graphStore'; import { useTimelineStore } from '../../stores/timelineStore'; import { useHistoryStore } from '../../stores/historyStore'; -import { resetWorkspaceStore } from '../../test/test-helpers'; +import { resetWorkspaceStore } from '../../test-utils/test-helpers'; /** * Integration tests for store synchronization patterns diff --git a/src/__tests__/integration/store-synchronization.test.tsx.bak b/src/__tests__/integration/store-synchronization.test.tsx.bak deleted file mode 100644 index e891484..0000000 --- a/src/__tests__/integration/store-synchronization.test.tsx.bak +++ /dev/null @@ -1,360 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { useWorkspaceStore } from '../../stores/workspaceStore'; -import { useGraphStore } from '../../stores/graphStore'; -import { useTimelineStore } from '../../stores/timelineStore'; -import { useHistoryStore } from '../../stores/historyStore'; -import { resetWorkspaceStore } from '../../test/test-helpers'; - -/** - * Integration tests for store synchronization patterns - * - * These tests verify that stores properly integrate through their documented APIs, - * NOT through UI interactions. They test the business logic layer - how stores - * communicate and stay synchronized. - * - * Key integration points tested: - * - Document creation initializes all related stores (timeline, history, graph types) - * - Document persistence includes data from all stores - * - Store state is properly isolated per document - */ -describe('Store Synchronization Integration', () => { - beforeEach(() => { - localStorage.clear(); - resetWorkspaceStore(); - }); - - describe('Document Creation Integration', () => { - it('should initialize timeline store when creating document', () => { - const docId = useWorkspaceStore.getState().createDocument('Timeline Test'); - - // Timeline should be loaded for this document - const timelineStore = useTimelineStore.getState(); - expect(timelineStore.currentState).toBeDefined(); - expect(timelineStore.states.length).toBeGreaterThan(0); - }); - - it('should persist node and edge types to document', () => { - const docId = useWorkspaceStore.getState().createDocument('Types Test'); - - // Get fresh state after creation - const doc = useWorkspaceStore.getState().documents.get(docId); - expect(doc).toBeDefined(); - expect(doc?.nodeTypes).toBeDefined(); - expect(doc?.nodeTypes.length).toBeGreaterThan(0); - expect(doc?.edgeTypes).toBeDefined(); - expect(doc?.edgeTypes.length).toBeGreaterThan(0); - }); - - it('should create empty initial timeline state', () => { - const docId = useWorkspaceStore.getState().createDocument('Initial State Test'); - - // Get fresh state after creation - const doc = useWorkspaceStore.getState().documents.get(docId); - expect(doc?.timeline).toBeDefined(); - expect(doc?.timeline.states).toBeDefined(); - }); - }); - - describe('Document Persistence Integration', () => { - it('should save document to localStorage with all data', () => { - const docId = useWorkspaceStore.getState().createDocument('Persistence Test'); - - // Document is already saved during creation, check localStorage - const stored = localStorage.getItem(`document-${docId}`); - expect(stored).toBeTruthy(); - - const storedData = JSON.parse(stored!); - expect(storedData.metadata.title).toBe('Persistence Test'); - expect(storedData.nodeTypes).toBeDefined(); - expect(storedData.edgeTypes).toBeDefined(); - expect(storedData.timeline).toBeDefined(); - expect(storedData.bibliography).toBeDefined(); - }); - - it('should save workspace state to localStorage', () => { - const doc1Id = useWorkspaceStore.getState().createDocument('Doc 1'); - const doc2Id = useWorkspaceStore.getState().createDocument('Doc 2'); - - // Workspace state should be in localStorage - const workspaceState = localStorage.getItem('workspace-state'); - expect(workspaceState).toBeTruthy(); - - const state = JSON.parse(workspaceState!); - expect(state.documentOrder).toContain(doc1Id); - expect(state.documentOrder).toContain(doc2Id); - expect(state.activeDocumentId).toBe(doc2Id); // Last created is active - }); - }); - - describe('Document Isolation', () => { - it('should maintain separate documents in workspace', () => { - const doc1Id = useWorkspaceStore.getState().createDocument('Document 1'); - const doc2Id = useWorkspaceStore.getState().createDocument('Document 2'); - const doc3Id = useWorkspaceStore.getState().createDocument('Document 3'); - - // Get fresh state - const state = useWorkspaceStore.getState(); - - // All documents should exist in workspace - expect(state.documents.size).toBe(3); - expect(state.documents.has(doc1Id)).toBe(true); - expect(state.documents.has(doc2Id)).toBe(true); - expect(state.documents.has(doc3Id)).toBe(true); - - // Document order should be correct - expect(state.documentOrder).toEqual([doc1Id, doc2Id, doc3Id]); - }); - - it('should isolate timeline states per document', async () => { - const doc1Id = useWorkspaceStore.getState().createDocument('Doc 1'); - const timelineStore1 = useTimelineStore.getState(); - const doc1StateCount = timelineStore1.states.length; - - const doc2Id = useWorkspaceStore.getState().createDocument('Doc 2'); - const timelineStore2 = useTimelineStore.getState(); - const doc2StateCount = timelineStore2.states.length; - - // Each document should have its own timeline - // (They may have the same count if both start with default state) - expect(doc1StateCount).toBeGreaterThan(0); - expect(doc2StateCount).toBeGreaterThan(0); - }); - }); - - describe('Document Deletion Integration', () => { - it('should remove document from workspace and storage', () => { - const workspaceStore = useWorkspaceStore.getState(); - - const doc1Id = workspaceStore.createDocument('Keep'); - const doc2Id = workspaceStore.createDocument('Delete'); - - // Mock confirm for deletion - vi.spyOn(window, 'confirm').mockReturnValue(true); - - workspaceStore.deleteDocument(doc2Id); - - // Document should be removed from workspace - expect(workspaceStore.documents.has(doc2Id)).toBe(false); - expect(workspaceStore.documentOrder).not.toContain(doc2Id); - - // Document should be removed from localStorage - const stored = localStorage.getItem(`document-${doc2Id}`); - expect(stored).toBeNull(); - - // Other document should still exist - expect(workspaceStore.documents.has(doc1Id)).toBe(true); - - vi.restoreAllMocks(); - }); - - it('should update active document when deleting current document', () => { - const workspaceStore = useWorkspaceStore.getState(); - - const doc1Id = workspaceStore.createDocument('Doc 1'); - const doc2Id = workspaceStore.createDocument('Doc 2'); - - expect(workspaceStore.activeDocumentId).toBe(doc2Id); - - // Mock confirm - vi.spyOn(window, 'confirm').mockReturnValue(true); - - workspaceStore.deleteDocument(doc2Id); - - // Active document should switch to doc1 - const state = useWorkspaceStore.getState(); - expect(state.activeDocumentId).toBe(doc1Id); - - vi.restoreAllMocks(); - }); - }); - - describe('Document Type Management Integration', () => { - it('should allow adding custom node types to document', () => { - const workspaceStore = useWorkspaceStore.getState(); - const docId = workspaceStore.createDocument('Custom Types'); - - const customType = { - id: 'custom-type', - label: 'Custom Type', - color: '#ff0000', - shape: 'diamond' as const, - icon: 'Star', - description: 'A custom node type', - }; - - workspaceStore.addNodeTypeToDocument(docId, customType); - - const doc = workspaceStore.documents.get(docId); - const hasCustomType = doc?.nodeTypes.some(t => t.id === 'custom-type'); - expect(hasCustomType).toBe(true); - }); - - it('should allow updating edge types in document', () => { - const workspaceStore = useWorkspaceStore.getState(); - const docId = workspaceStore.createDocument('Edge Types'); - - const doc = workspaceStore.documents.get(docId); - const firstEdgeType = doc?.edgeTypes[0]; - expect(firstEdgeType).toBeDefined(); - - workspaceStore.updateEdgeTypeInDocument(docId, firstEdgeType!.id, { - label: 'Updated Label', - color: '#00ff00', - }); - - const updatedDoc = workspaceStore.documents.get(docId); - const updatedType = updatedDoc?.edgeTypes.find(t => t.id === firstEdgeType!.id); - expect(updatedType?.label).toBe('Updated Label'); - expect(updatedType?.color).toBe('#00ff00'); - }); - }); - - describe('Document Duplication Integration', () => { - it('should duplicate document with all its data', () => { - const workspaceStore = useWorkspaceStore.getState(); - const originalId = workspaceStore.createDocument('Original'); - - // Add custom type to original - workspaceStore.addNodeTypeToDocument(originalId, { - id: 'custom', - label: 'Custom', - color: '#ff0000', - shape: 'circle' as const, - icon: 'Star', - description: 'Custom type', - }); - - const duplicateId = workspaceStore.duplicateDocument(originalId); - expect(duplicateId).toBeTruthy(); - expect(duplicateId).not.toBe(originalId); - - // Duplicate should have the custom type - const duplicateDoc = workspaceStore.documents.get(duplicateId); - const hasCustomType = duplicateDoc?.nodeTypes.some(t => t.id === 'custom'); - expect(hasCustomType).toBe(true); - - // Both documents should exist - expect(workspaceStore.documents.size).toBe(2); - }); - }); - - describe('History Store Integration', () => { - it('should initialize history for new document', () => { - const workspaceStore = useWorkspaceStore.getState(); - const docId = workspaceStore.createDocument('History Test'); - - const historyStore = useHistoryStore.getState(); - - // History should be initialized (can check if functions work) - const canUndo = historyStore.canUndo(docId); - const canRedo = historyStore.canRedo(docId); - - // New document shouldn't have undo/redo yet - expect(canUndo).toBe(false); - expect(canRedo).toBe(false); - }); - - it('should maintain separate history per document', () => { - const workspaceStore = useWorkspaceStore.getState(); - const doc1Id = workspaceStore.createDocument('Doc 1'); - const doc2Id = workspaceStore.createDocument('Doc 2'); - - const historyStore = useHistoryStore.getState(); - - // Both documents should have independent history - const doc1CanUndo = historyStore.canUndo(doc1Id); - const doc2CanUndo = historyStore.canUndo(doc2Id); - - // Both should be false for new documents - expect(doc1CanUndo).toBe(false); - expect(doc2CanUndo).toBe(false); - }); - - it('should remove history when document is deleted', () => { - const workspaceStore = useWorkspaceStore.getState(); - const docId = workspaceStore.createDocument('To Delete'); - - const historyStore = useHistoryStore.getState(); - - // Initialize history - historyStore.initializeHistory(docId); - - // Mock confirm - vi.spyOn(window, 'confirm').mockReturnValue(true); - - // Delete document - workspaceStore.deleteDocument(docId); - - // History should be removed (checking canUndo shouldn't error) - const canUndo = historyStore.canUndo(docId); - expect(canUndo).toBe(false); - - vi.restoreAllMocks(); - }); - }); - - describe('Document Rename Integration', () => { - it('should update document title and metadata', () => { - const workspaceStore = useWorkspaceStore.getState(); - const docId = workspaceStore.createDocument('Original Title'); - - workspaceStore.renameDocument(docId, 'New Title'); - - const doc = workspaceStore.documents.get(docId); - expect(doc?.metadata.title).toBe('New Title'); - - const metadata = workspaceStore.documentMetadata.get(docId); - expect(metadata?.title).toBe('New Title'); - }); - - it('should mark document as dirty after rename', () => { - const workspaceStore = useWorkspaceStore.getState(); - const docId = workspaceStore.createDocument('Test'); - - workspaceStore.renameDocument(docId, 'Renamed'); - - const metadata = workspaceStore.documentMetadata.get(docId); - expect(metadata?.isDirty).toBe(true); - }); - }); - - describe('Multiple Document Operations', () => { - it('should handle creating many documents', () => { - const workspaceStore = useWorkspaceStore.getState(); - - const docIds: string[] = []; - for (let i = 0; i < 10; i++) { - const docId = workspaceStore.createDocument(`Document ${i}`); - docIds.push(docId); - } - - expect(workspaceStore.documents.size).toBe(10); - expect(workspaceStore.documentOrder).toHaveLength(10); - - // All should be unique - const uniqueIds = new Set(docIds); - expect(uniqueIds.size).toBe(10); - }); - - it('should handle deleting multiple documents in sequence', () => { - const workspaceStore = useWorkspaceStore.getState(); - - const doc1Id = workspaceStore.createDocument('Doc 1'); - const doc2Id = workspaceStore.createDocument('Doc 2'); - const doc3Id = workspaceStore.createDocument('Doc 3'); - - // Mock confirm - vi.spyOn(window, 'confirm').mockReturnValue(true); - - workspaceStore.deleteDocument(doc2Id); - expect(workspaceStore.documents.size).toBe(2); - - workspaceStore.deleteDocument(doc3Id); - expect(workspaceStore.documents.size).toBe(1); - - expect(workspaceStore.documents.has(doc1Id)).toBe(true); - - vi.restoreAllMocks(); - }); - }); -}); diff --git a/src/stores/historyStore.test.ts b/src/stores/historyStore.test.ts index 0801d9e..fa45dcf 100644 --- a/src/stores/historyStore.test.ts +++ b/src/stores/historyStore.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { useHistoryStore, type DocumentSnapshot, type HistoryAction } from './historyStore'; -import { mockNodeTypes, mockEdgeTypes, mockLabels } from '../test/mocks'; +import { mockNodeTypes, mockEdgeTypes, mockLabels } from '../test-utils/mocks'; import type { ConstellationDocument } from './persistence/types'; import type { Timeline } from '../types/timeline'; diff --git a/src/stores/workspaceStore.test.ts b/src/stores/workspaceStore.test.ts index 830ea6d..a966cc7 100644 --- a/src/stores/workspaceStore.test.ts +++ b/src/stores/workspaceStore.test.ts @@ -5,7 +5,7 @@ import { loadDocumentFromStorage, clearWorkspaceStorage, } from './workspace/persistence'; -import { mockNodeTypes, mockEdgeTypes } from '../test/mocks'; +import { mockNodeTypes, mockEdgeTypes } from '../test-utils/mocks'; // Create a mock showToast that we can track const mockShowToast = vi.fn(); diff --git a/src/test/integration-utils.tsx b/src/test-utils/integration-utils.tsx similarity index 100% rename from src/test/integration-utils.tsx rename to src/test-utils/integration-utils.tsx diff --git a/src/test/mocks.ts b/src/test-utils/mocks.ts similarity index 100% rename from src/test/mocks.ts rename to src/test-utils/mocks.ts diff --git a/src/test/setup.ts b/src/test-utils/setup.ts similarity index 100% rename from src/test/setup.ts rename to src/test-utils/setup.ts diff --git a/src/test/test-helpers.ts b/src/test-utils/test-helpers.ts similarity index 100% rename from src/test/test-helpers.ts rename to src/test-utils/test-helpers.ts diff --git a/vite.config.ts b/vite.config.ts index 0278751..d4416b8 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -12,13 +12,13 @@ export default defineConfig({ test: { globals: true, environment: "happy-dom", - setupFiles: "./src/test/setup.ts", + setupFiles: "./src/test-utils/setup.ts", coverage: { provider: "v8", reporter: ["text", "json", "html"], exclude: [ "node_modules/", - "src/test/", + "src/test-utils/", "**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts",