Skip to content

Commit

Permalink
add ingredients to recipe form
Browse files Browse the repository at this point in the history
  • Loading branch information
Kevin Kirchhoff committed Aug 2, 2020
1 parent f860ec9 commit f83256a
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 3 deletions.
46 changes: 43 additions & 3 deletions app/webpack/components/recipe-form/RecipeForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Button, Col, Form } from 'react-bootstrap';

import { createRecipe } from '../../api/v1/recipes';
import RecipeImageCropper from './RecipeImageCropper';
import RecipeIngredients from './RecipeIngredients';
import RecipeInstructions from './RecipeInstructions';
import RecipePhotoUploader from './RecipePhotoUploader';

Expand All @@ -14,8 +15,8 @@ const RecipeForm = ({ history }) => {
title: '',
description: '',
link: '',
ingredients: [],
instructions: [''],
image: ''
});

const handleInputChange = ({ target: { name: field, value } }) => {
Expand All @@ -38,18 +39,48 @@ const RecipeForm = ({ history }) => {
}));
};

const addNewIngredient = () => {
setFormData((currentData) => ({
...currentData,
ingredients: currentData.ingredients.concat({
name: '',
quantity: 1,
unit: '',
}),
}));
};

const removeIngredient = (index) => {
setFormData((currentData) => ({
...currentData,
ingredients: currentData.ingredients.filter((value, i) => index !== i),
}));
};

const updateIngredient = (index, newValue) => {
setFormData((currentData) => ({
...currentData,
ingredients: currentData.ingredients.map((value, i) => (
index === i ? newValue : value
)),
}));
};

const handleFormSubmit = (e) => {
e.preventDefault();

const data = new FormData();
for (const key in formData) {
if (key == 'instructions') {
if (['instructions', 'ingredients'].includes(key)) {
data.append(key, JSON.stringify(formData[key]));
} else {
data.append(key, formData[key]);
}
}
data.append('image', croppedImageBlob);

if (croppedImageBlob) {
data.append('image', croppedImageBlob);
}

createRecipe(data)
.then(() => {
Expand Down Expand Up @@ -92,6 +123,15 @@ const RecipeForm = ({ history }) => {
onChange={handleInputChange}>
</Form.Control>
</Form.Group>
<Form.Group as={Col} xs='12' controlId='instructions'>
<Form.Label>Ingredients</Form.Label>
<RecipeIngredients
addNewIngredient={addNewIngredient}
ingredients={formData.ingredients}
removeIngredient={removeIngredient}
updateIngredient={updateIngredient}
/>
</Form.Group>
<Form.Group as={Col} xs='12' controlId='instructions'>
<Form.Label>Instructions</Form.Label>
<RecipeInstructions
Expand Down
77 changes: 77 additions & 0 deletions app/webpack/components/recipe-form/RecipeIngredients.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import PropTypes from 'prop-types';
import React from 'react';
import { Button, Col, Form, ListGroup, ListGroupItem } from 'react-bootstrap';

const RecipeIngredients = ({ addNewIngredient, ingredients, removeIngredient, updateIngredient }) => (
<>
<ListGroup>
{ingredients.map(({ name: ingredientName, quantity, unit }, index) => (
<ListGroupItem key={index}>
<Button
onClick={() => removeIngredient(index)}
variant="link"
className="float-right"
>
<i className="fas fa-times text-secondary" aria-hidden />
</Button>
<Form.Row>
<Form.Group as={Col} lg="4" xs="12" controlId={`ingredient-quantity-input-${index}`}>
<Form.Label column="sm">Amount</Form.Label>
<Form.Control
className="ingredient-quantity-input"
defaultValue={quantity}
required
type="number"
step="any"
onChange={(e) => updateIngredient(index, {
name: ingredientName,
quantity: Number(e.target.value),
unit,
})}>
</Form.Control>
</Form.Group>
<Form.Group as={Col} lg="4" xs="12" controlId={`ingredient-unit-input-${index}`}>
<Form.Label column="sm">Unit</Form.Label>
<Form.Control
className="ingredient-unit-input"
defaultValue={unit}
onChange={(e) => updateIngredient(index, {
name: ingredientName,
quantity,
unit: e.target.value,
})}>
</Form.Control>
</Form.Group>
<Form.Group as={Col} lg="4" xs="12" controlId={`ingredient-name-input-${index}`}>
<Form.Label column="sm">Ingredient</Form.Label>
<Form.Control
className="ingredient-name-input"
required
defaultValue={ingredientName}
onChange={(e) => updateIngredient(index, {
name: e.target.value,
quantity,
unit,
})}>
</Form.Control>
</Form.Group>
</Form.Row>
</ListGroupItem>
))}
</ListGroup>
<i className="fas fa-plus-square add-btn" tabIndex="0" onClick={addNewIngredient}></i>
</>
);

RecipeIngredients.propTypes = {
addNewIngredient: PropTypes.func.isRequired,
ingredients: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string.isRequired,
quantity: PropTypes.number.isRequired,
unit: PropTypes.string.isRequired,
})).isRequired,
removeIngredient: PropTypes.func.isRequired,
updateIngredient: PropTypes.func.isRequired,
};

export default RecipeIngredients;
105 changes: 105 additions & 0 deletions app/webpack/tests/components/recipe-form/RecipeIngredients.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React from 'react';
import { shallow } from 'enzyme';
import RecipeIngredients from '../../../components/recipe-form/RecipeIngredients';

const ingredients = [{
name: 'broccoli',
quantity: 1,
unit: 'head',
},
{
name: 'butter',
quantity: 1.5,
unit: 'tbsp',
}];

describe('RecipeIngredients', () => {
it('should render inputs for each ingredient', () => {
const wrapper = shallow(<RecipeIngredients
addNewIngredient={() => {}}
ingredients={ingredients}
removeIngredient={() => {}}
updateIngredient={() => {}}
/>);

['name', 'quantity', 'unit'].forEach((field) => {
const inputs = wrapper.find(`.ingredient-${field}-input`);
expect(inputs).toHaveLength(ingredients.length);
expect(inputs.at(0).props()).toHaveProperty('defaultValue', ingredients[0][field]);
expect(inputs.at(1).props()).toHaveProperty('defaultValue', ingredients[1][field]);
});
});

it('should call the updateIngredient when the quantity changes', () => {
const updateIngredient = jest.fn();
const wrapper = shallow(<RecipeIngredients
addNewIngredient={() => {}}
ingredients={ingredients}
removeIngredient={() => {}}
updateIngredient={updateIngredient}
/>);

wrapper.find('.ingredient-quantity-input').at(1).simulate('change', {
target: { value: '2.5' }
});

expect(updateIngredient).toHaveBeenCalledWith(1, {
name: 'butter',
quantity: 2.5,
unit: 'tbsp',
});
});

it('should call the updateIngredient when the unit changes', () => {
const updateIngredient = jest.fn();
const wrapper = shallow(<RecipeIngredients
addNewIngredient={() => {}}
ingredients={ingredients}
removeIngredient={() => {}}
updateIngredient={updateIngredient}
/>);

wrapper.find('.ingredient-unit-input').at(1).simulate('change', {
target: { value: 'tsp' }
});

expect(updateIngredient).toHaveBeenCalledWith(1, {
name: 'butter',
quantity: 1.5,
unit: 'tsp',
});
});
it('should call the updateIngredient when the unit changes', () => {
const updateIngredient = jest.fn();
const wrapper = shallow(<RecipeIngredients
addNewIngredient={() => {}}
ingredients={ingredients}
removeIngredient={() => {}}
updateIngredient={updateIngredient}
/>);

wrapper.find('.ingredient-name-input').at(1).simulate('change', {
target: { value: 'olive oil' }
});

expect(updateIngredient).toHaveBeenCalledWith(1, {
name: 'olive oil',
quantity: 1.5,
unit: 'tbsp',
});
});

it('should call addNewIngredient when the add button is clicked', () => {
const addNewIngredient = jest.fn();
const wrapper = shallow(<RecipeIngredients
addNewIngredient={addNewIngredient}
ingredients={[]}
removeIngredient={() => {}}
updateIngredient={() => {}}
/>);

wrapper.find('.add-btn').simulate('click');

expect(addNewIngredient).toHaveBeenCalled();
});
});

0 comments on commit f83256a

Please sign in to comment.