chore: remove hot air tests

This commit is contained in:
2026-02-11 12:40:38 +01:00
parent 4192498cde
commit 6b9aa3fb1e
2 changed files with 0 additions and 602 deletions

View File

@@ -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> = {}): 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');
});
});
});

View File

@@ -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');
});
});
});