From b77780616969048bb259e5e35eac34a11d855353 Mon Sep 17 00:00:00 2001 From: Adrien le Corvec Date: Thu, 28 Nov 2024 13:50:14 +0000 Subject: [PATCH] add: refactorisation of the detail logic, handled now in sub-functions --- .../FoodElem/FoodDetail/FoodDetail.js | 136 ++++++-------- .../Components/FoodElem/FoodDetail.test.js | 166 ++++++++++-------- 2 files changed, 143 insertions(+), 159 deletions(-) diff --git a/src/Components/FoodElem/FoodDetail/FoodDetail.js b/src/Components/FoodElem/FoodDetail/FoodDetail.js index 7b097ff..fb4b2b8 100644 --- a/src/Components/FoodElem/FoodDetail/FoodDetail.js +++ b/src/Components/FoodElem/FoodDetail/FoodDetail.js @@ -1,117 +1,86 @@ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import { useState } from "react"; - /** * Component : Component handling a specific detail of a food, user inputs, and displays the choices * * @component FoodDetail * @param {String} name Name of the food detail * @param {[Object]} data Object Array of the current detail information of a food - * @param {Object} multiple Boolean, used to know if multiple detail options can be chosen + * @param {Boolean} multiple Boolean, used to know if multiple detail options can be chosen * @param {Object} orderDetails Object used to persist detail and ingredient choices of a current food * @param {function} setOrderDetails state function to update the orderDetails object */ -function FoodDetail({name, data, multiple, orderDetails, setOrderDetails}) { +function FoodDetail({ name, data, multiple, orderDetails, setOrderDetails }) { + // Initialize data based on the previous state of the details buttons + const initializeData = () => { + const existingDetail = orderDetails.details.find(detail => detail.name === name); + const selectedItems = existingDetail ? existingDetail.list : []; + return data.map(item => ({ + name: item, + selected: selectedItems.includes(item), + color: selectedItems.includes(item) + ? 'bg-kitchen-food-detail-selected' + : 'bg-kitchen-food-detail', + })); + }; - let detailObj = {name: name, list: []}; - //map function to parse the data of a detail, to add a button color and a boolean - //uses current order details to update data, preventing resets between page change - const [fullData, setFullData] = useState(data.map((elem => { - let color = 'bg-kitchen-food-detail'; - let selected = false; - let copy = orderDetails.details; - let check = copy.filter(e => e.name === name); - if (check.length !== 0 && check[0].list.find(e => e === elem)) { - color = 'bg-kitchen-food-detail-selected'; - selected = true; - } - return { - name: elem, - color: color, - selected: selected + const [fullData, setFullData] = useState(initializeData); + + // Update order details object with the new details + const updateOrderDetails = (updatedItems) => { + const updatedDetails = orderDetails.details.filter(detail => detail.name !== name); + if (updatedItems.length > 0) { + updatedDetails.push({name, list: updatedItems}); } - }))); + setOrderDetails({...orderDetails, details: updatedDetails}); + }; - //function called when a choice of a detail is clicked : - //updates the button based on user inputs - const handleClick = (name) => { - setFullData(null) - setFullData(fullData.map((data => { - let color = data.color; - let selected = data.selected; - if (multiple === false) { - color = 'bg-kitchen-food-detail'; - selected = false; + // Handle click on a detail choice + const handleClick = (clickedName) => { + const updatedData = fullData.map(item => { + // if multiple is false, will deselects other options + if (!multiple && item.name !== clickedName) { + return {...item, selected: false, color: 'bg-kitchen-food-detail'}; } - if (data.name === name) { - selected = data.selected ? false : true; - let copy = orderDetails.details; - //adds the user choices to an object, else remove it - if (selected) { - color = 'bg-kitchen-food-detail-selected'; - copy = copy.filter(e => e.name === detailObj.name); - let temp = detailObj.list; - if (copy.length !== 0) { - temp = copy[0].list; - } - //If the detail is a single choice, will erase previous one, otherwise add it to a list - if (multiple === false) { - temp = [data.name]; - } else { - temp.push(data.name); - } - detailObj = {name: detailObj.name, list: temp}; - } else { - copy = copy.filter(e => e.name === detailObj.name); - let temp = detailObj.list; - if (copy.length !== 0) { - temp = copy[0].list; - temp = temp.filter(e => e !== data.name); - detailObj = {name: detailObj.name, list: temp}; - } - color = 'bg-kitchen-food-detail'; - } - //adds the new detail to the current order - let arr = orderDetails.details; - arr = arr.filter(e => e.name !== detailObj.name); - let sups = orderDetails.sups; - arr.push(detailObj); - setOrderDetails({details: arr, sups: sups}); + if (item.name === clickedName) { + const isSelected = !item.selected; + return { + ...item, + selected: isSelected, + color: isSelected ? 'bg-kitchen-food-detail-selected' : 'bg-kitchen-food-detail', + }; } - return { - name: data.name, - color: color, - selected: selected - } - }))) - } + return item; + }); + const selectedItems = updatedData.filter(item => item.selected).map(item => item.name); + setFullData(updatedData); + updateOrderDetails(selectedItems); + }; - //map function to display all choices of a detail - const choice = fullData.map((elem) => + const choice = fullData.map((elem) => (
- ); + )); return (
-
-

{name}

+
+

+ {name} +

{choice}
- ) + ); } FoodDetail.propTypes = { @@ -122,5 +91,4 @@ FoodDetail.propTypes = { setOrderDetails: PropTypes.func.isRequired } - export default FoodDetail; diff --git a/src/__tests__/Components/FoodElem/FoodDetail.test.js b/src/__tests__/Components/FoodElem/FoodDetail.test.js index 4ee1041..a1733d2 100644 --- a/src/__tests__/Components/FoodElem/FoodDetail.test.js +++ b/src/__tests__/Components/FoodElem/FoodDetail.test.js @@ -1,109 +1,125 @@ import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, fireEvent } from '@testing-library/react'; import FoodDetail from '../../../Components/FoodElem/FoodDetail/FoodDetail'; describe('FoodDetail Component', () => { const mockSetOrderDetails = jest.fn(); - const name = "Accompagnement"; - const data = ["Frite", "Salade", "Riz", "Pâte", "Semoule"]; - const orderDetails = {details: [], sups: []}; - beforeEach(() => { + const defaultProps = { + name: 'Accompagnement', + data: ['Frite', 'Salade', 'Riz', "Pâte"], + multiple: true, + orderDetails: { + details: [], + sups: [], + }, + setOrderDetails: mockSetOrderDetails, + }; + + afterEach(() => { jest.clearAllMocks(); }); - test('renders FoodDetail', () => { - render( - - ); - - expect(screen.getByText(name)).toBeInTheDocument(); - data.forEach(item => { - expect(screen.getByText(item)).toBeInTheDocument(); + test('renders component', () => { + const { getByText } = render(); + + expect(getByText('Accompagnement')).toBeInTheDocument(); + defaultProps.data.forEach((item) => { + expect(getByText(item)).toBeInTheDocument(); }); }); - test('updates an option when multiple = false', () => { - render( - - ); + test('initializes data with orderDetails', () => { + const customProps = { + ...defaultProps, + orderDetails: { + details: [{name: 'Accompagnement', list: ['Riz']}], + sups: [], + }, + }; - const selectedButton = screen.getByText("Frite").closest('div'); - fireEvent.click(selectedButton.querySelector('button')); + const { getByText } = render(); + const selectedButton = getByText('Riz').closest('div'); expect(selectedButton).toHaveClass('bg-kitchen-food-detail-selected'); + }); + + test('handles selection', () => { + const singleChoiceProps = {...defaultProps, multiple: false}; + const { getByText } = render(); + + const selectedButton = getByText('Riz').closest('button'); + const secondButton = getByText('Frite').closest('button'); + + fireEvent.click(selectedButton); expect(mockSetOrderDetails).toHaveBeenCalledWith({ - details: [{ name: name, list: ["Frite"] }], - sups: [] + details: [{name: 'Accompagnement', list: ['Riz']}], + sups: [], + }); + + fireEvent.click(secondButton); + expect(mockSetOrderDetails).toHaveBeenCalledWith({ + details: [{name: 'Accompagnement', list: ['Frite']}], + sups: [], }); }); - test('updates an option when mutiple = true', () => { - render( - - ); - - const firstButton = screen.getByText("Frite").closest('div'); - fireEvent.click(firstButton.querySelector('button')); + test('handles multiple selection', () => { + const { getByText } = render(); - const secondButton = screen.getByText("Semoule").closest('div'); - fireEvent.click(secondButton.querySelector('button')); + const firstButton = getByText('Riz').closest('button'); + const secondButton = getByText('Frite').closest('button'); - expect(firstButton).toHaveClass('bg-kitchen-food-detail-selected'); - expect(secondButton).toHaveClass('bg-kitchen-food-detail-selected'); + fireEvent.click(firstButton); + expect(mockSetOrderDetails).toHaveBeenCalledWith({ + details: [{name: 'Accompagnement', list: ['Riz']}], + sups: [], + }); + fireEvent.click(secondButton); expect(mockSetOrderDetails).toHaveBeenCalledWith({ - details: [{ name: name, list: expect.arrayContaining(["Frite"]) }], - sups: [] + details: [{name: 'Accompagnement', list: ['Frite', 'Riz']}], + sups: [], }); + + fireEvent.click(firstButton); expect(mockSetOrderDetails).toHaveBeenCalledWith({ - details: [{ name: name, list: expect.arrayContaining(["Semoule"]) }], - sups: [] + details: [{name: 'Accompagnement', list: ['Frite']}], + sups: [], }); }); - test('deselects an option', () => { - const orderDetailsWithSelection = { - details: [{ name: name, list: ["Frite"] }], - sups: [] + test('removes details', () => { + const initialProps = { + ...defaultProps, + orderDetails: { + details: [{name: 'Accompagnement', list: ['Riz']}], + sups: [], + }, }; - render( - - ); - - const selectedButton = screen.getByText("Frite").closest('div'); - expect(selectedButton).toHaveClass('bg-kitchen-food-detail-selected'); - fireEvent.click(selectedButton.querySelector('button')); + const { getByText } = render(); - expect(selectedButton).toHaveClass('bg-kitchen-food-detail'); + const firstButton = getByText('Riz').closest('button'); + + fireEvent.click(firstButton); expect(mockSetOrderDetails).toHaveBeenCalledWith({ - details: [{ name: name, list: [] }], - sups: [] + details: [], + sups: [], }); }); -}); \ No newline at end of file + + test('updates color', () => { + const { getByText } = render(); + + const firstButton = getByText('Riz').closest('div'); + const secondButton = getByText('Frite').closest('div'); + + expect(firstButton).toHaveClass('bg-kitchen-food-detail'); + expect(secondButton).toHaveClass('bg-kitchen-food-detail'); + + fireEvent.click(firstButton.querySelector('button')); + expect(firstButton).toHaveClass('bg-kitchen-food-detail-selected'); + expect(secondButton).toHaveClass('bg-kitchen-food-detail'); + }); +});