Skip to content

Commit

Permalink
Merge pull request #70 from Hello-Kitchen/65-refactorisation-fooddeta…
Browse files Browse the repository at this point in the history
…iljs-function-logic

add: refactorisation of the detail logic, handled now in sub-functions
  • Loading branch information
alecorvec authored Dec 4, 2024
2 parents e8f4ea0 + b777806 commit 5c50b61
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 159 deletions.
136 changes: 52 additions & 84 deletions src/Components/FoodElem/FoodDetail/FoodDetail.js
Original file line number Diff line number Diff line change
@@ -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) => (
<div
key={elem.name}
className={`${elem.color} h-20 col-span-1 border border-white ${elem.selected ? "shadow-button" : ""}`}
className={`${elem.color} h-20 col-span-1 border border-white ${elem.selected ? 'shadow-button' : ''}`}
>
<button className="h-full w-full" onClick={() => handleClick(elem.name)}>
<h1 className="text-2xl text-white float-left ml-4">
{elem.name}
</h1>
<h1 className="text-2xl text-white float-left ml-4">{elem.name}</h1>
</button>
</div>
);
));

return (
<div className="w-full grid grid-flow-row">
<div className='h-20 flex content-center'>
<h1 className="text-3xl font-bold text-black self-center ml-8">{name}</h1>
<div className="h-20 flex content-center">
<h1 className="text-3xl font-bold text-black self-center ml-8">
{name}
</h1>
</div>
<div className="w-full grid grid-cols-4 mb-2">
{choice}
</div>
</div>
)
);
}

FoodDetail.propTypes = {
Expand All @@ -122,5 +91,4 @@ FoodDetail.propTypes = {
setOrderDetails: PropTypes.func.isRequired
}


export default FoodDetail;
166 changes: 91 additions & 75 deletions src/__tests__/Components/FoodElem/FoodDetail.test.js
Original file line number Diff line number Diff line change
@@ -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(
<FoodDetail
name={name}
data={data}
multiple={false}
orderDetails={orderDetails}
setOrderDetails={mockSetOrderDetails}
/>
);

expect(screen.getByText(name)).toBeInTheDocument();
data.forEach(item => {
expect(screen.getByText(item)).toBeInTheDocument();
test('renders component', () => {
const { getByText } = render(<FoodDetail {...defaultProps} />);

expect(getByText('Accompagnement')).toBeInTheDocument();
defaultProps.data.forEach((item) => {
expect(getByText(item)).toBeInTheDocument();
});
});

test('updates an option when multiple = false', () => {
render(
<FoodDetail
name={name}
data={data}
multiple={false}
orderDetails={orderDetails}
setOrderDetails={mockSetOrderDetails}
/>
);
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(<FoodDetail {...customProps} />);

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(<FoodDetail {...singleChoiceProps} />);

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(
<FoodDetail
name={name}
data={data}
multiple={true}
orderDetails={orderDetails}
setOrderDetails={mockSetOrderDetails}
/>
);

const firstButton = screen.getByText("Frite").closest('div');
fireEvent.click(firstButton.querySelector('button'));
test('handles multiple selection', () => {
const { getByText } = render(<FoodDetail {...defaultProps} />);

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(
<FoodDetail
name={name}
data={data}
multiple={false}
orderDetails={orderDetailsWithSelection}
setOrderDetails={mockSetOrderDetails}
/>
);

const selectedButton = screen.getByText("Frite").closest('div');
expect(selectedButton).toHaveClass('bg-kitchen-food-detail-selected');
fireEvent.click(selectedButton.querySelector('button'));
const { getByText } = render(<FoodDetail {...initialProps} />);

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: [],
});
});
});

test('updates color', () => {
const { getByText } = render(<FoodDetail {...defaultProps} />);

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');
});
});

0 comments on commit 5c50b61

Please sign in to comment.