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*/}
-
-
-
-
-
+
+
+
+
+
-
)
}
-}
+}
\ No newline at end of file
diff --git a/frontend/components/AppFunctional.js b/frontend/components/AppFunctional.js
index 4c2b53a98..691d1cd63 100644
--- a/frontend/components/AppFunctional.js
+++ b/frontend/components/AppFunctional.js
@@ -1,78 +1,164 @@
-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
+
+
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() {
+ 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 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.
+ setMoves(0)
+ setXY(initialIndex)
+ setX(initialX)
+ setY(initialY)
+ setMessages(initialMessage)
+ setFormValue('')
}
function getNextIndex(direction) {
+ if(direction === 'left'){
+ if (x - 1 === 0){
+ setMessages("You can't go left")
+ return xy
+ }
+ setX(x-1)
+ setXY(xy - 1)
+ setMoves(moves + 1)
+ setMessages(initialMessage)
+ }
+ if(direction === 'down'){
+ if (y + 1 === 4){
+ setMessages("You can't go down")
+ return xy
+ }
+ setY(y+1)
+ setXY(xy + 3)
+ setMoves(moves + 1)
+ setMessages(initialMessage)
+ }
+ 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)
+ }
+ if(direction === 'up'){
+ if (y - 1 === 0){
+ setMessages("You can't go up")
+ return xy
+ }
+ 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 onChange(evt) {
- // You will need this to update the value of the input.
+ 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 (
-
Coordinates (2, 2)
-
You moved 0 times
+
{`Coordinates ${getXY()}`}
+
{`You moved ${moves} ${moves === 1 ? 'time' : 'times'}`}