diff --git a/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationList.tsx b/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationList.tsx index 8c6903e8ff..9d65fd1737 100644 --- a/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationList.tsx +++ b/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationList.tsx @@ -213,7 +213,7 @@ export const RecentProjectsNavigation: FC<{ {mode === 'full' && ( Recent project diff --git a/frontend/src/hooks/useCustomEvent.ts b/frontend/src/hooks/useCustomEvent.ts new file mode 100644 index 0000000000..db6b199682 --- /dev/null +++ b/frontend/src/hooks/useCustomEvent.ts @@ -0,0 +1,27 @@ +import { useEffect } from 'react'; + +/** + * A hook that provides methods to emit and listen to custom DOM events. + * @param eventName The name of the event to listen for and dispatch. + */ +export const useCustomEvent = ( + eventName: string, + handler: (event: CustomEvent) => void, +) => { + const emitEvent = () => { + const event = new CustomEvent(eventName); + document.dispatchEvent(event); + }; + + useEffect(() => { + const eventListener = (event: Event) => handler(event as CustomEvent); + + document.addEventListener(eventName, eventListener); + + return () => { + document.removeEventListener(eventName, eventListener); + }; + }, [eventName, handler]); + + return { emitEvent }; +}; diff --git a/frontend/src/hooks/useLastViewedProject.test.tsx b/frontend/src/hooks/useLastViewedProject.test.tsx new file mode 100644 index 0000000000..a2e574bca0 --- /dev/null +++ b/frontend/src/hooks/useLastViewedProject.test.tsx @@ -0,0 +1,58 @@ +import type React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { useLastViewedProject } from './useLastViewedProject'; + +const TestComponent: React.FC<{ testId: string; buttonLabel: string }> = ({ + testId, + buttonLabel, +}) => { + const { lastViewed, setLastViewed } = useLastViewedProject(); + + return ( +
+ {lastViewed} + +
+ ); +}; + +describe('Synchronization between multiple components using useLastViewedProject', () => { + beforeEach(() => { + localStorage.clear(); + render( + <> + + + , + ); + }); + + it('updates both components when one updates its last viewed project', async () => { + expect(screen.getByTestId('component1').textContent).toBe(''); + expect(screen.getByTestId('component2').textContent).toBe(''); + + fireEvent.click(screen.getByTestId('update-component1')); + + expect(await screen.findByTestId('component1')).toHaveTextContent( + 'First Project', + ); + expect(await screen.findByTestId('component2')).toHaveTextContent( + 'First Project', + ); + + fireEvent.click(screen.getByTestId('update-component2')); + + expect(await screen.findByTestId('component1')).toHaveTextContent( + 'Second Project', + ); + expect(await screen.findByTestId('component2')).toHaveTextContent( + 'Second Project', + ); + }); +}); diff --git a/frontend/src/hooks/useLastViewedProject.ts b/frontend/src/hooks/useLastViewedProject.ts index 83747a3663..9775b8f821 100644 --- a/frontend/src/hooks/useLastViewedProject.ts +++ b/frontend/src/hooks/useLastViewedProject.ts @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { getLocalStorageItem, setLocalStorageItem } from '../utils/storage'; import { basePath } from 'utils/formatPath'; +import { useCustomEvent } from './useCustomEvent'; export const useLastViewedProject = () => { const key = `${basePath}:unleash-lastViewedProject`; @@ -9,9 +10,14 @@ export const useLastViewedProject = () => { return getLocalStorageItem(key); }); + const { emitEvent } = useCustomEvent('lastViewedProjectUpdated', () => { + setLastViewed(getLocalStorageItem(key)); + }); + useEffect(() => { if (lastViewed) { setLocalStorageItem(key, lastViewed); + emitEvent(); } }, [lastViewed, key]);