From b8c58aa601c99c25769272e3e494222d86ce7e23 Mon Sep 17 00:00:00 2001 From: james starling Date: Wed, 21 Dec 2022 10:52:15 -0500 Subject: [PATCH 1/4] added to states and created two methods --- frontend/components/AppFunctional.js | 30 ++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/frontend/components/AppFunctional.js b/frontend/components/AppFunctional.js index 4c2b53a98..630323e8e 100644 --- a/frontend/components/AppFunctional.js +++ b/frontend/components/AppFunctional.js @@ -1,28 +1,38 @@ -import React from 'react' +import React, { useState } from 'react' +import axios from 'axios' +import * as yup from 'yup' +import { useSearchParams } from 'react-router-dom' + +const formSchema = yup.object().shape({ + formValue: yup.string() + .email('Error: email must be a valid email').required('Error:Email is required').notOneOf(['foo@bar.baz','foo@bar.baz failure #71']) +}) // Suggested initial states const initialMessage = '' const initialEmail = '' const initialSteps = 0 const initialIndex = 4 // the index the "B" is at +const initialX = 2 +const initialY = 2 export default function AppFunctional(props) { - // THE FOLLOWING HELPERS ARE JUST RECOMMENDATIONS. - // You can delete them and build your own logic from scratch. +const [x, setX] = useState(initialX) +const [y, setY] = useState(initialY) +const [xy, setxy] = useState(initialIndex) +const [move, setMoves] = useState(0) +const [messages, setMessages] = useState(initialMessage) +const [formValue, setFormValue] = useState('') function getXY() { - // It it not necessary to have a state to track the coordinates. - // It's enough to know what index the "B" is at, to be able to calculate them. + return (`(${x},${y})`) } function getXYMessage() { - // It it not necessary to have a state to track the "Coordinates (2, 2)" message for the user. - // You can use the `getXY` helper above to obtain the coordinates, and then `getXYMessage` - // returns the fully constructed string. + } - function reset() { - // Use this helper to reset all states to their initial values. + function reset() { setMoves(0); setxy(initialIndex); setX(initialX); setY(initialY); setMessages(initialMessage);setFormValue(''); } function getNextIndex(direction) { From 15ffa6ac906e17e9a387eab16f884b19ff1985ae Mon Sep 17 00:00:00 2001 From: james starling Date: Wed, 21 Dec 2022 11:01:39 -0500 Subject: [PATCH 2/4] getnextindex!!! --- frontend/components/AppFunctional.js | 30 ++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/frontend/components/AppFunctional.js b/frontend/components/AppFunctional.js index 630323e8e..9ddac9a28 100644 --- a/frontend/components/AppFunctional.js +++ b/frontend/components/AppFunctional.js @@ -20,7 +20,7 @@ export default function AppFunctional(props) { const [x, setX] = useState(initialX) const [y, setY] = useState(initialY) const [xy, setxy] = useState(initialIndex) -const [move, setMoves] = useState(0) +const [moves, setMoves] = useState(0) const [messages, setMessages] = useState(initialMessage) const [formValue, setFormValue] = useState('') @@ -36,9 +36,31 @@ const [formValue, setFormValue] = useState('') } function getNextIndex(direction) { - // This helper takes a direction ("left", "up", etc) and calculates what the next index - // of the "B" would be. If the move is impossible because we are at the edge of the grid, - // this helper should return the current index unchanged. + if(direction === 'left'){ + if(x -1 === 0){ + setMessages('You cannot go left') + return xy + } + setX(x-1); setxy( xy-1); setMoves(moves + 1); setMessages(initialMessage); + } + if(direction === 'down'){ + if(y + 1 === 4){ + setMessages('You cannot go down'); return xy + } + setY(y+1); setxy(xy + 3); setMoves( moves + 1); setMessages(initialMessage) + } + if(direction === ' right'){ + if(x + 1 === 4){ + setMessages('You cannot go right'); return xy; + } + setX(x + 1); setxy(xy + 1); setMoves(moves + 1); setMessages(initialMessage); + } + if(direction === 'up'){ + if(y-1 ===0){ + setMessages('You cannot go up'); return xy; + } + setY(y-1); setxy( xy - 3); setMoves(moves + 1); setMessages(initialMessage) + } } function move(evt) { From e934826657f8a1a56a63db7f0fbbdb53b1291b9e Mon Sep 17 00:00:00 2001 From: james starling Date: Wed, 21 Dec 2022 11:09:03 -0500 Subject: [PATCH 3/4] methods complete --- frontend/components/AppFunctional.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/frontend/components/AppFunctional.js b/frontend/components/AppFunctional.js index 9ddac9a28..71090928f 100644 --- a/frontend/components/AppFunctional.js +++ b/frontend/components/AppFunctional.js @@ -63,17 +63,21 @@ const [formValue, setFormValue] = useState('') } } - function move(evt) { - // This event handler can use the helper above to obtain a new index for the "B", - // and change any states accordingly. - } + function move(evt) {getNextIndex(evt)} - function onChange(evt) { - // You will need this to update the value of the input. - } + function onChange(evt) {setFormValue(evt.target.value)} + const validate = (name, value) => {yup.reach(formSchema, name).validate(value).then(()=> post()).catch(err => setMessages(err.errors[0]))} + function onSubmit(evt) { - // Use a POST request to send a payload to the server. + evt.preventDefault(); + validate('formValue',formValue) + } + + function post() { + const toSend = {"x": x, "y": y, "steps": moves, "email": formValue} + axios.post('http://localhost:9000/api/result', toSend) + .then(({data})=>{setMessages(data.message)}).finally(setFormValue('')) } return ( From e0960f537031142c3b10cc859907bd112c77ddaf Mon Sep 17 00:00:00 2001 From: james starling Date: Wed, 21 Dec 2022 11:56:54 -0500 Subject: [PATCH 4/4] everything functional --- frontend/components/App.test.js | 70 +++++++++++- frontend/components/AppClass.js | 128 ++++++++++++++++++---- frontend/components/AppFunctional.js | 156 ++++++++++++++++++--------- 3 files changed, 277 insertions(+), 77 deletions(-) diff --git a/frontend/components/App.test.js b/frontend/components/App.test.js index 96965c559..eba7fb0b6 100644 --- a/frontend/components/App.test.js +++ b/frontend/components/App.test.js @@ -1,4 +1,72 @@ +import React from 'react' +import AppFunctional from './AppFunctional' +import { render, fireEvent, screen } from '@testing-library/react' +import '@testing-library/jest-dom/extend-expect' // Write your tests here test('sanity', () => { - expect(true).toBe(false) + expect(true).toBe(true) }) + + +test('header renders', () => { + render() + + const coordinates = screen.queryByText(/coordinates/i) + const upButton = screen.queryByText('UP') + const leftButton = screen.queryByText('LEFT') + const rightButton = screen.queryByText('RIGHT') + const downButton = screen.queryByText('DOWN') + const resetButton = screen.queryByText('reset') + + expect(coordinates).toBeInTheDocument() + expect(leftButton).toBeInTheDocument() + expect(rightButton).toBeInTheDocument() + expect(upButton).toBeInTheDocument() + expect(downButton).toBeInTheDocument() + expect(resetButton).toBeInTheDocument() +}) + +test('typing in input results in text entered', () => { + render() + + const inputBox = screen.getByRole('textbox', {id:'email'}) + + expect(inputBox) + .toBeInTheDocument() + fireEvent.change(inputBox, { target: {value: 'pizzatime'}}) + expect(inputBox) + .toHaveValue('pizzatime') +}) + +test('clicking reset clears input box', () => { + render() + + const inputBox = screen.getByRole('textbox', {id:'email'}) + const resetButton = screen.getByTestId('reset') + + fireEvent.change(inputBox, { target: {value: 'pizzatime'}}) + expect(inputBox) + .toHaveValue('pizzatime') + fireEvent.click(resetButton) + expect(inputBox) + .toHaveValue('') +}) + +test('cannot go up past bounds', () => { + render() + + const upButton = screen.getByTestId('up') + + fireEvent.click(upButton) + fireEvent.click(upButton) + expect(screen.getByText("You can't go up")).toBeInTheDocument() +}) + +test('displays moves', () => { + render() + + const upButton = screen.getByTestId('up') + + fireEvent.click(upButton) + expect(screen.getByText("You moved 1 time")).toBeInTheDocument() +}) \ No newline at end of file diff --git a/frontend/components/AppClass.js b/frontend/components/AppClass.js index 5b964060a..ce73c61ab 100644 --- a/frontend/components/AppClass.js +++ b/frontend/components/AppClass.js @@ -1,10 +1,23 @@ -import React from 'react' +import React, {useState} from 'react' +import axios from 'axios' +import * as yup from 'yup' + + +const formSchema = yup.object().shape({ + formValue: yup + .string() + .email('Ouch: email must be a valid email') + .required('Ouch: email is required') + .notOneOf(['foo@bar.baz'],'foo@bar.baz failure #71') +}) // Suggested initial states const initialMessage = '' const initialEmail = '' const initialSteps = 0 const initialIndex = 4 // the index the "B" is at +const initialX = 2 +const initialY = 2 const initialState = { message: initialMessage, @@ -14,41 +27,110 @@ const initialState = { } export default class AppClass extends React.Component { + constructor(){ + super() + this.state= { + x: initialX, + y: initialY, + steps: initialSteps, + xy: initialIndex, + message: initialMessage, + formValues: '' + } + + } // THE FOLLOWING HELPERS ARE JUST RECOMMENDATIONS. // You can delete them and build your own logic from scratch. getXY = () => { + return(`(${this.state.x},${this.state.y})`) + // It it not necessary to have a state to track the coordinates. // It's enough to know what index the "B" is at, to be able to calculate them. } - getXYMessage = () => { - // It it not necessary to have a state to track the "Coordinates (2, 2)" message for the user. - // You can use the `getXY` helper above to obtain the coordinates, and then `getXYMessage` - // returns the fully constructed string. - } - reset = () => { - // Use this helper to reset all states to their initial values. + this.setState({ + x: initialX, + y: initialY, + steps: initialSteps, + message: initialMessage, + xy: initialIndex, + formValues: '' + }) } getNextIndex = (direction) => { + if(direction === 'left'){ + if(this.state.x - 1 === 0){ + return ({"x": this.state.x, "y":this.state.y}) + } + return ({"x": this.state.x - 1, "y":this.state.y,"xy":this.state.xy -1,"steps": this.state.steps + 1}) + } + if(direction === 'right'){ + if(this.state.x + 1 === 4){ + return ({"x": this.state.x, "y":this.state.y}) + } + return ({"x": this.state.x + 1, "y":this.state.y,"xy":this.state.xy + 1,"steps": this.state.steps + 1}) + } + if(direction === 'up'){ + if(this.state.y - 1 === 0){ + return ({"x": this.state.x, "y":this.state.y}) + } + return ({"x": this.state.x, "y":this.state.y - 1,"xy":this.state.xy - 3,"steps": this.state.steps + 1}) + } + if(direction === 'down'){ + if(this.state.y + 1 === 4){ + return ({"x": this.state.x, "y":this.state.y}) + } + return ({"x": this.state.x, "y":this.state.y + 1,"xy":this.state.xy + 3,"steps": this.state.steps + 1}) + } // This helper takes a direction ("left", "up", etc) and calculates what the next index // of the "B" would be. If the move is impossible because we are at the edge of the grid, // this helper should return the current index unchanged. } move = (evt) => { + let nextMove = this.getNextIndex(evt.target.id) + if (`(${nextMove.x},${nextMove.y})` === this.getXY()){ + return this.setState({message: `You can't go ${evt.target.id}`}) + } + this.setState({...this.state, + message: initialMessage, + x: nextMove.x, + y: nextMove.y, + steps: nextMove.steps, + xy: nextMove.xy}) // This event handler can use the helper above to obtain a new index for the "B", // and change any states accordingly. } onChange = (evt) => { - // You will need this to update the value of the input. + this.setState({formValues: evt.target.value}) + } + + validate = (name,value) => { + yup.reach(formSchema, name) + .validate(value) + .then(() => this.post()) + .catch(err => this.setState({message:err.errors[0]})) + } + + post = () => { + const toSend = { + "x": this.state.x, + "y": this.state.y, + "steps": this.state.steps, + "email": this.state.formValues + } + axios.post('http://localhost:9000/api/result', toSend) + .then(({data}) => {this.setState({message: data.message})}) + .finally(this.setState({formValues: ''})) } onSubmit = (evt) => { - // Use a POST request to send a payload to the server. + evt.preventDefault() + this.validate('formValue', this.state.formValues) } render() { @@ -56,33 +138,33 @@ export default class AppClass extends React.Component { return (
-

Coordinates (2, 2)

-

You moved 0 times

+

{`Coordinates ${this.getXY()}`}

+

{`You moved ${this.state.steps} ${this.state.steps === 1 ? 'time' : 'times'}`}

{/*Stateful move tracker*/}
{ [0, 1, 2, 3, 4, 5, 6, 7, 8].map(idx => ( -
- {idx === 4 ? 'B' : null} +
+ {idx === this.state.xy ? 'B' : null}
)) }
-

+

{this.state.message}

{/*display message from API call*/}
- - - - - + + + + +
-
- + this.onSubmit(e)}> + this.onChange(e)}>
) } -} +} \ No newline at end of file diff --git a/frontend/components/AppFunctional.js b/frontend/components/AppFunctional.js index 71090928f..691d1cd63 100644 --- a/frontend/components/AppFunctional.js +++ b/frontend/components/AppFunctional.js @@ -1,11 +1,13 @@ -import React, { useState } from 'react' +import React, {useState} from 'react' import axios from 'axios' import * as yup from 'yup' -import { useSearchParams } from 'react-router-dom' const formSchema = yup.object().shape({ - formValue: yup.string() - .email('Error: email must be a valid email').required('Error:Email is required').notOneOf(['foo@bar.baz','foo@bar.baz failure #71']) + formValue: yup + .string() + .email('Ouch: email must be a valid email') + .required('Ouch: email is required') + .notOneOf(['foo@bar.baz'],'foo@bar.baz failure #71') }) // Suggested initial states @@ -16,99 +18,147 @@ const initialIndex = 4 // the index the "B" is at const initialX = 2 const initialY = 2 -export default function AppFunctional(props) { -const [x, setX] = useState(initialX) -const [y, setY] = useState(initialY) -const [xy, setxy] = useState(initialIndex) -const [moves, setMoves] = useState(0) -const [messages, setMessages] = useState(initialMessage) -const [formValue, setFormValue] = useState('') - - function getXY() { - return (`(${x},${y})`) - } - function getXYMessage() { +export default function AppFunctional(props) { + // THE FOLLOWING HELPERS ARE JUST RECOMMENDATIONS. + // You can delete them and build your own logic from scratch. + const [x, setX] = useState(initialX) + const [y, setY] = useState(initialY) + const [xy, setXY] = useState(initialIndex) + const [moves, setMoves] = useState(0) + const [messages, setMessages] = useState(initialMessage) + const [formValue, setFormValue] = useState('') + + function getXY(){ + return (`(${x},${y})`) + // It it not necessary to have a state to track the coordinates. + // It's enough to know what index the "B" is at, to be able to calculate them. } - function reset() { setMoves(0); setxy(initialIndex); setX(initialX); setY(initialY); setMessages(initialMessage);setFormValue(''); + function reset() { + // Use this helper to reset all states to their initial values. + setMoves(0) + setXY(initialIndex) + setX(initialX) + setY(initialY) + setMessages(initialMessage) + setFormValue('') } function getNextIndex(direction) { if(direction === 'left'){ - if(x -1 === 0){ - setMessages('You cannot go left') + if (x - 1 === 0){ + setMessages("You can't go left") return xy } - setX(x-1); setxy( xy-1); setMoves(moves + 1); setMessages(initialMessage); + setX(x-1) + setXY(xy - 1) + setMoves(moves + 1) + setMessages(initialMessage) } if(direction === 'down'){ - if(y + 1 === 4){ - setMessages('You cannot go down'); return xy + if (y + 1 === 4){ + setMessages("You can't go down") + return xy } - setY(y+1); setxy(xy + 3); setMoves( moves + 1); setMessages(initialMessage) + setY(y+1) + setXY(xy + 3) + setMoves(moves + 1) + setMessages(initialMessage) } - if(direction === ' right'){ - if(x + 1 === 4){ - setMessages('You cannot go right'); return xy; + if(direction === 'right'){ + if (x + 1 === 4){ + setMessages("You can't go right") + return xy } - setX(x + 1); setxy(xy + 1); setMoves(moves + 1); setMessages(initialMessage); + setX(x+1) + setXY(xy + 1) + setMoves(moves + 1) + setMessages(initialMessage) } if(direction === 'up'){ - if(y-1 ===0){ - setMessages('You cannot go up'); return xy; + if (y - 1 === 0){ + setMessages("You can't go up") + return xy } - setY(y-1); setxy( xy - 3); setMoves(moves + 1); setMessages(initialMessage) + setY(y-1) + setXY(xy - 3) + setMoves(moves + 1) + setMessages(initialMessage) } + + + + // This helper takes a direction ("left", "up", etc) and calculates what the next index + // of the "B" would be. If the move is impossible because we are at the edge of the grid, + // this helper should return the current index unchanged. + } + + function move(evt) { + getNextIndex(evt) + + // This event handler can use the helper above to obtain a new index for the "B", + // and change any states accordingly. } - function move(evt) {getNextIndex(evt)} + function onChange(evt) { + setFormValue(evt.target.value) + } - function onChange(evt) {setFormValue(evt.target.value)} + const validate = (name,value) => { + yup.reach(formSchema, name) + .validate(value) + .then(() => post()) + .catch(err => setMessages(err.errors[0])) + } - const validate = (name, value) => {yup.reach(formSchema, name).validate(value).then(()=> post()).catch(err => setMessages(err.errors[0]))} - function onSubmit(evt) { - evt.preventDefault(); - validate('formValue',formValue) + evt.preventDefault() + validate('formValue', formValue); } - function post() { - const toSend = {"x": x, "y": y, "steps": moves, "email": formValue} + function post(){ + const toSend = { + "x": x, + "y": y, + "steps": moves, + "email": formValue + } axios.post('http://localhost:9000/api/result', toSend) - .then(({data})=>{setMessages(data.message)}).finally(setFormValue('')) + .then(({data}) => {setMessages(data.message)}) + .finally(setFormValue('')) } return (
-

Coordinates (2, 2)

-

You moved 0 times

+

{`Coordinates ${getXY()}`}

+

{`You moved ${moves} ${moves === 1 ? 'time' : 'times'}`}

{ [0, 1, 2, 3, 4, 5, 6, 7, 8].map(idx => ( -
- {idx === 4 ? 'B' : null} +
+ {idx === xy ? 'B' : null}
)) }
-

+

{messages}

- - - - - + + + + +
-
- - + onSubmit(e)}> + onChange(e)}> +
) -} +} \ No newline at end of file