Skip to content

Commit 641fe3b

Browse files
committed
test: added more tests for autocomplete and bookmarks
1 parent 37dd305 commit 641fe3b

File tree

7 files changed

+196
-77
lines changed

7 files changed

+196
-77
lines changed

phpmyfaq/assets/src/api/autocomplete.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
import { beforeEach, describe, expect, test, vi } from 'vitest';
22
import { fetchAutoCompleteData } from './autocomplete';
33
import createFetchMock, { FetchMock } from 'vitest-fetch-mock';
4+
import { AutocompleteSearchResponse } from '../interfaces';
45

56
const fetchMocker: FetchMock = createFetchMock(vi);
67

78
fetchMocker.enableMocks();
89

9-
describe('fetchAutoCompleteData', () => {
10-
beforeEach(() => {
10+
describe('fetchAutoCompleteData', (): void => {
11+
beforeEach((): void => {
1112
fetchMocker.resetMocks();
1213
});
1314

14-
test('should return autocomplete data when the response is successful', async () => {
15+
test('should return autocomplete data when the response is successful', async (): Promise<void> => {
1516
const mockData = { suggestions: ['apple', 'banana', 'orange'] };
1617

1718
fetchMocker.mockResponseOnce(JSON.stringify(mockData));
1819

1920
const searchString = 'fruit';
20-
const data = await fetchAutoCompleteData(searchString);
21+
const data: AutocompleteSearchResponse = await fetchAutoCompleteData(searchString);
2122

2223
expect(data).toEqual(mockData);
2324
expect(fetch).toHaveBeenCalledTimes(1);
@@ -32,7 +33,7 @@ describe('fetchAutoCompleteData', () => {
3233
});
3334
});
3435

35-
test('should throw an error when the response is not successful', async () => {
36+
test('should throw an error when the response is not successful', async (): Promise<void> => {
3637
fetchMocker.mockResponseOnce(null, { status: 500 });
3738

3839
const searchString = 'fruit';
@@ -50,8 +51,7 @@ describe('fetchAutoCompleteData', () => {
5051
});
5152
});
5253

53-
test('should handle fetch error', async () => {
54-
// Simulating a network failure or fetch error
54+
test('should handle fetch error', async (): Promise<void> => {
5555
fetchMocker.mockRejectOnce(new Error('API is down'));
5656

5757
const searchString = 'fruit';

phpmyfaq/assets/src/api/autocomplete.ts

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,22 @@
1313
* @since 2014-11-23
1414
*/
1515

16-
export const fetchAutoCompleteData = async (searchString: string) => {
17-
try {
18-
const response: Response = await fetch(`api/autocomplete?search=${searchString}`, {
19-
method: 'GET',
20-
cache: 'no-cache',
21-
headers: {
22-
'Content-Type': 'application/json',
23-
},
24-
redirect: 'follow',
25-
referrerPolicy: 'no-referrer',
26-
});
16+
import { AutocompleteSearchResponse } from '../interfaces';
2717

28-
if (!response.ok) {
29-
throw new Error('Network response was not ok.');
30-
}
18+
export const fetchAutoCompleteData = async (searchString: string): Promise<AutocompleteSearchResponse> => {
19+
const response: Response = await fetch(`api/autocomplete?search=${searchString}`, {
20+
method: 'GET',
21+
cache: 'no-cache',
22+
headers: {
23+
'Content-Type': 'application/json',
24+
},
25+
redirect: 'follow',
26+
referrerPolicy: 'no-referrer',
27+
});
3128

32-
return await response.json();
33-
} catch (error) {
34-
throw error;
29+
if (!response.ok) {
30+
throw new Error('Network response was not ok.');
3531
}
32+
33+
return await response.json();
3634
};
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { beforeEach, describe, expect, test, vi } from 'vitest';
2+
import { createBookmark, deleteBookmark, deleteAllBookmarks } from './bookmarks';
3+
import createFetchMock, { FetchMock } from 'vitest-fetch-mock';
4+
import { BookmarkResponse } from '../interfaces';
5+
6+
const fetchMocker: FetchMock = createFetchMock(vi);
7+
8+
fetchMocker.enableMocks();
9+
10+
describe('Bookmark API', (): void => {
11+
beforeEach((): void => {
12+
fetchMocker.resetMocks();
13+
});
14+
15+
test('createBookmark should return response when successful', async (): Promise<void> => {
16+
const mockResponse = { success: 'Bookmark created' };
17+
fetchMocker.mockResponseOnce(JSON.stringify(mockResponse));
18+
19+
const faqId = '123';
20+
const csrf = 'csrfToken';
21+
const data: BookmarkResponse | undefined = await createBookmark(faqId, csrf);
22+
23+
expect(data).toEqual(mockResponse);
24+
expect(fetch).toHaveBeenCalledTimes(1);
25+
expect(fetch).toHaveBeenCalledWith('api/bookmark/create', {
26+
method: 'POST',
27+
cache: 'no-cache',
28+
body: JSON.stringify({ id: faqId, csrfToken: csrf }),
29+
headers: { 'Content-Type': 'application/json' },
30+
redirect: 'follow',
31+
referrerPolicy: 'no-referrer',
32+
});
33+
});
34+
35+
test('createBookmark should handle error', async (): Promise<void> => {
36+
fetchMocker.mockResponseOnce(null, { status: 500 });
37+
38+
const faqId = '123';
39+
const csrf = 'csrfToken';
40+
41+
await expect(createBookmark(faqId, csrf)).rejects.toThrow('HTTP 500');
42+
expect(fetch).toHaveBeenCalledTimes(1);
43+
});
44+
45+
test('deleteBookmark should return response when successful', async (): Promise<void> => {
46+
const mockResponse = { success: 'Bookmark deleted' };
47+
fetchMocker.mockResponseOnce(JSON.stringify(mockResponse));
48+
49+
const faqId = '123';
50+
const csrf = 'csrfToken';
51+
const data: BookmarkResponse | undefined = await deleteBookmark(faqId, csrf);
52+
53+
expect(data).toEqual(mockResponse);
54+
expect(fetch).toHaveBeenCalledTimes(1);
55+
expect(fetch).toHaveBeenCalledWith('api/bookmark/delete', {
56+
method: 'DELETE',
57+
cache: 'no-cache',
58+
body: JSON.stringify({ id: faqId, csrfToken: csrf }),
59+
headers: { 'Content-Type': 'application/json' },
60+
redirect: 'follow',
61+
referrerPolicy: 'no-referrer',
62+
});
63+
});
64+
65+
test('deleteBookmark should handle error', async (): Promise<void> => {
66+
fetchMocker.mockResponseOnce(null, { status: 500 });
67+
68+
const faqId = '123';
69+
const csrf = 'csrfToken';
70+
71+
await expect(deleteBookmark(faqId, csrf)).rejects.toThrow('HTTP 500');
72+
expect(fetch).toHaveBeenCalledTimes(1);
73+
});
74+
75+
test('deleteAllBookmarks should return response when successful', async (): Promise<void> => {
76+
const mockResponse = { success: 'All bookmarks deleted' };
77+
fetchMocker.mockResponseOnce(JSON.stringify(mockResponse));
78+
79+
const csrf = 'csrfToken';
80+
const data: BookmarkResponse | undefined = await deleteAllBookmarks(csrf);
81+
82+
expect(data).toEqual(mockResponse);
83+
expect(fetch).toHaveBeenCalledTimes(1);
84+
expect(fetch).toHaveBeenCalledWith('api/bookmark/delete-all', {
85+
method: 'DELETE',
86+
cache: 'no-cache',
87+
body: JSON.stringify({ csrfToken: csrf }),
88+
headers: { 'Content-Type': 'application/json' },
89+
redirect: 'follow',
90+
referrerPolicy: 'no-referrer',
91+
});
92+
});
93+
94+
test('deleteAllBookmarks should handle error', async (): Promise<void> => {
95+
fetchMocker.mockResponseOnce(null, { status: 500 });
96+
97+
const csrf = 'csrfToken';
98+
99+
await expect(deleteAllBookmarks(csrf)).rejects.toThrow('HTTP 500');
100+
expect(fetch).toHaveBeenCalledTimes(1);
101+
});
102+
});

phpmyfaq/assets/src/api/bookmarks.ts

Lines changed: 58 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,64 +13,69 @@
1313
* @since 2023-09-19
1414
*/
1515

16-
export const createBookmark = async (faqId: string, csrf: string) => {
17-
try {
18-
const response: Response = await fetch(`api/bookmark/create`, {
19-
method: 'POST',
20-
cache: 'no-cache',
21-
body: JSON.stringify({
22-
id: faqId,
23-
csrfToken: csrf,
24-
}),
25-
headers: {
26-
'Content-Type': 'application/json',
27-
},
28-
redirect: 'follow',
29-
referrerPolicy: 'no-referrer',
30-
});
31-
return await response.json();
32-
} catch (error) {
33-
console.error('Error adding bookmark:', error);
16+
import { BookmarkResponse } from '../interfaces';
17+
18+
export const createBookmark = async (faqId: string, csrf: string): Promise<BookmarkResponse> => {
19+
const response: Response = await fetch(`api/bookmark/create`, {
20+
method: 'POST',
21+
cache: 'no-cache',
22+
body: JSON.stringify({
23+
id: faqId,
24+
csrfToken: csrf,
25+
}),
26+
headers: {
27+
'Content-Type': 'application/json',
28+
},
29+
redirect: 'follow',
30+
referrerPolicy: 'no-referrer',
31+
});
32+
33+
if (!response.ok) {
34+
throw new Error(`HTTP ${response.status}`);
3435
}
36+
37+
return await response.json();
3538
};
3639

37-
export const deleteBookmark = async (faqId: string, csrf: string) => {
38-
try {
39-
const response = await fetch(`api/bookmark/delete`, {
40-
method: 'DELETE',
41-
cache: 'no-cache',
42-
body: JSON.stringify({
43-
id: faqId,
44-
csrfToken: csrf,
45-
}),
46-
headers: {
47-
'Content-Type': 'application/json',
48-
},
49-
redirect: 'follow',
50-
referrerPolicy: 'no-referrer',
51-
});
52-
return await response.json();
53-
} catch (error) {
54-
console.error('Error removing bookmark:', error);
40+
export const deleteBookmark = async (faqId: string, csrf: string): Promise<BookmarkResponse> => {
41+
const response: Response = await fetch(`api/bookmark/delete`, {
42+
method: 'DELETE',
43+
cache: 'no-cache',
44+
body: JSON.stringify({
45+
id: faqId,
46+
csrfToken: csrf,
47+
}),
48+
headers: {
49+
'Content-Type': 'application/json',
50+
},
51+
redirect: 'follow',
52+
referrerPolicy: 'no-referrer',
53+
});
54+
55+
if (!response.ok) {
56+
throw new Error(`HTTP ${response.status}`);
5557
}
58+
59+
return await response.json();
5660
};
5761

58-
export const deleteAllBookmarks = async (csrf: string) => {
59-
try {
60-
const response = await fetch(`api/bookmark/delete-all`, {
61-
method: 'DELETE',
62-
cache: 'no-cache',
63-
body: JSON.stringify({
64-
csrfToken: csrf,
65-
}),
66-
headers: {
67-
'Content-Type': 'application/json',
68-
},
69-
redirect: 'follow',
70-
referrerPolicy: 'no-referrer',
71-
});
72-
return await response.json();
73-
} catch (error) {
74-
console.error('Error removing all bookmarks:', error);
62+
export const deleteAllBookmarks = async (csrf: string): Promise<BookmarkResponse> => {
63+
const response: Response = await fetch(`api/bookmark/delete-all`, {
64+
method: 'DELETE',
65+
cache: 'no-cache',
66+
body: JSON.stringify({
67+
csrfToken: csrf,
68+
}),
69+
headers: {
70+
'Content-Type': 'application/json',
71+
},
72+
redirect: 'follow',
73+
referrerPolicy: 'no-referrer',
74+
});
75+
76+
if (!response.ok) {
77+
throw new Error(`HTTP ${response.status}`);
7578
}
79+
80+
return await response.json();
7681
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
interface AutocompleteSearchResult {
2+
category: string;
3+
question: string;
4+
url: string;
5+
}
6+
7+
export type AutocompleteSearchResponse = AutocompleteSearchResult[];
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface BookmarkResponse {
2+
success: string;
3+
linkText?: string;
4+
csrfToken?: string;
5+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
export * from './Autocomplete';
12
export * from './apiResponse';
3+
export * from './Bookmark';
24
export * from './authenticatorResponse';
35
export * from './suggestionItem';

0 commit comments

Comments
 (0)