Skip to content

Commit 9bbe054

Browse files
test: add tests for navbar-utils functions handleLogout and deleteAllTasks (#360)
1 parent 1425be9 commit 9bbe054

1 file changed

Lines changed: 83 additions & 26 deletions

File tree

Lines changed: 83 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { Task } from '@/components/utils/types';
22
import { handleLogout, deleteAllTasks } from '../navbar-utils';
3+
import { toast } from 'react-toastify';
34

4-
// Mock external dependencies
5+
// Toast mock
56
jest.mock('react-toastify', () => ({
67
toast: {
78
success: jest.fn(),
@@ -11,18 +12,29 @@ jest.mock('react-toastify', () => ({
1112
},
1213
}));
1314

15+
// Dexie mock
1416
jest.mock('dexie', () => {
15-
return jest.fn().mockImplementation(() => ({
17+
const mockCount = jest.fn();
18+
const mockDelete = jest.fn();
19+
20+
const DexieMock = jest.fn().mockImplementation(() => ({
1621
version: jest.fn().mockReturnThis(),
1722
stores: jest.fn().mockReturnThis(),
1823
table: jest.fn().mockReturnValue({
1924
where: jest.fn().mockReturnThis(),
2025
equals: jest.fn().mockReturnThis(),
21-
delete: jest.fn().mockResolvedValue(undefined), // simulates delete success
26+
count: mockCount,
27+
delete: mockDelete,
2228
}),
2329
}));
30+
31+
return Object.assign(DexieMock, {
32+
__mockCount: mockCount,
33+
__mockDelete: mockDelete,
34+
});
2435
});
2536

37+
// URL mock
2638
jest.mock('@/components/utils/URLs.ts', () => ({
2739
url: {
2840
backendURL: 'http://localhost:3000/',
@@ -32,12 +44,26 @@ jest.mock('@/components/utils/URLs.ts', () => ({
3244
global.fetch = jest.fn();
3345

3446
describe('navbar-utils', () => {
47+
const mockToast = toast as jest.Mocked<typeof toast>;
48+
const Dexie = require('dexie');
49+
const mockCount = Dexie.__mockCount as jest.Mock;
50+
const mockDelete = Dexie.__mockDelete as jest.Mock;
51+
52+
beforeAll(() => {
53+
jest.spyOn(console, 'log').mockImplementation(() => {});
54+
jest.spyOn(console, 'error').mockImplementation(() => {});
55+
});
56+
57+
beforeEach(() => {
58+
mockToast.info.mockReturnValue('toast-id' as any);
59+
});
60+
3561
afterEach(() => {
3662
jest.clearAllMocks();
3763
});
3864

3965
describe('handleLogout', () => {
40-
it('should call fetch with correct URL and redirect on success', async () => {
66+
it('calls fetch with correct URL and redirects on success', async () => {
4167
(fetch as jest.Mock).mockResolvedValue({ ok: true });
4268

4369
await handleLogout();
@@ -46,47 +72,78 @@ describe('navbar-utils', () => {
4672
method: 'POST',
4773
credentials: 'include',
4874
});
75+
4976
expect(window.location.href).toBe('http://localhost/');
5077
});
5178

52-
it('should log an error if fetch fails', async () => {
53-
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
79+
it('logs error when response is not ok', async () => {
80+
const spy = jest.spyOn(console, 'error').mockImplementation();
5481

5582
(fetch as jest.Mock).mockResolvedValue({ ok: false });
5683

5784
await handleLogout();
5885

59-
expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to logout');
60-
consoleErrorSpy.mockRestore();
86+
expect(spy).toHaveBeenCalledWith('Failed to logout');
87+
spy.mockRestore();
6188
});
6289

63-
it('should log an error if fetch throws an exception', async () => {
64-
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
90+
it('logs error when fetch throws exception', async () => {
91+
const spy = jest.spyOn(console, 'error').mockImplementation();
6592

6693
(fetch as jest.Mock).mockRejectedValue(new Error('Network error'));
6794

6895
await handleLogout();
6996

70-
expect(consoleErrorSpy).toHaveBeenCalledWith(
71-
'Error logging out:',
72-
expect.any(Error)
73-
);
74-
consoleErrorSpy.mockRestore();
97+
expect(spy).toHaveBeenCalledWith('Error logging out:', expect.any(Error));
98+
spy.mockRestore();
7599
});
76100
});
77101

78102
describe('deleteAllTasks', () => {
79-
it('should delete tasks without error', async () => {
80-
const props = {
81-
imgurl: '',
82-
email: 'test@example.com',
83-
encryptionSecret: '',
84-
origin: '',
85-
UUID: '',
86-
tasks: [] as Task[] | null,
87-
};
88-
89-
await expect(deleteAllTasks(props)).resolves.toBeUndefined();
103+
const props = {
104+
imgurl: '',
105+
email: 'test@example.com',
106+
encryptionSecret: '',
107+
origin: '',
108+
UUID: '',
109+
tasks: [] as Task[] | null,
110+
};
111+
112+
it('shows error toast when no tasks exist', async () => {
113+
mockCount.mockResolvedValueOnce(0);
114+
115+
await deleteAllTasks(props);
116+
117+
expect(mockToast.info).toHaveBeenCalled();
118+
expect(mockToast.update).toHaveBeenCalledWith(
119+
'toast-id',
120+
expect.objectContaining({ type: 'error' })
121+
);
122+
});
123+
124+
it('deletes tasks and shows success toast when tasks exist', async () => {
125+
mockCount.mockResolvedValueOnce(3);
126+
mockDelete.mockResolvedValueOnce(undefined);
127+
128+
await deleteAllTasks(props);
129+
130+
expect(mockDelete).toHaveBeenCalled();
131+
expect(mockToast.update).toHaveBeenCalledWith(
132+
'toast-id',
133+
expect.objectContaining({ type: 'success' })
134+
);
135+
});
136+
137+
it('shows error toast when deletion fails', async () => {
138+
mockCount.mockResolvedValueOnce(2);
139+
mockDelete.mockRejectedValueOnce(new Error('DB error'));
140+
141+
await deleteAllTasks(props);
142+
143+
expect(mockToast.update).toHaveBeenCalledWith(
144+
'toast-id',
145+
expect.objectContaining({ type: 'error' })
146+
);
90147
});
91148
});
92149
});

0 commit comments

Comments
 (0)