Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/common/hooks/__tests__/useCacheResponse.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { renderHook } from '@testing-library/react';
import useCacheResponse from '../useCacheResponse';

describe('useCacheResponse', () => {
it('stores and retrieves cached data', () => {
const { result } = renderHook(() => useCacheResponse());
const [retrieveCache, createCache] = result.current;

createCache('testKey', { data: 'hello' });
expect(retrieveCache('testKey')).toEqual({ data: 'hello' });
});

it('returns null for a cache key that does not exist', () => {
const { result } = renderHook(() => useCacheResponse());
const [retrieveCache] = result.current;

expect(retrieveCache('nonExistentKey')).toBeNull();
});

it('overwrites existing cache entries', () => {
const { result } = renderHook(() => useCacheResponse());
const [retrieveCache, createCache] = result.current;

createCache('key', 'first');
createCache('key', 'second');
expect(retrieveCache('key')).toBe('second');
});

it('supports different data types', () => {
const { result } = renderHook(() => useCacheResponse());
const [retrieveCache, createCache] = result.current;

createCache('number', 42);
createCache('array', [1, 2, 3]);
createCache('null', null);

expect(retrieveCache('number')).toBe(42);
expect(retrieveCache('array')).toEqual([1, 2, 3]);
// null is falsy, so the hook returns null for it too
expect(retrieveCache('null')).toBeNull();
});
});
78 changes: 78 additions & 0 deletions src/common/hooks/__tests__/useContributors.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { renderHook, waitFor } from '@testing-library/react';
import useContributors from '../useContributors';

beforeEach(() => {
global.fetch = jest.fn();
process.env.REACT_APP_PLAY_API_URL = 'https://api.test.com';
});

afterEach(() => {
jest.restoreAllMocks();
delete process.env.REACT_APP_PLAY_API_URL;
});

const mockContributors = [
{ login: 'user1', type: 'User', contributions: 50 },
{ login: 'dependabot', type: 'Bot', contributions: 100 },
{ login: 'user2', type: 'User', contributions: 30 },
{ login: 'user3', type: 'User', contributions: 80 }
];

describe('useContributors', () => {
it('fetches contributors and filters out bots', async () => {
global.fetch.mockResolvedValueOnce({
json: () => Promise.resolve(mockContributors)
});

const { result } = renderHook(() => useContributors(false));

expect(result.current.isLoading).toBe(true);

await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

expect(result.current.data).toHaveLength(3);
expect(result.current.data.every((c) => c.type !== 'Bot')).toBe(true);
expect(result.current.error).toBeUndefined();
});

it('sorts contributors by contributions when sorted=true', async () => {
global.fetch.mockResolvedValueOnce({
json: () => Promise.resolve(mockContributors)
});

const { result } = renderHook(() => useContributors(true));

await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

const contributions = result.current.data.map((c) => c.contributions);
expect(contributions).toEqual([80, 50, 30]); // descending order, bot excluded
});

it('sets error on fetch failure', async () => {
const mockError = new Error('Network error');
global.fetch.mockRejectedValueOnce(mockError);

const { result } = renderHook(() => useContributors(false));

await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

expect(result.current.error).toBe(mockError);
expect(result.current.data).toBeUndefined();
});

it('fetches from the correct API URL', async () => {
global.fetch.mockResolvedValueOnce({
json: () => Promise.resolve([])
});

renderHook(() => useContributors(false));

expect(global.fetch).toHaveBeenCalledWith('https://api.test.com/react-play/contributors');
});
});
53 changes: 53 additions & 0 deletions src/common/hooks/__tests__/useFeaturedPlays.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { renderHook, waitFor } from '@testing-library/react';
import useFeaturedPlays from '../useFeaturedPlays';
import { submit } from 'common/services/request';

// Mock the services/request module
jest.mock('common/services/request', () => ({
submit: jest.fn()
}));

jest.mock('common/services/request/query/fetch-plays-filter', () => ({
FetchPlaysFilter: {
getAllFeaturedPlays: jest.fn(() => 'mock-query')
}
}));

describe('useFeaturedPlays', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('returns featured plays data on success', async () => {
const mockPlays = [
{ id: 1, name: 'Play 1' },
{ id: 2, name: 'Play 2' }
];
submit.mockResolvedValueOnce(mockPlays);

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

// [loading, error, data]
expect(result.current[0]).toBe(true); // loading

await waitFor(() => {
expect(result.current[0]).toBe(false); // loading done
});

expect(result.current[1]).toBeNull(); // no error
expect(result.current[2]).toEqual(mockPlays); // data
});

it('sets error on failure', async () => {
const mockError = { message: 'GraphQL error' };
submit.mockRejectedValueOnce([mockError]);

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

await waitFor(() => {
expect(result.current[0]).toBe(false);
});

expect(result.current[1]).toEqual(mockError); // error
});
});
58 changes: 58 additions & 0 deletions src/common/hooks/__tests__/useFetch.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { renderHook, waitFor } from '@testing-library/react';
import useFetch from '../useFetch';

// Mock global fetch
beforeEach(() => {
global.fetch = jest.fn();
});

afterEach(() => {
jest.restoreAllMocks();
});

describe('useFetch', () => {
it('returns data on successful fetch', async () => {
const mockData = [{ id: 1, name: 'Play 1' }];
global.fetch.mockResolvedValueOnce({
json: () => Promise.resolve(mockData)
});

const { result } = renderHook(() => useFetch('https://api.example.com/data'));

// Initially loading
expect(result.current.loading).toBe(true);
expect(result.current.data).toEqual([]);

await waitFor(() => {
expect(result.current.loading).toBe(false);
});

expect(result.current.data).toEqual(mockData);
expect(result.current.error).toBeNull();
});

it('sets error on fetch failure', async () => {
const mockError = new Error('Network error');
global.fetch.mockRejectedValueOnce(mockError);

const { result } = renderHook(() => useFetch('https://api.example.com/fail'));

await waitFor(() => {
expect(result.current.loading).toBe(false);
});

expect(result.current.error).toBe(mockError);
expect(result.current.data).toEqual([]);
});

it('passes options to fetch', async () => {
global.fetch.mockResolvedValueOnce({
json: () => Promise.resolve({})
});

const options = { method: 'POST', body: JSON.stringify({ test: true }) };
renderHook(() => useFetch('https://api.example.com/data', options));

expect(global.fetch).toHaveBeenCalledWith('https://api.example.com/data', options);
});
});
49 changes: 49 additions & 0 deletions src/common/hooks/__tests__/useGitHub.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { renderHook, waitFor } from '@testing-library/react';
import useGitHub from '../useGitHub';

beforeEach(() => {
global.fetch = jest.fn();
});

afterEach(() => {
jest.restoreAllMocks();
});

describe('useGitHub', () => {
it('fetches GitHub user data successfully', async () => {
const mockUser = {
login: 'octocat',
name: 'The Octocat',
public_repos: 8
};
global.fetch.mockResolvedValueOnce({
json: () => Promise.resolve(mockUser)
});

const { result } = renderHook(() => useGitHub('octocat'));

expect(result.current.isLoading).toBe(true);

await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

expect(result.current.data).toEqual(mockUser);
expect(result.current.error).toBeUndefined();
expect(global.fetch).toHaveBeenCalledWith('https://api.github.com/users/octocat');
});

it('handles fetch errors', async () => {
const mockError = new Error('Rate limited');
global.fetch.mockRejectedValueOnce(mockError);

const { result } = renderHook(() => useGitHub('unknown-user'));

await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

expect(result.current.error).toBe(mockError);
expect(result.current.data).toBeUndefined();
});
});
56 changes: 56 additions & 0 deletions src/common/hooks/__tests__/useLikePlays.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { renderHook } from '@testing-library/react';
import useLikePlays from '../useLikePlays';
import { submit } from 'common/services/request';

jest.mock('common/services/request', () => ({
submit: jest.fn()
}));

jest.mock('common/services/request/query/like-play', () => ({
likeIndividualPlay: jest.fn((obj) => ({ type: 'LIKE', ...obj })),
unlikeIndividualPlay: jest.fn((obj) => ({ type: 'UNLIKE', ...obj }))
}));

describe('useLikePlays', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('likePlay calls submit and resolves on success', async () => {
const mockResponse = { id: 1, liked: true };
submit.mockResolvedValueOnce(mockResponse);

const { result } = renderHook(() => useLikePlays());
const response = await result.current.likePlay({ play_id: 'abc' });

expect(response).toEqual(mockResponse);
expect(submit).toHaveBeenCalledTimes(1);
});

it('likePlay rejects on submit failure', async () => {
const mockError = new Error('Mutation failed');
submit.mockRejectedValueOnce(mockError);

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

await expect(result.current.likePlay({ play_id: 'abc' })).rejects.toThrow('Mutation failed');
});

it('unLikePlay calls submit and resolves on success', async () => {
const mockResponse = { id: 1, liked: false };
submit.mockResolvedValueOnce(mockResponse);

const { result } = renderHook(() => useLikePlays());
const response = await result.current.unLikePlay({ play_id: 'abc' });

expect(response).toEqual(mockResponse);
});

it('unLikePlay rejects on submit failure', async () => {
submit.mockRejectedValueOnce(new Error('Delete failed'));

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

await expect(result.current.unLikePlay({ play_id: 'abc' })).rejects.toThrow('Delete failed');
});
});
Loading
Loading