feat: settings panel

This commit is contained in:
2026-02-10 16:38:20 +01:00
parent 192969e75a
commit 3f0c767809
14 changed files with 1482 additions and 88 deletions

View File

@@ -861,8 +861,8 @@ describe('DropboxSyncEngine', () => {
}));
});
it('should start watching local directories', () => {
engine.startWatching();
it('should start watching local directories', async () => {
await engine.startWatching();
expect(mockChokidarWatch).toHaveBeenCalledWith(
expect.arrayContaining([
@@ -876,45 +876,45 @@ describe('DropboxSyncEngine', () => {
);
});
it('should set status to watching when watching starts', () => {
engine.startWatching();
it('should set status to watching when watching starts', async () => {
await engine.startWatching();
expect(engine.getStatus()).toBe('watching');
});
it('should stop watching when requested', () => {
engine.startWatching();
it('should stop watching when requested', async () => {
await engine.startWatching();
engine.stopWatching();
expect(mockWatcher.close).toHaveBeenCalled();
});
it('should set status to idle when watching stops', () => {
engine.startWatching();
it('should set status to idle when watching stops', async () => {
await engine.startWatching();
engine.stopWatching();
expect(engine.getStatus()).toBe('idle');
});
it('should emit watchStarted event', () => {
it('should emit watchStarted event', async () => {
const handler = vi.fn();
engine.on('watchStarted', handler);
engine.startWatching();
await engine.startWatching();
expect(handler).toHaveBeenCalled();
});
it('should emit watchStopped event', () => {
it('should emit watchStopped event', async () => {
const handler = vi.fn();
engine.on('watchStopped', handler);
engine.startWatching();
await engine.startWatching();
engine.stopWatching();
expect(handler).toHaveBeenCalled();
});
it('should register add, change, and unlink handlers', () => {
engine.startWatching();
it('should register add, change, and unlink handlers', async () => {
await engine.startWatching();
const onCalls = mockWatcher.on.mock.calls.map((call: any[]) => call[0]);
expect(onCalls).toContain('add');

View File

@@ -0,0 +1,221 @@
/**
* Tests for SettingsView component behavior
* Validates VS Code-style structured preferences with Dropbox sync settings
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { useAppStore } from '../../../src/renderer/store/appStore';
// Direct store access
const getStore = () => useAppStore.getState();
const setState = useAppStore.setState;
describe('SettingsView Behavior', () => {
beforeEach(() => {
setState({
syncConfigured: false,
syncStatus: 'idle',
});
vi.clearAllMocks();
localStorage.clear();
});
describe('Settings Categories', () => {
it('should have sync settings as a category in the store', () => {
// The activeView: 'settings' should be a valid view
getStore().setActiveView('settings');
expect(getStore().activeView).toBe('settings');
});
it('should persist preferred editor mode', () => {
getStore().setPreferredEditorMode('markdown');
expect(getStore().preferredEditorMode).toBe('markdown');
});
});
describe('Turso Cloud Sync Configuration', () => {
it('should call sync.configure with Turso credentials', async () => {
const mockConfigure = vi.fn().mockResolvedValue(undefined);
(window as any).electronAPI.sync.configure = mockConfigure;
await window.electronAPI?.sync.configure({
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: 'test-token',
autoSync: true,
syncInterval: 5,
});
expect(mockConfigure).toHaveBeenCalledWith({
tursoUrl: 'libsql://test.turso.io',
tursoAuthToken: 'test-token',
autoSync: true,
syncInterval: 5,
});
});
it('should update syncConfigured status after successful configure', () => {
getStore().setSyncConfigured(true);
expect(getStore().syncConfigured).toBe(true);
});
});
describe('Dropbox Sync Configuration', () => {
it('should call dropbox.configure with Dropbox credentials', 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(),
};
await window.electronAPI?.dropbox?.configure({
accessToken: 'dbx-test-token',
appKey: 'test-app-key',
remotePath: '/blog',
});
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('Credentials Storage', () => {
it('should save credentials to localStorage', () => {
const creds = {
tursoUrl: 'libsql://test.turso.io',
tursoToken: 'test-token',
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.tursoUrl).toBe('libsql://test.turso.io');
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 = {
tursoUrl: 'libsql://saved.turso.io',
tursoToken: 'saved-token',
dropboxAccessToken: 'saved-dbx-token',
};
localStorage.setItem('bds-credentials', JSON.stringify(creds));
const loaded = JSON.parse(localStorage.getItem('bds-credentials') || '{}');
expect(loaded.tursoUrl).toBe('libsql://saved.turso.io');
expect(loaded.dropboxAccessToken).toBe('saved-dbx-token');
});
it('should handle clearing sync credentials independently', () => {
const creds = {
tursoUrl: 'libsql://test.turso.io',
tursoToken: 'test-token',
dropboxAccessToken: 'dbx-token',
dropboxAppKey: 'dbx-key',
};
localStorage.setItem('bds-credentials', JSON.stringify(creds));
// Clear only Turso credentials
const loaded = JSON.parse(localStorage.getItem('bds-credentials') || '{}');
const cleared = { ...loaded, tursoUrl: '', tursoToken: '' };
localStorage.setItem('bds-credentials', JSON.stringify(cleared));
const result = JSON.parse(localStorage.getItem('bds-credentials') || '{}');
expect(result.tursoUrl).toBe('');
expect(result.tursoToken).toBe('');
// Dropbox credentials should be untouched
expect(result.dropboxAccessToken).toBe('dbx-token');
expect(result.dropboxAppKey).toBe('dbx-key');
});
it('should handle clearing Dropbox credentials independently', () => {
const creds = {
tursoUrl: 'libsql://test.turso.io',
tursoToken: 'test-token',
dropboxAccessToken: 'dbx-token',
dropboxAppKey: 'dbx-key',
dropboxRemotePath: '/blog',
};
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') || '{}');
// Turso credentials should be untouched
expect(result.tursoUrl).toBe('libsql://test.turso.io');
expect(result.tursoToken).toBe('test-token');
expect(result.dropboxAccessToken).toBe('');
expect(result.dropboxAppKey).toBe('');
});
});
describe('Data Management Settings', () => {
it('should call rebuild posts from files', 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 rebuild media from files', async () => {
const mockRebuild = vi.fn().mockResolvedValue(undefined);
(window as any).electronAPI.media.rebuildFromFiles = mockRebuild;
await window.electronAPI?.media.rebuildFromFiles();
expect(mockRebuild).toHaveBeenCalled();
});
});
});

View File

@@ -75,6 +75,19 @@ Object.defineProperty(globalThis, 'window', {
getLog: vi.fn(),
stopAutoSync: vi.fn(),
},
dropbox: {
configure: vi.fn(),
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(),
},
tasks: {
getAll: vi.fn(),
getRunning: vi.fn(),