diff --git a/backend-api/app/Http/Controllers/RequestController.php b/backend-api/app/Http/Controllers/RequestController.php index c56dbe8..f916f01 100644 --- a/backend-api/app/Http/Controllers/RequestController.php +++ b/backend-api/app/Http/Controllers/RequestController.php @@ -350,6 +350,10 @@ public function approveRequest(Request $request) return response()->json(['message' => "You are not allowed to approve this request"], 400); } + if (strlen($reason) > 255) { + $reason = substr($reason, 0, 255); + } + if ($requestDB->Status == 'Withdraw Pending' && $status == 'Withdrawn') { $newRequestLog = new RequestLog(); $newRequestLog->Request_ID = $requestDB->Request_ID; @@ -481,6 +485,10 @@ public function rejectRequest(Request $request) return response()->json(['message' => "Reason was not provided"], 401); } + if (strlen($reason) > 255) { + $reason = substr($reason, 0, 255); + } + // check if the requst is already rejected if ($requestDB->Status == "Withdraw Rejected") { return response()->json(['message' => "Request was already Withdraw Rejected"], 400); diff --git a/test/ApproveActionHandler.test.tsx b/test/ApproveActionHandler.test.tsx new file mode 100644 index 0000000..422d6b4 --- /dev/null +++ b/test/ApproveActionHandler.test.tsx @@ -0,0 +1,142 @@ +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import Swal from 'sweetalert2'; +import { toast } from 'react-toastify'; +import ActionHandler from '@/components/approve/actionHandler'; +import { Provider } from 'react-redux'; +import { createStore } from 'redux'; + +// Mock the Redux store +const mockStore = createStore(() => ({ + auth: { staffId: '12345' }, +})); + +// Mock React Toastify +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + error: jest.fn(), + }, +})); + +// Mock SweetAlert2 +jest.mock('sweetalert2', () => ({ + fire: jest.fn(), +})); + +// Setup axios mock adapter +const mock = new MockAdapter(axios); + +describe('ActionHandler Component', () => { + const mockProps = { + requestId: 1, + requestorId: 123, + dateRequested: '2024-10-13', + requestBatch: 'Batch A', + duration: 'AM', + status: 'pending', + onWithdraw: jest.fn(), + isDisabled: false, + proportionAfterApproval: 0.5, + onRefreshRequests: jest.fn(), + }; + + beforeEach(() => { + mock.reset(); // Reset the axios mock before each test + }); + + it('should display the Approve button and handle approval correctly', async () => { + // Mock Swal.fire for approval scenario + (Swal.fire as jest.Mock).mockResolvedValueOnce({ isConfirmed: true, value: 'Optional comments' }); + + mock.onPost('http://127.0.0.1:8085/api/approveRequest').reply(200, { + message: 'The request has been approved successfully!', + }); + + render( + + + + ); + + const approveButton = screen.getByText('Approve'); + fireEvent.click(approveButton); + + await waitFor(() => expect(Swal.fire).toHaveBeenCalled()); + await waitFor(() => expect(mock.history.post.length).toBe(1)); + await waitFor(() => + expect(mock.history.post[0].data).toContain('Optional comments') + ); + + expect(toast.success).toHaveBeenCalledWith('The request has been approved successfully!', { + position: 'top-right', + }); + expect(mockProps.onRefreshRequests).toHaveBeenCalled(); + }); + + it('should display the Reject button and handle rejection with reason', async () => { + // Mock Swal.fire for rejection scenario + (Swal.fire as jest.Mock).mockResolvedValueOnce({ isConfirmed: true, value: 'Rejection reason' }); + + mock.onPost('http://127.0.0.1:8085/api/rejectRequest').reply(200, { + message: 'The request has been rejected successfully!', + }); + + render( + + + + ); + + const rejectButton = screen.getByText('Reject'); + fireEvent.click(rejectButton); + + await waitFor(() => expect(Swal.fire).toHaveBeenCalled()); + await waitFor(() => expect(mock.history.post.length).toBe(1)); + await waitFor(() => + expect(mock.history.post[0].data).toContain('Rejection reason') + ); + + expect(toast.success).toHaveBeenCalledWith('The request has been rejected successfully!', { + position: 'top-right', + }); + expect(mockProps.onRefreshRequests).toHaveBeenCalled(); + }); + + it('should handle the Withdraw action and call the onWithdraw prop', async () => { + mock.onPost('http://127.0.0.1:8085/api/withdrawRequest').reply(200, { + message: 'The request has been withdrawn successfully!', + }); + + render( + + + + ); + + const withdrawButton = screen.getByText('Withdraw'); + fireEvent.click(withdrawButton); + + await waitFor(() => expect(mock.history.post.length).toBe(1)); + expect(mock.history.post[0].data).toContain('Request_ID'); + expect(toast.success).toHaveBeenCalledWith('The request has been withdrawn successfully!', { + position: 'top-right', + }); + expect(mockProps.onWithdraw).toHaveBeenCalledWith(mockProps.requestId); + expect(mockProps.onRefreshRequests).toHaveBeenCalled(); + }); + + it('should disable the Approve button when isDisabled is true', () => { + render( + + + + ); + + const approveButton = screen.getByText('Approve'); + expect(approveButton).toBeDisabled(); + }); +}); diff --git a/test/ApproveTable.test.tsx b/test/ApproveTable.test.tsx new file mode 100644 index 0000000..d878d38 --- /dev/null +++ b/test/ApproveTable.test.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import ApproveTable from '@/components/approve/table'; // Adjust the import path as necessary +import { Provider } from 'react-redux'; +import configureMockStore from 'redux-mock-store'; +import '@testing-library/jest-dom'; + +// Create a mock Redux store +const mockStore = configureMockStore(); +const store = mockStore({ + auth: { + staffId: 123, + }, +}); + +// Mock data for employees +const mockEmployees = [ + { Staff_ID: 1, Staff_FName: 'John', Staff_LName: 'Doe' }, + { Staff_ID: 2, Staff_FName: 'Jane', Staff_LName: 'Smith' }, + { Staff_ID: 3, Staff_FName: 'Jake', Staff_LName: 'Johnson' }, +]; + +describe('ApproveTable Component', () => { + it('should display all employees\' full names (first + last name)', () => { + render( + + + + ); + + // Check if all employees' full names are rendered on the screen + expect(screen.getByText('John Doe')).toBeInTheDocument(); + expect(screen.getByText('Jane Smith')).toBeInTheDocument(); + expect(screen.getByText('Jake Johnson')).toBeInTheDocument(); + }); + + it('should filter employees based on the search term', () => { + render( + + + + ); + + // Find the search input by its role + const searchInput = screen.getByRole('textbox', { name: /search by name/i }); + + // Simulate typing 'John' into the search input + fireEvent.change(searchInput, { target: { value: 'John' } }); + + // Check if only the employees whose names include 'John' are displayed + expect(screen.getByText('John Doe')).toBeInTheDocument(); + expect(screen.getByText('Jake Johnson')).toBeInTheDocument(); + + // Check if 'Jane Smith' is not displayed (since it doesn't match 'John') + expect(screen.queryByText('Jane Smith')).not.toBeInTheDocument(); + }); +}); diff --git a/yarn.lock b/yarn.lock index 35b37ef..15110bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1219,6 +1219,58 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62" integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig== +"@floating-ui/core@1.6.6": + version "1.6.6" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.6.tgz#f6edf703c8acb73e3802cf558c88ddb7cddc4f67" + integrity sha512-Vkvsw6EcpMHjvZZdMkSY+djMGFbt7CRssW99Ne8tar2WLnZ/l3dbxeTShbLQj+/s35h+Qb4cmnob+EzwtjrXGQ== + dependencies: + "@floating-ui/utils" "^0.2.6" + +"@floating-ui/core@^1.6.0": + version "1.6.8" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.8.tgz#aa43561be075815879305965020f492cdb43da12" + integrity sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA== + dependencies: + "@floating-ui/utils" "^0.2.8" + +"@floating-ui/dom@^1.0.0": + version "1.6.11" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.11.tgz#8631857838d34ee5712339eb7cbdfb8ad34da723" + integrity sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ== + dependencies: + "@floating-ui/core" "^1.6.0" + "@floating-ui/utils" "^0.2.8" + +"@floating-ui/react-dom@^2.1.1", "@floating-ui/react-dom@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31" + integrity sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A== + dependencies: + "@floating-ui/dom" "^1.0.0" + +"@floating-ui/react@0.26.21": + version "0.26.21" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.21.tgz#29fe23a5197650d48eb1b05c5c46ff61df368fb6" + integrity sha512-7P5ncDIiYd6RrwpCDbKyFzvabM014QlzlumtDbK3Bck0UueC+Rp8BLS34qcGBcN1pZCTodl4QNnCVmKv4tSxfQ== + dependencies: + "@floating-ui/react-dom" "^2.1.1" + "@floating-ui/utils" "^0.2.6" + tabbable "^6.0.0" + +"@floating-ui/react@^0.26.23": + version "0.26.24" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.24.tgz#072b9dfeca4e79ef4e3000ef1c28e0ffc86f4ed4" + integrity sha512-2ly0pCkZIGEQUq5H8bBK0XJmc1xIK/RM3tvVzY3GBER7IOD1UgmC2Y2tjj4AuS+TC+vTE1KJv2053290jua0Sw== + dependencies: + "@floating-ui/react-dom" "^2.1.2" + "@floating-ui/utils" "^0.2.8" + tabbable "^6.0.0" + +"@floating-ui/utils@^0.2.6", "@floating-ui/utils@^0.2.8": + version "0.2.8" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62" + integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig== + "@fortawesome/fontawesome-common-types@6.6.0": version "6.6.0" resolved "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz" @@ -2871,14 +2923,6 @@ bluebird@^3.7.2: resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - brace-expansion@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"