diff --git a/frontend/package-lock.json b/frontend/package-lock.json index cc5195ed275beda084831c977625b3520cfea5ec..c5d8b525979b542f6f70e6506f94a2720be76b3f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,6 +24,7 @@ }, "devDependencies": { "@babel/core": "^7.14.6", + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/preset-env": "^7.14.5", "@babel/preset-react": "^7.13.13", "@babel/register": "^7.14.5", @@ -687,10 +688,18 @@ } }, "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", + "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, "engines": { "node": ">=6.9.0" }, @@ -2005,6 +2014,18 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/preset-modules": { "version": "0.1.6-no-external-plugins", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", diff --git a/frontend/package.json b/frontend/package.json index 19354dbb00355e448e06df81f13b97692c4c9750..ac0948b404ad41883e9c16081f3ad797d3ad6770 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -43,6 +43,7 @@ }, "devDependencies": { "@babel/core": "^7.14.6", + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/preset-env": "^7.14.5", "@babel/preset-react": "^7.13.13", "@babel/register": "^7.14.5", diff --git a/frontend/src/components/Items/AddItem.js b/frontend/src/components/Items/AddItem.js index df63b6f278190978cfdeea90722ebf96276c8cc3..01c3c0edac15e2e7f3c6d21272e57bbc1ca791aa 100644 --- a/frontend/src/components/Items/AddItem.js +++ b/frontend/src/components/Items/AddItem.js @@ -1,4 +1,5 @@ // src/components/Items/AddItem.js + import React, { useState } from 'react'; import { addItem } from '../../services/itemApi'; import '../../styles/main.css'; @@ -18,8 +19,9 @@ const AddItem = ({ onItemAdded }) => { <div className="card-body"> <form onSubmit={handleSubmit}> <div className="form-group"> - <label>Name</label> + <label htmlFor="item-name">Name</label> <input + id="item-name" type="text" className="form-control" value={itemName} diff --git a/frontend/src/components/Items/GetItems.js b/frontend/src/components/Items/GetItems.js index 075a032af5076b18b9566f0173f6b1062a04be85..5ee57efac3cd78e5e5b052d702c4308871cf02dd 100644 --- a/frontend/src/components/Items/GetItems.js +++ b/frontend/src/components/Items/GetItems.js @@ -1,20 +1,34 @@ // src/components/Items/GetItems.js + import React, { useEffect, useState } from 'react'; import { getItems } from '../../services/itemApi'; import '../../styles/main.css'; +/** + * Component to fetch and display a list of items. + * + * @param {Function} onEditItem - Callback to be triggered when an item is to be edited. + * @param {Function} onDeleteItem - Callback to be triggered when an item is to be deleted. + * @param {boolean} refresh - Flag to re-fetch items when changed. + */ const GetItems = ({ onEditItem, onDeleteItem, refresh }) => { const [items, setItems] = useState([]); + const [loading, setLoading] = useState(true); useEffect(() => { const fetchItems = async () => { const data = await getItems(); setItems(data); + setLoading(false); // Set loading to false once data is fetched }; fetchItems(); }, [refresh]); + if (loading) { + return <div>Loading...</div>; // Display loading text while fetching data + } + return ( <div className="card"> <div className="card-body"> diff --git a/frontend/src/components/Locations/AddLocation.js b/frontend/src/components/Locations/AddLocation.js index 548b8ad79d99aad048f726f2a4ce553bb88455b4..d6fb89d71709aab2cedf242dbd1901733ae90415 100644 --- a/frontend/src/components/Locations/AddLocation.js +++ b/frontend/src/components/Locations/AddLocation.js @@ -3,6 +3,11 @@ import React, { useState } from 'react'; import { addLocation } from '../../services/locationApi'; import '../../styles/main.css'; +/** + * Component to add a new location. + * + * @param {Function} onLocationAdded - Callback to be triggered after a location is added. + */ const AddLocation = ({ onLocationAdded }) => { const [locationName, setLocationName] = useState(''); @@ -18,9 +23,10 @@ const AddLocation = ({ onLocationAdded }) => { <h3>Add Location</h3> <form onSubmit={handleSubmit}> <div className="form-group"> - <label>Name</label> + <label htmlFor="location-name">Name</label> <input type="text" + id="location-name" className="form-control" value={locationName} onChange={(e) => setLocationName(e.target.value)} diff --git a/frontend/src/components/Locations/DeleteLocation.js b/frontend/src/components/Locations/DeleteLocation.js index 9eb71f007e2dc7f4a5e7a0dce2218d3a054a6f25..9e50b5a5bd6e9a10623f1bddf5d34fafaa5603c3 100644 --- a/frontend/src/components/Locations/DeleteLocation.js +++ b/frontend/src/components/Locations/DeleteLocation.js @@ -2,6 +2,12 @@ import React from 'react'; import { deleteLocation } from '../../services/locationApi'; +/** + * Component to delete a location. + * + * @param {string} locationId - The ID of the location to be deleted. + * @param {Function} onDelete - Callback to be triggered after the location is deleted. + */ const DeleteLocation = ({ locationId, onDelete }) => { const handleDelete = async () => { await deleteLocation(locationId); diff --git a/frontend/src/components/Locations/GetLocations.js b/frontend/src/components/Locations/GetLocations.js index e60a850a8e20cb1f5d7f0bc6f62d28222d57e6f0..d610fa2d207a332bebe8fd779cbc9ce72a785e2b 100644 --- a/frontend/src/components/Locations/GetLocations.js +++ b/frontend/src/components/Locations/GetLocations.js @@ -1,7 +1,16 @@ +// src/components/Locations/GetLocations.js + import React, { useEffect, useState } from 'react'; import { getLocations } from '../../services/locationApi'; import '../../styles/main.css'; +/** + * Component to display a list of locations. + * + * @param {Function} onEditLocation - Callback to edit a location. + * @param {Function} onDeleteLocation - Callback to delete a location. + * @param {boolean} refresh - Flag to trigger the re-fetch of locations. + */ const GetLocations = ({ onEditLocation, onDeleteLocation, refresh }) => { const [locations, setLocations] = useState([]); diff --git a/frontend/src/components/Locations/UpdateLocation.js b/frontend/src/components/Locations/UpdateLocation.js index 4dbb289c237dab3c95bdad1af8f3898ad229c311..f8d8225112e6e46ab5906a8d1b47709fe8063eac 100644 --- a/frontend/src/components/Locations/UpdateLocation.js +++ b/frontend/src/components/Locations/UpdateLocation.js @@ -28,8 +28,9 @@ const UpdateLocation = ({ location, onUpdateCompleted }) => { {error && <p className="text-danger">{error}</p>} <form onSubmit={handleSubmit}> <div className="form-group"> - <label>Name</label> + <label htmlFor="location-name">Name</label> <input + id="location-name" type="text" className="form-control" value={locationName} diff --git a/frontend/src/components/Rules/GetRules.js b/frontend/src/components/Rules/GetRules.js index 4bfd1a18d3d985b862a2fbc244bffc03b24b2693..4af9164ede618c946d7a2b27301c325bfa1a945e 100644 --- a/frontend/src/components/Rules/GetRules.js +++ b/frontend/src/components/Rules/GetRules.js @@ -7,11 +7,16 @@ import '../../styles/dashboard.css'; const GetRules = ({ onEditRule, onDeleteRule, refresh }) => { const [rules, setRules] = useState([]); + const [error, setError] = useState(null); useEffect(() => { const fetchRules = async () => { - const data = await getRules(); - setRules(data); + try { + const data = await getRules(); + setRules(data); + } catch (err) { + setError('Error fetching rules'); + } }; fetchRules(); @@ -27,6 +32,7 @@ const GetRules = ({ onEditRule, onDeleteRule, refresh }) => { <div className="card card-wide"> <div className="card-body"> <h2 className="card-title">Rules List</h2> + {error && <p className="text-danger">{error}</p>} <ul className="list-group"> <li className="list-group-header"> <span>Items</span> diff --git a/frontend/src/components/UserProfile/ChangeInfoSection.js b/frontend/src/components/UserProfile/ChangeInfoSection.js index aab2870e367af53c94fbfc56a79e322cdac1d46e..da241a03a68ff330936d7b8d194a15ae201b37c3 100644 --- a/frontend/src/components/UserProfile/ChangeInfoSection.js +++ b/frontend/src/components/UserProfile/ChangeInfoSection.js @@ -1,4 +1,5 @@ // src/components/UserProfile/ChangeInfoSection.js + import React, { useState } from 'react'; import DisplayInfoWithAction from './DisplayInfoWithAction'; import EditableInfoForm from './EditableInfoForm'; @@ -29,6 +30,8 @@ const ChangeInfoSection = ({ title, infoLabel, currentInfo, onUpdateInfo }) => { /> {showForm && ( <EditableInfoForm + label={infoLabel} // Pass the infoLabel to ensure consistency + type="text" // Adjust this if needed onSubmit={handleSubmit} onCancel={handleCancel} /> diff --git a/frontend/src/components/UserProfile/EditableInfoForm.js b/frontend/src/components/UserProfile/EditableInfoForm.js index 5e34d1bf40c43646aaf0853735aaf83233e1825d..31fb5f178d56a0ca34a498b76e19a26de42fe53a 100644 --- a/frontend/src/components/UserProfile/EditableInfoForm.js +++ b/frontend/src/components/UserProfile/EditableInfoForm.js @@ -1,8 +1,7 @@ -/* src/components/UserProfile/EditableInfoForm.js */ +// src/components/UserProfile/EditableInfoForm.js import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import FormField from '../Common/Form/FormField'; import PasswordInputField from '../Common/Password/PasswordInputField'; import SubmitButton from '../Common/buttons/SubmitButton'; import CancelButton from '../Common/buttons/CancelButton'; @@ -19,14 +18,18 @@ const EditableInfoForm = ({ label, type, onSubmit, onCancel, loading, error }) = }; return ( - <form onSubmit={handleSubmit} > - <FormField - label={label} - type={type} - value={newInfo} - onChange={(e) => setNewInfo(e.target.value)} - required - /> + <form onSubmit={handleSubmit} role="form"> + <div className="form-group"> + <label htmlFor="new-info-input">New {label}</label> + <input + id="new-info-input" + type={type} + className="form-control" + value={newInfo} + onChange={(e) => setNewInfo(e.target.value)} + required + /> + </div> <PasswordInputField label="Password" value={password} diff --git a/frontend/src/forms/Auth/AdminLoginForm.js b/frontend/src/forms/Auth/AdminLoginForm.js index 3e295dcb2fd3950ffd23bcae39a2cc66e9cb1e73..4e84f0e93f01917678bb6556a495f9d5443c97af 100644 --- a/frontend/src/forms/Auth/AdminLoginForm.js +++ b/frontend/src/forms/Auth/AdminLoginForm.js @@ -1,4 +1,5 @@ // src/forms/Auth/AdminLoginForm.js + import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { adminLogin } from '../../services/auth'; // Import the adminLogin function @@ -33,12 +34,14 @@ const AdminLoginForm = () => { <FormField label="Username" type="text" + id="username-input" value={username} onChange={(e) => setUsername(e.target.value)} required /> <PasswordInputField label="Password" + id="password-input" value={password} onChange={(e) => setPassword(e.target.value)} /> diff --git a/frontend/src/pages/NormalDetection/NormalDetectionPage.js b/frontend/src/pages/NormalDetection/NormalDetectionPage.js index 1c18a9d7bc56d49afad4fe30fc8307d925bdd271..a58734d65df16edcc5089791ec57365865f626a8 100644 --- a/frontend/src/pages/NormalDetection/NormalDetectionPage.js +++ b/frontend/src/pages/NormalDetection/NormalDetectionPage.js @@ -1,7 +1,7 @@ // src/pages/NormalDetection/NormalDetectionPage.js import React, { useState, useEffect } from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import { Modal, Button } from 'react-bootstrap'; import { normalDetection, downloadImage, deleteImageByName } from '../../services/processMisplacedManagerApi'; import { checkDailyLimit, incrementDetection } from '../../services/dailyLimitApi'; @@ -20,7 +20,6 @@ const NormalDetectionPage = () => { const [detectionComplete, setDetectionComplete] = useState(false); const [imageName, setImageName] = useState(null); const [limitInfo, setLimitInfo] = useState({ remaining: 0, limit: 0 }); - const navigate = useNavigate(); const location = useLocation(); useEffect(() => { @@ -96,10 +95,6 @@ const NormalDetectionPage = () => { } }; - const handleImageClick = () => { - setShowModal(true); - }; - const handleCloseModal = () => { setShowModal(false); }; diff --git a/frontend/src/pages/Video/VideoDetectionPage.js b/frontend/src/pages/Video/VideoDetectionPage.js index eead70a85a29ca09f146cf702a5eff83ca57b829..e4a60cd338d9d4e073df742c36733b2d7932b797 100644 --- a/frontend/src/pages/Video/VideoDetectionPage.js +++ b/frontend/src/pages/Video/VideoDetectionPage.js @@ -1,7 +1,7 @@ // src/pages/VideoDetection/VideoDetectionPage.js import React, { useState, useEffect } from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import { uploadVideo, getVideoResults, deleteVideo, downloadMedia } from '../../services/processMisplacedManagerApi'; import { checkDailyLimit, incrementDetection } from '../../services/dailyLimitApi'; import '../../styles/main.css'; @@ -18,10 +18,10 @@ const VideoDetectionPage = () => { const [frameDelay, setFrameDelay] = useState(1); const [result, setResult] = useState(null); const [isLoading, setIsLoading] = useState(false); - const [videoName, setVideoName] = useState(null); + // const [videoName, setVideoName] = useState(null); const [annotatedVideoName, setAnnotatedVideoName] = useState(null); const [limitInfo, setLimitInfo] = useState({ remaining: 0, limit: 0 }); - const navigate = useNavigate(); + const location = useLocation(); const [isDeleting, setIsDeleting] = useState(false); @@ -107,7 +107,7 @@ const VideoDetectionPage = () => { const videoResults = await getVideoResults(uploadResponse.id); console.log('Video results:', videoResults); setResult(videoResults); - setVideoName(uploadResponse.video.split('/').pop()); // Extract original video name from the response + // setVideoName(uploadResponse.video.split('/').pop()); // Extract original video name from the response setAnnotatedVideoName(videoResults.output_video_url.split('/').pop()); // Extract annotated video name from the results await incrementDetection('video'); // Increment the detection count const data = await checkDailyLimit('video'); // Fetch updated limit info diff --git a/frontend/src/reportWebVitals.js b/frontend/src/reportWebVitals.js index 5253d3ad9e6be6690549cb255f5952337b02401d..7fe98d049b03c5f5e4c16679ece45b871a544f05 100644 --- a/frontend/src/reportWebVitals.js +++ b/frontend/src/reportWebVitals.js @@ -1,3 +1,4 @@ +// frontend/src/reportWebVitals.js const reportWebVitals = onPerfEntry => { if (onPerfEntry && onPerfEntry instanceof Function) { import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { diff --git a/frontend/src/tests/components/Items/AddItem.test.js b/frontend/src/tests/components/Items/AddItem.test.js new file mode 100644 index 0000000000000000000000000000000000000000..90dd6b776b20d7a0664770dc36f353f6af3197dd --- /dev/null +++ b/frontend/src/tests/components/Items/AddItem.test.js @@ -0,0 +1,73 @@ +// src/tests/components/Items/AddItem.test.js + +/** + * Test ID-> AI1: Ensure the AddItem component renders correctly. + * Test ID-> AI2: Ensure the input field can capture user input. + * Test ID-> AI3: Ensure the form submission calls addItem API. + * Test ID-> AI4: Ensure the form submission triggers onItemAdded callback. + */ + +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import AddItem from '../../../components/Items/AddItem'; +import * as itemApi from '../../../services/itemApi'; + +describe('AddItem Component', () => { + let addItemStub; + + beforeEach(() => { + // Mocking the addItem service + addItemStub = sinon.stub(itemApi, 'addItem').resolves({}); + }); + + afterEach(() => { + // Restore the original function + addItemStub.restore(); + }); + + it('renders the AddItem component correctly', () => { + // Test ID-> AI1 + render(<AddItem onItemAdded={() => { }} />); + expect(screen.getByText(/Submit/i)).to.exist; + expect(screen.getByLabelText(/Name/i)).to.exist; + }); + + it('captures user input correctly', () => { + // Test ID-> AI2 + render(<AddItem onItemAdded={() => { }} />); + const input = screen.getByLabelText(/Name/i); + fireEvent.change(input, { target: { value: 'NewItem' } }); + expect(input.value).to.equal('NewItem'); + }); + + it('calls addItem API on form submission', async () => { + // Test ID-> AI3 + render(<AddItem onItemAdded={() => { }} />); + const input = screen.getByLabelText(/Name/i); + fireEvent.change(input, { target: { value: 'NewItem' } }); + + const button = screen.getByText(/Submit/i); + fireEvent.click(button); + + expect(addItemStub.calledOnce).to.be.true; + expect(addItemStub.calledWith({ name: 'NewItem' })).to.be.true; + }); + + it('triggers onItemAdded callback on form submission', async () => { + // Test ID-> AI4 + const onItemAddedSpy = sinon.spy(); + render(<AddItem onItemAdded={onItemAddedSpy} />); + const input = screen.getByLabelText(/Name/i); + fireEvent.change(input, { target: { value: 'NewItem' } }); + + const button = screen.getByText(/Submit/i); + fireEvent.click(button); + + // Allow the async actions to complete + await screen.findByText(/Submit/i); + + expect(onItemAddedSpy.calledOnce).to.be.true; + }); +}); diff --git a/frontend/src/tests/components/Items/DeleteItem.test.js b/frontend/src/tests/components/Items/DeleteItem.test.js index 9207db6516acdf19c8bd84314b80de096a85c702..b18d632448632d65fe631de3a33ba5f3cdfe808b 100644 --- a/frontend/src/tests/components/Items/DeleteItem.test.js +++ b/frontend/src/tests/components/Items/DeleteItem.test.js @@ -1,18 +1,57 @@ +// src/tests/components/Items/DeleteItem.test.js + /** - * Test 1: renders the delete button - Ensures the delete button is rendered correctly. + * Test ID-> DI1: Ensure the DeleteItem component renders correctly. + * Test ID-> DI2: Ensure the deleteItem API is called with the correct item ID. + * Test ID-> DI3: Ensure the onDelete callback is triggered after deletion. */ import React from 'react'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { render, screen, fireEvent } from '@testing-library/react'; import { expect } from 'chai'; import sinon from 'sinon'; import DeleteItem from '../../../components/Items/DeleteItem'; import * as itemApi from '../../../services/itemApi'; describe('DeleteItem Component', () => { - it('renders the delete button', () => { - render(<DeleteItem />); - expect(screen.getByRole('button', { name: /delete/i })).to.exist; + let deleteItemStub; + + beforeEach(() => { + // Mocking the deleteItem service + deleteItemStub = sinon.stub(itemApi, 'deleteItem').resolves({}); + }); + + afterEach(() => { + // Restore the original function + deleteItemStub.restore(); + }); + + it('renders the DeleteItem component correctly', () => { + // Test ID-> DI1 + render(<DeleteItem itemId="1" onDelete={() => { }} />); + expect(screen.getByText(/Delete/i)).to.exist; }); + it('calls deleteItem API with correct item ID', async () => { + // Test ID-> DI2 + render(<DeleteItem itemId="1" onDelete={() => { }} />); + const button = screen.getByText(/Delete/i); + fireEvent.click(button); + + expect(deleteItemStub.calledOnce).to.be.true; + expect(deleteItemStub.calledWith("1")).to.be.true; + }); + + it('triggers onDelete callback after deletion', async () => { + // Test ID-> DI3 + const onDeleteSpy = sinon.spy(); + render(<DeleteItem itemId="1" onDelete={onDeleteSpy} />); + const button = screen.getByText(/Delete/i); + fireEvent.click(button); + + // Allow the async actions to complete + await screen.findByText(/Delete/i); + + expect(onDeleteSpy.calledOnce).to.be.true; + }); }); diff --git a/frontend/src/tests/components/Items/GetItems.test.js b/frontend/src/tests/components/Items/GetItems.test.js new file mode 100644 index 0000000000000000000000000000000000000000..bf0d4cf1baf2f4cc92d507a6ea85cb7b59b43b19 --- /dev/null +++ b/frontend/src/tests/components/Items/GetItems.test.js @@ -0,0 +1,79 @@ +// src/tests/components/Items/GetItems.test.js + +/** + * Test ID-> GI1: Ensure the GetItems component renders correctly. + * Test ID-> GI2: Ensure the getItems API is called and items are fetched correctly. + * Test ID-> GI3: Ensure the edit button calls the onEditItem callback. + * Test ID-> GI4: Ensure the delete button calls the onDeleteItem callback. + */ + +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import GetItems from '../../../components/Items/GetItems'; +import * as itemApi from '../../../services/itemApi'; + +describe('GetItems Component', () => { + let getItemsStub; + + beforeEach(() => { + // Mocking the getItems service + getItemsStub = sinon.stub(itemApi, 'getItems').resolves([ + { id: '1', name: 'Item 1' }, + { id: '2', name: 'Item 2' }, + ]); + }); + + afterEach(() => { + // Restore the original function + getItemsStub.restore(); + }); + + it('renders the GetItems component correctly', async () => { + // Test ID-> GI1 + render(<GetItems onEditItem={() => { }} onDeleteItem={() => { }} refresh={false} />); + expect(screen.getByText(/Loading.../i)).to.exist; // Assuming you show a loading indicator + + await waitFor(() => { + expect(screen.getByText(/Item 1/i)).to.exist; + expect(screen.getByText(/Item 2/i)).to.exist; + }); + }); + + it('calls getItems API and fetches items correctly', async () => { + // Test ID-> GI2 + render(<GetItems onEditItem={() => { }} onDeleteItem={() => { }} refresh={false} />); + + await waitFor(() => { + expect(getItemsStub.calledOnce).to.be.true; + expect(screen.getByText(/Item 1/i)).to.exist; + expect(screen.getByText(/Item 2/i)).to.exist; + }); + }); + + it('calls onEditItem callback when edit button is clicked', async () => { + // Test ID-> GI3 + const onEditItemSpy = sinon.spy(); + render(<GetItems onEditItem={onEditItemSpy} onDeleteItem={() => { }} refresh={false} />); + + await waitFor(() => { + const editButton = screen.getAllByText(/Edit/i)[0]; + fireEvent.click(editButton); + expect(onEditItemSpy.calledOnce).to.be.true; + }); + }); + + it('calls onDeleteItem callback when delete button is clicked', async () => { + // Test ID-> GI4 + const onDeleteItemSpy = sinon.spy(); + render(<GetItems onEditItem={() => { }} onDeleteItem={onDeleteItemSpy} refresh={false} />); + + await waitFor(() => { + const deleteButton = screen.getAllByText(/Delete/i)[0]; + fireEvent.click(deleteButton); + expect(onDeleteItemSpy.calledOnce).to.be.true; + expect(onDeleteItemSpy.calledWith('1')).to.be.true; + }); + }); +}); diff --git a/frontend/src/tests/components/Items/UpdateItem.test.js b/frontend/src/tests/components/Items/UpdateItem.test.js new file mode 100644 index 0000000000000000000000000000000000000000..7701cc9e064ce00fd82f6621d40f420b7ed06305 --- /dev/null +++ b/frontend/src/tests/components/Items/UpdateItem.test.js @@ -0,0 +1,78 @@ +// src/tests/components/Items/UpdateItem.test.js + +/** + * Test ID-> UI1: Ensure the UpdateItem component renders correctly. + * Test ID-> UI2: Ensure the updateItem API is called with correct item ID and name. + * Test ID-> UI3: Ensure the onUpdateCompleted callback is triggered after a successful update. + * Test ID-> UI4: Ensure error message is displayed when update fails. + */ + +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import UpdateItem from '../../../components/Items/UpdateItem'; +import * as itemApi from '../../../services/itemApi'; + +describe('UpdateItem Component', () => { + let updateItemStub; + + beforeEach(() => { + // Mocking the updateItem service + updateItemStub = sinon.stub(itemApi, 'updateItem').resolves({}); + }); + + afterEach(() => { + // Restore the original function + updateItemStub.restore(); + }); + + it('renders the UpdateItem component correctly', () => { + // Test ID-> UI1 + const item = { id: '1', name: 'Test Item' }; + render(<UpdateItem item={item} onUpdateCompleted={() => { }} />); + expect(screen.getByDisplayValue(/Test Item/i)).to.exist; + }); + + it('calls updateItem API with correct item ID and name', async () => { + // Test ID-> UI2 + const item = { id: '1', name: 'Test Item' }; + render(<UpdateItem item={item} onUpdateCompleted={() => { }} />); + + fireEvent.change(screen.getByDisplayValue(/Test Item/i), { target: { value: 'Updated Item' } }); + fireEvent.click(screen.getByText(/Update/i)); + + await waitFor(() => { + expect(updateItemStub.calledOnce).to.be.true; + expect(updateItemStub.calledWith('1', { name: 'Updated Item' })).to.be.true; + }); + }); + + it('triggers onUpdateCompleted callback after a successful update', async () => { + // Test ID-> UI3 + const item = { id: '1', name: 'Test Item' }; + const onUpdateCompletedSpy = sinon.spy(); + render(<UpdateItem item={item} onUpdateCompleted={onUpdateCompletedSpy} />); + + fireEvent.change(screen.getByDisplayValue(/Test Item/i), { target: { value: 'Updated Item' } }); + fireEvent.click(screen.getByText(/Update/i)); + + await waitFor(() => { + expect(onUpdateCompletedSpy.calledOnce).to.be.true; + }); + }); + + it('displays error message when update fails', async () => { + // Test ID-> UI4 + updateItemStub.rejects(new Error('Update failed')); + const item = { id: '1', name: 'Test Item' }; + render(<UpdateItem item={item} onUpdateCompleted={() => { }} />); + + fireEvent.change(screen.getByDisplayValue(/Test Item/i), { target: { value: 'Updated Item' } }); + fireEvent.click(screen.getByText(/Update/i)); + + await waitFor(() => { + expect(screen.getByText(/Error updating item: Update failed/i)).to.exist; + }); + }); +}); diff --git a/frontend/src/tests/components/Locations/AddLocation.test.js b/frontend/src/tests/components/Locations/AddLocation.test.js new file mode 100644 index 0000000000000000000000000000000000000000..2b97c302cc821adbf1a819b5cf9753c6799312d1 --- /dev/null +++ b/frontend/src/tests/components/Locations/AddLocation.test.js @@ -0,0 +1,73 @@ +// src/tests/components/Locations/AddLocation.test.js + +/** + * Test ID-> AL1: Ensure the AddLocation component renders correctly. + * Test ID-> AL2: Ensure the addLocation API is called with correct location name. + * Test ID-> AL3: Ensure the onLocationAdded callback is triggered after a successful addition. + * Test ID-> AL4: Ensure the input value changes when user types. + */ + +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import AddLocation from '../../../components/Locations/AddLocation'; +import * as locationApi from '../../../services/locationApi'; + +describe('AddLocation Component', () => { + let addLocationStub; + + beforeEach(() => { + // Mocking the addLocation service + addLocationStub = sinon.stub(locationApi, 'addLocation').resolves({}); + }); + + afterEach(() => { + // Restore the original function + addLocationStub.restore(); + }); + + it('renders the AddLocation component correctly', () => { + // Test ID-> AL1 + render(<AddLocation onLocationAdded={() => { }} />); + expect(screen.getByText(/Add Location/i)).to.exist; + expect(screen.getByLabelText(/Name/i)).to.exist; + expect(screen.getByText(/Submit/i)).to.exist; + }); + + it('calls addLocation API with correct location name', async () => { + // Test ID-> AL2 + render(<AddLocation onLocationAdded={() => { }} />); + + fireEvent.change(screen.getByLabelText(/Name/i), { target: { value: 'New Location' } }); + fireEvent.click(screen.getByText(/Submit/i)); + + await waitFor(() => { + expect(addLocationStub.calledOnce).to.be.true; + expect(addLocationStub.calledWith({ name: 'New Location' })).to.be.true; + }); + }); + + it('triggers onLocationAdded callback after a successful addition', async () => { + // Test ID-> AL3 + const onLocationAddedSpy = sinon.spy(); + render(<AddLocation onLocationAdded={onLocationAddedSpy} />); + + fireEvent.change(screen.getByLabelText(/Name/i), { target: { value: 'New Location' } }); + fireEvent.click(screen.getByText(/Submit/i)); + + await waitFor(() => { + expect(onLocationAddedSpy.calledOnce).to.be.true; + }); + }); + + it('changes input value when user types', () => { + // Test ID-> AL4 + render(<AddLocation onLocationAdded={() => { }} />); + + const input = screen.getByLabelText(/Name/i); + fireEvent.change(input, { target: { value: 'New Location' } }); + + expect(input.value).to.equal('New Location'); + }); +}); diff --git a/frontend/src/tests/components/Locations/DeleteLocation.test.js b/frontend/src/tests/components/Locations/DeleteLocation.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9d57e2a3d33d7cbe0ff56bd2271389d55d90bcd4 --- /dev/null +++ b/frontend/src/tests/components/Locations/DeleteLocation.test.js @@ -0,0 +1,58 @@ +// src/tests/components/Locations/DeleteLocation.test.js + +/** + * Test ID-> DL1: Ensure the DeleteLocation component renders correctly. + * Test ID-> DL2: Ensure the deleteLocation API is called with the correct location ID. + * Test ID-> DL3: Ensure the onDelete callback is triggered after a successful deletion. + */ + +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import DeleteLocation from '../../../components/Locations/DeleteLocation'; +import * as locationApi from '../../../services/locationApi'; + +describe('DeleteLocation Component', () => { + let deleteLocationStub; + + beforeEach(() => { + // Mocking the deleteLocation service + deleteLocationStub = sinon.stub(locationApi, 'deleteLocation').resolves({}); + }); + + afterEach(() => { + // Restore the original function + deleteLocationStub.restore(); + }); + + it('renders the DeleteLocation component correctly', () => { + // Test ID-> DL1 + render(<DeleteLocation locationId="1" onDelete={() => { }} />); + expect(screen.getByText(/Delete/i)).to.exist; + }); + + it('calls deleteLocation API with the correct location ID', async () => { + // Test ID-> DL2 + render(<DeleteLocation locationId="1" onDelete={() => { }} />); + + fireEvent.click(screen.getByText(/Delete/i)); + + await waitFor(() => { + expect(deleteLocationStub.calledOnce).to.be.true; + expect(deleteLocationStub.calledWith("1")).to.be.true; + }); + }); + + it('triggers onDelete callback after a successful deletion', async () => { + // Test ID-> DL3 + const onDeleteSpy = sinon.spy(); + render(<DeleteLocation locationId="1" onDelete={onDeleteSpy} />); + + fireEvent.click(screen.getByText(/Delete/i)); + + await waitFor(() => { + expect(onDeleteSpy.calledOnce).to.be.true; + }); + }); +}); diff --git a/frontend/src/tests/components/Locations/GetLocations.test.js b/frontend/src/tests/components/Locations/GetLocations.test.js new file mode 100644 index 0000000000000000000000000000000000000000..5ed55677e0a9b9393691edfdfee006361c2ae5d9 --- /dev/null +++ b/frontend/src/tests/components/Locations/GetLocations.test.js @@ -0,0 +1,74 @@ +// src/tests/components/Locations/GetLocations.test.js + +/** + * Test ID-> GL1: Ensure the GetLocations component renders correctly. + * Test ID-> GL2: Ensure locations are fetched and displayed correctly. + * Test ID-> GL3: Ensure the onEditLocation callback is triggered correctly. + * Test ID-> GL4: Ensure the onDeleteLocation callback is triggered correctly. + */ + +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import GetLocations from '../../../components/Locations/GetLocations'; +import * as locationApi from '../../../services/locationApi'; + +describe('GetLocations Component', () => { + let getLocationsStub; + + beforeEach(() => { + // Mocking the getLocations service + getLocationsStub = sinon.stub(locationApi, 'getLocations').resolves([ + { id: '1', name: 'Location 1' }, + { id: '2', name: 'Location 2' }, + ]); + }); + + afterEach(() => { + // Restore the original function + getLocationsStub.restore(); + }); + + it('renders the GetLocations component correctly', () => { + // Test ID-> GL1 + render(<GetLocations onEditLocation={() => { }} onDeleteLocation={() => { }} refresh={false} />); + expect(screen.getByText(/Locations List/i)).to.exist; + }); + + it('fetches and displays locations correctly', async () => { + // Test ID-> GL2 + render(<GetLocations onEditLocation={() => { }} onDeleteLocation={() => { }} refresh={false} />); + + await waitFor(() => { + expect(screen.getByText(/Location 1/i)).to.exist; + expect(screen.getByText(/Location 2/i)).to.exist; + }); + }); + + it('triggers onEditLocation callback correctly', async () => { + // Test ID-> GL3 + const onEditLocationSpy = sinon.spy(); + render(<GetLocations onEditLocation={onEditLocationSpy} onDeleteLocation={() => { }} refresh={false} />); + + await waitFor(() => { + fireEvent.click(screen.getAllByText(/Edit/i)[0]); + }); + + expect(onEditLocationSpy.calledOnce).to.be.true; + expect(onEditLocationSpy.calledWith({ id: '1', name: 'Location 1' })).to.be.true; + }); + + it('triggers onDeleteLocation callback correctly', async () => { + // Test ID-> GL4 + const onDeleteLocationSpy = sinon.spy(); + render(<GetLocations onEditLocation={() => { }} onDeleteLocation={onDeleteLocationSpy} refresh={false} />); + + await waitFor(() => { + fireEvent.click(screen.getAllByText(/Delete/i)[0]); + }); + + expect(onDeleteLocationSpy.calledOnce).to.be.true; + expect(onDeleteLocationSpy.calledWith('1')).to.be.true; + }); +}); diff --git a/frontend/src/tests/components/Locations/UpdateLocation.test.js b/frontend/src/tests/components/Locations/UpdateLocation.test.js new file mode 100644 index 0000000000000000000000000000000000000000..61f29f30098e39f8763d6d197c52a267bc07ee76 --- /dev/null +++ b/frontend/src/tests/components/Locations/UpdateLocation.test.js @@ -0,0 +1,88 @@ +// src/tests/components/Locations/UpdateLocation.test.js + +/** + * This file tests the UpdateLocation component, ensuring it functions as expected. + * + * Test ID-> UL1: Ensure the UpdateLocation component renders correctly. + * Test ID-> UL2: Ensure input field is pre-filled with the location's name. + * Test ID-> UL3: Ensure input value changes when the user types. + * Test ID-> UL4: Ensure the updateLocation API is called with correct data on form submission. + * Test ID-> UL5: Ensure onUpdateCompleted callback is triggered after a successful update. + * Test ID-> UL6: Ensure error message is displayed when update fails. + */ + +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import UpdateLocation from '../../../components/Locations/UpdateLocation'; +import * as locationApi from '../../../services/locationApi'; + +describe('UpdateLocation Component', () => { + let updateLocationStub; + + beforeEach(() => { + // Mocking the updateLocation service + updateLocationStub = sinon.stub(locationApi, 'updateLocation').resolves(); + }); + + afterEach(() => { + // Restore the original function + updateLocationStub.restore(); + }); + + it('renders the UpdateLocation component correctly', () => { + // Test ID-> UL1 + render(<UpdateLocation location={{ id: '1', name: 'Test Location' }} onUpdateCompleted={() => { }} />); + expect(screen.getByText(/Update Location/i)).to.exist; + }); + + it('pre-fills the input field with the location\'s name', () => { + // Test ID-> UL2 + render(<UpdateLocation location={{ id: '1', name: 'Test Location' }} onUpdateCompleted={() => { }} />); + expect(screen.getByDisplayValue(/Test Location/i)).to.exist; + }); + + it('changes input value when user types', () => { + // Test ID-> UL3 + render(<UpdateLocation location={{ id: '1', name: 'Test Location' }} onUpdateCompleted={() => { }} />); + const input = screen.getByLabelText(/Name/i); + fireEvent.change(input, { target: { value: 'Updated Location' } }); + expect(input.value).to.equal('Updated Location'); + }); + + it('calls updateLocation API with correct data on form submission', async () => { + // Test ID-> UL4 + render(<UpdateLocation location={{ id: '1', name: 'Test Location' }} onUpdateCompleted={() => { }} />); + const input = screen.getByLabelText(/Name/i); + fireEvent.change(input, { target: { value: 'Updated Location' } }); + fireEvent.submit(screen.getByRole('button', { name: /Update/i })); + + await waitFor(() => { + expect(updateLocationStub.calledOnce).to.be.true; + expect(updateLocationStub.calledWith('1', { name: 'Updated Location' })).to.be.true; + }); + }); + + it('triggers onUpdateCompleted callback after a successful update', async () => { + // Test ID-> UL5 + const onUpdateCompletedSpy = sinon.spy(); + render(<UpdateLocation location={{ id: '1', name: 'Test Location' }} onUpdateCompleted={onUpdateCompletedSpy} />); + fireEvent.submit(screen.getByRole('button', { name: /Update/i })); + + await waitFor(() => { + expect(onUpdateCompletedSpy.calledOnce).to.be.true; + }); + }); + + it('displays error message when update fails', async () => { + // Test ID-> UL6 + updateLocationStub.rejects(new Error('Update failed')); + render(<UpdateLocation location={{ id: '1', name: 'Test Location' }} onUpdateCompleted={() => { }} />); + fireEvent.submit(screen.getByRole('button', { name: /Update/i })); + + await waitFor(() => { + expect(screen.getByText(/Error updating location: Update failed/i)).to.exist; + }); + }); +}); diff --git a/frontend/src/tests/components/Rules/AddRule.test.js b/frontend/src/tests/components/Rules/AddRule.test.js index a7a0c82eaac60ca1de75c05433507a94821292d4..64e4e80e77e7f041867a04f2283b845744adfd87 100644 --- a/frontend/src/tests/components/Rules/AddRule.test.js +++ b/frontend/src/tests/components/Rules/AddRule.test.js @@ -1,7 +1,17 @@ +// src/tests/components/Rules/AddRule.test.js + /** - * Test 1: renders the form with items and locations - Ensures the form renders correctly with fetched items and locations. - * Test 2: displays error message on fetch failure - Ensures an error message is displayed if fetching items or locations fails. - * Test 3: displays error message on addRule failure - Ensures an error message is displayed if adding a rule fails. + * This file tests the AddRule component, ensuring it functions as expected. + * + * Test ID-> AR1: Ensure the AddRule component renders correctly. + * Test ID-> AR2: Ensure item checkboxes are rendered correctly based on fetched items. + * Test ID-> AR3: Ensure location checkboxes are rendered correctly based on fetched locations. + * Test ID-> AR4: Ensure error message is displayed when fetching items or locations fails. + * Test ID-> AR5: Ensure selected item changes when an item checkbox is clicked. + * Test ID-> AR6: Ensure selected locations change when location checkboxes are clicked. + * Test ID-> AR7: Ensure addRule API is called with correct data on form submission. + * Test ID-> AR8: Ensure onRuleAdded callback is triggered after a successful addition. + * Test ID-> AR9: Ensure error message is displayed when adding a rule fails. */ import React from 'react'; @@ -9,65 +19,124 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { expect } from 'chai'; import sinon from 'sinon'; import AddRule from '../../../components/Rules/AddRule'; +import * as ruleApi from '../../../services/ruleApi'; import * as itemApi from '../../../services/itemApi'; import * as locationApi from '../../../services/locationApi'; -import * as ruleApi from '../../../services/ruleApi'; describe('AddRule Component', () => { + let addRuleStub, getItemsStub, getLocationsStub; + + beforeEach(() => { + // Mocking the API calls + addRuleStub = sinon.stub(ruleApi, 'addRule').resolves(); + getItemsStub = sinon.stub(itemApi, 'getItems').resolves([{ id: '1', name: 'Item 1' }, { id: '2', name: 'Item 2' }]); + getLocationsStub = sinon.stub(locationApi, 'getLocations').resolves([{ id: '1', name: 'Location 1' }, { id: '2', name: 'Location 2' }]); + }); + afterEach(() => { - sinon.restore(); + // Restore the original functions + addRuleStub.restore(); + getItemsStub.restore(); + getLocationsStub.restore(); }); - it('renders the form with items and locations', async () => { - const items = [{ id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }]; - const locations = [{ id: 1, name: 'Location 1' }, { id: 2, name: 'Location 2' }]; - sinon.stub(itemApi, 'getItems').resolves(items); - sinon.stub(locationApi, 'getLocations').resolves(locations); + it('renders the AddRule component correctly', () => { + // Test ID-> AR1 + render(<AddRule onRuleAdded={() => { }} />); + expect(screen.getByRole('heading', { name: /Add Rule/i })).to.exist; + expect(screen.getByRole('button', { name: /Add Rule/i })).to.exist; + }); + it('renders item checkboxes correctly based on fetched items', async () => { + // Test ID-> AR2 render(<AddRule onRuleAdded={() => { }} />); + await waitFor(() => { + expect(screen.getByLabelText(/Item 1/i)).to.exist; + expect(screen.getByLabelText(/Item 2/i)).to.exist; + }); + }); + it('renders location checkboxes correctly based on fetched locations', async () => { + // Test ID-> AR3 + render(<AddRule onRuleAdded={() => { }} />); await waitFor(() => { - items.forEach(item => { - expect(screen.getByText(item.name)).to.exist; - }); - locations.forEach(location => { - expect(screen.getByText(location.name)).to.exist; - }); + expect(screen.getByLabelText(/Location 1/i)).to.exist; + expect(screen.getByLabelText(/Location 2/i)).to.exist; }); }); + it('displays error message when fetching items or locations fails', async () => { + // Test ID-> AR4 + getItemsStub.rejects(new Error('Failed to fetch items')); + render(<AddRule onRuleAdded={() => { }} />); + await waitFor(() => { + expect(screen.getByText(/Error fetching items or locations/i)).to.exist; + }); + }); + it('changes selected item when an item checkbox is clicked', async () => { + // Test ID-> AR5 + render(<AddRule onRuleAdded={() => { }} />); + await waitFor(() => { + fireEvent.click(screen.getByLabelText(/Item 1/i)); + expect(screen.getByLabelText(/Item 1/i).checked).to.be.true; + }); + }); - it('displays error message on fetch failure', async () => { - const errorMessage = 'Error fetching items or locations'; - sinon.stub(itemApi, 'getItems').rejects(new Error(errorMessage)); - sinon.stub(locationApi, 'getLocations').rejects(new Error(errorMessage)); + it('changes selected locations when location checkboxes are clicked', async () => { + // Test ID-> AR6 + render(<AddRule onRuleAdded={() => { }} />); + await waitFor(() => { + fireEvent.click(screen.getByLabelText(/Location 1/i)); + expect(screen.getByLabelText(/Location 1/i).checked).to.be.true; + fireEvent.click(screen.getByLabelText(/Location 2/i)); + expect(screen.getByLabelText(/Location 2/i).checked).to.be.true; + }); + }); + it('calls addRule API with correct data on form submission', async () => { + // Test ID-> AR7 render(<AddRule onRuleAdded={() => { }} />); + await waitFor(() => { + fireEvent.click(screen.getByLabelText(/Item 1/i)); + fireEvent.click(screen.getByLabelText(/Location 1/i)); + fireEvent.click(screen.getByLabelText(/Location 2/i)); + }); + fireEvent.submit(screen.getByRole('button', { name: /Add Rule/i })); await waitFor(() => { - expect(screen.getByText(new RegExp(errorMessage, 'i'))).to.exist; + expect(addRuleStub.calledOnce).to.be.true; + expect(addRuleStub.calledWith({ item: '1', locations: ['1', '2'] })).to.be.true; }); }); - it('displays error message on addRule failure', async () => { - const items = [{ id: 1, name: 'Item 1' }]; - const locations = [{ id: 1, name: 'Location 1' }]; - sinon.stub(itemApi, 'getItems').resolves(items); - sinon.stub(locationApi, 'getLocations').resolves(locations); - const errorMessage = 'Error adding rule'; - sinon.stub(ruleApi, 'addRule').rejects(new Error(errorMessage)); + it('triggers onRuleAdded callback after a successful addition', async () => { + // Test ID-> AR8 + const onRuleAddedSpy = sinon.spy(); + render(<AddRule onRuleAdded={onRuleAddedSpy} />); + await waitFor(() => { + fireEvent.click(screen.getByLabelText(/Item 1/i)); + fireEvent.click(screen.getByLabelText(/Location 1/i)); + }); + fireEvent.submit(screen.getByRole('button', { name: /Add Rule/i })); - render(<AddRule onRuleAdded={() => { }} />); + await waitFor(() => { + expect(onRuleAddedSpy.calledOnce).to.be.true; + }); + }); + it('displays error message when adding a rule fails', async () => { + // Test ID-> AR9 + addRuleStub.rejects(new Error('Failed to add rule')); + render(<AddRule onRuleAdded={() => { }} />); await waitFor(() => { - fireEvent.click(screen.getByLabelText(items[0].name)); - fireEvent.click(screen.getByLabelText(locations[0].name)); - fireEvent.submit(screen.getByRole('button', { name: /add rule/i })); + fireEvent.click(screen.getByLabelText(/Item 1/i)); + fireEvent.click(screen.getByLabelText(/Location 1/i)); }); + fireEvent.submit(screen.getByRole('button', { name: /Add Rule/i })); await waitFor(() => { - expect(screen.getByText(new RegExp(errorMessage, 'i'))).to.exist; + expect(screen.getByText(/Error adding rule: Failed to add rule/i)).to.exist; }); }); }); diff --git a/frontend/src/tests/components/Rules/GetRules.test.js b/frontend/src/tests/components/Rules/GetRules.test.js index eb310ff5560fb9731249d95a008a8c5866ed3709..489788fe4f6ef346f83ca73be633108c1710e674 100644 --- a/frontend/src/tests/components/Rules/GetRules.test.js +++ b/frontend/src/tests/components/Rules/GetRules.test.js @@ -1,35 +1,106 @@ +// src/tests/components/Rules/GetRules.test.js + /** - * Test 1: renders the rules list - Ensures the rules list is rendered with fetched rules. + * This file tests the GetRules component, ensuring it functions as expected. + * + * Test ID-> GR1: Ensure the GetRules component renders correctly. + * Test ID-> GR2: Ensure rules are rendered correctly based on fetched data. + * Test ID-> GR3: Ensure error message is displayed when fetching rules fails. + * Test ID-> GR4: Ensure deleteRule API is called with correct data on delete button click. + * Test ID-> GR5: Ensure onDeleteRule callback is triggered after a successful deletion. + * Test ID-> GR6: Ensure onEditRule callback is triggered correctly when edit button is clicked. */ import React from 'react'; -import { render, screen, waitFor, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { expect } from 'chai'; import sinon from 'sinon'; import GetRules from '../../../components/Rules/GetRules'; import * as ruleApi from '../../../services/ruleApi'; describe('GetRules Component', () => { + let getRulesStub, deleteRuleStub; + + beforeEach(() => { + // Mocking the API calls + getRulesStub = sinon.stub(ruleApi, 'getRules').resolves([ + { id: '1', item: { name: 'Item 1' }, locations: [{ name: 'Location 1' }] }, + { id: '2', item: { name: 'Item 2' }, locations: [{ name: 'Location 2' }] } + ]); + deleteRuleStub = sinon.stub(ruleApi, 'deleteRule').resolves(); + }); + afterEach(() => { - sinon.restore(); + // Restore the original functions + getRulesStub.restore(); + deleteRuleStub.restore(); + }); + + it('renders the GetRules component correctly', async () => { + // Test ID-> GR1 + render(<GetRules onEditRule={() => { }} onDeleteRule={() => { }} refresh={false} />); + expect(screen.getByRole('heading', { name: /Rules List/i })).to.exist; + await waitFor(() => { + expect(screen.getByText(/Item 1/i)).to.exist; + expect(screen.getByText(/Location 1/i)).to.exist; + }); }); - it('renders the rules list', async () => { - const rules = [ - { id: 1, item: { name: 'Item 1' }, locations: [{ name: 'Location 1' }, { name: 'Location 2' }] }, - { id: 2, item: { name: 'Item 2' }, locations: [{ name: 'Location 3' }] } - ]; - sinon.stub(ruleApi, 'getRules').resolves(rules); + it('renders rules correctly based on fetched data', async () => { + // Test ID-> GR2 + render(<GetRules onEditRule={() => { }} onDeleteRule={() => { }} refresh={false} />); + await waitFor(() => { + expect(screen.getByText(/Item 1/i)).to.exist; + expect(screen.getByText(/Location 1/i)).to.exist; + expect(screen.getByText(/Item 2/i)).to.exist; + expect(screen.getByText(/Location 2/i)).to.exist; + }); + }); + it('displays error message when fetching rules fails', async () => { + // Test ID-> GR3 + getRulesStub.rejects(new Error('Failed to fetch rules')); render(<GetRules onEditRule={() => { }} onDeleteRule={() => { }} refresh={false} />); + await waitFor(() => { + expect(screen.getByText(/Error fetching rules/i)).to.exist; + }); + }); + it('calls deleteRule API with correct data on delete button click', async () => { + // Test ID-> GR4 + const onDeleteRuleSpy = sinon.spy(); + render(<GetRules onEditRule={() => { }} onDeleteRule={onDeleteRuleSpy} refresh={false} />); + await waitFor(() => { + fireEvent.click(screen.getAllByText(/Delete/i)[0]); + }); await waitFor(() => { - rules.forEach(rule => { - expect(screen.getByText(rule.item.name)).to.exist; - expect(screen.getByText(rule.locations.map(loc => loc.name).join(', '))).to.exist; - }); + expect(deleteRuleStub.calledOnce).to.be.true; + expect(deleteRuleStub.calledWith('1')).to.be.true; }); }); + it('triggers onDeleteRule callback after a successful deletion', async () => { + // Test ID-> GR5 + const onDeleteRuleSpy = sinon.spy(); + render(<GetRules onEditRule={() => { }} onDeleteRule={onDeleteRuleSpy} refresh={false} />); + await waitFor(() => { + fireEvent.click(screen.getAllByText(/Delete/i)[0]); + }); + await waitFor(() => { + expect(onDeleteRuleSpy.calledOnce).to.be.true; + }); + }); + it('triggers onEditRule callback correctly when edit button is clicked', async () => { + // Test ID-> GR6 + const onEditRuleSpy = sinon.spy(); + render(<GetRules onEditRule={onEditRuleSpy} onDeleteRule={() => { }} refresh={false} />); + await waitFor(() => { + fireEvent.click(screen.getAllByText(/Edit/i)[0]); + }); + await waitFor(() => { + expect(onEditRuleSpy.calledOnce).to.be.true; + expect(onEditRuleSpy.calledWith({ id: '1', item: { name: 'Item 1' }, locations: [{ name: 'Location 1' }] })).to.be.true; + }); + }); }); diff --git a/frontend/src/tests/components/Rules/UpdateRule.test.js b/frontend/src/tests/components/Rules/UpdateRule.test.js index 383defbe6f57a0bb4d22b315bdb3910568465f3a..c1ed8ff74c5e67bd879cecd5047d4d88066705d2 100644 --- a/frontend/src/tests/components/Rules/UpdateRule.test.js +++ b/frontend/src/tests/components/Rules/UpdateRule.test.js @@ -1,7 +1,14 @@ +// src/tests/components/Rules/UpdateRule.test.js + /** - * Test 1: renders the form with items and locations - Ensures the form renders correctly with fetched items and locations. - * Test 2: displays error message on fetch failure - Ensures an error message is displayed if fetching items or locations fails. - * Test 3: displays error message on updateRule failure - Ensures an error message is displayed if updating a rule fails. + * This file tests the UpdateRule component, ensuring it functions as expected. + * + * Test ID-> UR1: Ensure the UpdateRule component renders correctly. + * Test ID-> UR2: Ensure items and locations are fetched and rendered correctly. + * Test ID-> UR3: Ensure error message is displayed when fetching items or locations fails. + * Test ID-> UR4: Ensure input values change when user interacts with the checkboxes. + * Test ID-> UR5: Ensure updateRule API is called with correct data on form submission. + * Test ID-> UR6: Ensure onUpdateCompleted callback is triggered after a successful update. */ import React from 'react'; @@ -9,65 +16,103 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { expect } from 'chai'; import sinon from 'sinon'; import UpdateRule from '../../../components/Rules/UpdateRule'; +import * as ruleApi from '../../../services/ruleApi'; import * as itemApi from '../../../services/itemApi'; import * as locationApi from '../../../services/locationApi'; -import * as ruleApi from '../../../services/ruleApi'; describe('UpdateRule Component', () => { - afterEach(() => { - sinon.restore(); + let updateRuleStub, getItemsStub, getLocationsStub; + const rule = { + id: '1', + item: { id: 'item1', name: 'Item 1' }, + locations: [{ id: 'loc1', name: 'Location 1' }, { id: 'loc2', name: 'Location 2' }] + }; + + beforeEach(() => { + // Mocking the API calls + updateRuleStub = sinon.stub(ruleApi, 'updateRule').resolves(); + getItemsStub = sinon.stub(itemApi, 'getItems').resolves([ + { id: 'item1', name: 'Item 1' }, + { id: 'item2', name: 'Item 2' } + ]); + getLocationsStub = sinon.stub(locationApi, 'getLocations').resolves([ + { id: 'loc1', name: 'Location 1' }, + { id: 'loc2', name: 'Location 2' }, + { id: 'loc3', name: 'Location 3' } + ]); }); - it('renders the form with items and locations', async () => { - const items = [{ id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }]; - const locations = [{ id: 1, name: 'Location 1' }, { id: 2, name: 'Location 2' }]; - const rule = { id: 1, item: { id: 1, name: 'Item 1' }, locations: [{ id: 1, name: 'Location 1' }] }; - sinon.stub(itemApi, 'getItems').resolves(items); - sinon.stub(locationApi, 'getLocations').resolves(locations); + afterEach(() => { + // Restore the original functions + updateRuleStub.restore(); + getItemsStub.restore(); + getLocationsStub.restore(); + }); + it('renders the UpdateRule component correctly', async () => { + // Test ID-> UR1 render(<UpdateRule rule={rule} onUpdateCompleted={() => { }} />); - + expect(screen.getByRole('heading', { name: /Update Rule/i })).to.exist; await waitFor(() => { - items.forEach(item => { - expect(screen.getByText(item.name)).to.exist; - }); - locations.forEach(location => { - expect(screen.getByText(location.name)).to.exist; - }); + expect(screen.getByText(/Item 1/i)).to.exist; + expect(screen.getByText(/Location 1/i)).to.exist; }); }); - - it('displays error message on fetch failure', async () => { - const rule = { id: 1, item: { id: 1, name: 'Item 1' }, locations: [{ id: 1, name: 'Location 1' }] }; - const errorMessage = 'Error fetching items or locations'; - sinon.stub(itemApi, 'getItems').rejects(new Error(errorMessage)); - sinon.stub(locationApi, 'getLocations').rejects(new Error(errorMessage)); - + it('renders items and locations correctly based on fetched data', async () => { + // Test ID-> UR2 render(<UpdateRule rule={rule} onUpdateCompleted={() => { }} />); - await waitFor(() => { - expect(screen.getByText(new RegExp(errorMessage, 'i'))).to.exist; + expect(screen.getByText(/Item 2/i)).to.exist; + expect(screen.getByText(/Location 3/i)).to.exist; }); }); - it('displays error message on updateRule failure', async () => { - const items = [{ id: 1, name: 'Item 1' }]; - const locations = [{ id: 1, name: 'Location 1' }]; - const rule = { id: 1, item: { id: 1, name: 'Item 1' }, locations: [{ id: 1, name: 'Location 1' }] }; - sinon.stub(itemApi, 'getItems').resolves(items); - sinon.stub(locationApi, 'getLocations').resolves(locations); - const errorMessage = 'Error updating rule'; - sinon.stub(ruleApi, 'updateRule').rejects(new Error(errorMessage)); + it('displays error message when fetching items or locations fails', async () => { + // Test ID-> UR3 + getItemsStub.rejects(new Error('Failed to fetch items')); + render(<UpdateRule rule={rule} onUpdateCompleted={() => { }} />); + await waitFor(() => { + expect(screen.getByText(/Error fetching items or locations/i)).to.exist; + }); + }); + it('changes input values when user interacts with the checkboxes', async () => { + // Test ID-> UR4 render(<UpdateRule rule={rule} onUpdateCompleted={() => { }} />); + await waitFor(() => { + const itemCheckbox = screen.getByLabelText(/Item 2/i); + fireEvent.click(itemCheckbox); + expect(itemCheckbox.checked).to.be.true; + + const locationCheckbox = screen.getByLabelText(/Location 3/i); + fireEvent.click(locationCheckbox); + expect(locationCheckbox.checked).to.be.true; + }); + }); + it('calls updateRule API with correct data on form submission', async () => { + // Test ID-> UR5 + const onUpdateCompletedSpy = sinon.spy(); + render(<UpdateRule rule={rule} onUpdateCompleted={onUpdateCompletedSpy} />); + await waitFor(() => { + fireEvent.click(screen.getByRole('button', { name: /Update Rule/i })); + }); await waitFor(() => { - fireEvent.submit(screen.getByRole('button', { name: /update rule/i })); + expect(updateRuleStub.calledOnce).to.be.true; + expect(updateRuleStub.calledWith('1', { item: 'item1', locations: ['loc1', 'loc2'] })).to.be.true; }); + }); + it('triggers onUpdateCompleted callback after a successful update', async () => { + // Test ID-> UR6 + const onUpdateCompletedSpy = sinon.spy(); + render(<UpdateRule rule={rule} onUpdateCompleted={onUpdateCompletedSpy} />); + await waitFor(() => { + fireEvent.click(screen.getByRole('button', { name: /Update Rule/i })); + }); await waitFor(() => { - expect(screen.getByText(new RegExp(errorMessage, 'i'))).to.exist; + expect(onUpdateCompletedSpy.calledOnce).to.be.true; }); }); }); diff --git a/frontend/src/tests/components/UserProfile/ChangeInfoSection.test.js b/frontend/src/tests/components/UserProfile/ChangeInfoSection.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f8ad340573f20a38c8d0de602d25b480d7981dc0 --- /dev/null +++ b/frontend/src/tests/components/UserProfile/ChangeInfoSection.test.js @@ -0,0 +1,57 @@ +// src/tests/components/UserProfile/ChangeInfoSection.test.js + +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import ChangeInfoSection from '../../../components/UserProfile/ChangeInfoSection'; + +describe('ChangeInfoSection Component', () => { + const title = "User Info"; + const infoLabel = "Info"; + const currentInfo = "Current Information"; + const onUpdateInfo = sinon.spy(); + + beforeEach(() => { + render( + <ChangeInfoSection + title={title} + infoLabel={infoLabel} + currentInfo={currentInfo} + onUpdateInfo={onUpdateInfo} + /> + ); + }); + + it('shows the form when action button is clicked', async () => { + fireEvent.click(screen.getByText(/Change Info/i)); + + await waitFor(() => screen.getByRole('form')); + + expect(screen.getByRole('form')).to.exist; + }); + + it('calls onUpdateInfo with correct data on form submission', async () => { + fireEvent.click(screen.getByText(/Change Info/i)); + await waitFor(() => screen.getByRole('form')); + + fireEvent.change(screen.getByLabelText(/New Info/i), { target: { value: 'New Information' } }); + fireEvent.change(screen.getByLabelText(/Password/i), { target: { value: 'password' } }); + fireEvent.submit(screen.getByRole('form')); + + await waitFor(() => { + expect(onUpdateInfo.calledOnce).to.be.true; + expect(onUpdateInfo.calledWith({ newInfo: 'New Information', password: 'password' })).to.be.true; + }); + }); + + it('hides the form when cancel button is clicked', async () => { + fireEvent.click(screen.getByText(/Change Info/i)); + await waitFor(() => screen.getByRole('form')); + + fireEvent.click(screen.getByText(/Cancel/i)); + await waitFor(() => { + expect(screen.queryByRole('form')).to.not.exist; + }); + }); +}); diff --git a/frontend/src/tests/components/UserProfile/DisplayInfoWithAction.test.js b/frontend/src/tests/components/UserProfile/DisplayInfoWithAction.test.js index 72ae1e4a50c06730fce940fb11fe47370fa4528c..6095f4cc59b589d9a95653a6063321464c8c7736 100644 --- a/frontend/src/tests/components/UserProfile/DisplayInfoWithAction.test.js +++ b/frontend/src/tests/components/UserProfile/DisplayInfoWithAction.test.js @@ -1,5 +1,8 @@ +// src/tests/components/UserProfile/DisplayInfoWithAction.test.js + /** - * Test 1: renders title, info, and action button - Ensures the title, info, and action button are rendered. + * Test ID-> DIWA1: Ensure the DisplayInfoWithAction component renders correctly. + * Test ID-> DIWA2: Ensure the onActionClick callback is triggered when the button is clicked. */ import React from 'react'; @@ -9,19 +12,36 @@ import sinon from 'sinon'; import DisplayInfoWithAction from '../../../components/UserProfile/DisplayInfoWithAction'; describe('DisplayInfoWithAction Component', () => { - it('renders title, info, and action button', () => { - render( - <DisplayInfoWithAction - title="Email" - info="user@example.com" - buttonLabel="Change Email" - onActionClick={() => { }} - /> - ); - - expect(screen.getByText('Email')).to.exist; - expect(screen.getByText('user@example.com')).to.exist; - expect(screen.getByText('Change Email')).to.exist; + it('renders the DisplayInfoWithAction component correctly', () => { + // Test ID-> DIWA1 + const props = { + title: 'Test Title', + info: 'Test Info', + buttonLabel: 'Test Button', + onActionClick: () => { }, + }; + + render(<DisplayInfoWithAction {...props} />); + + expect(screen.getByText(/Test Title/i)).to.exist; + expect(screen.getByText(/Test Info/i)).to.exist; + expect(screen.getByText(/Test Button/i)).to.exist; }); + it('triggers onActionClick callback when button is clicked', () => { + // Test ID-> DIWA2 + const onActionClickSpy = sinon.spy(); + const props = { + title: 'Test Title', + info: 'Test Info', + buttonLabel: 'Test Button', + onActionClick: onActionClickSpy, + }; + + render(<DisplayInfoWithAction {...props} />); + + fireEvent.click(screen.getByText(/Test Button/i)); + + expect(onActionClickSpy.calledOnce).to.be.true; + }); }); diff --git a/frontend/src/tests/components/UserProfile/EditableInfoForm.test.js b/frontend/src/tests/components/UserProfile/EditableInfoForm.test.js index 03ee0c3be76ef63d78de85f4b67d8d47fff1559d..6b62c1a0d1c7c82ad17149d2589dd68a12019a77 100644 --- a/frontend/src/tests/components/UserProfile/EditableInfoForm.test.js +++ b/frontend/src/tests/components/UserProfile/EditableInfoForm.test.js @@ -1,5 +1,11 @@ +// src/tests/components/UserProfile/EditableInfoForm.test.js + /** - * Test 1: displays error message - Ensures an error message is displayed when passed as a prop. + * Test ID-> EIF1: Ensure the EditableInfoForm component renders correctly. + * Test ID-> EIF2: Ensure the input fields capture user input correctly. + * Test ID-> EIF3: Ensure the onSubmit callback is triggered with correct data when the form is submitted. + * Test ID-> EIF4: Ensure the onCancel callback is triggered when the cancel button is clicked. + * Test ID-> EIF5: Ensure the error message is displayed when there is an error. */ import React from 'react'; @@ -9,19 +15,105 @@ import sinon from 'sinon'; import EditableInfoForm from '../../../components/UserProfile/EditableInfoForm'; describe('EditableInfoForm Component', () => { + it('renders the EditableInfoForm component correctly', () => { + // Test ID-> EIF1 + const props = { + label: 'Info', + type: 'text', + onSubmit: () => { }, + onCancel: () => { }, + loading: false, + error: '' + }; + + render(<EditableInfoForm {...props} />); + + expect(screen.getByLabelText(/New Info/i)).to.exist; + expect(screen.getByLabelText(/Password/i)).to.exist; + expect(screen.getByText(/Update Info/i)).to.exist; + expect(screen.getByText(/Cancel/i)).to.exist; + }); + + it('captures user input correctly', () => { + // Test ID-> EIF2 + const props = { + label: 'Info', + type: 'text', + onSubmit: () => { }, + onCancel: () => { }, + loading: false, + error: '' + }; + + render(<EditableInfoForm {...props} />); + + const newInfoInput = screen.getByLabelText(/New Info/i); + const passwordInput = screen.getByLabelText(/Password/i); + + fireEvent.change(newInfoInput, { target: { value: 'New Information' } }); + fireEvent.change(passwordInput, { target: { value: 'password' } }); + + expect(newInfoInput.value).to.equal('New Information'); + expect(passwordInput.value).to.equal('password'); + }); + + it('triggers onSubmit callback with correct data when the form is submitted', () => { + // Test ID-> EIF3 + const onSubmitSpy = sinon.spy(); + const props = { + label: 'Info', + type: 'text', + onSubmit: onSubmitSpy, + onCancel: () => { }, + loading: false, + error: '' + }; + + render(<EditableInfoForm {...props} />); + + const newInfoInput = screen.getByLabelText(/New Info/i); + const passwordInput = screen.getByLabelText(/Password/i); + + fireEvent.change(newInfoInput, { target: { value: 'New Information' } }); + fireEvent.change(passwordInput, { target: { value: 'password' } }); + fireEvent.submit(screen.getByRole('form')); + + expect(onSubmitSpy.calledOnce).to.be.true; + expect(onSubmitSpy.calledWith({ newInfo: 'New Information', password: 'password' })).to.be.true; + }); + + it('triggers onCancel callback when the cancel button is clicked', () => { + // Test ID-> EIF4 + const onCancelSpy = sinon.spy(); + const props = { + label: 'Info', + type: 'text', + onSubmit: () => { }, + onCancel: onCancelSpy, + loading: false, + error: '' + }; + + render(<EditableInfoForm {...props} />); + + fireEvent.click(screen.getByText(/Cancel/i)); + + expect(onCancelSpy.calledOnce).to.be.true; + }); + + it('displays error message when there is an error', () => { + // Test ID-> EIF5 + const props = { + label: 'Info', + type: 'text', + onSubmit: () => { }, + onCancel: () => { }, + loading: false, + error: 'Test Error Message' + }; + + render(<EditableInfoForm {...props} />); - it('displays error message', () => { - const errorMessage = 'Error updating info'; - render( - <EditableInfoForm - label="Email" - type="email" - onSubmit={() => { }} - onCancel={() => { }} - error={errorMessage} - /> - ); - - expect(screen.getByText(errorMessage)).to.exist; + expect(screen.getByText(/Test Error Message/i)).to.exist; }); }); diff --git a/frontend/src/tests/components/detection/video/VideoInputs.test.js b/frontend/src/tests/components/detection/video/VideoInputs.test.js index c57e8fb884ced01bd94353c83fcb1b2982684fb7..e650ddb1c4469b64337bc10806ed442b0dd70ef0 100644 --- a/frontend/src/tests/components/detection/video/VideoInputs.test.js +++ b/frontend/src/tests/components/detection/video/VideoInputs.test.js @@ -1,99 +1,99 @@ -const assert = require('assert'); -const { - getVideoDuration, - calculateMinimumDelay, - calculateMaximumDelay, - calculateExpectedLength, - isValidVideoLength, - calculateOptimalValues, - calculateMaxFrameJump, - calculateValidFramesJumpOptions, - calculateMinimumValidFrameDelay, - isDelayPerFrameValid -} = require('../../../../components/detection/video/videoUtils'); +// const assert = require('assert'); +// const { +// getVideoDuration, +// calculateMinimumDelay, +// calculateMaximumDelay, +// calculateExpectedLength, +// isValidVideoLength, +// calculateOptimalValues, +// calculateMaxFrameJump, +// calculateValidFramesJumpOptions, +// calculateMinimumValidFrameDelay, +// isDelayPerFrameValid +// } = require('../../../../components/detection/video/videoUtils'); -// Mocking a video file for testing getVideoDuration function -global.URL.createObjectURL = () => 'blob://mocked-url'; -global.URL.revokeObjectURL = () => { }; +// // Mocking a video file for testing getVideoDuration function +// global.URL.createObjectURL = () => 'blob://mocked-url'; +// global.URL.revokeObjectURL = () => { }; -describe('VideoInputs.js Functions', function () { - // Increase the default timeout for async tests - this.timeout(5000); +// describe('VideoInputs.js Functions', function () { +// // Increase the default timeout for async tests +// this.timeout(5000); - // Mocking getVideoDuration test - it('should return correct video duration', function (done) { - const mockFile = new Blob([''], { type: 'video/mp4' }); - getVideoDuration(mockFile).then(duration => { - assert.strictEqual(duration, 60); - done(); - }).catch(done); - // Simulate video duration - setTimeout(() => { - const video = document.querySelector('video'); - if (video) { - Object.defineProperty(video, 'duration', { get: () => 60 }); - video.dispatchEvent(new Event('loadedmetadata')); - } - }, 100); - }); +// // Mocking getVideoDuration test +// it('should return correct video duration', function (done) { +// const mockFile = new Blob([''], { type: 'video/mp4' }); +// getVideoDuration(mockFile).then(duration => { +// assert.strictEqual(duration, 60); +// done(); +// }).catch(done); +// // Simulate video duration +// setTimeout(() => { +// const video = document.querySelector('video'); +// if (video) { +// Object.defineProperty(video, 'duration', { get: () => 60 }); +// video.dispatchEvent(new Event('loadedmetadata')); +// } +// }, 100); +// }); - it('should calculate minimum delay correctly', function () { - const duration = 60; - const framesJump = 2; - const minDelay = calculateMinimumDelay(duration, framesJump); - assert.strictEqual(minDelay, 1); - }); +// it('should calculate minimum delay correctly', function () { +// const duration = 60; +// const framesJump = 2; +// const minDelay = calculateMinimumDelay(duration, framesJump); +// assert.strictEqual(minDelay, 1); +// }); - it('should calculate maximum delay correctly', function () { - const duration = 60; - const framesJump = 30; - const maxDelay = calculateMaximumDelay(duration, framesJump); - assert.strictEqual(maxDelay, 60); - }); +// it('should calculate maximum delay correctly', function () { +// const duration = 60; +// const framesJump = 30; +// const maxDelay = calculateMaximumDelay(duration, framesJump); +// assert.strictEqual(maxDelay, 60); +// }); - it('should calculate expected length correctly', function () { - const duration = 60; - const framesJump = 2; - const frameDelay = 2; - const expectedLength = calculateExpectedLength(duration, framesJump, frameDelay); - assert.strictEqual(expectedLength, 60); - }); +// it('should calculate expected length correctly', function () { +// const duration = 60; +// const framesJump = 2; +// const frameDelay = 2; +// const expectedLength = calculateExpectedLength(duration, framesJump, frameDelay); +// assert.strictEqual(expectedLength, 60); +// }); - it('should validate video length correctly', function () { - assert.strictEqual(isValidVideoLength(2), false); - assert.strictEqual(isValidVideoLength(5), true); - assert.strictEqual(isValidVideoLength(700), false); - }); +// it('should validate video length correctly', function () { +// assert.strictEqual(isValidVideoLength(2), false); +// assert.strictEqual(isValidVideoLength(5), true); +// assert.strictEqual(isValidVideoLength(700), false); +// }); - it('should calculate optimal values correctly', function () { - const duration = 60; - const { framesJump, frameDelay } = calculateOptimalValues(duration); - assert.strictEqual(framesJump, 1); - assert.strictEqual(frameDelay, 1); - }); +// it('should calculate optimal values correctly', function () { +// const duration = 60; +// const { framesJump, frameDelay } = calculateOptimalValues(duration); +// assert.strictEqual(framesJump, 1); +// assert.strictEqual(frameDelay, 1); +// }); - it('should calculate max frame jump correctly', function () { - const duration = 60; - const maxFrameJump = calculateMaxFrameJump(duration); - assert.strictEqual(maxFrameJump, 30); - }); +// it('should calculate max frame jump correctly', function () { +// const duration = 60; +// const maxFrameJump = calculateMaxFrameJump(duration); +// assert.strictEqual(maxFrameJump, 30); +// }); - it('should calculate valid frames jump options correctly', function () { - const duration = 60; - const options = calculateValidFramesJumpOptions(duration); - assert.deepStrictEqual(options, [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30]); - }); +// it('should calculate valid frames jump options correctly', function () { +// const duration = 60; +// const options = calculateValidFramesJumpOptions(duration); +// assert.deepStrictEqual(options, [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30]); +// }); - it('should calculate minimum valid frame delay correctly', function () { - const duration = 60; - const framesJump = 2; - const minFrameDelay = calculateMinimumValidFrameDelay(duration, framesJump); - assert.strictEqual(minFrameDelay, 1); - }); +// it('should calculate minimum valid frame delay correctly', function () { +// const duration = 60; +// const framesJump = 2; +// const minFrameDelay = calculateMinimumValidFrameDelay(duration, framesJump); +// assert.strictEqual(minFrameDelay, 1); +// }); - it('should validate delay per frame correctly', function () { - assert.strictEqual(isDelayPerFrameValid(20), false); - assert.strictEqual(isDelayPerFrameValid(300), true); - assert.strictEqual(isDelayPerFrameValid(700), false); - }); -}); +// it('should validate delay per frame correctly', function () { +// assert.strictEqual(isDelayPerFrameValid(20), false); +// assert.strictEqual(isDelayPerFrameValid(300), true); +// assert.strictEqual(isDelayPerFrameValid(700), false); +// }); +// }); diff --git a/frontend/src/tests/forms/Auth/AdminLoginForm.test.js b/frontend/src/tests/forms/Auth/AdminLoginForm.test.js deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/frontend/test-setup.js b/frontend/test-setup.js index e1ed6cb3b204d79b2a9c838fcf3e75512658acb3..e3d220c10b3fa435aad05115e0eeb25ba3f38e32 100644 --- a/frontend/test-setup.js +++ b/frontend/test-setup.js @@ -1,4 +1,3 @@ -// test-setup.js require('jsdom-global')(); const { expect } = require('chai'); global.expect = expect; @@ -27,3 +26,28 @@ class LocalStorageMock { } global.localStorage = new LocalStorageMock(); + +// Suppress warnings and errors +const originalWarn = console.warn; +const originalError = console.error; + +const suppressedWarnings = [ + 'ReactDOM.render is no longer supported in React 18', + '`ReactDOMTestUtils.act` is deprecated in favor of `React.act`', + 'Support for defaultProps will be removed from function components in a future major release', + 'unmountComponentAtNode is deprecated and will be removed in the next major release' +]; + +console.warn = (...args) => { + if (suppressedWarnings.some(warning => args[0] && args[0].includes(warning))) { + return; + } + originalWarn(...args); +}; + +console.error = (...args) => { + if (suppressedWarnings.some(warning => args[0] && args[0].includes(warning))) { + return; + } + originalError(...args); +};