diff --git a/src/renderer/components/GitDiffView/GitDiffView.tsx b/src/renderer/components/GitDiffView/GitDiffView.tsx index 1c33830..97e3925 100644 --- a/src/renderer/components/GitDiffView/GitDiffView.tsx +++ b/src/renderer/components/GitDiffView/GitDiffView.tsx @@ -36,7 +36,7 @@ function detectLanguage(filePath: string): string { } export const GitDiffView: React.FC = ({ filePath }) => { - const { activeProject } = useAppStore(); + const { activeProject, gitDiffPreferences } = useAppStore(); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [original, setOriginal] = useState(''); @@ -105,14 +105,17 @@ export const GitDiffView: React.FC = ({ filePath }) => { height="100%" options={{ readOnly: true, - renderSideBySide: false, + renderSideBySide: gitDiffPreferences.viewStyle === 'side-by-side', minimap: { enabled: false }, lineNumbers: 'on', scrollBeyondLastLine: false, renderOverviewRuler: true, originalEditable: false, diffCodeLens: false, - wordWrap: 'off', + wordWrap: gitDiffPreferences.wordWrap ? 'on' : 'off', + hideUnchangedRegions: { + enabled: gitDiffPreferences.hideUnchangedRegions, + }, ignoreTrimWhitespace: false, }} /> diff --git a/src/renderer/components/SettingsView/SettingsView.tsx b/src/renderer/components/SettingsView/SettingsView.tsx index 4902d7a..435720c 100644 --- a/src/renderer/components/SettingsView/SettingsView.tsx +++ b/src/renderer/components/SettingsView/SettingsView.tsx @@ -90,7 +90,14 @@ const SettingSection: React.FC<{ }; export const SettingsView: React.FC = () => { - const { preferredEditorMode, setPreferredEditorMode, activeProject, setActiveProject } = useAppStore(); + const { + preferredEditorMode, + setPreferredEditorMode, + gitDiffPreferences, + setGitDiffPreferences, + activeProject, + setActiveProject, + } = useAppStore(); const [searchQuery, setSearchQuery] = useState(''); const [credentials, setCredentials] = useState(defaultCredentials); const [showSecrets, setShowSecrets] = useState(false); @@ -410,6 +417,65 @@ export const SettingsView: React.FC = () => { + + + + + + + + setGitDiffPreferences({ + ...gitDiffPreferences, + wordWrap: e.target.checked, + }) + } + /> + + + + + setGitDiffPreferences({ + ...gitDiffPreferences, + hideUnchangedRegions: e.target.checked, + }) + } + /> + ); diff --git a/src/renderer/store/appStore.ts b/src/renderer/store/appStore.ts index 3ed4c6b..161b673 100644 --- a/src/renderer/store/appStore.ts +++ b/src/renderer/store/appStore.ts @@ -38,6 +38,13 @@ export interface ErrorDetails { export type { DeleteReference, ConfirmDeleteDetails }; export type EditorMode = 'wysiwyg' | 'markdown' | 'preview'; +export type GitDiffViewStyle = 'inline' | 'side-by-side'; + +export interface GitDiffPreferences { + wordWrap: boolean; + viewStyle: GitDiffViewStyle; + hideUnchangedRegions: boolean; +} // App State Store interface AppState { @@ -56,6 +63,7 @@ interface AppState { selectedPostId: string | null; selectedMediaId: string | null; preferredEditorMode: EditorMode; + gitDiffPreferences: GitDiffPreferences; // Data posts: PostData[]; @@ -102,6 +110,7 @@ interface AppState { setSelectedPost: (id: string | null) => void; setSelectedMedia: (id: string | null) => void; setPreferredEditorMode: (mode: EditorMode) => void; + setGitDiffPreferences: (preferences: GitDiffPreferences) => void; setPosts: (posts: PostData[], hasMore?: boolean, total?: number) => void; appendPosts: (posts: PostData[], hasMore: boolean) => void; @@ -153,6 +162,11 @@ export const useAppStore = create()( selectedPostId: null, selectedMediaId: null, preferredEditorMode: 'wysiwyg', + gitDiffPreferences: { + wordWrap: true, + viewStyle: 'inline', + hideUnchangedRegions: false, + }, // Initial Data posts: [], @@ -270,6 +284,7 @@ export const useAppStore = create()( setSelectedPost: (id) => set({ selectedPostId: id }), setSelectedMedia: (id) => set({ selectedMediaId: id }), setPreferredEditorMode: (mode) => set({ preferredEditorMode: mode }), + setGitDiffPreferences: (preferences) => set({ gitDiffPreferences: preferences }), // Post Actions setPosts: (posts, hasMore = false, total = 0) => set({ posts, hasMorePosts: hasMore, totalPosts: total }), @@ -355,6 +370,7 @@ export const useAppStore = create()( selectedPostId: state.selectedPostId, selectedMediaId: state.selectedMediaId, preferredEditorMode: state.preferredEditorMode, + gitDiffPreferences: state.gitDiffPreferences, // Tabs are persisted here for now (project-specific persistence handled separately) tabs: state.tabs, activeTabId: state.activeTabId, @@ -370,6 +386,7 @@ export const useAppStore = create()( tabs: persistedState.tabs || [], activeTabId: persistedState.activeTabId || null, dirtyPosts: new Set(persistedState.dirtyPosts || []), + gitDiffPreferences: persistedState.gitDiffPreferences || current.gitDiffPreferences, }; }, } diff --git a/tests/renderer/components/GitDiffView.test.tsx b/tests/renderer/components/GitDiffView.test.tsx index 9c1cef7..9ae7378 100644 --- a/tests/renderer/components/GitDiffView.test.tsx +++ b/tests/renderer/components/GitDiffView.test.tsx @@ -7,11 +7,14 @@ import { useAppStore } from '../../../src/renderer/store'; vi.mock('@monaco-editor/react', () => ({ __esModule: true, default: (_props: unknown) => null, - DiffEditor: (props: { original: string; modified: string; language?: string }) => ( + DiffEditor: (props: { original: string; modified: string; language?: string; options?: Record }) => (
original:{props.original}
modified:{props.modified}
language:{props.language}
+
renderSideBySide:{String(props.options?.renderSideBySide)}
+
wordWrap:{String(props.options?.wordWrap)}
+
hideUnchanged:{String((props.options?.hideUnchangedRegions as { enabled?: boolean } | undefined)?.enabled)}
), })); @@ -30,6 +33,11 @@ describe('GitDiffView', () => { createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, + gitDiffPreferences: { + wordWrap: true, + viewStyle: 'inline', + hideUnchangedRegions: false, + }, }); (window as any).electronAPI = { @@ -56,5 +64,8 @@ describe('GitDiffView', () => { expect((window as any).electronAPI.git.getDiffContent).toHaveBeenCalledWith('/repo/path', 'posts/first.md'); expect(screen.getByText('original:# old line')).toBeInTheDocument(); expect(screen.getByText('modified:# new line')).toBeInTheDocument(); + expect(screen.getByText('renderSideBySide:false')).toBeInTheDocument(); + expect(screen.getByText('wordWrap:on')).toBeInTheDocument(); + expect(screen.getByText('hideUnchanged:false')).toBeInTheDocument(); }); }); diff --git a/tests/renderer/components/SettingsView.test.tsx b/tests/renderer/components/SettingsView.test.tsx new file mode 100644 index 0000000..06a0e38 --- /dev/null +++ b/tests/renderer/components/SettingsView.test.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { SettingsView } from '../../../src/renderer/components/SettingsView/SettingsView'; +import { useAppStore } from '../../../src/renderer/store'; + +describe('SettingsView Diff Preferences', () => { + beforeEach(() => { + vi.clearAllMocks(); + + useAppStore.setState({ + activeProject: { + id: 'project-1', + name: 'Test Project', + slug: 'test-project', + isActive: true, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }, + gitDiffPreferences: { + wordWrap: true, + viewStyle: 'inline', + hideUnchangedRegions: false, + }, + }); + + (window as any).electronAPI = { + ...(window as any).electronAPI, + app: { + ...(window as any).electronAPI?.app, + getDefaultProjectPath: vi.fn().mockResolvedValue('/repo/path'), + }, + meta: { + ...(window as any).electronAPI?.meta, + getCategories: vi.fn().mockResolvedValue(['article', 'picture', 'aside', 'page']), + getProjectMetadata: vi.fn().mockResolvedValue({}), + }, + chat: { + ...(window as any).electronAPI?.chat, + getSystemPrompt: vi.fn().mockResolvedValue({ success: true, prompt: '' }), + getApiKey: vi.fn().mockResolvedValue({ hasKey: false, maskedKey: '' }), + getAvailableModels: vi.fn().mockResolvedValue({ success: true, models: [], selectedModel: '' }), + }, + projects: { + ...(window as any).electronAPI?.projects, + update: vi.fn().mockResolvedValue(null), + }, + }; + }); + + it('updates git diff preferences from settings controls', async () => { + render(); + + const viewStyle = await screen.findByLabelText(/diff view style/i); + fireEvent.change(viewStyle, { target: { value: 'side-by-side' } }); + + const wrapCheckbox = screen.getByLabelText(/wrap long lines in diff/i); + fireEvent.click(wrapCheckbox); + + const hideCheckbox = screen.getByLabelText(/hide unchanged regions/i); + fireEvent.click(hideCheckbox); + + expect(useAppStore.getState().gitDiffPreferences).toEqual({ + wordWrap: false, + viewStyle: 'side-by-side', + hideUnchangedRegions: true, + }); + }); +}); diff --git a/tests/renderer/store/appStore.test.ts b/tests/renderer/store/appStore.test.ts index 6fc7cd2..5964884 100644 --- a/tests/renderer/store/appStore.test.ts +++ b/tests/renderer/store/appStore.test.ts @@ -166,6 +166,28 @@ describe('AppStore', () => { expect(getStore().preferredEditorMode).toBe('markdown'); }); + + it('should default git diff preferences to wrapped inline and visible unchanged regions', () => { + expect(getStore().gitDiffPreferences).toEqual({ + wordWrap: true, + viewStyle: 'inline', + hideUnchangedRegions: false, + }); + }); + + it('should update git diff preferences', () => { + getStore().setGitDiffPreferences({ + wordWrap: false, + viewStyle: 'side-by-side', + hideUnchangedRegions: true, + }); + + expect(getStore().gitDiffPreferences).toEqual({ + wordWrap: false, + viewStyle: 'side-by-side', + hideUnchangedRegions: true, + }); + }); }); describe('Type Contract', () => {