From 1a56106148b17bce2dd376660741dff762e6a4d5 Mon Sep 17 00:00:00 2001 From: David Butenhof Date: Fri, 23 Jan 2026 14:03:42 -0500 Subject: [PATCH 1/2] Persist sort order list across reloads --- frontend/src/App.jsx | 21 ++++++++++- frontend/src/App.test.jsx | 79 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 8100286..a1076c5 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -35,7 +35,17 @@ const App = () => { const [isManageMilestonesOpen, setIsManageMilestonesOpen] = useState(false); const [isManageLabelsOpen, setIsManageLabelsOpen] = useState(false); const [isManageSortOpen, setIsManageSortOpen] = useState(false); - const [sortOrder, setSortOrder] = useState([]); + + // Load sort order from localStorage on mount + const [sortOrder, setSortOrder] = useState(() => { + try { + const saved = localStorage.getItem('issueSortOrder'); + return saved ? JSON.parse(saved) : []; + } catch (error) { + console.error('Failed to load sort order from localStorage:', error); + return []; + } + }); useEffect(() => { fetchProject() @@ -142,6 +152,15 @@ const App = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // Save sort order to localStorage whenever it changes + useEffect(() => { + try { + localStorage.setItem('issueSortOrder', JSON.stringify(sortOrder)); + } catch (error) { + console.error('Failed to save sort order to localStorage:', error); + } + }, [sortOrder]); + const loadMilestones = () => { setLoading(true); setError(null); diff --git a/frontend/src/App.test.jsx b/frontend/src/App.test.jsx index aa02253..2d7fd6d 100644 --- a/frontend/src/App.test.jsx +++ b/frontend/src/App.test.jsx @@ -10,8 +10,29 @@ import assigneesCache from './utils/assigneesCache'; vi.mock('./services/api'); describe('App', () => { + // Mock localStorage + let store = {}; + const localStorageMock = { + getItem: vi.fn((key) => store[key] || null), + setItem: vi.fn((key, value) => { + store[key] = value.toString(); + }), + removeItem: vi.fn((key) => { + delete store[key]; + }), + clear: vi.fn(() => { + store = {}; + }), + }; + beforeEach(() => { vi.clearAllMocks(); + // Clear localStorage mock store + store = {}; + Object.defineProperty(window, 'localStorage', { + value: localStorageMock, + writable: true, + }); clearMilestonesCache(); clearLabelsCache(); // Clear assignees cache @@ -139,4 +160,62 @@ describe('App', () => { expect(screen.getByText(/No milestones found/i)).toBeInTheDocument(); }); }); + + it('loads sort order from localStorage on mount', async () => { + const savedSortOrder = ['label1', 'label2', 'label3']; + localStorageMock.setItem( + 'issueSortOrder', + JSON.stringify(savedSortOrder) + ); + api.fetchMilestones.mockResolvedValue([]); + + await act(async () => { + render(); + }); + + await waitFor(() => { + // Verify localStorage.getItem was called + expect(localStorageMock.getItem).toHaveBeenCalledWith('issueSortOrder'); + }); + }); + + it('saves sort order to localStorage when changed', async () => { + api.fetchMilestones.mockResolvedValue([]); + api.fetchLabels.mockResolvedValue([ + { name: 'label1', color: 'ff0000', description: 'Label 1' }, + { name: 'label2', color: '00ff00', description: 'Label 2' }, + ]); + + await act(async () => { + render(); + }); + + await waitFor(() => { + expect(screen.getByText('Sort')).toBeInTheDocument(); + }); + + // Open the Sort modal + const sortButton = screen.getByText('Sort'); + await act(async () => { + sortButton.click(); + }); + + await waitFor(() => { + expect(screen.getByText(/Sort Issues by Labels/i)).toBeInTheDocument(); + }); + + // Add a label to the sort order + const addLabelButton = screen.getByText('+ label1'); + await act(async () => { + addLabelButton.click(); + }); + + // Verify localStorage.setItem was called with the new sort order + await waitFor(() => { + expect(localStorageMock.setItem).toHaveBeenCalledWith( + 'issueSortOrder', + JSON.stringify(['label1']) + ); + }); + }); }); From acfc74ec40436b2ee2a1823ca3dd5de01f3798ff Mon Sep 17 00:00:00 2001 From: David Butenhof Date: Fri, 23 Jan 2026 14:12:02 -0500 Subject: [PATCH 2/2] prettierized --- frontend/src/App.jsx | 2 +- frontend/src/App.test.jsx | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index a1076c5..d2dc8c7 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -35,7 +35,7 @@ const App = () => { const [isManageMilestonesOpen, setIsManageMilestonesOpen] = useState(false); const [isManageLabelsOpen, setIsManageLabelsOpen] = useState(false); const [isManageSortOpen, setIsManageSortOpen] = useState(false); - + // Load sort order from localStorage on mount const [sortOrder, setSortOrder] = useState(() => { try { diff --git a/frontend/src/App.test.jsx b/frontend/src/App.test.jsx index 2d7fd6d..3f004b3 100644 --- a/frontend/src/App.test.jsx +++ b/frontend/src/App.test.jsx @@ -163,10 +163,7 @@ describe('App', () => { it('loads sort order from localStorage on mount', async () => { const savedSortOrder = ['label1', 'label2', 'label3']; - localStorageMock.setItem( - 'issueSortOrder', - JSON.stringify(savedSortOrder) - ); + localStorageMock.setItem('issueSortOrder', JSON.stringify(savedSortOrder)); api.fetchMilestones.mockResolvedValue([]); await act(async () => {