From 6b9aa3fb1e2e21008e94e4258a6220aa9a663b21 Mon Sep 17 00:00:00 2001 From: hugo Date: Wed, 11 Feb 2026 12:40:38 +0100 Subject: [PATCH] chore: remove hot air tests --- tests/renderer/components/Editor.test.ts | 335 ------------------ .../renderer/components/SettingsView.test.ts | 267 -------------- 2 files changed, 602 deletions(-) delete mode 100644 tests/renderer/components/Editor.test.ts delete mode 100644 tests/renderer/components/SettingsView.test.ts diff --git a/tests/renderer/components/Editor.test.ts b/tests/renderer/components/Editor.test.ts deleted file mode 100644 index f15eb8a..0000000 --- a/tests/renderer/components/Editor.test.ts +++ /dev/null @@ -1,335 +0,0 @@ -/** - * Editor Component Behavior Tests - * - * Tests the editor's store integration and behavior patterns. - * Given the complexity of the Editor (Monaco, TipTap, async effects), - * these tests focus on: - * 1. Store integration - how editor state syncs with app store - * 2. API call patterns - validating expected API interactions - * 3. State management - dirty tracking, post selection - * - * For full E2E UI tests, Playwright or Electron testing would be more appropriate. - */ - -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { useAppStore, PostData } from '../../../src/renderer/store/appStore'; - -// Helper to create a mock post -const createMockPost = (overrides: Partial = {}): PostData => ({ - id: `post-${Date.now()}-${Math.random().toString(36).substring(7)}`, - title: 'Test Post', - slug: 'test-post', - content: '# Test Content', - status: 'draft', - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - tags: [], - categories: ['article'], - ...overrides, -}); - -// Store access helpers -const getStore = () => useAppStore.getState(); -const setState = useAppStore.setState; - -describe('Editor Behavior', () => { - beforeEach(() => { - // Reset store state - setState({ - posts: [], - selectedPostId: null, - dirtyPosts: new Set(), - activeView: 'posts', - errorModal: null, - isLoading: false, - preferredEditorMode: 'wysiwyg', - }); - - vi.clearAllMocks(); - }); - - describe('Post Selection', () => { - it('should set selected post in store', () => { - const post = createMockPost({ id: 'post-1' }); - getStore().addPost(post); - - getStore().setSelectedPost('post-1'); - - expect(getStore().selectedPostId).toBe('post-1'); - }); - - it('should clear selection when set to null', () => { - const post = createMockPost({ id: 'post-1' }); - getStore().addPost(post); - getStore().setSelectedPost('post-1'); - - getStore().setSelectedPost(null); - - expect(getStore().selectedPostId).toBeNull(); - }); - - it('should clear selection when selected post is removed', () => { - const post = createMockPost({ id: 'post-1' }); - getStore().addPost(post); - getStore().setSelectedPost('post-1'); - - getStore().removePost('post-1'); - - expect(getStore().selectedPostId).toBeNull(); - }); - }); - - describe('Dirty Tracking', () => { - it('should mark post as dirty', () => { - getStore().markDirty('post-1'); - - expect(getStore().isDirty('post-1')).toBe(true); - }); - - it('should mark post as clean', () => { - getStore().markDirty('post-1'); - getStore().markClean('post-1'); - - expect(getStore().isDirty('post-1')).toBe(false); - }); - - it('should track multiple dirty posts independently', () => { - getStore().markDirty('post-1'); - getStore().markDirty('post-2'); - - expect(getStore().isDirty('post-1')).toBe(true); - expect(getStore().isDirty('post-2')).toBe(true); - - getStore().markClean('post-1'); - - expect(getStore().isDirty('post-1')).toBe(false); - expect(getStore().isDirty('post-2')).toBe(true); - }); - - it('should return false for non-dirty posts', () => { - expect(getStore().isDirty('non-existent')).toBe(false); - }); - }); - - describe('Post Update Flow', () => { - it('should update store when save returns successfully', async () => { - const originalPost = createMockPost({ - id: 'post-1', - title: 'Original Title', - content: 'Original content', - }); - - getStore().addPost(originalPost); - - // Simulate the save response from the backend - const updatedPost = { - ...originalPost, - title: 'Updated Title', - content: 'Updated content', - updatedAt: new Date().toISOString(), - }; - - // Simulate the component's save flow - getStore().updatePost('post-1', updatedPost); - getStore().markClean('post-1'); - - // Verify store was updated - const storePost = getStore().posts.find((p) => p.id === 'post-1'); - expect(storePost?.title).toBe('Updated Title'); - expect(storePost?.content).toBe('Updated content'); - expect(getStore().isDirty('post-1')).toBe(false); - }); - - it('should NOT clear store data when save returns undefined', async () => { - const originalPost = createMockPost({ - id: 'post-1', - title: 'Original Title', - content: 'Original content', - }); - - getStore().addPost(originalPost); - - // Simulate save returning undefined (API error/issue) - const result = undefined; - - // Following the component's pattern: only update if result is truthy - if (result) { - getStore().updatePost('post-1', result); - getStore().markClean('post-1'); - } - - // Verify store was NOT corrupted - const storePost = getStore().posts.find((p) => p.id === 'post-1'); - expect(storePost?.title).toBe('Original Title'); - expect(storePost?.content).toBe('Original content'); - }); - }); - - describe('Post Status', () => { - it('should update post status to published', () => { - const post = createMockPost({ id: 'post-1', status: 'draft' }); - getStore().addPost(post); - - getStore().updatePost('post-1', { status: 'published' }); - - const storePost = getStore().posts.find((p) => p.id === 'post-1'); - expect(storePost?.status).toBe('published'); - }); - - it('should update post status to draft when unpublishing', () => { - const post = createMockPost({ id: 'post-1', status: 'published' }); - getStore().addPost(post); - - getStore().updatePost('post-1', { status: 'draft' }); - - const storePost = getStore().posts.find((p) => p.id === 'post-1'); - expect(storePost?.status).toBe('draft'); - }); - }); - - describe('Editor Mode Preference', () => { - it('should store preferred editor mode', () => { - getStore().setPreferredEditorMode('markdown'); - - expect(getStore().preferredEditorMode).toBe('markdown'); - }); - - it('should switch between editor modes', () => { - getStore().setPreferredEditorMode('wysiwyg'); - expect(getStore().preferredEditorMode).toBe('wysiwyg'); - - getStore().setPreferredEditorMode('markdown'); - expect(getStore().preferredEditorMode).toBe('markdown'); - - getStore().setPreferredEditorMode('preview'); - expect(getStore().preferredEditorMode).toBe('preview'); - }); - }); - - describe('Error Modal', () => { - it('should show error modal with details', () => { - const error = { - title: 'Save Failed', - message: 'Network error', - stack: 'Error: Network error\n at save...', - }; - - getStore().showErrorModal(error); - - expect(getStore().errorModal).toEqual(error); - }); - - it('should hide error modal', () => { - getStore().showErrorModal({ - title: 'Test Error', - message: 'Test message', - }); - - getStore().hideErrorModal(); - - expect(getStore().errorModal).toBeNull(); - }); - }); - - describe('Post Switching Behavior', () => { - it('should persist changes when switching between posts', () => { - const post1 = createMockPost({ id: 'post-1', title: 'Post 1' }); - const post2 = createMockPost({ id: 'post-2', title: 'Post 2' }); - - getStore().addPost(post1); - getStore().addPost(post2); - - // Simulate editing and saving post 1 - getStore().updatePost('post-1', { title: 'Post 1 Updated' }); - getStore().markClean('post-1'); - - // Select post 2 - getStore().setSelectedPost('post-2'); - - // Then select post 1 again - getStore().setSelectedPost('post-1'); - - // Verify post 1 still has the saved changes - const storePost1 = getStore().posts.find((p) => p.id === 'post-1'); - expect(storePost1?.title).toBe('Post 1 Updated'); - }); - - it('should track dirty state correctly when switching posts', () => { - const post1 = createMockPost({ id: 'post-1' }); - const post2 = createMockPost({ id: 'post-2' }); - - getStore().addPost(post1); - getStore().addPost(post2); - getStore().setSelectedPost('post-1'); - - // Mark post 1 as dirty - getStore().markDirty('post-1'); - - // Switch to post 2 - getStore().setSelectedPost('post-2'); - - // Post 1 should still be dirty (store doesn't auto-clean on switch) - expect(getStore().isDirty('post-1')).toBe(true); - expect(getStore().isDirty('post-2')).toBe(false); - }); - }); - - describe('Post Deletion', () => { - it('should remove post from store', () => { - const post = createMockPost({ id: 'post-1' }); - getStore().addPost(post); - - getStore().removePost('post-1'); - - expect(getStore().posts).toHaveLength(0); - }); - - it('should clear dirty state when post is removed', () => { - const post = createMockPost({ id: 'post-1' }); - getStore().addPost(post); - getStore().markDirty('post-1'); - expect(getStore().isDirty('post-1')).toBe(true); - - getStore().removePost('post-1'); - - // Store auto-clears dirty state when post is removed - expect(getStore().isDirty('post-1')).toBe(false); - }); - }); - - describe('Edge Cases', () => { - it('should handle update on non-existent post', () => { - // Updating a non-existent post should not add it - getStore().updatePost('non-existent', { title: 'Updated' }); - - expect(getStore().posts).toHaveLength(0); - }); - - it('should handle rapid consecutive saves', () => { - const post = createMockPost({ id: 'post-1', title: 'Original' }); - getStore().addPost(post); - - // Simulate rapid saves - getStore().updatePost('post-1', { title: 'First Update' }); - getStore().updatePost('post-1', { title: 'Second Update' }); - - const storePost = getStore().posts.find((p) => p.id === 'post-1'); - expect(storePost?.title).toBe('Second Update'); - }); - - it('should handle publish after save', () => { - const post = createMockPost({ id: 'post-1', status: 'draft' }); - getStore().addPost(post); - - // Save first - getStore().updatePost('post-1', { title: 'Saved' }); - - // Then publish - getStore().updatePost('post-1', { status: 'published' }); - - const storePost = getStore().posts.find((p) => p.id === 'post-1'); - expect(storePost?.status).toBe('published'); - expect(storePost?.title).toBe('Saved'); - }); - }); -}); diff --git a/tests/renderer/components/SettingsView.test.ts b/tests/renderer/components/SettingsView.test.ts deleted file mode 100644 index 5aa52ad..0000000 --- a/tests/renderer/components/SettingsView.test.ts +++ /dev/null @@ -1,267 +0,0 @@ -/** - * SettingsView Behavior Tests - * - * Tests the settings view's store integration and API call patterns. - * Given the complexity of UI interactions, these tests focus on: - * 1. Store integration - how settings sync with app store - * 2. API call patterns - validating expected API interactions - * 3. LocalStorage persistence - credentials and preferences - */ - -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { useAppStore } from '../../../src/renderer/store/appStore'; - -// Store access helpers -const getStore = () => useAppStore.getState(); -const setState = useAppStore.setState; - -describe('SettingsView Behavior', () => { - beforeEach(() => { - // Reset store state - setState({ - syncConfigured: false, - syncStatus: 'idle', - preferredEditorMode: 'wysiwyg', - }); - - // Clear localStorage - localStorage.clear(); - - vi.clearAllMocks(); - }); - - describe('Editor Preferences', () => { - it('should store preferred editor mode', () => { - getStore().setPreferredEditorMode('markdown'); - - expect(getStore().preferredEditorMode).toBe('markdown'); - }); - - it('should support all editor modes', () => { - getStore().setPreferredEditorMode('wysiwyg'); - expect(getStore().preferredEditorMode).toBe('wysiwyg'); - - getStore().setPreferredEditorMode('markdown'); - expect(getStore().preferredEditorMode).toBe('markdown'); - - getStore().setPreferredEditorMode('preview'); - expect(getStore().preferredEditorMode).toBe('preview'); - }); - }); - - describe('Sync Configuration', () => { - it('should track sync configured status', () => { - getStore().setSyncConfigured(true); - - expect(getStore().syncConfigured).toBe(true); - }); - - it('should default to not configured', () => { - expect(getStore().syncConfigured).toBe(false); - }); - - it('should track sync status', () => { - getStore().setSyncStatus('syncing'); - - expect(getStore().syncStatus).toBe('syncing'); - }); - }); - - describe('Credentials Storage (localStorage)', () => { - it('should save Dropbox credentials to localStorage', () => { - const creds = { - dropboxAccessToken: 'dbx-token', - dropboxAppKey: 'dbx-key', - dropboxRemotePath: '/blog', - }; - - localStorage.setItem('bds-credentials', JSON.stringify(creds)); - - const saved = JSON.parse(localStorage.getItem('bds-credentials') || '{}'); - expect(saved.dropboxAccessToken).toBe('dbx-token'); - expect(saved.dropboxAppKey).toBe('dbx-key'); - expect(saved.dropboxRemotePath).toBe('/blog'); - }); - - it('should load credentials from localStorage', () => { - const creds = { - dropboxAccessToken: 'saved-dbx-token', - dropboxAppKey: 'saved-key', - dropboxRemotePath: '/blog', - }; - - localStorage.setItem('bds-credentials', JSON.stringify(creds)); - - const loaded = JSON.parse(localStorage.getItem('bds-credentials') || '{}'); - expect(loaded.dropboxAccessToken).toBe('saved-dbx-token'); - }); - - it('should handle clearing Dropbox credentials', () => { - const creds = { - dropboxAccessToken: 'dbx-token', - dropboxAppKey: 'dbx-key', - dropboxRemotePath: '/blog', - ftpHost: 'ftp.example.com', - ftpUser: 'user', - }; - - localStorage.setItem('bds-credentials', JSON.stringify(creds)); - - // Clear only Dropbox credentials - const loaded = JSON.parse(localStorage.getItem('bds-credentials') || '{}'); - const cleared = { - ...loaded, - dropboxAccessToken: '', - dropboxAppKey: '', - dropboxRemotePath: '', - }; - localStorage.setItem('bds-credentials', JSON.stringify(cleared)); - - const result = JSON.parse(localStorage.getItem('bds-credentials') || '{}'); - expect(result.dropboxAccessToken).toBe(''); - expect(result.dropboxAppKey).toBe(''); - // FTP credentials should be untouched - expect(result.ftpHost).toBe('ftp.example.com'); - expect(result.ftpUser).toBe('user'); - }); - }); - - // Note: Post categories are now managed via MetaEngine (project-scoped) - // and tested in tests/engine/MetaEngine.test.ts - - describe('API Integration Patterns', () => { - beforeEach(() => { - // Setup window.electronAPI mocks - const mockElectronAPI = (window as any).electronAPI; - if (mockElectronAPI) { - vi.mocked(mockElectronAPI.sync.configure).mockResolvedValue(undefined); - vi.mocked(mockElectronAPI.posts.rebuildFromFiles).mockResolvedValue(undefined); - vi.mocked(mockElectronAPI.posts.getAll).mockResolvedValue([]); - vi.mocked(mockElectronAPI.media.rebuildFromFiles).mockResolvedValue(undefined); - vi.mocked(mockElectronAPI.media.getAll).mockResolvedValue([]); - } - }); - - it('should call sync.configure with correct structure', async () => { - const mockConfigure = vi.fn().mockResolvedValue(undefined); - (window as any).electronAPI.sync.configure = mockConfigure; - - const config = { - autoSync: true, - syncInterval: 5, - }; - - await window.electronAPI?.sync.configure(config); - - expect(mockConfigure).toHaveBeenCalledWith({ - autoSync: true, - syncInterval: 5, - }); - }); - - it('should call posts.rebuildFromFiles', async () => { - const mockRebuild = vi.fn().mockResolvedValue(undefined); - (window as any).electronAPI.posts.rebuildFromFiles = mockRebuild; - - await window.electronAPI?.posts.rebuildFromFiles(); - - expect(mockRebuild).toHaveBeenCalled(); - }); - - it('should call media.rebuildFromFiles', async () => { - const mockRebuild = vi.fn().mockResolvedValue(undefined); - (window as any).electronAPI.media.rebuildFromFiles = mockRebuild; - - await window.electronAPI?.media.rebuildFromFiles(); - - expect(mockRebuild).toHaveBeenCalled(); - }); - - it('should call dropbox.configure with correct structure', async () => { - const mockConfigure = vi.fn().mockResolvedValue(undefined); - (window as any).electronAPI.dropbox = { - configure: mockConfigure, - isConfigured: vi.fn(), - getStatus: vi.fn(), - syncAll: vi.fn(), - startWatching: vi.fn(), - stopWatching: vi.fn(), - startPolling: vi.fn(), - stopPolling: vi.fn(), - getConflicts: vi.fn(), - resolveConflict: vi.fn(), - getLastSyncTime: vi.fn(), - }; - - const config = { - accessToken: 'dbx-test-token', - appKey: 'test-app-key', - remotePath: '/blog', - }; - - await window.electronAPI?.dropbox?.configure(config); - - expect(mockConfigure).toHaveBeenCalledWith({ - accessToken: 'dbx-test-token', - appKey: 'test-app-key', - remotePath: '/blog', - }); - }); - - it('should check dropbox configuration status', async () => { - const mockIsConfigured = vi.fn().mockResolvedValue(true); - (window as any).electronAPI.dropbox = { - ...((window as any).electronAPI.dropbox || {}), - isConfigured: mockIsConfigured, - }; - - const result = await window.electronAPI?.dropbox?.isConfigured(); - - expect(mockIsConfigured).toHaveBeenCalled(); - expect(result).toBe(true); - }); - - it('should trigger Dropbox full sync', async () => { - const mockSyncAll = vi.fn().mockResolvedValue({ uploaded: 0, downloaded: 0, conflicts: 0 }); - (window as any).electronAPI.dropbox = { - ...((window as any).electronAPI.dropbox || {}), - syncAll: mockSyncAll, - }; - - await window.electronAPI?.dropbox?.syncAll(); - - expect(mockSyncAll).toHaveBeenCalled(); - }); - - it('should get last sync time', async () => { - const mockGetLastSyncTime = vi.fn().mockResolvedValue('2026-02-10T12:00:00Z'); - (window as any).electronAPI.dropbox = { - ...((window as any).electronAPI.dropbox || {}), - getLastSyncTime: mockGetLastSyncTime, - }; - - const result = await window.electronAPI?.dropbox?.getLastSyncTime(); - - expect(result).toBe('2026-02-10T12:00:00Z'); - }); - }); - - describe('Active View', () => { - it('should support settings as an active view', () => { - getStore().setActiveView('settings'); - - expect(getStore().activeView).toBe('settings'); - }); - - it('should switch between views', () => { - getStore().setActiveView('posts'); - expect(getStore().activeView).toBe('posts'); - - getStore().setActiveView('media'); - expect(getStore().activeView).toBe('media'); - - getStore().setActiveView('settings'); - expect(getStore().activeView).toBe('settings'); - }); - }); -});