diff --git a/phpmyfaq/admin/assets/src/api/forms.test.ts b/phpmyfaq/admin/assets/src/api/forms.test.ts new file mode 100644 index 0000000000..24dd247aff --- /dev/null +++ b/phpmyfaq/admin/assets/src/api/forms.test.ts @@ -0,0 +1,260 @@ +import { describe, it, expect, vi } from 'vitest'; +import { + fetchActivateInput, + fetchSetInputAsRequired, + fetchEditTranslation, + fetchDeleteTranslation, + fetchAddTranslation, +} from './forms'; + +describe('fetchActivateInput', () => { + it('should activate input and return JSON response if successful', async () => { + const mockResponse = { success: true }; + global.fetch = vi.fn(() => + Promise.resolve({ + status: 200, + json: () => Promise.resolve(mockResponse), + } as Response) + ); + + const csrf = 'csrfToken'; + const formId = 'formId'; + const inputId = 'inputId'; + const checked = true; + const result = await fetchActivateInput(csrf, formId, inputId, checked); + + expect(result).toEqual(mockResponse); + expect(global.fetch).toHaveBeenCalledWith('api/forms/activate', { + method: 'POST', + headers: { + Accept: 'application/json, text/plain, */*', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + csrf: csrf, + formid: formId, + inputid: inputId, + checked: checked, + }), + }); + }); + + it('should throw an error if the network response is not ok', async () => { + global.fetch = vi.fn(() => + Promise.resolve({ + status: 500, + } as Response) + ); + + const csrf = 'csrfToken'; + const formId = 'formId'; + const inputId = 'inputId'; + const checked = true; + + await expect(fetchActivateInput(csrf, formId, inputId, checked)).rejects.toThrow('Network response was not ok.'); + }); +}); + +describe('fetchSetInputAsRequired', () => { + it('should set input as required and return JSON response if successful', async () => { + const mockResponse = { success: true }; + global.fetch = vi.fn(() => + Promise.resolve({ + status: 200, + json: () => Promise.resolve(mockResponse), + } as Response) + ); + + const csrf = 'csrfToken'; + const formId = 'formId'; + const inputId = 'inputId'; + const checked = true; + const result = await fetchSetInputAsRequired(csrf, formId, inputId, checked); + + expect(result).toEqual(mockResponse); + expect(global.fetch).toHaveBeenCalledWith('api/forms/required', { + method: 'POST', + headers: { + Accept: 'application/json, text/plain, */*', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + csrf: csrf, + formid: formId, + inputid: inputId, + checked: checked, + }), + }); + }); + + it('should throw an error if the network response is not ok', async () => { + global.fetch = vi.fn(() => + Promise.resolve({ + status: 500, + } as Response) + ); + + const csrf = 'csrfToken'; + const formId = 'formId'; + const inputId = 'inputId'; + const checked = true; + + await expect(fetchSetInputAsRequired(csrf, formId, inputId, checked)).rejects.toThrow( + 'Network response was not ok.' + ); + }); +}); + +describe('fetchEditTranslation', () => { + it('should edit translation and return JSON response if successful', async () => { + const mockResponse = { success: true }; + global.fetch = vi.fn(() => + Promise.resolve({ + status: 200, + json: () => Promise.resolve(mockResponse), + } as Response) + ); + + const csrf = 'csrfToken'; + const formId = 'formId'; + const inputId = 'inputId'; + const label = 'label'; + const lang = 'en'; + const result = await fetchEditTranslation(csrf, formId, inputId, label, lang); + + expect(result).toEqual(mockResponse); + expect(global.fetch).toHaveBeenCalledWith('api/forms/translation-edit', { + method: 'POST', + headers: { + Accept: 'application/json, text/plain, */*', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + csrf: csrf, + formId: formId, + inputId: inputId, + lang: lang, + label: label, + }), + }); + }); + + it('should throw an error if the network response is not ok', async () => { + global.fetch = vi.fn(() => + Promise.resolve({ + status: 500, + } as Response) + ); + + const csrf = 'csrfToken'; + const formId = 'formId'; + const inputId = 'inputId'; + const label = 'label'; + const lang = 'en'; + + await expect(fetchEditTranslation(csrf, formId, inputId, label, lang)).rejects.toThrow( + 'Network response was not ok.' + ); + }); +}); + +describe('fetchDeleteTranslation', () => { + it('should delete translation and return JSON response if successful', async () => { + const mockResponse = { success: true }; + global.fetch = vi.fn(() => + Promise.resolve({ + status: 200, + json: () => Promise.resolve(mockResponse), + } as Response) + ); + + const csrf = 'csrfToken'; + const formId = 'formId'; + const inputId = 'inputId'; + const lang = 'en'; + const result = await fetchDeleteTranslation(csrf, formId, inputId, lang); + + expect(result).toEqual(mockResponse); + expect(global.fetch).toHaveBeenCalledWith('api/forms/translation-delete', { + method: 'POST', + headers: { + Accept: 'application/json, text/plain, */*', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + csrf: csrf, + formId: formId, + inputId: inputId, + lang: lang, + }), + }); + }); + + it('should throw an error if the network response is not ok', async () => { + global.fetch = vi.fn(() => + Promise.resolve({ + status: 500, + } as Response) + ); + + const csrf = 'csrfToken'; + const formId = 'formId'; + const inputId = 'inputId'; + const lang = 'en'; + + await expect(fetchDeleteTranslation(csrf, formId, inputId, lang)).rejects.toThrow('Network response was not ok.'); + }); +}); + +describe('fetchAddTranslation', () => { + it('should add translation and return JSON response if successful', async () => { + const mockResponse = { success: true }; + global.fetch = vi.fn(() => + Promise.resolve({ + status: 200, + json: () => Promise.resolve(mockResponse), + } as Response) + ); + + const csrf = 'csrfToken'; + const formId = 'formId'; + const inputId = 'inputId'; + const lang = 'en'; + const translation = 'translation'; + const result = await fetchAddTranslation(csrf, formId, inputId, lang, translation); + + expect(result).toEqual(mockResponse); + expect(global.fetch).toHaveBeenCalledWith('api/forms/translation-add', { + method: 'POST', + headers: { + Accept: 'application/json, text/plain, */*', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + csrf: csrf, + formId: formId, + inputId: inputId, + lang: lang, + translation: translation, + }), + }); + }); + + it('should throw an error if the network response is not ok', async () => { + global.fetch = vi.fn(() => + Promise.resolve({ + status: 500, + } as Response) + ); + + const csrf = 'csrfToken'; + const formId = 'formId'; + const inputId = 'inputId'; + const lang = 'en'; + const translation = 'translation'; + + await expect(fetchAddTranslation(csrf, formId, inputId, lang, translation)).rejects.toThrow( + 'Network response was not ok.' + ); + }); +}); diff --git a/phpmyfaq/admin/assets/src/api/forms.ts b/phpmyfaq/admin/assets/src/api/forms.ts index 86f6d5659b..e463c0f9f8 100644 --- a/phpmyfaq/admin/assets/src/api/forms.ts +++ b/phpmyfaq/admin/assets/src/api/forms.ts @@ -14,9 +14,6 @@ * @since 2014-03-21 */ -import { pushNotification } from '../../../../assets/src/utils'; -import { Response } from '../interfaces'; - export const fetchActivateInput = async ( csrf: string, formId: string, @@ -38,18 +35,12 @@ export const fetchActivateInput = async ( }), }); - if (response.ok) { - const result: Response = await response.json(); - if (result.success) { - pushNotification(result.success); // @todo move that to the forms.ts file in the content folder - } else { - console.error(result.error); - } + if (response.status === 200) { + return await response.json(); } else { - throw new Error('Network response was not ok: ' + (await response.text())); + throw new Error('Network response was not ok.'); } } catch (error) { - console.error('Error activating/deactivating input:', error); throw error; } }; @@ -75,18 +66,12 @@ export const fetchSetInputAsRequired = async ( }), }); - if (response.ok) { - const result: Response = await response.json(); - if (result.success) { - pushNotification(result.success); // @todo move that to the forms.ts file in the content folder - } else { - console.error(result.error); - } + if (response.status === 200) { + return await response.json(); } else { - throw new Error('Network response was not ok: ' + (await response.text())); + throw new Error('Network response was not ok.'); } } catch (error) { - console.error('Error setting input as required:', error); throw error; } }; @@ -114,18 +99,12 @@ export const fetchEditTranslation = async ( }), }); - if (response.ok) { - const result: Response = await response.json(); - if (result.success) { - pushNotification(result.success); // @todo move that to the forms.ts file in the content folder - } else { - console.error(result.error); - } + if (response.status === 200) { + return await response.json(); } else { - throw new Error('Network response was not ok: ' + (await response.text())); + throw new Error('Network response was not ok.'); } } catch (error) { - console.error('Error editing translation:', error); throw error; } }; @@ -134,8 +113,7 @@ export const fetchDeleteTranslation = async ( csrf: string, formId: string, inputId: string, - lang: string, - element: HTMLElement + lang: string ): Promise => { try { const response = await fetch('api/forms/translation-delete', { @@ -152,23 +130,12 @@ export const fetchDeleteTranslation = async ( }), }); - if (response.ok) { - const result: Response = await response.json(); - if (result.success) { - // @todo move that to the forms.ts file in the content folder - pushNotification(result.success); - document.getElementById('item_' + element.getAttribute('data-pmf-lang'))?.remove(); - const option = document.createElement('option'); - option.innerText = element.getAttribute('data-pmf-langname')!; - document.getElementById('languageSelect')?.appendChild(option); - } else { - console.error(result.error); - } + if (response.status === 200) { + return await response.json(); } else { - throw new Error('Network response was not ok: ' + (await response.text())); + throw new Error('Network response was not ok.'); } } catch (error) { - console.error('Error deleting translation:', error); throw error; } }; @@ -196,22 +163,12 @@ export const fetchAddTranslation = async ( }), }); - if (response.ok) { - const result: Response = await response.json(); - if (result.success) { - // @todo move that to the forms.ts file in the content folder - pushNotification(result.success); - setTimeout(function () { - window.location.reload(); - }, 3000); - } else { - console.error(result.error); - } + if (response.status === 200) { + return await response.json(); } else { - throw new Error('Network response was not ok: ' + (await response.text())); + throw new Error('Network response was not ok.'); } } catch (error) { - console.error('Error adding translation:', error); throw error; } }; diff --git a/phpmyfaq/admin/assets/src/api/group.test.ts b/phpmyfaq/admin/assets/src/api/group.test.ts new file mode 100644 index 0000000000..f3d2a9dc85 --- /dev/null +++ b/phpmyfaq/admin/assets/src/api/group.test.ts @@ -0,0 +1,186 @@ +import { describe, it, expect, vi } from 'vitest'; +import { fetchAllGroups, fetchAllUsersForGroups, fetchAllMembers, fetchGroup, fetchGroupRights } from './group'; + +describe('fetchAllGroups', () => { + it('should fetch all groups and return JSON response if successful', async () => { + const mockResponse = { success: true, data: 'Groups data' }; + global.fetch = vi.fn(() => + Promise.resolve({ + status: 200, + json: () => Promise.resolve(mockResponse), + } as Response) + ); + + const result = await fetchAllGroups(); + + expect(result).toEqual(mockResponse); + expect(global.fetch).toHaveBeenCalledWith('./api/group/groups', { + method: 'GET', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + }, + redirect: 'follow', + referrerPolicy: 'no-referrer', + }); + }); + + it('should throw an error if the network response is not ok', async () => { + global.fetch = vi.fn(() => + Promise.resolve({ + status: 500, + } as Response) + ); + + await expect(fetchAllGroups()).rejects.toThrow('Network response was not ok.'); + }); +}); + +describe('fetchAllUsersForGroups', () => { + it('should fetch all users for groups and return JSON response if successful', async () => { + const mockResponse = { success: true, data: 'Users data' }; + global.fetch = vi.fn(() => + Promise.resolve({ + status: 200, + json: () => Promise.resolve(mockResponse), + } as Response) + ); + + const result = await fetchAllUsersForGroups(); + + expect(result).toEqual(mockResponse); + expect(global.fetch).toHaveBeenCalledWith('./api/group/users', { + method: 'GET', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + }, + redirect: 'follow', + referrerPolicy: 'no-referrer', + }); + }); + + it('should throw an error if the network response is not ok', async () => { + global.fetch = vi.fn(() => + Promise.resolve({ + status: 500, + } as Response) + ); + + await expect(fetchAllUsersForGroups()).rejects.toThrow('Network response was not ok.'); + }); +}); + +describe('fetchAllMembers', () => { + it('should fetch all members of a group and return JSON response if successful', async () => { + const mockResponse = { success: true, data: 'Members data' }; + global.fetch = vi.fn(() => + Promise.resolve({ + status: 200, + json: () => Promise.resolve(mockResponse), + } as Response) + ); + + const groupId = '123'; + const result = await fetchAllMembers(groupId); + + expect(result).toEqual(mockResponse); + expect(global.fetch).toHaveBeenCalledWith(`./api/group/members/${groupId}`, { + method: 'GET', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + }, + redirect: 'follow', + referrerPolicy: 'no-referrer', + }); + }); + + it('should throw an error if the network response is not ok', async () => { + global.fetch = vi.fn(() => + Promise.resolve({ + status: 500, + } as Response) + ); + + const groupId = '123'; + + await expect(fetchAllMembers(groupId)).rejects.toThrow('Network response was not ok.'); + }); +}); + +describe('fetchGroup', () => { + it('should fetch a group and return JSON response if successful', async () => { + const mockResponse = { success: true, data: 'Group data' }; + global.fetch = vi.fn(() => + Promise.resolve({ + status: 200, + json: () => Promise.resolve(mockResponse), + } as Response) + ); + + const groupId = '123'; + const result = await fetchGroup(groupId); + + expect(result).toEqual(mockResponse); + expect(global.fetch).toHaveBeenCalledWith(`./api/group/data/${groupId}`, { + method: 'GET', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + }, + redirect: 'follow', + referrerPolicy: 'no-referrer', + }); + }); + + it('should throw an error if the network response is not ok', async () => { + global.fetch = vi.fn(() => + Promise.resolve({ + status: 500, + } as Response) + ); + + const groupId = '123'; + + await expect(fetchGroup(groupId)).rejects.toThrow('Network response was not ok.'); + }); +}); + +describe('fetchGroupRights', () => { + it('should fetch group rights and return JSON response if successful', async () => { + const mockResponse = { success: true, data: 'Group rights data' }; + global.fetch = vi.fn(() => + Promise.resolve({ + status: 200, + json: () => Promise.resolve(mockResponse), + } as Response) + ); + + const groupId = '123'; + const result = await fetchGroupRights(groupId); + + expect(result).toEqual(mockResponse); + expect(global.fetch).toHaveBeenCalledWith(`./api/group/permissions/${groupId}`, { + method: 'GET', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + }, + redirect: 'follow', + referrerPolicy: 'no-referrer', + }); + }); + + it('should throw an error if the network response is not ok', async () => { + global.fetch = vi.fn(() => + Promise.resolve({ + status: 500, + } as Response) + ); + + const groupId = '123'; + + await expect(fetchGroupRights(groupId)).rejects.toThrow('Network response was not ok.'); + }); +}); diff --git a/phpmyfaq/admin/assets/src/api/group.ts b/phpmyfaq/admin/assets/src/api/group.ts index 1a98589f47..1dcc6647ed 100644 --- a/phpmyfaq/admin/assets/src/api/group.ts +++ b/phpmyfaq/admin/assets/src/api/group.ts @@ -33,7 +33,6 @@ export const fetchAllGroups = async (): Promise => { throw new Error('Network response was not ok.'); } } catch (error) { - console.error('Error fetching groups:', error); throw error; } }; @@ -56,7 +55,6 @@ export const fetchAllUsersForGroups = async (): Promise => throw new Error('Network response was not ok.'); } } catch (error) { - console.error('Error fetching users for groups:', error); throw error; } }; @@ -79,7 +77,6 @@ export const fetchAllMembers = async (groupId: string): Promise throw new Error('Network response was not ok.'); } } catch (error) { - console.error(`Error fetching data for group ${groupId}:`, error); throw error; } }; @@ -125,7 +121,6 @@ export const fetchGroupRights = async (groupId: string): Promise { const forms = document.getElementById('forms'); @@ -32,7 +34,12 @@ export const handleFormEdit = (): void => { const csrf = element.getAttribute('data-pmf-csrf-token') as string; const inputId = element.getAttribute('data-pmf-inputid') as string; const formId = element.getAttribute('data-pmf-formid') as string; - await fetchActivateInput(csrf, formId, inputId, checked); + const response = (await fetchActivateInput(csrf, formId, inputId, checked)) as unknown as Response; + if (typeof response.success === 'string') { + pushNotification(response.success); + } else { + console.error(response.error); + } }); }); // Handle required checkboxes @@ -42,7 +49,12 @@ export const handleFormEdit = (): void => { const csrf = element.getAttribute('data-pmf-csrf-token') as string; const inputId = element.getAttribute('data-pmf-inputid') as string; const formId = element.getAttribute('data-pmf-formid') as string; - await fetchSetInputAsRequired(csrf, formId, inputId, checked); + const response = (await fetchSetInputAsRequired(csrf, formId, inputId, checked)) as unknown as Response; + if (typeof response.success === 'string') { + pushNotification(response.success); + } else { + console.error(response.error); + } }); }); @@ -93,7 +105,18 @@ export const handleFormTranslations = (): void => { const csrf = element.getAttribute('data-pmf-csrf') as string; const formId = element.getAttribute('data-pmf-formId') as string; const inputId = element.getAttribute('data-pmf-inputId') as string; - await fetchEditTranslation(csrf, formId, inputId, lang, input.value); + const response = (await fetchEditTranslation( + csrf, + formId, + inputId, + lang, + input.value + )) as unknown as Response; + if (typeof response.success === 'string') { + pushNotification(response.success); + } else { + console.error(response.error); + } } }); }); @@ -105,7 +128,16 @@ export const handleFormTranslations = (): void => { const inputId = element.getAttribute('data-pmf-inputId') as string; const formId = element.getAttribute('data-pmf-formId') as string; const lang = element.getAttribute('data-pmf-lang') as string; - await fetchDeleteTranslation(csrf, formId, inputId, lang, element); + const response = (await fetchDeleteTranslation(csrf, formId, inputId, lang)) as unknown as Response; + if (typeof response.success === 'string') { + pushNotification(response.success); + document.getElementById('item_' + lang)?.remove(); + const option = document.createElement('option'); + option.innerText = element.getAttribute('data-pmf-langname')!; + document.getElementById('languageSelect')?.appendChild(option); + } else { + console.error(response.error); + } }); }); // Add Translation @@ -117,7 +149,21 @@ export const handleFormTranslations = (): void => { const csrf = addTranslationButton.getAttribute('data-pmf-csrf') as string; const inputId = addTranslationButton.getAttribute('data-pmf-inputId') as string; const formId = addTranslationButton.getAttribute('data-pmf-formId') as string; - await fetchAddTranslation(csrf, formId, inputId, languageSelect.value, translationInput.value); + const response = (await fetchAddTranslation( + csrf, + formId, + inputId, + languageSelect.value, + translationInput.value + )) as unknown as Response; + if (typeof response.success === 'string') { + pushNotification(response.success); + setTimeout(function () { + window.location.reload(); + }, 3000); + } else { + console.error(response.error); + } }); } };