fix: final refactoring pass
This commit is contained in:
@@ -398,6 +398,7 @@ Reduce repeated local code with tiny helpers while preserving readability.
|
|||||||
### Progress Check
|
### Progress Check
|
||||||
- Completed: consolidated repeated suggestion item rendering in `AISuggestionsModal` into one shared rendering path.
|
- Completed: consolidated repeated suggestion item rendering in `AISuggestionsModal` into one shared rendering path.
|
||||||
- Completed: added focused component tests to guard selection/apply and empty-suggestions behavior.
|
- Completed: added focused component tests to guard selection/apply and empty-suggestions behavior.
|
||||||
|
- Completed: extracted shared tab-state persistence utility and replaced duplicate local-storage logic in `App` and `ProjectSelector`.
|
||||||
|
|
||||||
### Coverage & Test Quality (fresh run: `npm run test:coverage`)
|
### Coverage & Test Quality (fresh run: `npm run test:coverage`)
|
||||||
- `src/renderer/components/ProjectSelector/ProjectSelector.tsx`: 0% statements/functions/branches.
|
- `src/renderer/components/ProjectSelector/ProjectSelector.tsx`: 0% statements/functions/branches.
|
||||||
|
|||||||
@@ -1,22 +1,9 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { ActivityBar, Sidebar, Editor, StatusBar, Panel, TabBar, ToastContainer, showToast, ResizablePanel } from './components';
|
import { ActivityBar, Sidebar, Editor, StatusBar, Panel, TabBar, ToastContainer, showToast, ResizablePanel } from './components';
|
||||||
import { useAppStore, PostData, MediaData, TaskProgress, TabState } from './store';
|
import { useAppStore, PostData, MediaData, TaskProgress } from './store';
|
||||||
|
import { loadTabsForProject, saveTabsForProject } from './utils';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
// Helper to load tabs for a project from localStorage
|
|
||||||
const TAB_STATE_PREFIX = 'bds-tabs-';
|
|
||||||
const loadTabsForProject = (projectId: string): TabState | null => {
|
|
||||||
try {
|
|
||||||
const stored = localStorage.getItem(`${TAB_STATE_PREFIX}${projectId}`);
|
|
||||||
if (stored) {
|
|
||||||
return JSON.parse(stored) as TabState;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load tab state:', error);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
setPosts,
|
setPosts,
|
||||||
@@ -93,11 +80,7 @@ const App: React.FC = () => {
|
|||||||
const projectId = state.activeProject?.id;
|
const projectId = state.activeProject?.id;
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
const tabState = state.getTabState();
|
const tabState = state.getTabState();
|
||||||
try {
|
saveTabsForProject(projectId, tabState);
|
||||||
localStorage.setItem(`${TAB_STATE_PREFIX}${projectId}`, JSON.stringify(tabState));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to save tab state on unload:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,9 @@
|
|||||||
import React, { useState, useRef, useEffect } from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
import { useAppStore, ProjectData, PostData, MediaData, TabState } from '../../store';
|
import { useAppStore, ProjectData, PostData, MediaData } from '../../store';
|
||||||
|
import { loadTabsForProject, saveTabsForProject } from '../../utils';
|
||||||
import { showToast } from '../Toast';
|
import { showToast } from '../Toast';
|
||||||
import './ProjectSelector.css';
|
import './ProjectSelector.css';
|
||||||
|
|
||||||
// Helper functions for project-specific tab persistence
|
|
||||||
const TAB_STATE_PREFIX = 'bds-tabs-';
|
|
||||||
|
|
||||||
const saveTabsForProject = (projectId: string, tabState: TabState): void => {
|
|
||||||
try {
|
|
||||||
localStorage.setItem(`${TAB_STATE_PREFIX}${projectId}`, JSON.stringify(tabState));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to save tab state:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadTabsForProject = (projectId: string): TabState | null => {
|
|
||||||
try {
|
|
||||||
const stored = localStorage.getItem(`${TAB_STATE_PREFIX}${projectId}`);
|
|
||||||
if (stored) {
|
|
||||||
return JSON.parse(stored) as TabState;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load tab state:', error);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ProjectSelector: React.FC = () => {
|
export const ProjectSelector: React.FC = () => {
|
||||||
const { projects, activeProject, setProjects, setActiveProject, setPosts, setMedia, setSelectedPost, setSelectedMedia, removeProject, getTabState, restoreTabState, clearTabs } = useAppStore();
|
const { projects, activeProject, setProjects, setActiveProject, setPosts, setMedia, setSelectedPost, setSelectedMedia, removeProject, getTabState, restoreTabState, clearTabs } = useAppStore();
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ export { AutoSaveManager, type AutoSaveConfig } from './autoSave';
|
|||||||
export { getContrastColor } from './color';
|
export { getContrastColor } from './color';
|
||||||
export { unescapeMacroSyntax } from './markdownEscape';
|
export { unescapeMacroSyntax } from './markdownEscape';
|
||||||
export { groupPostsByStatus, type GroupedPosts, type PostStatus } from './postGrouping';
|
export { groupPostsByStatus, type GroupedPosts, type PostStatus } from './postGrouping';
|
||||||
|
export { loadTabsForProject, saveTabsForProject } from './tabPersistence';
|
||||||
|
|||||||
23
src/renderer/utils/tabPersistence.ts
Normal file
23
src/renderer/utils/tabPersistence.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { TabState } from '../store';
|
||||||
|
|
||||||
|
const TAB_STATE_PREFIX = 'bds-tabs-';
|
||||||
|
|
||||||
|
export const saveTabsForProject = (projectId: string, tabState: TabState): void => {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(`${TAB_STATE_PREFIX}${projectId}`, JSON.stringify(tabState));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save tab state:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadTabsForProject = (projectId: string): TabState | null => {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(`${TAB_STATE_PREFIX}${projectId}`);
|
||||||
|
if (stored) {
|
||||||
|
return JSON.parse(stored) as TabState;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load tab state:', error);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
50
tests/renderer/utils/tabPersistence.test.ts
Normal file
50
tests/renderer/utils/tabPersistence.test.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||||
|
import type { TabState } from '../../../src/renderer/store';
|
||||||
|
import { loadTabsForProject, saveTabsForProject } from '../../../src/renderer/utils/tabPersistence';
|
||||||
|
|
||||||
|
const projectId = 'project-1';
|
||||||
|
|
||||||
|
const sampleTabState: TabState = {
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
id: 'post-1',
|
||||||
|
type: 'post',
|
||||||
|
title: 'Hello World',
|
||||||
|
dirty: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
activeTabId: 'post-1',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('tabPersistence', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('saves and loads tab state for a project', () => {
|
||||||
|
saveTabsForProject(projectId, sampleTabState);
|
||||||
|
|
||||||
|
const loaded = loadTabsForProject(projectId);
|
||||||
|
|
||||||
|
expect(loaded).toEqual(sampleTabState);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when no tab state is stored', () => {
|
||||||
|
expect(loadTabsForProject(projectId)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when stored tab state is invalid JSON', () => {
|
||||||
|
localStorage.setItem('bds-tabs-project-1', '{invalid-json');
|
||||||
|
|
||||||
|
expect(loadTabsForProject(projectId)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not throw when localStorage.setItem fails', () => {
|
||||||
|
vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => {
|
||||||
|
throw new Error('quota exceeded');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => saveTabsForProject(projectId, sampleTabState)).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user