Skip to content

Commit

Permalink
Store viewedItemHistory, sync with localStorage
Browse files Browse the repository at this point in the history
  • Loading branch information
bryophyta committed Feb 24, 2025
1 parent c1f464c commit fbcb657
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 2 deletions.
65 changes: 65 additions & 0 deletions newswires/client/src/context/SearchContext.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ describe('SearchContext', () => {

beforeEach(() => {
jest.clearAllMocks();
localStorage.clear();
});

it('should fetch data and initialise the state', async () => {
Expand Down Expand Up @@ -94,4 +95,68 @@ describe('SearchContext', () => {

expect(global.fetch).toHaveBeenCalledTimes(2);
});

it('should add item ids to the view history on item navigation', async () => {
const contextRef = await renderWithContext();

if (!contextRef.current) {
throw new Error('Context ref was null after render.');
}

expect(contextRef.current.viewedItemIds).toEqual([]);

act(() => {
contextRef.current?.handleSelectItem('111');
});

expect(contextRef.current.viewedItemIds).toEqual(['111']);
});

it('should store the view history in local storage', async () => {
const contextRef = await renderWithContext();

if (!contextRef.current) {
throw new Error('Context ref was null after render.');
}

expect(contextRef.current.viewedItemIds).toEqual([]);

act(() => {
contextRef.current?.handleSelectItem('111');
});

expect(contextRef.current.viewedItemIds).toEqual(['111']);
expect(localStorage.getItem('viewedItemIds')).toEqual('["111"]');

// Re-render the component
const newContextRef = await renderWithContext();

if (!newContextRef.current) {
throw new Error('Context ref was null after render.');
}

expect(newContextRef.current.viewedItemIds).toEqual(['111']);
});

it('should deduplicate item ids in the view history', async () => {
const contextRef = await renderWithContext();

if (!contextRef.current) {
throw new Error('Context ref was null after render.');
}

expect(contextRef.current.viewedItemIds).toEqual([]);

act(() => {
contextRef.current?.handleSelectItem('1');
});
act(() => {
contextRef.current?.handleSelectItem('2');
});
act(() => {
contextRef.current?.handleSelectItem('1');
});

expect(contextRef.current.viewedItemIds).toEqual(['1', '2']);
});
});
17 changes: 15 additions & 2 deletions newswires/client/src/context/SearchContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from '../sharedTypes.ts';
import { configToUrl, defaultConfig, urlToConfig } from '../urlState.ts';
import { fetchResults } from './fetchResults.ts';
import { loadFromLocalStorage, saveToLocalStorage } from './localStorage.tsx';
import { SearchReducer } from './SearchReducer.ts';

const SearchHistorySchema = z.array(
Expand Down Expand Up @@ -98,6 +99,7 @@ export type Action = z.infer<typeof ActionSchema>;
export type SearchContextShape = {
config: Config;
state: State;
viewedItemIds: string[];
handleEnterQuery: (query: Query) => void;
handleRetry: () => void;
handleSelectItem: (item: string) => void;
Expand All @@ -111,9 +113,12 @@ export const SearchContext: Context<SearchContextShape | null> =
createContext<SearchContextShape | null>(null);

export function SearchContextProvider({ children }: PropsWithChildren) {
const [currentConfig, setConfig] = useState<Config>(
const [currentConfig, setConfig] = useState<Config>(() =>
urlToConfig(window.location),
);
const [viewedItemIds, setViewedItemIds] = useState<string[]>(() =>
loadFromLocalStorage<string[]>('viewedItemIds', z.array(z.string()), []),
);

const [state, dispatch] = useReducer(SearchReducer, {
error: undefined,
Expand All @@ -137,9 +142,16 @@ export function SearchContextProvider({ children }: PropsWithChildren) {
const pushConfigState = useCallback(
(config: Config) => {
history.pushState(config, '', configToUrl(config));
if (config.view === 'item') {
const updatedViewedItemIds = Array.from(
new Set([config.itemId, ...viewedItemIds]),
);
setViewedItemIds(updatedViewedItemIds);
saveToLocalStorage<string[]>('viewedItemIds', updatedViewedItemIds);
}
setConfig(config);
},
[setConfig],
[setConfig, viewedItemIds, setViewedItemIds],
);

const popConfigStateCallback = useCallback(
Expand Down Expand Up @@ -309,6 +321,7 @@ export function SearchContextProvider({ children }: PropsWithChildren) {
handlePreviousItem,
toggleAutoUpdate,
loadMoreResults,
viewedItemIds,
}}
>
{children}
Expand Down

0 comments on commit fbcb657

Please sign in to comment.