Skip to content

Commit

Permalink
feat: AppContext (#188)
Browse files Browse the repository at this point in the history
- Add a concept of AppContext which houses common data used by the application. For now this is just the formats and genres, which should be fairly static while using the app.
- Add hooks to generate and use the app context within the app. Throw an error if attempting to use the app context outside of the provider
- (drive-by): clean up the useSyncTransactionStatus.test
  • Loading branch information
dylants authored Apr 25, 2024
1 parent 5de20ed commit fa6667e
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 1 deletion.
13 changes: 13 additions & 0 deletions src/app/AppContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use client';

import { Format, Genre } from '@prisma/client';
import { createContext } from 'react';

export type AppContextType = {
formats: Array<Format>;
genres: Array<Genre>;
};

const AppContext = createContext<AppContextType | null>(null);

export default AppContext;
31 changes: 31 additions & 0 deletions src/lib/hooks/useAppContext.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* @jest-environment jsdom
*/

import AppContext, { AppContextType } from '@/app/AppContext';
import useAppContext from '@/lib/hooks/useAppContext';
import { renderHook } from '@testing-library/react';
import { ReactNode } from 'react';

describe('useAppContext', () => {
it('should return context when it exists', () => {
const wrapper = ({ children }: { children: ReactNode }) => (
<AppContext.Provider value={{ foo: 'bar' } as unknown as AppContextType}>
{children}
</AppContext.Provider>
);
const { result } = renderHook(() => useAppContext(), { wrapper });

expect(result.current).toEqual({ foo: 'bar' });
});

it('should throw error when no context exists', () => {
jest.spyOn(console, 'error').mockImplementation(() => {});

expect(() =>
renderHook(() => useAppContext()),
).toThrowErrorMatchingInlineSnapshot(
`"useAppContext used outside of provider"`,
);
});
});
14 changes: 14 additions & 0 deletions src/lib/hooks/useAppContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use client';

import AppContext, { AppContextType } from '@/app/AppContext';
import { useContext } from 'react';

export default function useAppContext(): AppContextType {
const context = useContext(AppContext);

if (!context) {
throw new Error('useAppContext used outside of provider');
}

return context;
}
38 changes: 38 additions & 0 deletions src/lib/hooks/useGenerateAppContext.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* @jest-environment jsdom
*/

import useGenerateAppContext from '@/lib/hooks/useGenerateAppContext';
import { renderHook, waitFor } from '@testing-library/react';

const mockGetFormats = jest.fn();
jest.mock('../actions/format', () => ({
getFormats: (...args: unknown[]) => mockGetFormats(...args),
}));
const mockGetGenres = jest.fn();
jest.mock('../actions/genre', () => ({
getGenres: (...args: unknown[]) => mockGetGenres(...args),
}));

describe('useGenerateAppContext', () => {
it('perform correctly', async () => {
const FORMATS = ['format1', 'format2'];
const GENRES = ['genre1', 'genre2'];

mockGetFormats.mockResolvedValue(FORMATS);
mockGetGenres.mockResolvedValue(GENRES);

const { result } = renderHook(() => useGenerateAppContext());

// initially null on first render
expect(result.current).toEqual(null);

// after async completes, is populated with context
await waitFor(() => {
expect(result.current).toEqual({
formats: FORMATS,
genres: GENRES,
});
});
});
});
36 changes: 36 additions & 0 deletions src/lib/hooks/useGenerateAppContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use client';

import { AppContextType } from '@/app/AppContext';
import { getFormats } from '@/lib/actions/format';
import { getGenres } from '@/lib/actions/genre';
import { Format, Genre } from '@prisma/client';
import { useCallback, useEffect, useState } from 'react';

export default function useGenerateAppContext(): AppContextType | null {
const [formats, setFormats] = useState<Array<Format>>();
const [genres, setGenres] = useState<Array<Genre>>();

const loadFormats = useCallback(async () => {
const formats = await getFormats();
setFormats(formats);
}, []);

const loadGenres = useCallback(async () => {
const genres = await getGenres();
setGenres(genres);
}, []);

useEffect(() => {
loadFormats();
loadGenres();
}, [loadFormats, loadGenres]);

if (!formats || !genres) {
return null;
}

return {
formats,
genres,
};
}
4 changes: 3 additions & 1 deletion src/lib/hooks/useSyncTransactionStatus.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('useSyncTransactionStatus', () => {
mockSyncTransactionStatusSafe.mockReset();
});

it('should return perform correctly', async () => {
it('should update sync status correctly', async () => {
const { result } = renderHook(() =>
useSyncTransactionStatus({ transactionUID: 'abc123' }),
);
Expand Down Expand Up @@ -108,6 +108,8 @@ describe('useSyncTransactionStatus', () => {
});

it('should skip updates on error', async () => {
jest.spyOn(console, 'error').mockImplementation(() => {});

const { result } = renderHook(() =>
useSyncTransactionStatus({ transactionUID: 'abc123' }),
);
Expand Down

0 comments on commit fa6667e

Please sign in to comment.