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"