Skip to content
This repository was archived by the owner on May 5, 2025. It is now read-only.

Commit 5536cdc

Browse files
committed
add: implement tests for giftService and recipientService, including add, delete, and update functionalities
1 parent b20b154 commit 5536cdc

File tree

4 files changed

+491
-0
lines changed

4 files changed

+491
-0
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { addGift, deleteGift, updateGift } from '@/features/gifts/giftService';
2+
3+
describe('giftService', () => {
4+
it('should add a gift correctly', async () => {
5+
const newGift = {
6+
title: 'Gift 1',
7+
image: 'https://example.com/gift1.jpg',
8+
price: 20,
9+
recipient: '1',
10+
selectedDate: '2025-05-01'
11+
};
12+
const result = await addGift(newGift);
13+
expect(result).toEqual({
14+
id: expect.any(String),
15+
...newGift
16+
});
17+
});
18+
19+
it('should delete a gift correctly', async () => {
20+
const giftId = '1'; // Updated to string to match GiftIdea type
21+
const result = await deleteGift(giftId);
22+
expect(result).toBe(true);
23+
});
24+
25+
it('should update a gift correctly', async () => {
26+
const updatedGift = {
27+
id: '1', // Updated to string to match GiftIdea type
28+
title: 'Updated Gift',
29+
image: 'https://example.com/gift1.jpg',
30+
price: 25,
31+
recipient: '1',
32+
selectedDate: '2025-05-01'
33+
};
34+
const result = await updateGift(updatedGift);
35+
expect(result).toEqual(updatedGift);
36+
});
37+
});
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import reducer, {
2+
addGift,
3+
deleteGift,
4+
updateGift,
5+
} from '@/features/gifts/giftSlice';
6+
7+
describe('giftSlice', () => {
8+
const initialState = {
9+
gifts: [],
10+
loading: false,
11+
error: null
12+
};
13+
14+
it('should return the initial state', () => {
15+
expect(reducer(undefined, { type: 'UNKNOWN_ACTION' })).toEqual(initialState);
16+
});
17+
18+
it('should set loading to true when addGift.pending', () => {
19+
const action = { type: addGift.pending.type };
20+
const state = reducer(initialState, action);
21+
expect(state.loading).toBe(true);
22+
expect(state.error).toBeNull();
23+
});
24+
25+
it('should add gift when addGift.fulfilled', () => {
26+
const newGift = {
27+
id: '1',
28+
title: 'Gift 1',
29+
image: 'https://example.com/gift1.jpg',
30+
price: 20,
31+
recipient: '1',
32+
selectedDate: '2025-05-01'
33+
};
34+
const action = { type: addGift.fulfilled.type, payload: newGift };
35+
const state = reducer(initialState, action);
36+
expect(state.loading).toBe(false);
37+
expect(state.gifts).toHaveLength(1);
38+
expect(state.gifts[0]).toEqual(newGift);
39+
});
40+
41+
it('should remove gift when deleteGift.fulfilled', () => {
42+
const startState = {
43+
...initialState,
44+
gifts: [{
45+
id: '1',
46+
title: 'Gift 1',
47+
image: 'https://example.com/gift1.jpg',
48+
price: 20,
49+
recipient: '1',
50+
selectedDate: '2025-05-01'
51+
}]
52+
};
53+
const action = { type: deleteGift.fulfilled.type, payload: '1' };
54+
const state = reducer(startState, action);
55+
expect(state.gifts).toHaveLength(0);
56+
});
57+
58+
it('should update gift when updateGift.fulfilled', () => {
59+
const startState = {
60+
...initialState,
61+
gifts: [{
62+
id: '1',
63+
title: 'Gift 1',
64+
image: 'https://example.com/gift1.jpg',
65+
price: 20,
66+
recipient: '1',
67+
selectedDate: '2025-05-01'
68+
}]
69+
};
70+
const updatedGift = {
71+
id: '1',
72+
title: 'Updated Gift',
73+
image: 'https://example.com/gift1.jpg',
74+
price: 25,
75+
recipient: '1',
76+
selectedDate: '2025-05-01'
77+
};
78+
const action = { type: updateGift.fulfilled.type, payload: updatedGift };
79+
const state = reducer(startState, action);
80+
expect(state.gifts[0]).toEqual(updatedGift);
81+
});
82+
});
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import {
2+
addRecipient,
3+
deleteRecipient,
4+
fetchRecipients,
5+
findRecipientById,
6+
updateRecipient,
7+
} from '@/features/recipients/recipientService';
8+
import supabase from '@/services/supabaseClient';
9+
10+
// Mock the entire Supabase client
11+
jest.mock('@/services/supabaseClient', () => {
12+
// Mock implementation functions
13+
const mockSingle = jest.fn();
14+
const mockEq = jest.fn(() => ({ single: mockSingle }));
15+
const mockSelect = jest.fn(() => ({ eq: mockEq, single: mockSingle }));
16+
const mockInsert = jest.fn(() => ({ single: mockSingle }));
17+
const mockUpdate = jest.fn(() => ({ eq: mockEq }));
18+
const mockDelete = jest.fn(() => ({ eq: mockEq }));
19+
20+
// From function returns an object with all the chainable methods
21+
const mockFrom = jest.fn(() => ({
22+
select: mockSelect,
23+
insert: mockInsert,
24+
update: mockUpdate,
25+
delete: mockDelete
26+
}));
27+
28+
// Return the mock client
29+
return {
30+
from: mockFrom,
31+
// Expose the internal mocks for test access
32+
__mocks: {
33+
from: mockFrom,
34+
select: mockSelect,
35+
insert: mockInsert,
36+
update: mockUpdate,
37+
delete: mockDelete,
38+
eq: mockEq,
39+
single: mockSingle
40+
}
41+
};
42+
});
43+
44+
// Get the mocks for easy access in tests
45+
const mocks = (supabase as any).__mocks;
46+
47+
describe('recipientService', () => {
48+
beforeEach(() => {
49+
jest.clearAllMocks();
50+
});
51+
52+
describe('fetchRecipients', () => {
53+
it('should fetch all recipients', async () => {
54+
const mockRecipients = [
55+
{
56+
id: '1',
57+
name: 'John Doe',
58+
image: 'https://example.com/john.jpg',
59+
budget: 100,
60+
spent: 50,
61+
},
62+
{
63+
id: '2',
64+
name: 'Jane Smith',
65+
image: 'https://example.com/jane.jpg',
66+
budget: 150,
67+
spent: 75,
68+
},
69+
];
70+
71+
// Setup the mock to return data
72+
mocks.single.mockResolvedValueOnce({
73+
data: mockRecipients,
74+
error: null,
75+
});
76+
77+
const result = await fetchRecipients();
78+
79+
expect(supabase.from).toHaveBeenCalledWith('recipients');
80+
expect(mocks.select).toHaveBeenCalledWith('*');
81+
expect(result).toEqual(mockRecipients);
82+
});
83+
84+
it('should throw an error when the request fails', async () => {
85+
const mockError = new Error('Failed to fetch recipients');
86+
87+
mocks.single.mockResolvedValueOnce({
88+
data: null,
89+
error: mockError,
90+
});
91+
92+
await expect(fetchRecipients()).rejects.toThrow();
93+
});
94+
});
95+
96+
describe('addRecipient', () => {
97+
it('should add a new recipient', async () => {
98+
const newRecipient = {
99+
name: 'New Person',
100+
image: 'https://example.com/new-person.jpg',
101+
budget: 200,
102+
spent: 0,
103+
};
104+
105+
const mockResult = {
106+
id: '3',
107+
...newRecipient,
108+
createdAt: '2025-05-02T12:00:00Z',
109+
};
110+
111+
mocks.single.mockResolvedValueOnce({
112+
data: mockResult,
113+
error: null,
114+
});
115+
116+
const result = await addRecipient(newRecipient);
117+
118+
expect(supabase.from).toHaveBeenCalledWith('recipients');
119+
expect(mocks.insert).toHaveBeenCalledWith([newRecipient]);
120+
expect(mocks.single).toHaveBeenCalled();
121+
expect(result).toEqual(mockResult);
122+
});
123+
});
124+
125+
describe('deleteRecipient', () => {
126+
it('should delete a recipient', async () => {
127+
mocks.eq.mockResolvedValueOnce({ error: null });
128+
129+
await deleteRecipient('1');
130+
131+
expect(supabase.from).toHaveBeenCalledWith('recipients');
132+
expect(mocks.delete).toHaveBeenCalled();
133+
expect(mocks.eq).toHaveBeenCalledWith('id', '1');
134+
});
135+
});
136+
137+
describe('updateRecipient', () => {
138+
it('should update a recipient', async () => {
139+
const updates = {
140+
name: 'Updated Name',
141+
budget: 300,
142+
};
143+
144+
const mockResult = {
145+
id: '1',
146+
name: 'Updated Name',
147+
image: 'https://example.com/john.jpg',
148+
budget: 300,
149+
spent: 50,
150+
};
151+
152+
mocks.single.mockResolvedValueOnce({
153+
data: mockResult,
154+
error: null,
155+
});
156+
157+
const result = await updateRecipient('1', updates);
158+
159+
expect(supabase.from).toHaveBeenCalledWith('recipients');
160+
expect(mocks.update).toHaveBeenCalledWith(updates);
161+
expect(mocks.eq).toHaveBeenCalledWith('id', '1');
162+
expect(result).toEqual(mockResult);
163+
});
164+
});
165+
166+
describe('findRecipientById', () => {
167+
it('should find a recipient by id', async () => {
168+
mocks.single.mockResolvedValueOnce({
169+
data: { name: 'John Doe' },
170+
error: null,
171+
});
172+
173+
const result = await findRecipientById(1);
174+
175+
expect(supabase.from).toHaveBeenCalledWith('recipients');
176+
expect(mocks.select).toHaveBeenCalledWith('name');
177+
expect(mocks.eq).toHaveBeenCalledWith('id', 1);
178+
expect(result).toBe('John Doe');
179+
});
180+
181+
it('should return null when recipient is not found', async () => {
182+
mocks.single.mockResolvedValueOnce({
183+
data: null,
184+
error: new Error('Not found'),
185+
});
186+
187+
const result = await findRecipientById(999);
188+
189+
expect(result).toBeNull();
190+
});
191+
});
192+
});

0 commit comments

Comments
 (0)